From 1d2d9500214a832393ebce04122c6c32c12c19f7 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 11 Oct 2024 20:14:29 +0300 Subject: [PATCH] add task solution --- README.md | 2 +- src/index.html | 5 +- src/modules/Game.class.js | 385 ++++++++++++++++++++++++++++++++++++-- src/scripts/main.js | 28 ++- 4 files changed, 399 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 5aab92544..d7ab2cfc0 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ You can change the HTML/CSS layout if you need it. ## Deploy and Pull Request 1. Replace `` with your Github username in the link - - [DEMO LINK](https://.github.io/js_2048_game/) + - [DEMO LINK](https://19Eduard99.github.io/js_2048_game/) 2. Follow [this instructions](https://mate-academy.github.io/layout_task-guideline/) - Run `npm run test` command to test your code; - Run `npm run test:only -- -n` to run fast test ignoring linter; diff --git a/src/index.html b/src/index.html index aff3d1a98..cdfcfd518 100644 --- a/src/index.html +++ b/src/index.html @@ -65,6 +65,9 @@

2048

- + diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index 65cd219c9..cd127c287 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -22,47 +22,402 @@ class Game { */ constructor(initialState) { // eslint-disable-next-line no-console - console.log(initialState); + this.board = initialState; + this.score = 0; + this.button = document.querySelector('.button'); + this.isStarted = false; + this.handleClick = this.startOrRestart.bind(this); + this.button.addEventListener('click', this.handleClick); + this.tableCells = document.querySelectorAll('.field-cell'); } - moveLeft() {} - moveRight() {} - moveUp() {} - moveDown() {} + moveLeft() { + let boardChanged = false; + + for (let row = 0; row < this.board.length; row++) { + const currentRow = this.board[row]; + + let newRow = currentRow.filter((value) => value !== 0); + + for (let i = 0; i < newRow.length - 1; i++) { + if (newRow[i] === newRow[i + 1]) { + newRow[i] = newRow[i] * 2; + newRow[i + 1] = 0; + this.score += newRow[i]; + boardChanged = true; + } + } + + newRow = newRow.filter((value) => value !== 0); + + while (newRow.length < 4) { + newRow.push(0); + } + + this.board[row] = newRow; + + if (JSON.stringify(currentRow) !== JSON.stringify(newRow)) { + boardChanged = true; + } + } + + if (boardChanged) { + this.placeRandomNumber(this.board); + this.renderBoard(); + this.renderScore(); + } + + this.has2048Tile(); + + if (this.checkLose()) { + this.showLoseMessage(); + } + } + + moveRight() { + let boardChanged = false; + + for (let row = 0; row < this.board.length; row++) { + const currentRow = this.board[row]; + + let newRow = currentRow.filter((value) => value !== 0); + + for (let i = newRow.length - 1; i > 0; i--) { + if (newRow[i] === newRow[i - 1]) { + newRow[i] = newRow[i] * 2; + newRow[i - 1] = 0; + this.score += newRow[i]; + boardChanged = true; + } + } + + newRow = newRow.filter((value) => value !== 0); + + while (newRow.length < 4) { + newRow.unshift(0); + } + + this.board[row] = newRow; + + if (JSON.stringify(currentRow) !== JSON.stringify(newRow)) { + boardChanged = true; + } + } + + if (boardChanged) { + this.placeRandomNumber(this.board); + this.renderBoard(); + this.renderScore(); + } + + this.has2048Tile(); + + if (this.checkLose()) { + this.showLoseMessage(); + } + } + + moveUp() { + let boardChanged = false; + + this.board = this.transpose(this.board); + + for (let row = 0; row < this.board.length; row++) { + const currentRow = this.board[row]; + + let newRow = currentRow.filter((value) => value !== 0); + + for (let i = 0; i < newRow.length - 1; i++) { + if (newRow[i] === newRow[i + 1]) { + newRow[i] = newRow[i] * 2; + newRow[i + 1] = 0; + this.score += newRow[i]; + boardChanged = true; + } + } + + newRow = newRow.filter((value) => value !== 0); + + while (newRow.length < 4) { + newRow.push(0); + } + + this.board[row] = newRow; + + if (JSON.stringify(currentRow) !== JSON.stringify(newRow)) { + boardChanged = true; + } + } + + this.board = this.transpose(this.board); + + if (boardChanged) { + this.placeRandomNumber(this.board); + this.renderBoard(); + this.renderScore(); + } + + this.has2048Tile(); + + if (this.checkLose()) { + this.showLoseMessage(); + } + } + + moveDown() { + let boardChanged = false; + + this.board = this.transpose(this.board); + + for (let row = 0; row < this.board.length; row++) { + const currentRow = this.board[row]; + + let newRow = currentRow.filter((value) => value !== 0); + + for (let i = newRow.length - 1; i > 0; i--) { + if (newRow[i] === newRow[i - 1]) { + newRow[i] = newRow[i] * 2; + newRow[i - 1] = 0; + this.score += newRow[i]; + boardChanged = true; + } + } + + newRow = newRow.filter((value) => value !== 0); + + while (newRow.length < 4) { + newRow.unshift(0); + } + + this.board[row] = newRow; + + if (JSON.stringify(currentRow) !== JSON.stringify(newRow)) { + boardChanged = true; + } + } + + this.board = this.transpose(this.board); + + if (boardChanged) { + this.placeRandomNumber(this.board); + this.renderBoard(); + this.renderScore(); + } + + this.has2048Tile(); + + if (this.checkLose()) { + this.showLoseMessage(); + } + } + + transpose(board) { + return board[0].map((_, colIndex) => board.map((row) => row[colIndex])); + } /** * @returns {number} */ - getScore() {} + getScore() { + const scoreElement = document.querySelector('.game-score'); + + scoreElement.textContent = this.score; + + return this.score; + } /** * @returns {number[][]} */ - getState() {} + getState() { + return this.board; + } /** * Returns the current game status. * * @returns {string} One of: 'idle', 'playing', 'win', 'lose' * - * `idle` - the game has not started yet (the initial state); - * `playing` - the game is in progress; - * `win` - the game is won; - * `lose` - the game is lost + * idle - the game has not started yet (the initial state); + * playing - the game is in progress; + * win - the game is won; + * lose - the game is lost */ - getStatus() {} + + getStatus() { + if (!this.isStarted) { + return 'idle'; + } else if (this.checkWin()) { + return 'win'; + } else if (this.checkLose()) { + return 'lose'; + } else { + return 'playing'; + } + } + + startOrRestart() { + if (this.isStarted) { + this.restart(); + } else { + this.start(); + } + } /** * Starts the game. */ - start() {} + start() { + const messageElement = document.querySelector('.message.message-start'); + + messageElement.textContent = + 'The game has started! Use the arrow keys to play.'; + + this.button.classList.remove('start'); + this.button.classList.add('restart'); + this.button.textContent = 'Restart'; + this.isStarted = true; + this.placeRandomNumber(this.board); + this.placeRandomNumber(this.board); + this.renderBoard(); + } /** * Resets the game. */ - restart() {} + restart() { + const messageElement = document.querySelector('.message.message-start'); + const messageLoseElement = document.querySelector('.message-lose'); + const gameScore = document.querySelector('.game-score'); + + if (!messageLoseElement.classList.contains('hidden')) { + messageLoseElement.classList.add('hidden'); + } + + messageElement.textContent = 'Press "Start" to begin game. Good luck!'; + messageElement.classList.remove('hidden'); + this.button.classList.remove('restart'); + this.button.classList.add('start'); + this.button.textContent = 'Start'; + this.isStarted = false; + + this.board = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + + this.score = 0; + gameScore.textContent = 0; + this.renderBoard(); + } + + getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; + } + + placeRandomNumber(board) { + const emptyCells = []; - // Add your own methods here + for (let row = 0; row < board.length; row++) { + for (let col = 0; col < board[row].length; col++) { + if (board[row][col] === 0) { + emptyCells.push([row, col]); + } + } + } + + if (emptyCells.length > 0) { + const randomIndex = this.getRandomInt(0, emptyCells.length); + const [randomRow, randomCol] = emptyCells[randomIndex]; + + board[randomRow][randomCol] = Math.random() > 0.1 ? 2 : 4; + } + } + + renderBoard() { + this.tableCells.forEach((cell, index) => { + const row = Math.floor(index / 4); + const col = index % 4; + + if (this.board[row][col] !== 0) { + cell.textContent = this.board[row][col]; + cell.className = `field-cell field-cell--${this.board[row][col]}`; + } else { + cell.textContent = ''; + cell.className = 'field-cell'; + } + }); + } + + renderScore() { + const scoreElement = document.querySelector('.game-score'); + + scoreElement.textContent = this.score; + } + + has2048Tile() { + for (let row = 0; row < this.board.length; row++) { + for (let col = 0; col < this.board[row].length; col++) { + if (this.board[row][col] >= 2048) { + return this.showWinMessage(); + } + } + } + } + + showWinMessage() { + const startMessage = document.querySelector('.message.message-start'); + const winMessage = document.querySelector('.message.message-win'); + const loseMessage = document.querySelector('.message.message-lose'); + + startMessage.classList.add('hidden'); + loseMessage.classList.add('hidden'); + + winMessage.classList.remove('hidden'); + } + + showLoseMessage() { + const startMessage = document.querySelector('.message.message-start'); + const winMessage = document.querySelector('.message.message-win'); + const loseMessage = document.querySelector('.message.message-lose'); + + startMessage.classList.add('hidden'); + winMessage.classList.add('hidden'); + + loseMessage.classList.remove('hidden'); + } + + checkLose() { + for (let row = 0; row < this.board.length; row++) { + for (let col = 0; col < this.board[row].length; col++) { + if (this.board[row][col] === 0) { + return false; + } + } + } + + for (let row = 0; row < this.board.length; row++) { + for (let col = 0; col < this.board[row].length; col++) { + if ( + col < this.board[row].length - 1 && + this.board[row][col] === this.board[row][col + 1] + ) { + return false; + } + + if ( + row < this.board.length - 1 && + this.board[row][col] === this.board[row + 1][col] + ) { + return false; + } + } + } + + return true; + } } module.exports = Game; diff --git a/src/scripts/main.js b/src/scripts/main.js index dc7f045a3..87578e4f1 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,7 +1,27 @@ 'use strict'; -// Uncomment the next lines to use your game instance in the browser -// const Game = require('../modules/Game.class'); -// const game = new Game(); +const Game = require('../modules/Game.class'); +const initialState = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], +]; +const game = new Game(initialState); -// Write your code here +document.addEventListener('keydown', (e) => { + switch (e.key) { + case 'ArrowLeft': + game.moveLeft(); + break; + case 'ArrowRight': + game.moveRight(); + break; + case 'ArrowUp': + game.moveUp(); + break; + case 'ArrowDown': + game.moveDown(); + break; + } +});