Код IT
← Каталог

Веб-игры на HTML5 и Canvas — 2048

Фрагмент из «Веб-игры на HTML5 и Canvas»: 2048.

HTML main.html
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>2048</title>
  <style>
    * { box-sizing: border-box; }
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      margin: 0;
      background: #faf8ef;
      color: #776e65;
    }
    #header {
      text-align: center;
      margin-bottom: 20px;
    }
    #score, #best {
      font-size: 20px;
      font-weight: bold;
    }
    #grid {
      position: relative;
      width: 400px;
      height: 400px;
      background: #bbada0;
      border-radius: 6px;
      padding: 16px;
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      grid-gap: 16px;
    }
    .cell {
      width: 100%;
      aspect-ratio: 1 / 1;
      background: rgba(238, 228, 218, 0.35);
      border-radius: 6px;
    }
    .tile {
      position: absolute;
      width: calc(25% - 20px);
      height: calc(25% - 20px);
      border-radius: 6px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-weight: bold;
      font-size: 32px;
      transition: all 0.1s ease;
    }
    /* Цвета плиток — по оригиналу */
    .tile-2    { background: #eee4da; color: #776e65; }
    .tile-4    { background: #ede0c8; color: #776e65; }
    .tile-8    { background: #f2b179; color: #f9f6f2; }
    .tile-16   { background: #f59563; color: #f9f6f2; }
    .tile-32   { background: #f67c5f; color: #f9f6f2; }
    .tile-64   { background: #f65e3b; color: #f9f6f2; }
    .tile-128  { background: #edcf72; color: #f9f6f2; font-size: 28px; }
    .tile-256  { background: #edcc61; color: #f9f6f2; font-size: 28px; }
    .tile-512  { background: #edc850; color: #f9f6f2; font-size: 28px; }
    .tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 24px; }
    .tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 24px; }
    #message {
      margin-top: 20px;
      font-size: 24px;
      font-weight: bold;
      height: 30px;
    }
    #restart {
      margin-top: 10px;
      padding: 8px 16px;
      font-size: 16px;
      background: #8f7a66;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div id="header">
    <h1>2048</h1>
    <div>
      <span id="score">Счёт: 0</span> | 
      <span id="best">Рекорд: 0</span>
    </div>
  </div>

  <div id="grid"></div>
  <div id="message"></div>
  <button id="restart">Новая игра</button>

  <script>
    const gridEl = document.getElementById('grid');
    const scoreEl = document.getElementById('score');
    const bestEl = document.getElementById('best');
    const messageEl = document.getElementById('message');
    const restartBtn = document.getElementById('restart');

    let board = Array(4).fill().map(() => Array(4).fill(0));
    let score = 0;
    let best = localStorage.getItem('2048-best') || 0;
    bestEl.textContent = `Рекорд: ${best}`;

    // Инициализация сетки визуальных ячеек (фон)
    for (let i = 0; i < 16; i++) {
      const cell = document.createElement('div');
      cell.className = 'cell';
      gridEl.appendChild(cell);
    }

    function addTile(value, row, col) {
      const tile = document.createElement('div');
      tile.className = `tile tile-${value}`;
      tile.textContent = value;
      tile.style.top = `${row * 25 + 4}%`;
      tile.style.left = `${col * 25 + 4}%`;
      tile.dataset.row = row;
      tile.dataset.col = col;
      gridEl.appendChild(tile);
    }

    function clearTiles() {
      const tiles = gridEl.querySelectorAll('.tile');
      tiles.forEach(t => t.remove());
    }

    function render() {
      clearTiles();
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          if (board[r][c] !== 0) {
            addTile(board[r][c], r, c);
          }
        }
      }
      scoreEl.textContent = `Счёт: ${score}`;
    }

    function addRandomTile() {
      const empty = [];
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          if (board[r][c] === 0) empty.push([r, c]);
        }
      }
      if (empty.length === 0) return false;
      const [r, c] = empty[Math.floor(Math.random() * empty.length)];
      board[r][c] = Math.random() < 0.9 ? 2 : 4;
      return true;
    }

    // Сжатие строки влево (без учёта границ)
    function slide(row) {
      const filtered = row.filter(v => v !== 0);
      const merged = [];
      let i = 0;
      while (i < filtered.length) {
        if (i + 1 < filtered.length && filtered[i] === filtered[i + 1]) {
          const newVal = filtered[i] * 2;
          merged.push(newVal);
          score += newVal;
          i += 2;
        } else {
          merged.push(filtered[i]);
          i += 1;
        }
      }
      while (merged.length < 4) merged.push(0);
      return merged;
    }

    function moveLeft() {
      let moved = false;
      for (let r = 0; r < 4; r++) {
        const original = [...board[r]];
        board[r] = slide(board[r]);
        if (!arraysEqual(original, board[r])) moved = true;
      }
      return moved;
    }

    function moveRight() {
      let moved = false;
      for (let r = 0; r < 4; r++) {
        const original = [...board[r]];
        board[r] = slide([...board[r]].reverse()).reverse();
        if (!arraysEqual(original, board[r])) moved = true;
      }
      return moved;
    }

    function moveUp() {
      let moved = false;
      for (let c = 0; c < 4; c++) {
        const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
        const original = [...col];
        const newCol = slide(col);
        for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
        if (!arraysEqual(original, newCol)) moved = true;
      }
      return moved;
    }

    function moveDown() {
      let moved = false;
      for (let c = 0; c < 4; c++) {
        const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
        const original = [...col];
        const newCol = slide([...col].reverse()).reverse();
        for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
        if (!arraysEqual(original, newCol)) moved = true;
      }
      return moved;
    }

    function arraysEqual(a, b) {
      return a.length === b.length && a.every((v, i) => v === b[i]);
    }

    function canMove() {
      // Есть пустые ячейки?
      for (let r = 0; r < 4; r++)
        for (let c = 0; c < 4; c++)
          if (board[r][c] === 0) return true;

      // Есть соседи для слияния?
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          const v = board[r][c];
          if (
            (r > 0 && board[r - 1][c] === v) ||
            (r < 3 && board[r + 1][c] === v) ||
            (c > 0 && board[r][c - 1] === v) ||
            (c < 3 && board[r][c + 1] === v)
          ) return true;
        }
      }
      return false;
    }

    function gameOver() {
      messageEl.textContent = 'Игра окончена';
      if (score > best) {
        best = score;
        localStorage.setItem('2048-best', best);
        bestEl.textContent = `Рекорд: ${best}`;
      }
    }

    function winCheck() {
      for (let r = 0; r < 4; r++)
        for (let c = 0; c < 4; c++)
          if (board[r][c] === 2048) {
            messageEl.textContent = 'Победа!';
            return true;
          }
      return false;
    }

    function handleMove(moved) {
      if (!moved) return;
      if (addRandomTile()) {
        render();
        if (!canMove()) gameOver();
        else winCheck();
      } else {
        gameOver();
      }
    }

    function resetGame() {
      board = Array(4).fill().map(() => Array(4).fill(0));
      score = 0;
      messageEl.textContent = '';
      addRandomTile();
      addRandomTile();
      render();
    }

    window.addEventListener('keydown', e => {
      if (messageEl.textContent) return; // игнорируем ввод после конца
      let moved = false;
      switch (e.key) {
        case 'ArrowLeft':  moved = moveLeft();  break;
        case 'ArrowRight': moved = moveRight(); break;
        case 'ArrowUp':    moved = moveUp();    break;
        case 'ArrowDown':  moved = moveDown();  break;
        case 'a': case 'A': moved = moveLeft();  break;
        case 'd': case 'D': moved = moveRight(); break;
        case 'w': case 'W': moved = moveUp();    break;
        case 's': case 'S': moved = moveDown();  break;
      }
      if (moved) handleMove(true);
    });

    restartBtn.addEventListener('click', resetGame);

    // Запуск
    resetGame();
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>2048</title>
  <style>
    * { box-sizing: border-box; }
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      margin: 0;
      background: #faf8ef;
      color: #776e65;
    }
    #header {
      text-align: center;
      margin-bottom: 20px;
    }
    #score, #best {
      font-size: 20px;
      font-weight: bold;
    }
    #grid {
      position: relative;
      width: 400px;
      height: 400px;
      background: #bbada0;
      border-radius: 6px;
      padding: 16px;
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      grid-gap: 16px;
    }
    .cell {
      width: 100%;
      aspect-ratio: 1 / 1;
      background: rgba(238, 228, 218, 0.35);
      border-radius: 6px;
    }
    .tile {
      position: absolute;
      width: calc(25% - 20px);
      height: calc(25% - 20px);
      border-radius: 6px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-weight: bold;
      font-size: 32px;
      transition: all 0.1s ease;
    }
    /* Цвета плиток — по оригиналу */
    .tile-2    { background: #eee4da; color: #776e65; }
    .tile-4    { background: #ede0c8; color: #776e65; }
    .tile-8    { background: #f2b179; color: #f9f6f2; }
    .tile-16   { background: #f59563; color: #f9f6f2; }
    .tile-32   { background: #f67c5f; color: #f9f6f2; }
    .tile-64   { background: #f65e3b; color: #f9f6f2; }
    .tile-128  { background: #edcf72; color: #f9f6f2; font-size: 28px; }
    .tile-256  { background: #edcc61; color: #f9f6f2; font-size: 28px; }
    .tile-512  { background: #edc850; color: #f9f6f2; font-size: 28px; }
    .tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 24px; }
    .tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 24px; }
    #message {
      margin-top: 20px;
      font-size: 24px;
      font-weight: bold;
      height: 30px;
    }
    #restart {
      margin-top: 10px;
      padding: 8px 16px;
      font-size: 16px;
      background: #8f7a66;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div id="header">
    <h1>2048</h1>
    <div>
      <span id="score">Счёт: 0</span> | 
      <span id="best">Рекорд: 0</span>
    </div>
  </div>

  <div id="grid"></div>
  <div id="message"></div>
  <button id="restart">Новая игра</button>

  <script>
    const gridEl = document.getElementById('grid');
    const scoreEl = document.getElementById('score');
    const bestEl = document.getElementById('best');
    const messageEl = document.getElementById('message');
    const restartBtn = document.getElementById('restart');

    let board = Array(4).fill().map(() => Array(4).fill(0));
    let score = 0;
    let best = localStorage.getItem('2048-best') || 0;
    bestEl.textContent = `Рекорд: ${best}`;

    // Инициализация сетки визуальных ячеек (фон)
    for (let i = 0; i < 16; i++) {
      const cell = document.createElement('div');
      cell.className = 'cell';
      gridEl.appendChild(cell);
    }

    function addTile(value, row, col) {
      const tile = document.createElement('div');
      tile.className = `tile tile-${value}`;
      tile.textContent = value;
      tile.style.top = `${row * 25 + 4}%`;
      tile.style.left = `${col * 25 + 4}%`;
      tile.dataset.row = row;
      tile.dataset.col = col;
      gridEl.appendChild(tile);
    }

    function clearTiles() {
      const tiles = gridEl.querySelectorAll('.tile');
      tiles.forEach(t => t.remove());
    }

    function render() {
      clearTiles();
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          if (board[r][c] !== 0) {
            addTile(board[r][c], r, c);
          }
        }
      }
      scoreEl.textContent = `Счёт: ${score}`;
    }

    function addRandomTile() {
      const empty = [];
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          if (board[r][c] === 0) empty.push([r, c]);
        }
      }
      if (empty.length === 0) return false;
      const [r, c] = empty[Math.floor(Math.random() * empty.length)];
      board[r][c] = Math.random() < 0.9 ? 2 : 4;
      return true;
    }

    // Сжатие строки влево (без учёта границ)
    function slide(row) {
      const filtered = row.filter(v => v !== 0);
      const merged = [];
      let i = 0;
      while (i < filtered.length) {
        if (i + 1 < filtered.length && filtered[i] === filtered[i + 1]) {
          const newVal = filtered[i] * 2;
          merged.push(newVal);
          score += newVal;
          i += 2;
        } else {
          merged.push(filtered[i]);
          i += 1;
        }
      }
      while (merged.length < 4) merged.push(0);
      return merged;
    }

    function moveLeft() {
      let moved = false;
      for (let r = 0; r < 4; r++) {
        const original = [...board[r]];
        board[r] = slide(board[r]);
        if (!arraysEqual(original, board[r])) moved = true;
      }
      return moved;
    }

    function moveRight() {
      let moved = false;
      for (let r = 0; r < 4; r++) {
        const original = [...board[r]];
        board[r] = slide([...board[r]].reverse()).reverse();
        if (!arraysEqual(original, board[r])) moved = true;
      }
      return moved;
    }

    function moveUp() {
      let moved = false;
      for (let c = 0; c < 4; c++) {
        const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
        const original = [...col];
        const newCol = slide(col);
        for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
        if (!arraysEqual(original, newCol)) moved = true;
      }
      return moved;
    }

    function moveDown() {
      let moved = false;
      for (let c = 0; c < 4; c++) {
        const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
        const original = [...col];
        const newCol = slide([...col].reverse()).reverse();
        for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
        if (!arraysEqual(original, newCol)) moved = true;
      }
      return moved;
    }

    function arraysEqual(a, b) {
      return a.length === b.length && a.every((v, i) => v === b[i]);
    }

    function canMove() {
      // Есть пустые ячейки?
      for (let r = 0; r < 4; r++)
        for (let c = 0; c < 4; c++)
          if (board[r][c] === 0) return true;

      // Есть соседи для слияния?
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          const v = board[r][c];
          if (
            (r > 0 && board[r - 1][c] === v) ||
            (r < 3 && board[r + 1][c] === v) ||
            (c > 0 && board[r][c - 1] === v) ||
            (c < 3 && board[r][c + 1] === v)
          ) return true;
        }
      }
      return false;
    }

    function gameOver() {
      messageEl.textContent = 'Игра окончена';
      if (score > best) {
        best = score;
        localStorage.setItem('2048-best', best);
        bestEl.textContent = `Рекорд: ${best}`;
      }
    }

    function winCheck() {
      for (let r = 0; r < 4; r++)
        for (let c = 0; c < 4; c++)
          if (board[r][c] === 2048) {
            messageEl.textContent = 'Победа!';
            return true;
          }
      return false;
    }

    function handleMove(moved) {
      if (!moved) return;
      if (addRandomTile()) {
        render();
        if (!canMove()) gameOver();
        else winCheck();
      } else {
        gameOver();
      }
    }

    function resetGame() {
      board = Array(4).fill().map(() => Array(4).fill(0));
      score = 0;
      messageEl.textContent = '';
      addRandomTile();
      addRandomTile();
      render();
    }

    window.addEventListener('keydown', e => {
      if (messageEl.textContent) return; // игнорируем ввод после конца
      let moved = false;
      switch (e.key) {
        case 'ArrowLeft':  moved = moveLeft();  break;
        case 'ArrowRight': moved = moveRight(); break;
        case 'ArrowUp':    moved = moveUp();    break;
        case 'ArrowDown':  moved = moveDown();  break;
        case 'a': case 'A': moved = moveLeft();  break;
        case 'd': case 'D': moved = moveRight(); break;
        case 'w': case 'W': moved = moveUp();    break;
        case 's': case 'S': moved = moveDown();  break;
      }
      if (moved) handleMove(true);
    });

    restartBtn.addEventListener('click', resetGame);

    // Запуск
    resetGame();
  </script>
</body>
</html>