diff --git a/src/index.html b/src/index.html index 513269a6..cbfdcb15 100644 --- a/src/index.html +++ b/src/index.html @@ -21,49 +21,106 @@

2048

Score: 0

- + + - - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + +
+

- Press "Start" to begin the game. Good luck! + Press "Start" to begin game. Good luck!

diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index f6afd2eb..5cf5f842 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -1,6 +1,4 @@ -/* eslint-disable no-shadow */ -/* eslint-disable max-len */ -/* eslint-disable prefer-const */ +/* eslint-disable prettier/prettier */ 'use strict'; /** @@ -10,190 +8,148 @@ */ class Game { constructor(initialState = null) { - this.grid = initialState || this.createEmptyGrid(); + this.boardSize = 4; // Fixed 4x4 grid + this.board = initialState || this.createEmptyBoard(); this.score = 0; - this.status = 'game-start'; // 'game-start', 'game-over', 'game-win' - this.won = false; - this.addRandomTile(); // Начинаем с случайной плитки + this.status = 'idle'; // Possible values: 'idle', 'playing', 'win', 'lose' } - // Создание пустого поля 4x4 - createEmptyGrid() { - return [ - [null, null, null, null], - [null, null, null, null], - [null, null, null, null], - [null, null, null, null], - ]; + createEmptyBoard() { + return Array(this.boardSize) + .fill() + .map(() => Array(this.boardSize).fill(0)); } - // Получить состояние игры (поле и счет) getState() { - return this.grid; + return this.board; } - // Получить текущий счет getScore() { return this.score; } - // Получить статус игры ('game-start', 'game-over', 'game-win') getStatus() { return this.status; } - // Начать игру заново (сбросить поле и счет) start() { - this.grid = this.createEmptyGrid(); + this.board = this.createEmptyBoard(); this.score = 0; - this.status = 'game-start'; - this.won = false; + this.status = 'playing'; + this.addRandomTile(); this.addRandomTile(); - this.addRandomTile(); // Две стартовые плитки } - // Перезапуск игры (сброс) restart() { this.start(); } - // Добавить случайную плитку (2 или 4) addRandomTile() { - let emptyCells = []; + const emptyCells = []; - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - if (this.grid[row][col] === null) { - emptyCells.push([row, col]); + for (let r = 0; r < this.boardSize; r++) { + for (let c = 0; c < this.boardSize; c++) { + if (this.board[r][c] === 0) { + emptyCells.push([r, c]); } } } - if (emptyCells.length === 0) { - return; - } - - const [row, col] = - emptyCells[Math.floor(Math.random() * emptyCells.length)]; + if (emptyCells.length > 0) { + const [row, col] = + emptyCells[Math.floor(Math.random() * emptyCells.length)]; - this.grid[row][col] = Math.random() < 0.1 ? 4 : 2; + this.board[row][col] = Math.random() < 0.9 ? 2 : 4; + } } - // Обработать перемещение клеток в заданном направлении - move(direction) { - let moved = false; + slide(row) { + const filteredRow = row.filter((num) => num !== 0); // Remove all zeros + const newRow = []; - if (direction === 'left') { - for (let row = 0; row < 4; row++) { - moved = this.moveRowLeft(row) || moved; - } - } else if (direction === 'right') { - for (let row = 0; row < 4; row++) { - moved = this.moveRowRight(row) || moved; - } - } else if (direction === 'up') { - for (let col = 0; col < 4; col++) { - moved = this.moveColumnUp(col) || moved; - } - } else if (direction === 'down') { - for (let col = 0; col < 4; col++) { - moved = this.moveColumnDown(col) || moved; + while (filteredRow.length > 0) { + if (filteredRow.length > 1 && filteredRow[0] === filteredRow[1]) { + newRow.push(filteredRow[0] * 2); + this.score += filteredRow[0] * 2; + filteredRow.splice(0, 2); // Remove the merged cells + } else { + newRow.push(filteredRow[0]); + filteredRow.splice(0, 1); } } - if (moved) { - this.addRandomTile(); // Добавляем новую случайную плитку после хода - } + while (newRow.length < this.boardSize) { + newRow.push(0); + } // Fill with zeros - return moved; + return newRow; } - // Перемещение строки влево - moveRowLeft(row) { - let changed = false; - let newRow = this.grid[row].filter((val) => val !== null); // Убираем пустые значения + moveLeft() { + const oldBoard = JSON.stringify(this.board); - for (let i = 0; i < newRow.length - 1; i++) { - if (newRow[i] === newRow[i + 1]) { - newRow[i] = newRow[i] * 2; - this.score += newRow[i]; - newRow[i + 1] = null; - changed = true; - } - } - newRow = newRow.filter((val) => val !== null); // Убираем пустые значения + this.board = this.board.map((row) => this.slide(row)); - while (newRow.length < 4) { - newRow.push(null); // Дополняем до 4 элементов + if (JSON.stringify(this.board) !== oldBoard) { + this.addRandomTile(); + this.checkGameState(); } - this.grid[row] = newRow; - - return changed; } - // Перемещение строки вправо (обратный порядок) - moveRowRight(row) { - this.grid[row] = this.grid[row].reverse(); - - const changed = this.moveRowLeft(row); + moveRight() { + const oldBoard = JSON.stringify(this.board); - this.grid[row] = this.grid[row].reverse(); + this.board = this.board.map((row) => this.slide(row.reverse()).reverse()); - return changed; + if (JSON.stringify(this.board) !== oldBoard) { + this.addRandomTile(); + this.checkGameState(); + } } - // Перемещение столбца вверх - moveColumnUp(col) { - let column = this.grid.map((row) => row[col]); - let changed = this.moveRowLeft(column); - - for (let i = 0; i < 4; i++) { - this.grid[i][col] = column[i]; - } + moveUp() { + this.transposeBoard(); + this.moveLeft(); + this.transposeBoard(); + } - return changed; + moveDown() { + this.transposeBoard(); + this.moveRight(); + this.transposeBoard(); } - // Перемещение столбца вниз (обратный порядок) - moveColumnDown(col) { - let column = this.grid.map((row) => row[col]).reverse(); - let changed = this.moveRowLeft(column); + transposeBoard() { + this.board = this.board[0].map((_, colIndex) => + this.board.map((row) => row[colIndex])); + } - for (let i = 0; i < 4; i++) { - this.grid[i][col] = column[3 - i]; + checkGameState() { + if (this.board.flat().includes(2048)) { + this.status = 'win'; + } else if (!this.canMove()) { + this.status = 'lose'; } - - return changed; } - // Проверка на окончание игры (нет доступных ходов) - checkGameOver() { - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - if (this.grid[row][col] === null) { - return false; - } - - if (row < 3 && this.grid[row][col] === this.grid[row + 1][col]) { - return false; - } + canMove() { + // Check for empty cells + if (this.board.flat().includes(0)) { + return true; + } - if (col < 3 && this.grid[row][col] === this.grid[row][col + 1]) { - return false; + // Check for possible merges + for (let r = 0; r < this.boardSize; r++) { + for (let c = 0; c < this.boardSize - 1; c++) { + if (this.board[r][c] === this.board[r][c + 1]) { + return true; } } } - return true; - } - - // Проверка на победу (2048 в любой клетке) - checkWin() { - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - if (this.grid[row][col] === 2048) { - this.status = 'game-win'; - + for (let c = 0; c < this.boardSize; c++) { + for (let r = 0; r < this.boardSize - 1; r++) { + if (this.board[r][c] === this.board[r + 1][c]) { return true; } } @@ -201,34 +157,6 @@ class Game { return false; } - - // Обработка нажатий клавиш - handleKeyPress(event) { - let moved = false; - - switch (event.key) { - case 'ArrowLeft': - moved = this.move('left'); - break; - case 'ArrowRight': - moved = this.move('right'); - break; - case 'ArrowUp': - moved = this.move('up'); - break; - case 'ArrowDown': - moved = this.move('down'); - break; - } - - if (moved) { - if (this.checkWin()) { - this.status = 'game-win'; - } else if (this.checkGameOver()) { - this.status = 'game-over'; - } - } - } } -export default Game; +module.exports = Game; diff --git a/src/scripts/main.js b/src/scripts/main.js index 8a80398c..252290ba 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,69 +1,73 @@ +/* eslint-disable function-paren-newline */ +/* eslint-disable no-console */ /* eslint-disable no-shadow */ 'use strict'; import Game from '../modules/Game.class.js'; -document.addEventListener('DOMContentLoaded', () => { - const game = new Game(); +const game = new Game(); +const field = document.querySelector('.game-field'); +const scoreDisplay = document.querySelector('.game-score'); +const startButton = document.querySelector('#ButtonStart'); +const messages = { + start: document.querySelector('.message-start'), + win: document.querySelector('.message-win'), + lose: document.querySelector('.message-lose'), +}; - const startButton = document.querySelector('.button.start'); - const restartButton = document.querySelector('.button.restart'); - const scoreElement = document.querySelector('.game-score'); - const messageLose = document.querySelector('.message-lose'); - const messageWin = document.querySelector('.message-win'); - const messageStart = document.querySelector('.message-start'); - const gameField = document.querySelector('.game-field'); +function renderBoard() { + const state = game.getState(); - function updateUI() { - // Обновление счета - scoreElement.textContent = game.getScore(); + field.querySelectorAll('.field-cell').forEach((cell) => { + const [row, col] = cell.dataset.position.split('-').map(Number); + const value = state[row][col]; - // Проверка на выигрыш или окончание игры - if (game.getStatus() === 'game-win') { - messageLose.classList.add('hidden'); - messageStart.classList.add('hidden'); - messageWin.classList.remove('hidden'); - } else if (game.getStatus() === 'game-over') { - messageWin.classList.add('hidden'); - messageStart.classList.add('hidden'); - messageLose.classList.remove('hidden'); - } + cell.textContent = value > 0 ? value : ''; + cell.className = `field-cell ${value > 0 ? `field-cell--${value}` : ''}`; + }); + scoreDisplay.textContent = game.getScore(); +} - // Обновление игрового поля - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - const cell = gameField.rows[row].cells[col]; - const value = game.getState()[row][col]; +function showMessage(type) { + Object.values(messages).forEach((msg) => msg.classList.add('hidden')); + messages[type].classList.remove('hidden'); +} - if (value) { - cell.textContent = value; - cell.classList.add(`field-cell--${value}`); - } else { - cell.textContent = ''; - cell.classList.remove(/field-cell--\d+/); - } - } - } +function handleMove(key) { + if (game.getStatus() !== 'playing') { + return; } - startButton.addEventListener('click', () => { - game.start(); - startButton.classList.add('hidden'); - updateUI(); - }); + if (key === 'ArrowLeft') { + game.moveLeft(); + } - restartButton.addEventListener('click', () => { - game.restart(); - updateUI(); - }); + if (key === 'ArrowRight') { + game.moveRight(); + } - // Слушаем нажатия клавиш со стрелками - document.addEventListener('keydown', (event) => { - if (game.getStatus() === 'game-start') { - return; - } - game.handleKeyPress(event); - updateUI(); - }); + if (key === 'ArrowUp') { + game.moveUp(); + } - updateUI(); + if (key === 'ArrowDown') { + game.moveDown(); + } + renderBoard(); + + if (game.getStatus() === 'win') { + showMessage('win'); + } + + if (game.getStatus() === 'lose') { + showMessage('lose'); + } +} + +startButton.addEventListener('click', () => { + game.start(); + renderBoard(); + showMessage(''); + startButton.textContent = 'Restart'; }); + +document.addEventListener('keydown', (event) => handleMove(event.key));