🎮ArcadeLab

Pixel Quest - Fantasy

by ShadowTurtle30
307 lines7.2 KB
▶ Play
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pixel Quest - Fantasy</title>
<style>
body {
  margin: 0;
  background: #171831;
  color: #fff1c4;
  font-family: monospace;
  text-align: center;
}
h1 { color: #ffe68c; }
#hud {
  display: flex;
  justify-content: space-around;
  padding: 12px;
  background: #29264c;
}
canvas {
  width: min(96vw, 720px);
  height: auto;
  image-rendering: pixelated;
  border: 3px solid #7564ad;
  background: #252951;
}
button {
  background: #494278;
  color: white;
  border: 2px solid #a69de9;
  padding: 12px;
  margin: 4px;
  font: bold 14px monospace;
  border-radius: 6px;
  cursor: pointer;
  touch-action: none;
}
#msg { padding: 12px; color: #ffe68c; }
</style>
</head>
<body>
<h1>⚔ PIXEL QUEST</h1>
<div id="hud">
  <span id="score">💎 0/5</span>
  <span id="lives">♥ 3</span>
</div>
<canvas id="game" width="480" height="320"></canvas>
<div id="msg">Raccogli 5 gemme e raggiungi il portale!</div>
<div>
  <button data-key="w">⬆ W</button><br>
  <button data-key="a">⬅ A</button>
  <button data-key="s">⬇ S</button>
  <button data-key="d">➡ D</button>
  <button data-key="jump">🦘 SALTO</button>
  <button id="reset">↻ RICOMINCIA</button>
</div>
<p>W avanti · A sinistra · S indietro · D destra · Spazio salta</p>

<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const W = 480, H = 320;

const walls = [
  {x:0,y:0,w:480,h:12},
  {x:0,y:308,w:480,h:12},
  {x:0,y:0,w:12,h:320},
  {x:468,y:0,w:12,h:320},
  {x:75,y:65,w:15,h:115},
  {x:145,y:0,w:15,h:100},
  {x:145,y:170,w:15,h:100},
  {x:215,y:65,w:15,h:115},
  {x:285,y:0,w:15,h:100},
  {x:285,y:170,w:15,h:100},
  {x:355,y:65,w:15,h:115},
  {x:390,y:210,w:48,h:15}
];

let player, gems, enemies, keys, lives, won, over;
let frame = 0;

function reset() {
  player = {x:25,y:25,w:12,h:15,inv:0};
  gems = [[120,38],[185,140],[250,225],
          [330,45],[420,265]]
    .map(([x,y])=>({x,y,got:false}));

  enemies = [
    {x:112,y:220,vx:1.1,vy:.7},
    {x:245,y:35,vx:-.8,vy:1},
    {x:410,y:135,vx:.7,vy:-1}
  ];

  keys = {};
  lives = 3;
  won = false;
  over = false;
  document.getElementById("msg").textContent =
    "Raccogli le 5 gemme e raggiungi il portale!";
  updateHUD();
}

function updateHUD() {
  document.getElementById("score").textContent =
    "💎 " + gems.filter(g=>g.got).length + "/5";
  document.getElementById("lives").textContent = "♥ " + lives;
}

function rect(x,y,w,h,color) {
  ctx.fillStyle = color;
  ctx.fillRect(Math.round(x),Math.round(y),w,h);
}

function hit(a,b) {
  return a.x < b.x+b.w && a.x+a.w > b.x &&
         a.y < b.y+b.h && a.y+a.h > b.y;
}

function blocked(a) {
  return walls.some(w=>hit(a,w));
}

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

  // Stelle
  for(let i=0;i<55;i++) {
    rect((i*83+17)%W,(i*47+8)%H,2,2,
      i%2 ? "#dce4ff" : "#ffe8a0");
  }

  // Muri del labirinto
  walls.forEach(w=>{
    rect(w.x,w.y,w.w,w.h,"#654b5c");
    rect(w.x,w.y,w.w,3,"#61c779");
    for(let y=w.y+8;y<w.y+w.h-3;y+=10)
      rect(w.x+3,y,4,3,"#a57a70");
  });

  // Portale
  rect(425,275,22,25,"#302253");
  rect(429,278,14,19,
    gems.every(g=>g.got) ? "#5bffe0" : "#9260cb");
  rect(433,281,6,13,"#e7c6ff");
  ctx.fillStyle="#ffe99c";
  ctx.font="bold 8px monospace";
  ctx.fillText("PORTALE",413,270);

  // Gemme
  gems.forEach(g=>{
    if(g.got) return;
    const y=g.y+Math.sin(frame*.09+g.x)*2;
    rect(g.x+3,y-3,4,4,"#edffff");
    rect(g.x,y,10,6,"#48dafa");
    rect(g.x+3,y+6,4,3,"#9d83ff");
  });

  // Goblin
  enemies.forEach(e=>{
    rect(e.x,e.y+3,13,10,"#64d57c");
    rect(e.x+1,e.y,4,5,"#9df28c");
    rect(e.x+8,e.y,4,5,"#9df28c");
    rect(e.x+3,e.y+5,2,3,"white");
    rect(e.x+9,e.y+5,2,3,"white");
    rect(e.x+4,e.y+6,1,1,"#171831");
    rect(e.x+10,e.y+6,1,1,"#171831");
  });

  // Eroe
  if(player.inv%8<5) {
    rect(player.x+2,player.y,8,6,"#f5c39c");
    rect(player.x,player.y-2,12,5,"#64417a");
    rect(player.x+1,player.y+6,10,7,"#578fed");
    rect(player.x,player.y+12,5,3,"#e9ca84");
    rect(player.x+7,player.y+12,5,3,"#e9ca84");
    rect(player.x+8,player.y+2,2,2,"white");
  }

  if(won || over) {
    rect(68,108,344,91,"#20203f");
    rect(72,112,336,83,"#494278");
    ctx.fillStyle=won ? "#fff09b" : "#ff9eaf";
    ctx.font="bold 22px monospace";
    ctx.fillText(won?"VITTORIA!":"GAME OVER",150,145);
    ctx.fillStyle="white";
    ctx.font="11px monospace";
    ctx.fillText(
      won?"Hai salvato il regno!":"Premi R per riprovare",
      160,166
    );
  }
}

function update() {
  frame++;

  if(!won && !over) {
    let dx=(keys.d?2:0)-(keys.a?2:0);
    let dy=(keys.s?2:0)-(keys.w?2:0);

    if(dx && dy) { dx*=.707; dy*=.707; }

    let next={...player,x:player.x+dx};
    if(!blocked(next)) player.x=next.x;

    next={...player,y:player.y+dy};
    if(!blocked(next)) player.y=next.y;

    if(player.inv>0) player.inv--;

    // Raccogli gemme
    gems.forEach(g=>{
      if(!g.got && hit(player,
        {x:g.x-2,y:g.y-2,w:13,h:13})) {
        g.got=true;
        updateHUD();
        document.getElementById("msg").textContent =
          "✨ Hai raccolto una gemma!";
      }
    });

    // Movimento dei goblin
    enemies.forEach(e=>{
      e.x+=e.vx;
      e.y+=e.vy;
      if(e.x<18 || e.x>450) e.vx*=-1;
      if(e.y<18 || e.y>292) e.vy*=-1;

      if(!player.inv && hit(player,
        {x:e.x,y:e.y,w:13,h:13})) {
        lives--;
        player.inv=80;
        updateHUD();

        if(lives<=0) {
          over=true;
          document.getElementById("msg").textContent =
            "Game over! Premi R per riprovare.";
        } else {
          document.getElementById("msg").textContent =
            "Attento ai goblin!";
        }
      }
    });

    // Vittoria
    if(player.x>418 && player.y>265) {
      if(gems.every(g=>g.got)) {
        won=true;
        document.getElementById("msg").textContent =
          "👑 Vittoria! Hai liberato il regno!";
      } else {
        document.getElementById("msg").textContent =
          "Ti mancano " + gems.filter(g=>!g.got).length +
          " gemme!";
      }
    }
  }

  draw();
  requestAnimationFrame(update);
}

function setKey(e,down) {
  const k=e.key.toLowerCase();
  const map={
    w:"w",arrowup:"w",
    a:"a",arrowleft:"a",
    s:"s",arrowdown:"s",
    d:"d",arrowright:"d",
    " ":"jump"
  };

  if(map[k]) {
    keys[map[k]]=down;
    e.preventDefault();
  }

  if(down && k==="r") reset();
}

window.addEventListener("keydown",e=>setKey(e,true));
window.addEventListener("keyup",e=>setKey(e,false));

document.querySelectorAll("[data-key]").forEach(btn=>{
  const k=btn.dataset.key;
  btn.addEventListener("pointerdown",e=>{
    e.preventDefault();
    keys[k]=true;
    btn.setPointerCapture(e.pointerId);
  });
  ["pointerup","pointercancel","lostpointercapture"]
    .forEach(ev=>btn.addEventListener(ev,()=>keys[k]=false));
});

document.getElementById("reset").onclick=reset;

reset();
update();
</script>
</body>
</html>

Game Source: Pixel Quest - Fantasy

Creator: ShadowTurtle30

Libraries: none

Complexity: complex (307 lines, 7.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: pixel-quest-fantasy-shadowturtle30" to link back to the original. Then publish at arcadelab.ai/publish.