Skip to content

Instantly share code, notes, and snippets.

@nunesfb
Last active October 23, 2025 20:06
Show Gist options
  • Select an option

  • Save nunesfb/baee2af475d990fd116986cc1ce99fac to your computer and use it in GitHub Desktop.

Select an option

Save nunesfb/baee2af475d990fd116986cc1ce99fac to your computer and use it in GitHub Desktop.
Canvas HTML

Jogos Simples com HTML5 Canvas

1. Jogo do Quadrado que Desvia de Obstáculos

Objetivo

  • Controle o quadrado azul usando as setas do teclado.
  • Desvie dos obstáculos vermelhos que descem pela tela.
  • Se bater, o jogo acaba!

Código HTML

<!DOCTYPE html>
<html lang="pt-br">
<head>
  <meta charset="UTF-8">
  <title>Jogo do Quadrado - Canvas</title>
  <style>
    /* Centraliza o canvas na tela e define fundo da página */
    body { background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; }
    /* Estilo do canvas: fundo branco e borda preta */
    canvas { background: #fff; border: 2px solid #222; }
  </style>
</head>
<body>
  <!-- Área do jogo: 400px de largura, 600px de altura -->
  <canvas id="game" width="400" height="600"></canvas>
  <script>
    // Pega o elemento canvas e o contexto 2D para desenhar
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');

    // Define o jogador como um quadrado azul com posição inicial, tamanho e velocidade
    const player = { x: 180, y: 500, w: 40, h: 40, speed: 6 };

    // Array para armazenar obstáculos
    let obstacles = [];

    // Controle do estado do jogo e pontuação
    let gameOver = false;
    let score = 0;

    // Função para adicionar novos obstáculos com largura e posição aleatória no topo da tela
    function addObstacle() {
      const w = 40 + Math.random() * 60; // largura entre 40 e 100
      const x = Math.random() * (canvas.width - w); // posição X aleatória
      // Cria o obstáculo logo acima da tela e velocidade aleatória
      obstacles.push({ x, y: -50, w, h: 30, speed: 3 + Math.random() * 3 });
    }

    // Desenha o jogador (quadrado azul)
    function drawPlayer() {
      ctx.fillStyle = '#2196f3';
      ctx.fillRect(player.x, player.y, player.w, player.h);
    }

    // Desenha todos os obstáculos (retângulos vermelhos)
    function drawObstacles() {
      ctx.fillStyle = '#e53935';
      obstacles.forEach(obs => {
        ctx.fillRect(obs.x, obs.y, obs.w, obs.h);
      });
    }

    // Move todos os obstáculos para baixo e remove os que já passaram
    function moveObstacles() {
      obstacles.forEach(obs => {
        obs.y += obs.speed; // move cada obstáculo para baixo
      });
      // Remove obstáculos que passaram do final da tela e aumenta score
      obstacles = obstacles.filter(obs => {
        if (obs.y > canvas.height) {
          score += 1; // Ganha ponto se desviar do obstáculo
          return false; // Remove do array
        }
        return true;
      });
    }

    // Verifica colisão entre o jogador e qualquer obstáculo
    function checkCollision() {
      for (const obs of obstacles) {
        if (
          player.x < obs.x + obs.w &&
          player.x + player.w > obs.x &&
          player.y < obs.y + obs.h &&
          player.y + player.h > obs.y
        ) {
          gameOver = true; // Se colidir, fim de jogo!
          break;
        }
      }
    }

    // Escuta as teclas de seta para mover o jogador, se o jogo não acabou
    document.addEventListener('keydown', e => {
      if (gameOver) return;
      if (e.key === 'ArrowLeft' && player.x > 0) player.x -= player.speed;
      if (e.key === 'ArrowRight' && player.x + player.w < canvas.width) player.x += player.speed;
      if (e.key === 'ArrowUp' && player.y > 0) player.y -= player.speed;
      if (e.key === 'ArrowDown' && player.y + player.h < canvas.height) player.y += player.speed;
    });

    // Função principal do jogo (loop): desenha, move, verifica colisão, pontua
    function loop() {
      ctx.clearRect(0, 0, canvas.width, canvas.height); // Limpa tela
      drawPlayer();         // Desenha o jogador
      drawObstacles();      // Desenha obstáculos
      moveObstacles();      // Move obstáculos
      checkCollision();     // Verifica colisão

      // Exibe a pontuação no canto superior esquerdo
      ctx.fillStyle = '#222';
      ctx.font = '20px Arial';
      ctx.fillText('Score: ' + score, 10, 25);

      // Se perdeu, exibe mensagem de fim de jogo
      if (gameOver) {
        ctx.fillStyle = 'rgba(0,0,0,0.7)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = '#fff';
        ctx.font = '40px Arial';
        ctx.fillText('Game Over', 100, 300);
        ctx.font = '24px Arial';
        ctx.fillText('Score: ' + score, 150, 340);
        ctx.fillText('Recarregue a página para jogar de novo', 35, 390);
        return; // Não continua o loop
      }

      // Continua o jogo chamando o próximo quadro de animação
      requestAnimationFrame(loop);
    }

    // Cria novos obstáculos automaticamente a cada segundo
    setInterval(() => {
      if (!gameOver) addObstacle();
    }, 1000);

    // Inicia o jogo chamando o loop
    loop();
  </script>
</body>
</html>

Como funciona?

  • Use as setas do teclado para mover o quadrado azul.
  • Os obstáculos vermelhos descem, e o objetivo é não encostar neles.
  • Se bater, aparece "Game Over" e sua pontuação.

2. Jogo da Snake (Cobrinha)

Como jogar

  • Use as setas do teclado para mover a cobrinha.
  • Coma a comida vermelha para crescer.
  • Se bater nas paredes ou em si mesma, o jogo acaba.

Código HTML

<!DOCTYPE html>
<html lang="pt-br">
  <head>
    <meta charset="UTF-8" />
    <title>Snake Game — Canvas</title>
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover"
    />
    <style>
      :root {
        --bg1: #0f0f13;
        --bg2: #1a1a22;
        --panel: #101018cc;
        --accent: #34d399; /* verde menta */
        --accent-dark: #10b981;
        --danger: #ef4444;
        --text: #e5e7eb;
        --muted: #9ca3af;
        --shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
        --radius: 16px;
        --gap: 12px;
      }
      * {
        box-sizing: border-box;
      }
      html,
      body {
        height: 100%;
        margin: 0;
      }
      body {
        font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu,
          "Helvetica Neue", Arial;
        color: var(--text);
        background: radial-gradient(
            1200px 1200px at 10% 10%,
            #232335 0%,
            transparent 60%
          ),
          radial-gradient(1200px 1200px at 90% 20%, #1b2a2a 0%, transparent 55%),
          linear-gradient(160deg, var(--bg1), var(--bg2) 60%);
        display: flex;
        align-items: center;
        justify-content: center;
        /* paddings mínimos, mas considerados no cálculo do JS */
        padding: max(8px, env(safe-area-inset-top))
          max(8px, env(safe-area-inset-right))
          max(8px, env(safe-area-inset-bottom))
          max(8px, env(safe-area-inset-left));
      }

      .wrap {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--gap);
        width: min(92vw, 820px);
        /* importante: o JS vai cuidar da altura do stage para caber em 100% do viewport */
      }

      .panel {
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        background: var(--panel);
        border: 1px solid #232334;
        border-radius: var(--radius);
        padding: 10px 12px;
        backdrop-filter: blur(8px);
        box-shadow: var(--shadow);
      }
      .title {
        display: flex;
        align-items: center;
        gap: 10px;
        font-weight: 700;
        letter-spacing: 0.3px;
      }
      .dot {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: linear-gradient(135deg, var(--accent), var(--accent-dark));
        box-shadow: 0 0 14px #34d39999;
      }
      .stats {
        display: flex;
        gap: 14px;
        align-items: center;
        font-variant-numeric: tabular-nums;
      }
      .badge {
        background: #151521;
        border: 1px solid #2a2a3a;
        border-radius: 999px;
        padding: 6px 10px;
        font-size: 13px;
        color: var(--muted);
      }
      .btn {
        appearance: none;
        border: 1px solid #2a2a3a;
        background: #151521;
        color: var(--text);
        border-radius: 10px;
        padding: 8px 12px;
        cursor: pointer;
      }
      .btn:hover {
        background: #1b1b2a;
      }
      .btn-accent {
        background: linear-gradient(135deg, var(--accent), var(--accent-dark));
        border: none;
        color: #042016;
        font-weight: 700;
      }
      .btn-danger {
        background: #2a1214;
        border: 1px solid #4d1c20;
        color: #fda4a4;
      }

      .stage {
        position: relative;
        /* width/height serão definidos via JS para caber 100% do alto */
        border-radius: var(--radius);
        overflow: hidden;
        box-shadow: var(--shadow);
        border: 1px solid #24243a;
        background: radial-gradient(
          1200px 800px at 50% 10%,
          #151826 0%,
          #10131f 70%
        );
      }
      canvas {
        width: 100%;
        height: 100%;
        display: block;
      }

      /* Overlay */
      .overlay {
        position: absolute;
        inset: 0;
        display: none;
        align-items: center;
        justify-content: center;
        background: rgba(0, 0, 0, 0.45);
        backdrop-filter: blur(6px);
      }
      .overlay.show {
        display: flex;
      }
      .card {
        width: min(92%, 420px);
        background: #0f1222ee;
        border: 1px solid #272a45;
        border-radius: 16px;
        padding: 18px;
        text-align: center;
        box-shadow: var(--shadow);
      }
      .card h2 {
        margin: 6px 0 8px 0;
        font-size: 28px;
      }
      .card p {
        margin: 6px 0;
        color: var(--muted);
      }
      .row {
        display: flex;
        gap: 10px;
        justify-content: center;
        margin-top: 12px;
      }

      /* D-Pad Mobile */
      .dpad {
        position: absolute;
        right: 12px;
        bottom: 12px;
        display: grid;
        grid-template-columns: 60px 60px 60px;
        grid-template-rows: 60px 60px 60px;
        gap: 8px;
        opacity: 0.92;
        user-select: none;
      }
      .dpad button {
        border: none;
        border-radius: 12px;
        background: #0f1424aa;
        color: #e8fdf5;
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), inset 0 0 0 1px #2a2f4a;
        backdrop-filter: blur(6px);
        font-size: 20px;
        line-height: 60px;
        text-align: center;
        padding: 0;
        cursor: pointer;
        touch-action: manipulation;
      }
      .dpad .ghost {
        visibility: hidden;
      }
      .dpad button:active {
        outline: 2px solid #34d39966;
      }
      @media (max-width: 480px) {
        .dpad {
          grid-template-columns: 56px 56px 56px;
          grid-template-rows: 56px 56px 56px;
        }
        .dpad button {
          line-height: 56px;
        }
      }

      .hint {
        font-size: 12px;
        color: var(--muted);
        text-align: center;
        opacity: 0.8;
      }
    </style>
  </head>
  <body>
    <div class="wrap">
      <div class="panel" id="panel">
        <div class="title">
          <span class="dot"></span> <span>Snake — Edição Canvas</span>
        </div>
        <div class="stats">
          <span class="badge">Score: <b id="uiScore">0</b></span>
          <span class="badge">Recorde: <b id="uiBest">0</b></span>
          <button id="btnPause" class="btn">Pausar</button>
          <button id="btnRestart" class="btn btn-accent">Reiniciar</button>
        </div>
      </div>

      <div class="stage" id="stage">
        <canvas id="snake"></canvas>

        <!-- Overlay -->
        <div id="overlay" class="overlay">
          <div class="card">
            <h2 id="ovTitle">Game Over</h2>
            <p id="ovScore">Score: 0</p>
            <p id="ovTip">Dica: mantenha o controle e antecipe as curvas!</p>
            <div class="row">
              <button class="btn btn-accent" id="ovPlay">Jogar de novo</button>
              <button class="btn btn-danger" id="ovClose">Fechar</button>
            </div>
          </div>
        </div>

        <!-- D-Pad Mobile -->
        <div class="dpad" id="dpad" aria-label="Controles de toque">
          <button class="ghost"></button>
          <button data-dir="UP"></button>
          <button class="ghost"></button>
          <button data-dir="LEFT"></button>
          <button class="ghost"></button>
          <button data-dir="RIGHT"></button>
          <button class="ghost"></button>
          <button data-dir="DOWN"></button>
          <button class="ghost"></button>
        </div>
      </div>

      <div class="hint" id="hint">
        Desktop: setas do teclado • Mobile: botões / gesto de deslizar
      </div>
    </div>

    <script>
      // ========= Medição de layout para ocupar 100% da vertical =========
      const wrap = document.querySelector(".wrap");
      const panel = document.getElementById("panel");
      const stage = document.getElementById("stage");
      const hint = document.getElementById("hint");

      function getViewportHeight() {
        // Usa visualViewport em mobile p/ considerar barras dinâmicas
        return Math.floor(
          (window.visualViewport && window.visualViewport.height) ||
            window.innerHeight
        );
      }
      function getBodyPadding() {
        const cs = getComputedStyle(document.body);
        return {
          top: parseFloat(cs.paddingTop) || 0,
          bottom: parseFloat(cs.paddingBottom) || 0,
          left: parseFloat(cs.paddingLeft) || 0,
          right: parseFloat(cs.paddingRight) || 0,
        };
      }
      function fitStageBox() {
        const vh = getViewportHeight();
        const pad = getBodyPadding();
        const wrapWidth = wrap.clientWidth;

        const panelH = panel.offsetHeight;
        const hintH = hint.offsetHeight;
        const gaps = parseFloat(getComputedStyle(wrap).rowGap) || 12; // entre panel-stage e stage-hint

        // Altura disponível = viewport - paddings - painel - dica - gaps duplos
        const availH = vh - pad.top - pad.bottom - panelH - hintH - gaps * 2;
        const availW = wrapWidth; // já limitado por 92vw/820px no CSS

        const size = Math.max(120, Math.floor(Math.min(availW, availH))); // mínimo seguro
        stage.style.width = size + "px";
        stage.style.height = size + "px";
      }

      // ========= Canvas e jogo =========
      const canvas = document.getElementById("snake");
      const ctx = canvas.getContext("2d");
      const overlay = document.getElementById("overlay");
      const uiScore = document.getElementById("uiScore");
      const uiBest = document.getElementById("uiBest");
      const btnPause = document.getElementById("btnPause");
      const btnRestart = document.getElementById("btnRestart");
      const ovPlay = document.getElementById("ovPlay");
      const ovClose = document.getElementById("ovClose");
      const dpad = document.getElementById("dpad");

      const DPR = Math.max(1, Math.floor(window.devicePixelRatio || 1));
      const COLS = 20,
        ROWS = 20;
      let BOX = 20;

      function fitCanvas() {
        const rect = stage.getBoundingClientRect();
        const size = Math.floor(Math.min(rect.width, rect.height));
        BOX = Math.floor(size / COLS);
        const cssSize = BOX * COLS; // garante múltiplo exato
        canvas.style.width = cssSize + "px";
        canvas.style.height = cssSize + "px";
        canvas.width = cssSize * DPR;
        canvas.height = cssSize * DPR;
        ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
      }

      function fitAll() {
        fitStageBox();
        fitCanvas();
      }

      // Fit inicial
      fitAll();

      // Refit em resize/scroll (mobile muda viewport ao mostrar/ocultar barras)
      let resizeTimeout;
      ["resize", "orientationchange", "scroll"].forEach((evt) => {
        window.addEventListener(
          evt,
          () => {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(() => {
              const oldDir = direction;
              fitAll();
              restart(); // recomeça mantendo tudo visível (mantém simples)
              direction = nextDirection = oldDir || "RIGHT";
            }, 100);
          },
          { passive: true }
        );
      });

      // ========= Estado do jogo =========
      const DIRS = { LEFT: [-1, 0], RIGHT: [1, 0], UP: [0, -1], DOWN: [0, 1] };
      let snake,
        direction,
        nextDirection,
        food,
        score,
        best,
        alive,
        paused,
        timer,
        speed;

      function init() {
        snake = [{ x: Math.floor(COLS / 2) - 1, y: Math.floor(ROWS / 2) }];
        direction = "RIGHT";
        nextDirection = "RIGHT";
        score = 0;
        alive = true;
        paused = false;
        speed = 110;
        spawnFood();
        uiScore.textContent = score;
        best = Number(localStorage.getItem("snake_best") || 0);
        uiBest.textContent = best;
        overlay.classList.remove("show");
        loopStart();
        draw();
      }

      function restart() {
        clearInterval(timer);
        init();
      }

      function spawnFood() {
        while (true) {
          const x = Math.floor(Math.random() * COLS);
          const y = Math.floor(Math.random() * ROWS);
          if (!snake.some((seg) => seg.x === x && seg.y === y)) {
            food = { x, y };
            return;
          }
        }
      }

      // ========= Controles =========
      document.addEventListener("keydown", (e) => {
        const k = e.key;
        if (k === " ") {
          togglePause();
          return;
        }
        if (k === "ArrowLeft" && direction !== "RIGHT") nextDirection = "LEFT";
        if (k === "ArrowRight" && direction !== "LEFT") nextDirection = "RIGHT";
        if (k === "ArrowUp" && direction !== "DOWN") nextDirection = "UP";
        if (k === "ArrowDown" && direction !== "UP") nextDirection = "DOWN";
      });

      dpad.addEventListener(
        "touchstart",
        (e) => {
          const btn = e.target.closest("button[data-dir]");
          if (!btn) return;
          setDir(btn.dataset.dir);
        },
        { passive: true }
      );

      function setDir(dir) {
        if (dir === "LEFT" && direction !== "RIGHT") nextDirection = "LEFT";
        if (dir === "RIGHT" && direction !== "LEFT") nextDirection = "RIGHT";
        if (dir === "UP" && direction !== "DOWN") nextDirection = "UP";
        if (dir === "DOWN" && direction !== "UP") nextDirection = "DOWN";
      }

      // Swipe
      let touchStartX = 0,
        touchStartY = 0,
        touching = false;
      canvas.addEventListener(
        "touchstart",
        (e) => {
          if (!e.touches[0]) return;
          touching = true;
          touchStartX = e.touches[0].clientX;
          touchStartY = e.touches[0].clientY;
        },
        { passive: true }
      );
      canvas.addEventListener(
        "touchend",
        (e) => {
          if (!touching) return;
          touching = false;
          const dx =
            (e.changedTouches[0]?.clientX || touchStartX) - touchStartX;
          const dy =
            (e.changedTouches[0]?.clientY || touchStartY) - touchStartY;
          if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
          if (Math.abs(dx) > Math.abs(dy)) {
            setDir(dx > 0 ? "RIGHT" : "LEFT");
          } else {
            setDir(dy > 0 ? "DOWN" : "UP");
          }
        },
        { passive: true }
      );

      btnPause.addEventListener("click", togglePause);
      btnRestart.addEventListener("click", restart);
      ovPlay.addEventListener("click", restart);
      ovClose.addEventListener("click", () => overlay.classList.remove("show"));

      function togglePause() {
        if (!alive) return;
        paused = !paused;
        btnPause.textContent = paused ? "Retomar" : "Pausar";
        clearInterval(timer);
        if (!paused) {
          overlay.classList.remove("show");
          loopStart();
        } else
          showOverlay(
            "Pausado",
            `Score: ${score}`,
            "Toque em Retomar ou use Espaço"
          );
      }

      // ========= Loop =========
      function loopStart() {
        overlay.classList.remove("show");
        timer = setInterval(step, speed);
      }

      function step() {
        if (!alive || paused) return;

        direction = nextDirection;

        const head = snake[0];
        const [dx, dy] = DIRS[direction];
        const nx = head.x + dx;
        const ny = head.y + dy;

        if (nx < 0 || ny < 0 || nx >= COLS || ny >= ROWS) return gameOver();

        for (let i = 0; i < snake.length; i++) {
          if (snake[i].x === nx && snake[i].y === ny) return gameOver();
        }

        snake.unshift({ x: nx, y: ny });

        if (nx === food.x && ny === food.y) {
          score++;
          uiScore.textContent = score;
          if (score % 5 === 0 && speed > 70) {
            speed -= 5;
            clearInterval(timer);
            loopStart();
          }
          spawnFood();
        } else {
          snake.pop();
        }

        draw();
      }

      // ========= Render =========
      function draw() {
        const W = COLS * BOX,
          H = ROWS * BOX;
        ctx.clearRect(0, 0, W, H);
        drawGrid(W, H, BOX);
        drawFood(food.x * BOX, food.y * BOX, BOX);
        for (let i = snake.length - 1; i >= 0; i--) {
          const seg = snake[i];
          drawSegment(seg.x * BOX, seg.y * BOX, BOX, i === 0);
        }
        // HUD
        ctx.save();
        ctx.fillStyle = "rgba(0,0,0,.35)";
        roundRect(8, 8, 120, 28, 8);
        ctx.fill();
        ctx.fillStyle = "#e6fff6";
        ctx.font = "14px system-ui, -apple-system, Segoe UI, Roboto, Arial";
        ctx.fillText("Score: " + score, 16, 27);
        ctx.restore();
      }

      function drawGrid(W, H, S) {
        ctx.save();
        ctx.fillStyle = "#0f1322";
        ctx.fillRect(0, 0, W, H);
        ctx.strokeStyle = "rgba(255,255,255,.04)";
        ctx.lineWidth = 1;
        for (let x = 0; x <= W; x += S) {
          line(x, 0, x, H);
        }
        for (let y = 0; y <= H; y += S) {
          line(0, y, W, y);
        }
        ctx.restore();
      }

      function drawFood(x, y, s) {
        ctx.save();
        const r = Math.floor(s * 0.38);
        const cx = x + s / 2,
          cy = y + s / 2;
        const g = ctx.createRadialGradient(cx, cy, r * 0.3, cx, cy, r * 1.2);
        g.addColorStop(0, "#fca5a5");
        g.addColorStop(1, "#ef444466");
        ctx.fillStyle = g;
        circle(cx, cy, r);
        ctx.fill();
        ctx.fillStyle = "#ef4444";
        circle(cx, cy, r * 0.75);
        ctx.fill();
        ctx.restore();
      }

      function drawSegment(x, y, s, isHead) {
        ctx.save();
        ctx.shadowColor = isHead ? "#34d39966" : "#10b98133";
        ctx.shadowBlur = isHead ? 18 : 10;
        ctx.fillStyle = isHead ? "#34d399" : "#10b981";
        roundRect(x + 2, y + 2, s - 4, s - 4, Math.floor(s * 0.25));
        ctx.fill();
        ctx.globalAlpha = 0.18;
        ctx.fillStyle = "#ffffff";
        roundRect(
          x + 4,
          y + 4,
          s - 8,
          Math.floor((s - 8) / 3),
          Math.floor(s * 0.18)
        );
        ctx.fill();
        ctx.restore();
      }

      function gameOver() {
        alive = false;
        clearInterval(timer);
        best = Math.max(Number(localStorage.getItem("snake_best") || 0), score);
        localStorage.setItem("snake_best", String(best));
        uiBest.textContent = best;
        showOverlay(
          "Game Over",
          `Score: ${score}`,
          "Toque em “Jogar de novo” para reiniciar"
        );
        btnPause.textContent = "Pausar";
      }

      function showOverlay(title, line1, tip) {
        document.getElementById("ovTitle").textContent = title;
        document.getElementById("ovScore").textContent = line1;
        document.getElementById("ovTip").textContent = tip || "";
        overlay.classList.add("show");
      }

      // helpers
      function line(x1, y1, x2, y2) {
        ctx.beginPath();
        ctx.moveTo(x1 + 0.5, y1 + 0.5);
        ctx.lineTo(x2 + 0.5, y2 + 0.5);
        ctx.stroke();
      }
      function circle(cx, cy, r) {
        ctx.beginPath();
        ctx.arc(cx, cy, r, 0, Math.PI * 2);
        ctx.closePath();
      }
      function roundRect(x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.arcTo(x + w, y, x + w, y + h, r);
        ctx.arcTo(x + w, y + h, x, y + h, r);
        ctx.arcTo(x, y + h, x, y, r);
        ctx.arcTo(x, y, x + w, y, r);
        ctx.closePath();
      }

      // start
      init();
    </script>
  </body>
</html>

Dicas

  • Ambos os jogos funcionam em qualquer navegador moderno.
  • Basta copiar o código para um arquivo .html e abrir.
  • Experimente, modifique e divirta-se!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment