From 45f4120b90c9943e66a964cb5411156c3d58b7ce Mon Sep 17 00:00:00 2001 From: Kateryna Date: Wed, 18 Dec 2024 14:31:11 +0100 Subject: [PATCH 1/5] add 2048 game --- README.md | 2 +- src/index.html | 5 +- src/modules/Game.class.js | 345 +++++++++++++++++++++++++++++++------- src/scripts/main.js | 54 +++++- 4 files changed, 340 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 5aab92544..4164930c9 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://moskkat40.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..afb1016e1 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..26c48f84a 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -1,68 +1,293 @@ '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], + ], + ) { + this.grid = initialState; + this.score = 0; + this.status = 'idle'; } - moveLeft() {} - moveRight() {} - moveUp() {} - moveDown() {} - - /** - * @returns {number} - */ - getScore() {} - - /** - * @returns {number[][]} - */ - getState() {} - - /** - * 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 - */ - getStatus() {} - - /** - * Starts the game. - */ - start() {} - - /** - * Resets the game. - */ - restart() {} - - // Add your own methods here + moveLeft() { + for (let row = 0; row < this.grid.length; row++) { + let rowWithoutZero = this.grid[row].filter((item) => item > 0); + + for (let i = 0; i < rowWithoutZero.length - 1; i++) { + if (rowWithoutZero[i] === rowWithoutZero[i + 1]) { + rowWithoutZero[i] *= 2; + rowWithoutZero[i + 1] = 0; + this.score += rowWithoutZero[i]; + } + } + + rowWithoutZero = rowWithoutZero.filter((cell) => cell !== 0); + + while (rowWithoutZero.length < 4) { + rowWithoutZero.push(0); + } + + this.grid[row] = rowWithoutZero; + } + + this.updateDOM(); + this.checkWin(); + + if (this.status === 'playing') { + this.addStartCell(); + this.checkLose(); + } + } + + moveRight() { + for (let row = 0; row < this.grid.length; row++) { + let rowWithoutZero = this.grid[row].filter((item) => item > 0); + + for (let i = rowWithoutZero.length - 1; i > 0; i--) { + if (rowWithoutZero[i] === rowWithoutZero[i - 1]) { + rowWithoutZero[i] *= 2; + rowWithoutZero[i - 1] = 0; + this.score += rowWithoutZero[i]; + } + } + + rowWithoutZero = rowWithoutZero.filter((cell) => cell !== 0); + + while (rowWithoutZero.length < 4) { + rowWithoutZero.unshift(0); + } + + this.grid[row] = rowWithoutZero; + } + + this.updateDOM(); + this.checkWin(); + + if (this.status === 'playing') { + this.addStartCell(); + this.checkLose(); + } + } + + moveUp() { + for (let col = 0; col < this.grid[0].length; col++) { + let colWithoutZero = []; + + for (let row = 0; row < this.grid.length; row++) { + if (this.grid[row][col] > 0) { + colWithoutZero.push(this.grid[row][col]); + } + } + + for (let i = 0; i < colWithoutZero.length - 1; i++) { + if (colWithoutZero[i] === colWithoutZero[i + 1]) { + colWithoutZero[i] *= 2; + colWithoutZero[i + 1] = 0; + this.score += colWithoutZero[i]; + } + } + + colWithoutZero = colWithoutZero.filter((cell) => cell !== 0); + + while (colWithoutZero.length < 4) { + colWithoutZero.push(0); + } + + for (let row = 0; row < this.grid.length; row++) { + this.grid[row][col] = colWithoutZero[row]; + } + } + + this.updateDOM(); + this.checkWin(); + + if (this.status === 'playing') { + this.addStartCell(); + this.checkLose(); + } + } + + moveDown() { + for (let col = 0; col < this.grid.length; col++) { + let colWithoutZero = []; + + for (let row = 0; row < this.grid.length; row++) { + if (this.grid[row][col] > 0) { + colWithoutZero.push(this.grid[row][col]); + } + } + + for (let i = colWithoutZero.length - 1; i > 0; i--) { + if (colWithoutZero[i] === colWithoutZero[i - 1]) { + colWithoutZero[i] *= 2; + colWithoutZero[i - 1] = 0; + this.score += colWithoutZero[i]; + } + } + + colWithoutZero = colWithoutZero.filter((cell) => cell !== 0); + + while (colWithoutZero.length < 4) { + colWithoutZero.unshift(0); + } + + for (let row = 0; row < this.grid.length; row++) { + this.grid[row][col] = colWithoutZero[row] || 0; + } + } + + this.updateDOM(); + this.checkWin(); + + if (this.status === 'playing') { + this.addStartCell(); + this.checkLose(); + } + } + + checkWin() { + for (let row = 0; row < this.grid.length; row++) { + for (let col = 0; col < this.grid[row].length; col++) { + if (this.grid[row][col] === 2048) { + this.status = 'win'; + + return true; + } + } + } + + this.status = 'playing'; + + return false; + } + + getScore() { + return this.score; + } + + getState() { + return this.grid; + } + + getStatus() { + return this.status; + } + + start() { + this.grid = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + this.score = 0; + this.status = 'playing'; + this.updateDOM(); + + this.addStartCell(); + this.addStartCell(); + } + + restart() { + const cells = document.querySelectorAll('.field-cell'); + + cells.forEach((cell) => { + cell.innerHTML = ''; + cell.className = 'field-cell'; + }); + + this.grid = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + this.score = 0; + this.status = 'idle'; + this.updateDOM(); + } + + addStartCell() { + const basicCells = Math.random() >= 0.9 ? 4 : 2; + const emptyCells = this.grid.flat().filter((cell) => cell === 0); + + if (emptyCells.length === 0) { + return; + } + + let foundedEmpty = false; + + while (!foundedEmpty) { + try { + const rowIndex = Math.floor(Math.random() * 4); + const cellIndex = Math.floor(Math.random() * 4); + + if (this.grid[rowIndex] && this.grid[rowIndex][cellIndex] === 0) { + this.grid[rowIndex][cellIndex] = basicCells; + this.updateDOM(); + foundedEmpty = true; + } + } catch (error) { + return; + } + } + } + + updateDOM() { + const cells = document.querySelectorAll('.field-cell'); + + for (let row = 0; row < this.grid.length; row++) { + for (let col = 0; col < this.grid[row].length; col++) { + const cell = cells[row * 4 + col]; + const value = this.grid[row][col]; + + cell.className = 'field-cell'; + cell.innerHTML = ''; + + if (value > 0) { + cell.classList.add(`field-cell--${value}`); + cell.innerHTML = value; + } + } + } + } + + checkLose() { + for (let row = 0; row < this.grid.length; row++) { + for (let col = 0; col < this.grid[row].length; col++) { + if (this.grid[row][col] === 0) { + this.status = 'playing'; + + return false; + } + } + } + + for (let row = 0; row < this.grid.length; row++) { + for (let col = 0; col < this.grid[row].length; col++) { + if ( + (row > 0 && this.grid[row][col] === this.grid[row - 1][col]) || + (row < this.grid.length - 1 && + this.grid[row][col] === this.grid[row + 1][col]) || + (col > 0 && this.grid[row][col] === this.grid[row][col - 1]) || + (col < this.grid[row].length - 1 && + this.grid[row][col] === this.grid[row][col + 1]) + ) { + this.status = 'playing'; + + return false; + } + } + } + + this.status = 'lose'; + + return true; + } } module.exports = Game; diff --git a/src/scripts/main.js b/src/scripts/main.js index dc7f045a3..24728ccd9 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,7 +1,53 @@ '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(); +const startButton = document.querySelector('.button.start'); +const gameMessage = document.querySelector('.message.message-start'); +const loseMessage = document.querySelector('.message.message-lose'); +const winMessage = document.querySelector('.message.message-win'); +const score = document.querySelector('.game-score'); -// Write your code here +document.addEventListener('keydown', (e) => { + if (e.key === 'ArrowLeft') { + game.moveLeft(); + score.innerText = game.score; + } else if (e.key === 'ArrowRight') { + game.moveRight(); + score.innerText = game.score; + } else if (e.key === 'ArrowUp') { + game.moveUp(); + score.innerText = game.score; + } else if (e.key === 'ArrowDown') { + game.moveDown(); + score.innerText = game.score; + } + + if (game.getStatus() === 'lose') { + loseMessage.classList.remove('hidden'); + gameMessage.classList.add('hidden'); + } + + if (game.getStatus() === 'win') { + winMessage.classList.remove('hidden'); + gameMessage.classList.add('hidden'); + } +}); + +startButton.addEventListener('click', () => { + if (startButton.classList.contains('restart')) { + startButton.classList.replace('restart', 'start'); + startButton.innerHTML = 'Start'; + gameMessage.classList.remove('hidden'); + loseMessage.classList.add('hidden'); + score.innerText = 0; + + game.restart(); + } else { + startButton.classList.replace('start', 'restart'); + startButton.innerHTML = 'Restart'; + game.start(); + gameMessage.classList.add('hidden'); + loseMessage.classList.add('hidden'); + } +}); From b2310eda1619abe97d7573b133221d78790ae7f0 Mon Sep 17 00:00:00 2001 From: Kateryna Date: Wed, 18 Dec 2024 14:43:59 +0100 Subject: [PATCH 2/5] update commit --- src/modules/Game.class.js | 62 +++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/modules/Game.class.js b/src/modules/Game.class.js index 26c48f84a..298689405 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -14,6 +14,8 @@ class Game { } moveLeft() { + let moved = false; + for (let row = 0; row < this.grid.length; row++) { let rowWithoutZero = this.grid[row].filter((item) => item > 0); @@ -22,6 +24,7 @@ class Game { rowWithoutZero[i] *= 2; rowWithoutZero[i + 1] = 0; this.score += rowWithoutZero[i]; + moved = true; } } @@ -31,19 +34,28 @@ class Game { rowWithoutZero.push(0); } + if (this.grid[row].toString() !== rowWithoutZero.toString()) { + moved = true; + } + this.grid[row] = rowWithoutZero; } this.updateDOM(); - this.checkWin(); - if (this.status === 'playing') { + if (this.checkWin()) { + return; + } + + if (moved) { this.addStartCell(); - this.checkLose(); } + this.checkLose(); } moveRight() { + let moved = false; + for (let row = 0; row < this.grid.length; row++) { let rowWithoutZero = this.grid[row].filter((item) => item > 0); @@ -52,6 +64,7 @@ class Game { rowWithoutZero[i] *= 2; rowWithoutZero[i - 1] = 0; this.score += rowWithoutZero[i]; + moved = true; } } @@ -61,19 +74,28 @@ class Game { rowWithoutZero.unshift(0); } + if (this.grid[row].toString() !== rowWithoutZero.toString()) { + moved = true; + } + this.grid[row] = rowWithoutZero; } this.updateDOM(); - this.checkWin(); - if (this.status === 'playing') { + if (this.checkWin()) { + return; + } + + if (moved) { this.addStartCell(); - this.checkLose(); } + this.checkLose(); } moveUp() { + let moved = false; + for (let col = 0; col < this.grid[0].length; col++) { let colWithoutZero = []; @@ -88,6 +110,7 @@ class Game { colWithoutZero[i] *= 2; colWithoutZero[i + 1] = 0; this.score += colWithoutZero[i]; + moved = true; } } @@ -98,20 +121,28 @@ class Game { } for (let row = 0; row < this.grid.length; row++) { + if (this.grid[row][col] !== colWithoutZero[row]) { + moved = true; + } this.grid[row][col] = colWithoutZero[row]; } } this.updateDOM(); - this.checkWin(); - if (this.status === 'playing') { + if (this.checkWin()) { + return; + } + + if (moved) { this.addStartCell(); - this.checkLose(); } + this.checkLose(); } moveDown() { + let moved = false; + for (let col = 0; col < this.grid.length; col++) { let colWithoutZero = []; @@ -126,6 +157,7 @@ class Game { colWithoutZero[i] *= 2; colWithoutZero[i - 1] = 0; this.score += colWithoutZero[i]; + moved = true; } } @@ -136,17 +168,23 @@ class Game { } for (let row = 0; row < this.grid.length; row++) { + if (this.grid[row][col] !== colWithoutZero[row]) { + moved = true; + } this.grid[row][col] = colWithoutZero[row] || 0; } } this.updateDOM(); - this.checkWin(); - if (this.status === 'playing') { + if (this.checkWin()) { + return; + } + + if (moved) { this.addStartCell(); - this.checkLose(); } + this.checkLose(); } checkWin() { From 7b867f49f0fcde5fbad46bc20f951b5856af4e96 Mon Sep 17 00:00:00 2001 From: Kateryna Date: Wed, 18 Dec 2024 14:52:21 +0100 Subject: [PATCH 3/5] rewrite if statmant to switch --- src/scripts/main.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/scripts/main.js b/src/scripts/main.js index 24728ccd9..37910bcd7 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -9,18 +9,23 @@ const winMessage = document.querySelector('.message.message-win'); const score = document.querySelector('.game-score'); document.addEventListener('keydown', (e) => { - if (e.key === 'ArrowLeft') { - game.moveLeft(); - score.innerText = game.score; - } else if (e.key === 'ArrowRight') { - game.moveRight(); - score.innerText = game.score; - } else if (e.key === 'ArrowUp') { - game.moveUp(); - score.innerText = game.score; - } else if (e.key === 'ArrowDown') { - game.moveDown(); - score.innerText = game.score; + switch (e.key) { + case 'ArrowLeft': + game.moveLeft(); + score.innerText = game.score; + break; + case 'ArrowRight': + game.moveRight(); + score.innerText = game.score; + break; + case 'ArrowUp': + game.moveUp(); + score.innerText = game.score; + break; + case 'ArrowDown': + game.moveDown(); + score.innerText = game.score; + break; } if (game.getStatus() === 'lose') { From 04993528badd4d8754bc3aa5a70cc62c354bc78a Mon Sep 17 00:00:00 2001 From: Kateryna Date: Wed, 18 Dec 2024 15:00:05 +0100 Subject: [PATCH 4/5] test --- src/scripts/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/scripts/main.js b/src/scripts/main.js index 37910bcd7..f8dce9f1d 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -56,3 +56,5 @@ startButton.addEventListener('click', () => { loseMessage.classList.add('hidden'); } }); + +// comments From 0dd53dcf24bdb9ed33125f8a786578d21b366c21 Mon Sep 17 00:00:00 2001 From: Kateryna Date: Wed, 18 Dec 2024 15:20:26 +0100 Subject: [PATCH 5/5] test 2 --- src/scripts/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/scripts/main.js b/src/scripts/main.js index f8dce9f1d..37910bcd7 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -56,5 +56,3 @@ startButton.addEventListener('click', () => { loseMessage.classList.add('hidden'); } }); - -// comments