Title of State:

Add 3 Maps to Game

Goal of Change:

implement map selection in the MENU, preload the 12 tile images, and in GAME we draw at native size (2560×2560 tiles, no scaling) and let the 1500×1500 canvas clip what doesn’t fit.

PATCH 1 — Replace the MENU body with a 3-map selector + disabled Start

Code to Remove

141 -         <div class="menuBody">
          <button id="btnStartGame" class="btnPrimary" type="button">START GAME</button>
        </div>

Code to Add

141 -         <div class="menuBody">
          <div class="menuGrid">
            <button class="mapCard" id="mapGrass" type="button">
              <div class="mapName">Grass</div>
              <div class="mapMeta">5120×5120 (4 tiles)</div>
            </button>

            <button class="mapCard" id="mapSand" type="button">
              <div class="mapName">Sand</div>
              <div class="mapMeta">5120×5120 (4 tiles)</div>
            </button>

            <button class="mapCard" id="mapSnow" type="button">
              <div class="mapName">Snow</div>
              <div class="mapMeta">5120×5120 (4 tiles)</div>
            </button>
          </div>

          <div class="menuActions">
            <button id="btnStartGame" class="btnPrimary" type="button" disabled>START GAME</button>
          </div>
        </div>


PATCH 2 — Replace .menuBody{...} CSS with menu grid styles

Code to Remove

88 -   .menuBody{
    flex:1;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:26px;
  }

Code to Add

88 -   .menuBody{
    flex:1;
    display:flex;
    flex-direction:column;
    gap:16px;
    padding:22px 22px 20px 22px;
  }

  .menuGrid{
    width:100%;
    display:grid;
    grid-template-columns: repeat(3, 1fr);
    gap:14px;
  }

  .mapCard{
    cursor:pointer;
    text-align:left;
    padding:16px;
    border-radius:14px;
    border:1px solid rgba(255,255,255,.14);
    background:rgba(255,255,255,.06);
    color:#e9f0ff;
    min-height:130px;
  }
  .mapCard:hover{ background:rgba(255,255,255,.10); }

  .mapCard.selected{
    border-color:rgba(255,255,255,.34);
    background:rgba(255,255,255,.12);
    box-shadow:0 12px 34px rgba(0,0,0,.38);
  }

  .mapName{ font-size:18px; letter-spacing:.2px; }
  .mapMeta{ margin-top:8px; font-size:12px; opacity:.85; }

  .menuActions{
    width:100%;
    display:flex;
    justify-content:center;
    padding-top:4px;
  }

  #btnStartGame[disabled]{
    opacity:.45;
    cursor:not-allowed;
  }


PATCH 3 — Add map buttons to the overlay element section

Code to Remove

223 - const btnBegin = document.getElementById("btnBegin");
const btnStartGame = document.getElementById("btnStartGame");

Code to Add

223 - const btnBegin = document.getElementById("btnBegin");
const btnStartGame = document.getElementById("btnStartGame");

/* Map buttons */
const mapGrassBtn = document.getElementById("mapGrass");
const mapSandBtn  = document.getElementById("mapSand");
const mapSnowBtn  = document.getElementById("mapSnow");


PATCH 4 — Add selected map + map assets to State

Code to Remove

232 - const S = {
  paused:false,
  view:"TITLE",   // "TITLE" | "MENU" | "GAME"
  mouseX:0, mouseY:0,
  fps:0, _acc:0, _frames:0,
  last: performance.now(),
};

Code to Add

232 - const S = {
  paused:false,
  view:"TITLE",        // "TITLE" | "MENU" | "GAME"
  mapKey:null,         // "GRASS" | "SAND" | "SNOW"
  mouseX:0, mouseY:0,
  fps:0, _acc:0, _frames:0,
  last: performance.now(),
};


PATCH 5 — Replace the “Buttons” block with map logic + preload + guarded start

Code to Remove

295 - btnBegin.addEventListener("click", ()=> setView("MENU"));
btnStartGame.addEventListener("click", ()=> setView("GAME"));

Code to Add

295 - /* =========================
   MAPS (NO SCALING)
   ========================= */
const MAPS = {
  GRASS: [
    "https://chrisdeasy.com/wp-content/uploads/20260212-Grass-1.png",
    "https://chrisdeasy.com/wp-content/uploads/20260212-Grass-2.png",
    "https://chrisdeasy.com/wp-content/uploads/20260212-Grass-3.png",
    "https://chrisdeasy.com/wp-content/uploads/20260212-Grass-4.png",
  ],
  SAND: [
    "https://chrisdeasy.com/wp-content/uploads/20260213-Sand-1.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Sand-2.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Sand-3.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Sand-4.png",
  ],
  SNOW: [
    "https://chrisdeasy.com/wp-content/uploads/20260213-Snow-1.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Snow-2.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Snow-3.png",
    "https://chrisdeasy.com/wp-content/uploads/20260213-Snow-4.png",
  ],
};

const MapImages = { GRASS:[null,null,null,null], SAND:[null,null,null,null], SNOW:[null,null,null,null] };

function loadImage(url){
  return new Promise((resolve)=>{
    const img = new Image();
    img.onload = ()=> resolve(img);
    img.onerror = ()=> resolve(null);
    img.src = url;
  });
}

async function preloadMaps(){
  for(const key of Object.keys(MAPS)){
    const imgs = await Promise.all(MAPS[key].map(loadImage));
    MapImages[key] = imgs;
  }
}

/* Map selection UI */
function setSelectedMap(key){
  S.mapKey = key;

  mapGrassBtn.classList.toggle("selected", key === "GRASS");
  mapSandBtn.classList.toggle("selected",  key === "SAND");
  mapSnowBtn.classList.toggle("selected",  key === "SNOW");

  btnStartGame.disabled = false;
}

/* Buttons */
btnBegin.addEventListener("click", ()=> setView("MENU"));

mapGrassBtn.addEventListener("click", ()=> setSelectedMap("GRASS"));
mapSandBtn.addEventListener("click",  ()=> setSelectedMap("SAND"));
mapSnowBtn.addEventListener("click",  ()=> setSelectedMap("SNOW"));

btnStartGame.addEventListener("click", ()=>{
  if(!S.mapKey) return;
  setView("GAME");
});


PATCH 6 — Draw the selected map in GAME (no scaling, native tile placement)

Code to Remove

360 - /* Update / Draw */
function update(dt){
  // your game logic goes here
}

function draw(){
  // background
  ctx.fillStyle = "#0f1422";
  ctx.fillRect(0,0,W,H);

  // anchor mark so you know it is drawing
  const cx=W/2, cy=H/2;
  ctx.strokeStyle="rgba(255,255,255,.22)";
  ctx.lineWidth=1;
  ctx.beginPath();
  ctx.moveTo(cx-18,cy); ctx.lineTo(cx+18,cy);
  ctx.moveTo(cx,cy-18); ctx.lineTo(cx,cy+18);
  ctx.stroke();

  // mouse dot (proves input works)
  ctx.fillStyle="rgba(255,255,255,.65)";
  ctx.beginPath();
  ctx.arc(S.mouseX, S.mouseY, 3, 0, Math.PI*2);
  ctx.fill();
}

Code to Add

360 - /* Update / Draw */
function update(dt){
  // your game logic goes here
}

function drawMapNative(){
  if(S.view !== "GAME") return;
  if(!S.mapKey) return;

  const tiles = MapImages[S.mapKey];
  const t1 = tiles[0], t2 = tiles[1], t3 = tiles[2], t4 = tiles[3];

  // NO SCALING.
  // Each tile is 2560×2560. We draw them at native pixel coordinates.
  // The 1500×1500 canvas will clip what doesn't fit.
  if(t1) ctx.drawImage(t1, 0,    0);
  if(t2) ctx.drawImage(t2, 2560, 0);
  if(t3) ctx.drawImage(t3, 0,    2560);
  if(t4) ctx.drawImage(t4, 2560, 2560);
}

function draw(){
  // background
  ctx.fillStyle = "#0f1422";
  ctx.fillRect(0,0,W,H);

  // map first (so other debug overlays draw on top)
  drawMapNative();

  // anchor mark so you know it is drawing
  const cx=W/2, cy=H/2;
  ctx.strokeStyle="rgba(255,255,255,.22)";
  ctx.lineWidth=1;
  ctx.beginPath();
  ctx.moveTo(cx-18,cy); ctx.lineTo(cx+18,cy);
  ctx.moveTo(cx,cy-18); ctx.lineTo(cx,cy+18);
  ctx.stroke();

  // mouse dot (proves input works)
  ctx.fillStyle="rgba(255,255,255,.65)";
  ctx.beginPath();
  ctx.arc(S.mouseX, S.mouseY, 3, 0, Math.PI*2);
  ctx.fill();
}


PATCH 7 — Preload maps at boot

Code to Remove

429 - setHUD("READY");
setView("TITLE");
requestAnimationFrame(loop);

Code to Add

429 - setHUD("READY");
setView("TITLE");
preloadMaps();
requestAnimationFrame(loop);


Result