A Game I Coded (WIP)

A game I made just recently, please give it some love because I am new to the platform and this took 8 hours to code.

let grid, w = 3.5, cols, rows, currentElement = 1, brushSize = 4;
let menuHeight = 120, sideWidth = 60, ignoreInput = false, lastMouseX, lastMouseY;
let timeScale = 1.0, isPaused = false, frameSubCount = 0, xIndices = [];
let lightningTimer = 0;
let gameVersion = "v0.0.64"; 

function setup() {
  let canvas = createCanvas(windowWidth, windowHeight);
  canvas.style('display', 'block'); 
  refreshGrid(null, true); 
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  refreshGrid(grid); 
}

function refreshGrid(oldGrid = null, isFirstTime = false) {
  cols = floor(width / w); rows = floor(height / w);
  let newGrid = make2DArray(cols, rows);
  
  if (oldGrid) {
    for (let i = 0; i < min(cols, oldGrid.length); i++) {
      for (let r = 0; r < min(rows, oldGrid.length); r++) {
        let tI = min(i, cols-1), tR = min(r, rows-1);
        newGrid[tI][tR] = oldGrid[i][r];
      }
    }
  } else if (isFirstTime) {
    for (let i = 0; i < cols; i++) {
      let floorDepth = floor(rows - 12 + sin(i * 0.1) * 4);
      for (let r = floorDepth; r < rows; r++) {
        newGrid[i][r] = { type: random() > 0.7 ? 6 : 1, v: random(-10, 10), life: -1, temp: 0 };
      }
    }
    for (let k = 0; k < 400; k++) {
      let rx = floor(random(cols)), ry = floor(random(rows * 0.4, rows - 15));
      newGrid[rx][ry] = { type: random() > 0.4 ? 2 : 8, v: random(-10, 10), life: -1, temp: 0 };
    }
  }
  grid = newGrid; xIndices = Array.from({length: cols}, (_, i) => i);
}

function mousePressed() {
  // Fullscreen Button Check (Bottom Right Icon Area)
  if (mouseX > width - 50 && mouseY > height - 65 && mouseY < height - 25) {
    fullscreen(!fullscreen());
    ignoreInput = true;
    return;
  }

  if (mouseX < sideWidth && mouseY < menuHeight) { currentElement = (mouseY < menuHeight/2) ? 21 : 23; ignoreInput = true; } 
  else if (mouseX > width - sideWidth && mouseY < menuHeight) { currentElement = (mouseY < menuHeight/2) ? 20 : 24; ignoreInput = true; }
  else if (mouseY < menuHeight) {
    let sw = (width - (sideWidth * 2)) / 7, sh = menuHeight / 3;
    let col = floor((mouseX - sideWidth) / sw), row = floor(mouseY / sh);
    let idxMap = [
      [1, 2, 3, 4, 5, 6, 7],      
      [8, 9, 10, 11, 12, 13, 14],    
      [15, 16, 17, 18, 19, 26, 27] 
    ];
    if (idxMap[row] && idxMap[row][col]) currentElement = idxMap[row][col];
    ignoreInput = true;
  } else ignoreInput = false;
  lastMouseX = mouseX; lastMouseY = mouseY;
}

function draw() {
  background(245);
  if (lightningTimer > 0) lightningTimer--;

  if (mouseIsPressed && !ignoreInput && mouseY > menuHeight) {
    let steps = max(1, ceil(dist(lastMouseX, lastMouseY, mouseX, mouseY) / (w / 2)));
    for (let s = 0; s <= steps; s++) {
      let t = s / steps;
      let iX = lerp(lastMouseX, mouseX, t), iY = lerp(lastMouseY, mouseY, t);
      let mC = floor(iX / w), mR = floor(iY / w);
      if (currentElement === 27) {
        if (lightningTimer <= 0) { createBranchingLightning(mC, mR, mouseX - pmouseX, mouseY - pmouseY, dist(mouseX, mouseY, pmouseX, pmouseY)); lightningTimer = 25; }
      } else {
        for (let i = -brushSize; i <= brushSize; i++) {
          for (let j = -brushSize; j <= brushSize; j++) {
            let c = mC+i, r = mR+j;
            if (c >= 0 && c < cols && r >= 0 && r < rows && dist(i,j,0,0) <= brushSize) applyTool(c, r);
          }
        }
      }
    }
  }
  lastMouseX = mouseX; lastMouseY = mouseY;

  if (!isPaused) {
    frameSubCount += timeScale;
    while (frameSubCount >= 1.0) { updatePhysics(); frameSubCount -= 1.0; }
  }
  renderGrid();
  renderUI();
  renderOverlays();
}

function applyTool(c, r) {
  if (currentElement === 18) grid[c][r] = { type: 0 }; 
  else if (grid[c][r].type === 0 || currentElement === 20 || currentElement === 24) {
    if (currentElement === 20) grid[c][r].temp += 40;
    else {
      let life = (currentElement === 3 || currentElement === 22) ? random(40, 100) : -1;
      grid[c][r] = { type: currentElement, v: random(-20, 20), life: life, temp: 0 };
    }
  }
}

function updatePhysics() {
  let nextGrid = make2DArray(cols, rows);
  let roofY = floor(menuHeight / w) + 2;

  // PASS 1: Indestructibles
  for (let r = 0; r < rows; r++) {
    for (let i = 0; i < cols; i++) {
      let t = grid[i][r].type;
      if (t === 19 || t === 12 || t === 17) nextGrid[i][r] = grid[i][r];
    }
  }

  // PASS 2: Dynamics
  shuffle(xIndices, true);
  for (let r = rows - 1; r >= 0; r--) {
    for (let x = 0; x < cols; x++) {
      let i = xIndices[x]; let cell = grid[i][r];
      if (cell.type === 0 || nextGrid[i][r].type !== 0) {
        if (cell.type === 19) {
          for(let ox=-5; ox<=5; ox++) for(let oy=-5; oy<=5; oy++) {
            let ni=i+ox, nr=r+oy;
            if(ni>=0 && ni<cols && nr>=0 && nr<rows && grid[ni][nr].type > 0 && grid[ni][nr].type !== 19 && grid[ni][nr].type !== 12) {
              if(abs(ox)<=1 && abs(oy)<=1) grid[ni][nr] = {type:0}; 
              else if(random(1)<0.2) { 
                let mi=ox>0?-1:1, mr=oy>0?-1:1;
                if (ni+mi >= 0 && ni+mi < cols && nr+mr >= 0 && nr+mr < rows && grid[ni+mi][nr+mr].type === 0) {
                   let mvd=grid[ni][nr]; grid[ni][nr]={type:0}; grid[ni+mi][nr+mr]=mvd;
                }
              }
            }
          }
        }
        continue;
      }
      
      if (cell.type === 27) { cell.life--; if(cell.life > 0) nextGrid[i][r] = cell; continue; }
      if (cell.life > 0) { cell.life--; if (cell.life <= 0) { if(cell.type === 3) nextGrid[i][r] = {type:22, v:0, life:60}; continue; } }
      
      let typeB = (r < rows-1) ? grid[i][r+1].type : -1;
      let dir = random() < 0.5 ? 1 : -1;
      let sideType = (i+dir >= 0 && i+dir < cols) ? grid[i+dir][r].type : -1;

      if (cell.type === 3 || cell.type === 7 || cell.type === 22 || cell.type === 9) {
        if (r > roofY && (r>0 && grid[i][r-1].type === 0)) nextGrid[i][r-1] = cell; 
        else if (r > roofY && sideType === 0) nextGrid[i+dir][r] = cell; 
        else nextGrid[i][r] = cell;
      } else {
        if (typeB === 0 || typeB === 7) nextGrid[i][r+1] = cell;
        else if (sideType === 0) { 
          let tr = (cell.type === 2 || cell.type === 10) ? r : r+1; 
          if (tr < rows && i+dir >= 0 && i+dir < cols && grid[i+dir][tr].type === 0) nextGrid[i+dir][tr] = cell; 
          else nextGrid[i][r] = cell; 
        }
        else nextGrid[i][r] = cell;
        if (cell.type === 10 && typeB === 2) nextGrid[i][r+1] = {type: 12, v:0, life:-1};
      }
    }
  }
  grid = nextGrid;
}

function renderGrid() {
  noStroke();
  for (let i = 0; i < cols; i++) {
    for (let r = 0; r < rows; r++) {
      let c = grid[i][r]; if (c.type === 0) continue;
      if (c.type === 1) fill(210, 180, 100); 
      else if (c.type === 2) fill(50, 150, 255);
      else if (c.type === 3) fill(255, random(100,200), 0);
      else if (c.type === 26) fill(120+c.v, 160+c.v, 160+c.v); 
      else if (c.type === 19) { 
        let n = noise(i*0.05, r*0.05); fill(lerp(20,60,n), 0, lerp(40,120,n)); rect(i*w, r*w, w, w);
        if(noise(i*1.5, r*1.5) > 0.92) { fill(255); rect(i*w, r*w, 1, 1); } continue;
      }
      else if (c.type === 27) { if (c.v === 100) fill(220, 240, 255); else fill(0, 80, 255); }
      else if (c.type === 17) fill(160, 230, 255, 200); 
      else if (c.type === 15) fill(255, 215, 0);
      else if (c.type === 12) fill(80);
      else if (c.type === 8) fill(150, 50, 250);
      else if (c.type === 4) fill(34, 139, 34);
      else fill(100);
      rect(i * w, r * w, w, w);
    }
  }
}

function renderUI() {
  let sw = (width - sideWidth * 2) / 7, sh = menuHeight / 3;
  let lbls = [
    ["Sand", "Water", "Fire", "Plant", "Wood", "Soil", "Gas"], 
    ["Seed", "Rocket", "Lava", "Acid", "Wall", "Steel", "Copper"], 
    ["Gold", "Tungsten", "Diamond", "Erase", "Void", "Glass", "Lightning"]
  ];
  let vals = [
    [1, 2, 3, 4, 5, 6, 7],      
    [8, 9, 10, 11, 12, 13, 14],    
    [15, 16, 17, 18, 19, 26, 27]
  ];

  for(let row=0; row<3; row++) {
    for(let col=0; col<7; col++) {
      let isSpecial = (row === 2); let x = sideWidth + col * sw, y = row * sh;
      fill(isSpecial ? color(180, 140, 20) : (currentElement === vals[row][col] ? 100 : 50));
      stroke(0); rect(x, y, sw, sh);
      if(isSpecial) { 
        let progress = (frameCount % 120) / 100;
        if(progress < 1.2) {
          let sx = x + (progress * (sw + 40)) - 20;
          fill(255, 255, 200, 150); noStroke(); quad(sx, y, sx + 8, y, sx - 2, y + sh, sx - 10, y + sh);
        }
      }
      let txt = lbls[row][col]; drawingContext.strokeStyle = 'black'; drawingContext.lineWidth = 2.5;
      fill(255); textAlign(CENTER, CENTER); textSize(10);
      drawingContext.strokeText(txt, x + sw/2, y + sh/2); drawingContext.fillText(txt, x + sw/2, y + sh/2);
    }
  }
  drawSidebar(0, "COOL", "FREEZE", color(50, 100, 200), 21, 23);
  drawSidebar(width - sideWidth, "HEAT", "VAPOR", color(200, 50, 50), 20, 24);
}

function renderOverlays() {
  // Version History (Bottom Right)
  fill(50, 180); noStroke(); rect(width - 70, height - 25, 65, 20, 5);
  fill(255); textAlign(CENTER, CENTER); textSize(11);
  text(gameVersion, width - 37, height - 15);

  // Fullscreen Icon Button (Corner Brackets Box)
  fill(80, 180); stroke(255); strokeWeight(1.5);
  let bx = width - 50, by = height - 65, bs = 35;
  rect(bx, by, bs, bs, 6);
  
  // Draw the bracket corners
  strokeWeight(2);
  let p = 8; // padding
  line(bx+p, by+p, bx+p+6, by+p); line(bx+p, by+p, bx+p, by+p+6); // Top left
  line(bx+bs-p, by+p, bx+bs-p-6, by+p); line(bx+bs-p, by+p, bx+bs-p, by+p+6); // Top right
  line(bx+p, by+bs-p, bx+p+6, by+bs-p); line(bx+p, by+bs-p, bx+p, by+bs-p-6); // Bottom left
  line(bx+bs-p, by+bs-p, bx+bs-p-6, by+bs-p); line(bx+bs-p, by+bs-p, bx+bs-p, by+bs-p-6); // Bottom right
}

function createBranchingLightning(x, y, dx, dy, moveVel) {
  let cx = x, cy = y, angle = atan2(dy, dx);
  for (let s = 0; s < 300; s++) {
    let icx = floor(cx), icy = floor(cy);
    if (icx < 1 || icx >= cols-1 || icy < floor(menuHeight/w)+2 || icy >= rows-1) break;
    grid[icx][icy] = { type: 27, v: 100, life: 12 };
    cx += cos(angle) * 2 + random(-2.5, 2.5); cy += sin(angle) * 2 + random(-2.5, 2.5);
    if (icx >= 0 && icy >= 0 && grid[icx][icy].type > 0 && grid[icx][icy].type !== 27) break;
  }
}

function drawSidebar(x, t1, t2, col, v1, v2) {
  fill(col); stroke(0); rect(x, 0, sideWidth, menuHeight);
  line(x, menuHeight/2, x+sideWidth, menuHeight/2);
  noStroke(); fill(255); textAlign(CENTER, CENTER); textSize(10);
  fill(currentElement === v1 ? 255 : 200); text(t1, x + sideWidth/2, menuHeight/4);
  fill(currentElement === v2 ? 255 : 200); text(t2, x + sideWidth/2, 3*menuHeight/4);
}

function make2DArray(c, r) {
  let arr = new Array(c);
  for (let i = 0; i < c; i++) {
    arr[i] = new Array(r);
    for (let j = 0; j < r; j++) arr[i][j] = { type: 0, v: 0, life: -1, temp: 0 };
  }
  return arr;
}

I am not sure what the aim of the game is but the graphics look good.

I have posted your code on the p5js sketch editor and embedded it below. I suggest you might create your own editor account so you can display your creations.

@quark Thx for commenting, its mainly js supposed to be like a sandbox game like sandspiel or sandboxels, there really is no goal

An update on the game!

Added:

New & better graphics

interaction changes

A better game overall!

https://editor.p5js.org/thethinginparticular/full/IA5h7-rG7

Game code:

let grid, w = 3.5, cols, rows, currentElement = 1, brushSize = 4;
let menuHeight = 120, sideWidth = 60, ignoreInput = false;
let timeScale = 1.0, isPaused = false, frameSubCount = 0, xIndices = [];
let lightningTimer = 0, bolts = [];
let gameVersion = "v0.1.22"; 

let buttonStates = [];
const BURNABLE = new Set([4, 5, 8]); 
const IGNITE_TEMP = { 4: 80, 5: 120, 8: 90 };

function setup() {
  createCanvas(windowWidth, windowHeight);
  refreshGrid(null, true);
  for (let i = 0; i < 7; i++) {
    buttonStates.push({ isSlashing: false, slashStart: 0, stars: [] });
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  refreshGrid(grid);
}

function refreshGrid(oldGrid = null, isFirstTime = false) {
  cols = floor(width / w); rows = floor(height / w);
  let newGrid = make2DArray(cols, rows);
  if (oldGrid) {
    for (let i = 0; i < min(cols, oldGrid.length); i++) {
      for (let r = 0; r < min(rows, oldGrid[i].length); r++) {
        newGrid[i][r] = oldGrid[i][r];
      }
    }
  } else if (isFirstTime) {
    for (let i = 0; i < cols; i++) {
      let floorDepth = floor(rows - 12 + sin(i * 0.1) * 4);
      for (let r = floorDepth; r < rows; r++) {
        newGrid[i][r] = { type: random() > 0.7 ? 6 : 1, v: random(-10, 10), life: -1, temp: 0, maxLife: -1 };
      }
    }
  }
  grid = newGrid; xIndices = Array.from({length: cols}, (_, i) => i);
}

function keyPressed() {
  if (key === ' ') isPaused = !isPaused;
  if (keyCode === RIGHT_ARROW) timeScale = min(timeScale + 0.25, 2.0);
  if (keyCode === LEFT_ARROW) timeScale = max(timeScale - 0.25, 0.25);
}

function draw() {
  background(245);
  if (lightningTimer > 0) lightningTimer--;
  
  if (mouseIsPressed && mouseY > menuHeight) {
    let mouseVel = dist(mouseX, mouseY, pmouseX, pmouseY);
    let steps = max(1, ceil(mouseVel / (w / 1.5)));
    for (let s = 0; s <= steps; s++) {
      let t = s / steps;
      let iX = lerp(pmouseX, mouseX, t), iY = lerp(pmouseY, mouseY, t);
      let ic = floor(iX / w), ir = floor(iY / w);
      if (currentElement === 27) {
        if (mouseVel > 1.5 && lightningTimer <= 0) { 
          let pwr = constrain(map(mouseVel, 1.5, 60, 1.5, 5), 1.5, 5);
          generateFullBolt(mouseX, mouseY, (mouseX - pmouseX), (mouseY - pmouseY), pwr);
          lightningTimer = 10;
        }
      } else {
        for (let i = -brushSize; i <= brushSize; i++) {
          for (let j = -brushSize; j <= brushSize; j++) {
            let c = ic + i, r = ir + j;
            if (c >= 0 && c < cols && r >= 0 && r < rows && dist(i, j, 0, 0) <= brushSize) applyTool(c, r);
          }
        }
      }
    }
  }
  
  if (!isPaused) {
    frameSubCount += timeScale;
    while (frameSubCount >= 1.0) { updatePhysics(); frameSubCount -= 1.0; }
  }
  
  renderGrid();
  drawBolts();
  renderUI();
  renderOverlays();
}

function applyTool(c, r) {
  let id = currentElement;
  if (id === 18) grid[c][r] = { type: 0, v: 0, life: -1, temp: 0, maxLife: -1 };
  else if (id === 9) { if (grid[c][r].type !== 19) grid[c][r] = { type: 9, v: -5, life: 100, temp: 0, maxLife: 100 }; }
  else {
    if (grid[c][r].type === 19) return;
    let t = (id === 3 || id === 10) ? 500 : 0;
    let life = (id === 3) ? random(25, 45) : (id === 10 || id === 11) ? random(200, 500) : -1;
    grid[c][r] = { type: id, v: random(-15, 15), life: life, temp: t, maxLife: life };
  }
}

function updatePhysics() {
  let nextGrid = make2DArray(cols, rows);
  for (let r = 0; r < rows; r++) {
    for (let i = 0; i < cols; i++) {
      let cell = grid[i][r];
      if ([4,5,12,13,14,15,16,19].includes(cell.type)) {
        nextGrid[i][r] = cell;
        if (cell.type === 19) {
          for (let ny = -10; ny <= 10; ny++) {
            for (let nx = -10; nx <= 10; nx++) {
              if ((nx === 0 && ny === 0) || nx*nx + ny*ny > 100) continue;
              let ni = i + nx, nr = r + ny;
              if (ni >= 0 && ni < cols && nr >= 0 && nr < rows) {
                let target = grid[ni][nr];
                if (target.type !== 0 && target.type !== 19 && target.type !== 12) {
                  if (random() < 0.2) {
                    let stepX = nx > 0 ? -1 : 1, stepY = ny > 0 ? -1 : 1;
                    let mx = ni + stepX, my = nr + stepY;
                    if (mx >= 0 && mx < cols && my >= 0 && my < rows) {
                      if (grid[mx][my].type === 19) grid[ni][nr] = {type: 0, v: 0, life: -1, temp: 0};
                      else if (grid[mx][my].type === 0) { grid[mx][my] = target; grid[ni][nr] = {type: 0, v: 0, life: -1, temp: 0}; }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  shuffle(xIndices, true);
  for (let r = rows - 1; r >= 0; r--) {
    for (let x = 0; x < cols; x++) {
      let i = xIndices[x]; let cell = grid[i][r];
      if (cell.type === 0 || nextGrid[i][r].type !== 0) continue;

      let neighbors = [[0,-1],[0,1],[1,0],[-1,0]];
      for (let n of neighbors) {
        let ni = i + n[0], nr = r + n[1];
        if (ni >= 0 && ni < cols && nr >= 0 && nr < rows) {
          let other = grid[ni][nr];
          if (cell.temp > 20) other.temp = max(other.temp, cell.temp * 0.9);
          if ((cell.type === 3 || cell.type === 10) && BURNABLE.has(other.type)) {
            if (other.temp > (IGNITE_TEMP[other.type] || 80) || random() < 0.1) {
              grid[ni][nr] = {type: 3, v: random(-10,10), life:random(30,50), temp:500, maxLife:50};
            }
          }
        }
      }
      if (cell.temp > 0) cell.temp *= 0.96;

      let typeB = (r < rows - 1) ? grid[i][r+1].type : -1;
      let typeA = (r > 0) ? grid[i][r-1].type : -1;
      let dir = random() < 0.5 ? 1 : -1;
      let sideType = (i+dir >= 0 && i+dir < cols) ? grid[i+dir][r].type : -1;
      let moved = false;

      if (cell.type === 3 || cell.type === 7) { 
        if (typeA === 0) { nextGrid[i][r-1] = cell; moved = true; }
        else if (sideType === 0) { nextGrid[i+dir][r] = cell; moved = true; }
      } else if (cell.type === 9) { 
        if (r > 0 && grid[i][r-1].type === 0) { nextGrid[i][r-1] = cell; moved = true; }
        else { nextGrid[i][r] = {type: 3, v:0, life:30, temp:1000, maxLife:30}; moved = true; }
      } else if (typeB === 0) { 
        nextGrid[i][r+1] = cell; moved = true;
      } else if (sideType === 0 && [1,2,10,11,17].includes(cell.type)) { 
        let targetR = (cell.type === 1 || cell.type === 17) ? r+1 : r;
        if (targetR < rows && grid[i+dir][targetR].type === 0) { nextGrid[i+dir][targetR] = cell; moved = true; }
      }

      if (!moved) nextGrid[i][r] = cell;
      if (cell.life > 0) cell.life--;
      if (cell.life === 0) {
        if (cell.type === 3) nextGrid[i][r] = { type: 7, v: 0, life: random(100, 200), temp: 20, maxLife: 200 };
        else nextGrid[i][r] = { type: 0, v: 0, life: -1, temp: 0 };
      }
    }
  }
  grid = nextGrid;
}

function renderGrid() {
  noStroke();
  let edgeFlare = null;
  let highestR = 10000;
  for (let i = 0; i < cols; i++) {
    for (let r = 0; r < rows; r++) {
      let c = grid[i][r]; if (c.type === 0) continue;
      let bC;
      if (c.type === 1) bC = color(210, 180, 100);
      else if (c.type === 2) bC = color(50, 120, 255);
      else if (c.type === 3) bC = color(255, random(100, 220), 0);
      else if (c.type === 4) bC = color(34, 139, 34);
      else if (c.type === 5) bC = color(101, 67, 33);
      else if (c.type === 6) bC = color(80, 50, 20);
      else if (c.type === 7) bC = color(0, 0, 0, map(c.life, 0, c.maxLife, 0, 180));
      else if (c.type === 8) bC = color(200, 170, 80);
      else if (c.type === 10) bC = color(255, 60, 0);
      else if (c.type === 11) bC = color(150, 255, 0);
      else if (c.type === 12) bC = color(80);
      else if (c.type === 13) bC = color(160, 170, 180);
      else if (c.type === 14) bC = color(184, 115, 51);
      else if (c.type === 15) bC = color(255, 215, 0);
      else if (c.type === 17) {
          bC = color(110 + c.v, 170 + c.v, 240 + c.v); 
          if (i > 1 && grid[i-1][r].type === 0 && r < highestR) {
              let depth = 0;
              for(let k=1; k<30; k++) if(r+k < rows && grid[i][r+k].type === 17) depth++;
              if (depth > 20) { highestR = r; edgeFlare = {x: (i-1)*w, y: r*w + 65}; } 
          }
          if (random() < 0.0003) drawUIStar(i * w, r * w, random(5, 12)); 
      }
      else if (c.type === 20) bC = color(220, 240, 255, 120);
      else if (c.type === 19) {
        fill(10, 10, 20); rect(i * w, r * w, w, w);
        if (random() < 0.1) { fill(255); rect(i * w + random(w), r * w + random(w), 0.7, 0.7); }
        continue;
      }
      else bC = color(100);

      if (c.temp > 40 && c.type !== 7) {
        let f = constrain(c.temp / 400, 0, 1);
        fill(lerpColor(bC, color(255, 100, 0), f));
      } else fill(bC);
      rect(i * w, r * w, w, w);
    }
  }
  if (edgeFlare) drawBigShine(edgeFlare.x, edgeFlare.y);
}

function drawBigShine(x, y) {
  push(); translate(x, y);
  fill(255); noStroke();
  let sz = 60; 
  beginShape(); let h = sz / 2;
  vertex(0, -h); bezierVertex(0, -h*0.1, h*0.1, 0, h, 0); bezierVertex(h*0.1, 0, 0, h*0.1, 0, h);
  bezierVertex(0, h*0.2, -h*0.2, 0, -h, 0); bezierVertex(-h*0.1, 0, 0, -h*0.1, 0, -h);
  endShape(CLOSE);
  fill(255, 200); circle(0, 0, 8);
  pop();
}

function drawUIStar(x, y, sz) {
    push(); translate(x, y);
    fill(255, random(150, 255)); noStroke();
    let h = sz / 2;
    beginShape();
    vertex(0, -h); bezierVertex(0, -h*0.2, h*0.2, 0, h, 0); bezierVertex(h*0.2, 0, 0, h*0.2, 0, h);
    bezierVertex(0, h*0.2, -h*0.2, 0, -h, 0); bezierVertex(-h*0.2, 0, 0, -h*0.2, 0, -h);
    endShape(CLOSE); pop();
}

function renderUI() {
  let sw = (width - sideWidth * 2) / 7, sh = menuHeight / 3;
  let lbls = [["Sand","Water","Fire","Plant","Wood","Soil","Gas"],
              ["Seed","Rocket","Lava","Acid","Wall","Steel","Copper"],
              ["Gold","Tungsten","Diamond","Erase","Void","Glass","Lightning"]];
  let vals = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 27]];

  for(let row=0; row<3; row++) {
    for(let col=0; col<7; col++) {
      let x = sideWidth + col * sw, y = row * sh;
      if (row === 2) drawGoldButton(x, y, sw, sh, col);
      else {
        fill(currentElement === vals[row][col] ? 100 : 50);
        stroke(0); rect(x, y, sw, sh);
      }
      let txt = lbls[row][col]; fill(255); textAlign(CENTER, CENTER); textSize(10);
      noStroke(); text(txt, x + sw/2, y + sh/2);
      if (mouseIsPressed && mouseX > x && mouseX < x+sw && mouseY > y && mouseY < y+sh) currentElement = vals[row][col];
    }
  }
  drawSidebar(0, "COOL", "FREEZE", color(50, 100, 200), 23, 24);
  drawSidebar(width - sideWidth, "HEAT", "VAPOR", color(200, 50, 50), 25, 26);
}

function drawSidebar(x, t1, t2, col, v1, v2) {
  fill(col); stroke(0); rect(x, 0, sideWidth, menuHeight);
  line(x, menuHeight/2, x+sideWidth, menuHeight/2);
  noStroke(); fill(255); textAlign(CENTER, CENTER); textSize(10);
  fill(currentElement === v1 ? 255 : 200); text(t1, x + sideWidth/2, menuHeight/4);
  fill(currentElement === v2 ? 255 : 200); text(t2, x + sideWidth/2, 3*menuHeight/4);
  if (mouseIsPressed && mouseX > x && mouseX < x+sideWidth && mouseY > 0 && mouseY < menuHeight) {
     currentElement = (mouseY < menuHeight / 2) ? v1 : v2;
  }
}

function renderOverlays() {
  fill(50, 180); noStroke(); rect(width - 70, height - 25, 65, 20, 5);
  fill(255); textAlign(CENTER, CENTER); textSize(11); text(gameVersion, width - 37, height - 15);
  fill(255); textAlign(LEFT, TOP); textSize(12);
  text("Speed: " + timeScale + "x", 10, height - 20);
  if (isPaused) text("PAUSED", 10, height - 40);
}

function generateFullBolt(x, y, dx, dy, pwr) {
  let segments = [];
  let cx = x, cy = y;
  for (let i = 0; i < 55; i++) {
    let nx = cx + (dx * 3.5) + random(-15, 15), ny = cy + (dy * 3.5) + random(-15, 15);
    let ic = floor(nx / w), ir = floor(ny / w);
    if (ic >= 0 && ic < cols && ir >= 0 && ir < rows) {
      if (grid[ic][ir].type !== 0 && grid[ic][ir].type !== 27 && grid[ic][ir].type !== 7) {
        grid[ic][ir].temp = 1000; segments.push({ x1: cx, y1: cy, x2: nx, y2: ny }); break;
      }
    }
    segments.push({ x1: cx, y1: cy, x2: nx, y2: ny });
    cx = nx; cy = ny;
    if (cx < 0 || cx > width || cy < menuHeight || cy > height) break;
  }
  bolts.push({ segments, life: 42, power: pwr, maxLife: 42 });
}

function drawBolts() {
  for (let i = bolts.length - 1; i >= 0; i--) {
    let b = bolts[i];
    let age = b.maxLife - b.life;
    if (age > 18 && frameCount % 4 < 2) { b.life--; if (b.life <= 0) bolts.splice(i, 1); continue; }
    push(); strokeCap(ROUND);
    stroke(140, 180, 255, map(b.life, 0, b.maxLife, 0, 180)); strokeWeight(b.power * 2.5);
    for (let s of b.segments) line(s.x1, s.y1, s.x2, s.y2);
    stroke(255, map(b.life, 0, b.maxLife, 0, 255)); strokeWeight(b.power * 0.8); 
    for (let s of b.segments) line(s.x1, s.y1, s.x2, s.y2);
    pop();
    b.life--; if (b.life <= 0) bolts.splice(i, 1);
  }
}

function drawGoldButton(x, y, w, h, idx) {
  push(); drawingContext.save(); drawingContext.beginPath(); drawingContext.rect(x, y, w, h); drawingContext.clip();
  let bG = color(255, 225, 110), dG = color(110, 70, 15);
  for (let i = 0; i < (w + h); i++) {
    let inter = i / (w + h); stroke(lerpColor(bG, dG, inter)); line(x + i, y, x, y + i); 
  }
  handleButtonAnimations(idx, x, y, w, h);
  drawingContext.restore(); pop();
  stroke(0); noFill(); rect(x, y, w, h);
}

function handleButtonAnimations(idx, x, y, w, h) {
  let b = buttonStates[idx];
  if (!b.isSlashing && random() < 0.003) { b.isSlashing = true; b.slashStart = frameCount; }
  if (b.isSlashing) {
    let prog = (frameCount - b.slashStart) / 120;
    if (prog < 1.0) {
      let sx = x + (prog * (w + 40)) - 20; fill(255, 120); noStroke();
      quad(sx, y, sx + 12, y, sx + 2, y + h, sx - 10, y + h);
    } else b.isSlashing = false;
  }
  if (random() < 0.012) {
    b.stars.push({ x: random(5, w-5), y: random(5, h-5), life: 80, size: random(10, h * 0.85), rot: random(TWO_PI) });
  }
  for (let i = b.stars.length - 1; i >= 0; i--) {
    let s = b.stars[i]; push(); translate(x + s.x, y + s.y); rotate(s.rot + frameCount * 0.04);
    scale(sin(frameCount * 0.1) * 0.1 + 0.9); fill(255); noStroke();
    beginShape(); let hS = s.size / 2; vertex(0, -hS);
    bezierVertex(0, -hS*0.2, hS*0.2, 0, hS, 0); bezierVertex(hS*0.2, 0, 0, hS*0.2, 0, hS);
    bezierVertex(0, hS*0.2, -hS*0.2, 0, -hS, 0); bezierVertex(-hS*0.2, 0, 0, -hS*0.2, 0, -hS);
    endShape(CLOSE); pop();
    s.life--; if (s.life <= 0) b.stars.splice(i, 1);
  }
}

function make2DArray(c, r) {
  let arr = new Array(c);
  for (let i = 0; i < c; i++) {
    arr[i] = new Array(r);
    for (let j = 0; j < r; j++) arr[i][j] = { type: 0, v: 0, life: -1, temp: 0, maxLife: -1 };
  }
  return arr;
}