From d0563e4d2097b854386979176f00b4475f66196c Mon Sep 17 00:00:00 2001 From: Cirillo Negruzzi Date: Wed, 12 Feb 2025 15:54:27 +0100 Subject: [PATCH 1/3] solution --- README.md | 2 +- src/images/favicon.png | Bin 0 -> 14801 bytes src/index.html | 10 +- src/modules/Game.class.js | 266 +++++++++++++++++++++++++++++++------- src/scripts/main.js | 84 +++++++++++- src/styles/main.scss | 9 +- 6 files changed, 318 insertions(+), 53 deletions(-) create mode 100644 src/images/favicon.png diff --git a/README.md b/README.md index 5aab92544..4ba1b64ae 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://CNegruzzi.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/images/favicon.png b/src/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..e804423bbf405d28d7b6eee6654ad1f63f07a8b2 GIT binary patch literal 14801 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 Gq5lt;qy-)T literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index aff3d1a98..e5ecdae4d 100644 --- a/src/index.html +++ b/src/index.html @@ -11,6 +11,11 @@ rel="stylesheet" href="./styles/main.scss" /> +
@@ -65,6 +70,9 @@

2048

- + diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index 65cd219c9..fadc06393 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -1,44 +1,159 @@ 'use strict'; - -/** - * This class represents the game. - * Now it has a basic structure, that is needed for testing. - * Feel free to add more props and methods if needed. - */ class Game { - /** - * Creates a new game instance. - * - * @param {number[][]} initialState - * The initial state of the board. - * @default - * [[0, 0, 0, 0], - * [0, 0, 0, 0], - * [0, 0, 0, 0], - * [0, 0, 0, 0]] - * - * If passed, the board will be initialized with the provided - * initial state. - */ - constructor(initialState) { - // eslint-disable-next-line no-console - console.log(initialState); + constructor( + initialState = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ], + ) { + this.board = initialState; + this.score = 0; + this.status = 'idle'; } - moveLeft() {} - moveRight() {} - moveUp() {} - moveDown() {} + moveLeft() { + const shiftAndFilter = (array) => { + const filteredArray = array.filter((cell) => cell !== 0); - /** - * @returns {number} - */ - getScore() {} + while (filteredArray.length < 4) { + filteredArray.push(0); + } - /** - * @returns {number[][]} - */ - getState() {} + return filteredArray; + }; + + for (let row = 0; row < 4; row++) { + this.board[row] = shiftAndFilter(this.board[row]); + + for (let col = 0; col < 3; col++) { + if (this.board[row][col] === this.board[row][col + 1]) { + this.board[row][col] += this.board[row][col + 1]; + this.board[row][col + 1] = 0; + this.score += this.board[row][col]; + + col++; + } + } + + this.board[row] = shiftAndFilter(this.board[row]); + } + } + + moveRight() { + const shiftAndFilter = (array) => { + const filteredArray = array.filter((cell) => cell !== 0); + + while (filteredArray.length < 4) { + filteredArray.unshift(0); + } + + return filteredArray; + }; + + for (let row = 0; row < 4; row++) { + this.board[row] = shiftAndFilter(this.board[row]); + + for (let col = 0; col < 3; col++) { + if (this.board[row][col] === this.board[row][col + 1]) { + this.board[row][col] += this.board[row][col + 1]; + this.board[row][col + 1] = 0; + this.score += this.board[row][col]; + + col++; + } + } + + this.board[row] = shiftAndFilter(this.board[row]); + } + } + + moveUp() { + const shiftAndFilter = (array) => { + const filteredArray = array.filter((cell) => cell !== 0); + + while (filteredArray.length < 4) { + filteredArray.push(0); + } + + return filteredArray; + }; + + for (let col = 0; col < 4; col++) { + let column = []; + + for (let row = 0; row < 4; row++) { + column.push(this.board[row][col]); + } + + column = shiftAndFilter(column); + + for (let r = 0; r < 3; r++) { + if (column[r] === column[r + 1]) { + column[r] += column[r + 1]; + column[r + 1] = 0; + this.score += column[r]; + + r++; + } + } + + column = shiftAndFilter(column); + + for (let rowIndex = 0; rowIndex < 4; rowIndex++) { + this.board[rowIndex][col] = column[rowIndex]; + } + } + } + + moveDown() { + const shiftAndFilter = (array) => { + const filteredArray = array.filter((cell) => cell !== 0); + + while (filteredArray.length < 4) { + filteredArray.push(0); + } + + return filteredArray; + }; + + for (let col = 0; col < 4; col++) { + let column = []; + + for (let row = 0; row < 4; row++) { + column.push(this.board[row][col]); + } + + column.reverse(); + column = shiftAndFilter(column); + + for (let r = 0; r < 3; r++) { + if (column[r] === column[r + 1]) { + column[r] += column[r + 1]; + column[r + 1] = 0; + this.score += column[r]; + + r++; + } + } + + column = shiftAndFilter(column); + column.reverse(); + + for (let rowIndex = 0; rowIndex < 4; rowIndex++) { + this.board[rowIndex][col] = column[rowIndex]; + } + } + } + + getScore() { + return this.score; + } + + getState() { + return this.board; + } /** * Returns the current game status. @@ -50,19 +165,80 @@ class Game { * `win` - the game is won; * `lose` - the game is lost */ - getStatus() {} + getStatus() { + for (let row = 0; row < 4; row++) { + for (let col = 0; col < 4; col++) { + if (this.board[row][col] === 2048) { + this.status = 'win'; - /** - * Starts the game. - */ - start() {} + return this.status; + } + } + } - /** - * Resets the game. - */ - restart() {} + const hasEmptyTiles = this.board.some((row) => row.includes(0)); + + if (!hasEmptyTiles) { + const canMove = (board) => { + for (let row = 0; row < 4; row++) { + for (let col = 0; col < 4; col++) { + if ( + (col < 3 && board[row][col] === board[row][col + 1]) || + (row < 3 && board[row][col] === board[row + 1][col]) + ) { + return true; + } + } + } + + return false; + }; + + if (!canMove(this.board)) { + this.status = 'lose'; + + return this.status; + } + } + + return this.status; + } + + start() { + this.board = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + + this.score = 0; + this.status = 'playing'; - // Add your own methods here + this.addRandomTiles(); + this.addRandomTiles(); + } + + restart() { + this.start(); + } + + addRandomTiles() { + const emptyTiles = []; + + for (let row = 0; row < 4; row++) { + for (let col = 0; col < 4; col++) { + if (this.board[row][col] === 0) { + emptyTiles.push({ row, col }); + } + } + } + + const randomCell = + emptyTiles[Math.floor(Math.random() * emptyTiles.length)]; + + this.board[randomCell.row][randomCell.col] = Math.random() < 0.9 ? 2 : 4; + } } module.exports = Game; diff --git a/src/scripts/main.js b/src/scripts/main.js index dc7f045a3..45029415a 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,7 +1,83 @@ '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 game = new Game(); -// Write your code here +const mainScore = document.querySelector('.game-score'); +const startButton = document.querySelector('.button.start'); +const messageWin = document.querySelector('.message-win'); +const messageLose = document.querySelector('.message-lose'); +const messageStart = document.querySelector('.message-start'); + +function renderGame() { + const boardState = game.getState(); + const cells = document.querySelectorAll('.field-cell'); + + cells.forEach((cell, index) => { + const row = Math.floor(index / 4); + const col = index % 4; + const value = boardState[row][col]; + + cell.textContent = value === 0 ? '' : value; + cell.className = 'field-cell'; + + if (value) { + cell.classList.add(`field-cell--${value}`); + } + }); + + mainScore.textContent = game.getScore(); + + startButton.addEventListener('click', () => { + if (startButton.classList.contains('start')) { + game.start(); + renderGame(); + + startButton.classList.remove('start'); + startButton.classList.add('restart'); + startButton.textContent = 'Restart'; + } else if (startButton.classList.contains('restart')) { + game.restart(); + renderGame(); + startButton.classList.remove('restart'); + startButton.classList.add('start'); + startButton.textContent = 'Start'; + } + }); + + const gameStatus = game.getStatus(); + + messageWin.classList.toggle('hidden', gameStatus !== 'win'); + messageLose.classList.toggle('hidden', gameStatus !== 'lose'); + messageStart.classList.toggle('hidden', gameStatus !== 'idle'); +} + +document.addEventListener('keydown', (gameEvent) => { + gameEvent.preventDefault(); + + switch (gameEvent.key) { + case 'ArrowUp': + game.moveUp(); + game.addRandomTiles(); + break; + + case 'ArrowDown': + game.moveDown(); + game.addRandomTiles(); + break; + + case 'ArrowLeft': + game.moveLeft(); + game.addRandomTiles(); + break; + + case 'ArrowRight': + game.moveRight(); + game.addRandomTiles(); + break; + } + + renderGame(); +}); + +renderGame(); diff --git a/src/styles/main.scss b/src/styles/main.scss index c43f37dcf..dd7c629a0 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -14,7 +14,7 @@ body { background: #d6cdc4; width: 75px; height: 75px; - border-radius: 5px; + border-radius: 12px; color: #776e65; box-sizing: border-box; text-align: center; @@ -78,7 +78,7 @@ body { .game-field { background: #bbada0; border-spacing: 10px; - border-radius: 5px; + border-radius: 12px; } .game-header { @@ -181,6 +181,11 @@ h1 { color: #f9f6f2; } +.message-lose { + background: #f87474; + color: #f9f6f2; +} + .message-container { width: 100%; height: 150px; From d807bd5e20c926ad030682a3e5b98fa430ba4b0e Mon Sep 17 00:00:00 2001 From: Cirillo Negruzzi Date: Thu, 13 Feb 2025 16:08:46 +0100 Subject: [PATCH 2/3] added changes --- src/modules/Game.class.js | 24 ++++++++++++++++++++++++ src/scripts/main.js | 19 ++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index fadc06393..aefc60060 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -14,6 +14,8 @@ class Game { } moveLeft() { + const oldBoard = JSON.stringify(this.board); + const shiftAndFilter = (array) => { const filteredArray = array.filter((cell) => cell !== 0); @@ -39,9 +41,15 @@ class Game { this.board[row] = shiftAndFilter(this.board[row]); } + + const newBoard = JSON.stringify(this.board); + + return oldBoard !== newBoard; } moveRight() { + const oldBoard = JSON.stringify(this.board); + const shiftAndFilter = (array) => { const filteredArray = array.filter((cell) => cell !== 0); @@ -67,9 +75,15 @@ class Game { this.board[row] = shiftAndFilter(this.board[row]); } + + const newBoard = JSON.stringify(this.board); + + return oldBoard !== newBoard; } moveUp() { + const oldBoard = JSON.stringify(this.board); + const shiftAndFilter = (array) => { const filteredArray = array.filter((cell) => cell !== 0); @@ -105,9 +119,15 @@ class Game { this.board[rowIndex][col] = column[rowIndex]; } } + + const newBoard = JSON.stringify(this.board); + + return oldBoard !== newBoard; } moveDown() { + const oldBoard = JSON.stringify(this.board); + const shiftAndFilter = (array) => { const filteredArray = array.filter((cell) => cell !== 0); @@ -145,6 +165,10 @@ class Game { this.board[rowIndex][col] = column[rowIndex]; } } + + const newBoard = JSON.stringify(this.board); + + return oldBoard !== newBoard; } getScore() { diff --git a/src/scripts/main.js b/src/scripts/main.js index 45029415a..29a66582e 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -55,29 +55,30 @@ function renderGame() { document.addEventListener('keydown', (gameEvent) => { gameEvent.preventDefault(); + let movePerfomed = false; + switch (gameEvent.key) { case 'ArrowUp': - game.moveUp(); - game.addRandomTiles(); + movePerfomed = game.moveUp(); break; case 'ArrowDown': - game.moveDown(); - game.addRandomTiles(); + movePerfomed = game.moveDown(); break; case 'ArrowLeft': - game.moveLeft(); - game.addRandomTiles(); + movePerfomed = game.moveLeft(); break; case 'ArrowRight': - game.moveRight(); - game.addRandomTiles(); + movePerfomed = game.moveRight(); break; } - renderGame(); + if (movePerfomed) { + game.addRandomTiles(); + renderGame(); + } }); renderGame(); From a47c9cacbafdd053b3a4dc1f8773b5f664624e77 Mon Sep 17 00:00:00 2001 From: Cirillo Negruzzi Date: Fri, 14 Feb 2025 22:35:05 +0100 Subject: [PATCH 3/3] fixed bugs --- src/modules/Game.class.js | 10 ++++++- src/scripts/main.js | 56 +++++++++++++++++++++++---------------- src/styles/main.scss | 23 ++++++++++++++-- 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index aefc60060..4cb688be2 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -244,7 +244,15 @@ class Game { } restart() { - this.start(); + this.board = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + + this.score = 0; + this.status = 'idle'; } addRandomTiles() { diff --git a/src/scripts/main.js b/src/scripts/main.js index 29a66582e..65641f616 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -23,28 +23,15 @@ function renderGame() { if (value) { cell.classList.add(`field-cell--${value}`); + + if (value === 2 || value === 4) { + cell.classList.add('new'); + } } }); mainScore.textContent = game.getScore(); - startButton.addEventListener('click', () => { - if (startButton.classList.contains('start')) { - game.start(); - renderGame(); - - startButton.classList.remove('start'); - startButton.classList.add('restart'); - startButton.textContent = 'Restart'; - } else if (startButton.classList.contains('restart')) { - game.restart(); - renderGame(); - startButton.classList.remove('restart'); - startButton.classList.add('start'); - startButton.textContent = 'Start'; - } - }); - const gameStatus = game.getStatus(); messageWin.classList.toggle('hidden', gameStatus !== 'win'); @@ -52,30 +39,53 @@ function renderGame() { messageStart.classList.toggle('hidden', gameStatus !== 'idle'); } +startButton.addEventListener('click', () => { + if (startButton.classList.contains('start')) { + game.start(); + renderGame(); + + startButton.classList.remove('start'); + startButton.classList.add('restart'); + startButton.textContent = 'Restart'; + } else if (startButton.classList.contains('restart')) { + game.restart(); + renderGame(); + startButton.classList.remove('restart'); + startButton.classList.add('start'); + startButton.textContent = 'Start'; + } +}); + document.addEventListener('keydown', (gameEvent) => { + if (game.getStatus() !== 'playing') { + return; + } + gameEvent.preventDefault(); - let movePerfomed = false; + const oldBoardState = JSON.stringify(game.getState()); switch (gameEvent.key) { case 'ArrowUp': - movePerfomed = game.moveUp(); + game.moveUp(); break; case 'ArrowDown': - movePerfomed = game.moveDown(); + game.moveDown(); break; case 'ArrowLeft': - movePerfomed = game.moveLeft(); + game.moveLeft(); break; case 'ArrowRight': - movePerfomed = game.moveRight(); + game.moveRight(); break; } - if (movePerfomed) { + const newBoardState = JSON.stringify(game.getState()); + + if (oldBoardState !== newBoardState) { game.addRandomTiles(); renderGame(); } diff --git a/src/styles/main.scss b/src/styles/main.scss index dd7c629a0..39aeffbe5 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -11,9 +11,13 @@ body { } .field-cell { + position: relative; + transition: + transform 0.5s ease, + opacity 0.5s ease; background: #d6cdc4; - width: 75px; - height: 75px; + width: 80px; + height: 80px; border-radius: 12px; color: #776e65; box-sizing: border-box; @@ -75,6 +79,10 @@ body { } } +.field-cell.new { + animation: appear 0.3s ease-in-out; +} + .game-field { background: #bbada0; border-spacing: 10px; @@ -190,3 +198,14 @@ h1 { width: 100%; height: 150px; } + +@keyframes appear { + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +}