From 9b335b61b0cede94acd84d18f507912c22578916 Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 19 Dec 2024 17:18:55 +0100 Subject: [PATCH] solution --- README.md | 2 +- src/index.html | 10 +- src/modules/Game.class.js | 304 ++++++++++++++++++++++++++++++-------- src/scripts/main.js | 90 ++++++++++- 4 files changed, 340 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 5aab92544..83b72f7f6 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://Grifion.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..93f771c3a 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..45093754b 100644 --- a/src/modules/Game.class.js +++ b/src/modules/Game.class.js @@ -1,68 +1,252 @@ '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); + static STATUS = { + idle: 'idle', + playing: 'playing', + win: 'win', + lose: 'lose', + }; + + static INITIAL_STATE = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + + constructor(initialState = Game.INITIAL_STATE) { + this.status = Game.STATUS.idle; + this.initialState = initialState; + this.score = 0; + this.state = this.initialState.map((row) => row.slice()); + } + + moveLeft() { + if (this.status === Game.STATUS.playing) { + const newStateArr = []; + + for (let i = 0; i < this.state.length; i++) { + // this code remove all 0 and push it back to the end + + let newRowArr = this.state[i].filter((el) => el > 0); + + for (let index = newRowArr.length; index < this.state.length; index++) { + newRowArr.push(0); + } + + // Here i merged neigber numbers in row + for (let j = 0; j < newRowArr.length; j++) { + if (newRowArr[j] === newRowArr[j + 1]) { + newRowArr[j] = newRowArr[j] + newRowArr[j + 1]; + newRowArr[j + 1] = 0; + this.score += newRowArr[j] + newRowArr[j + 1]; + } + } + + newRowArr = newRowArr.filter((el) => el > 0); + + for (let index = newRowArr.length; index < this.state.length; index++) { + newRowArr.push(0); + } + + newStateArr.push(newRowArr); + } + + if (!this.checkSameArr(newStateArr)) { + this.state = newStateArr; + this.randomNumber(); + } + this.loseOrWin(); + } + } + moveRight() { + if (this.status === Game.STATUS.playing) { + const newStateArr = []; + + for (let i = 0; i < this.state.length; i++) { + // this code remove all 0 and unshift it back to the end + + let newRowArr = this.state[i].filter((el) => el > 0); + + for (let index = newRowArr.length; index < this.state.length; index++) { + newRowArr.unshift(0); + } + + // Here i merged neigber numbers in row + for (let j = newRowArr.length - 1; j > 0; j--) { + if (newRowArr[j] === newRowArr[j - 1]) { + newRowArr[j] = newRowArr[j] + newRowArr[j - 1]; + newRowArr[j - 1] = 0; + this.score += newRowArr[j] + newRowArr[j - 1]; + } + } + + newRowArr = newRowArr.filter((el) => el > 0); + + for (let index = newRowArr.length; index < this.state.length; index++) { + newRowArr.unshift(0); + } + + newStateArr.push(newRowArr); + } + + if (!this.checkSameArr(newStateArr)) { + this.state = newStateArr; + this.randomNumber(); + } + this.loseOrWin(); + } + } + moveUp() { + if (this.status === Game.STATUS.playing) { + let newStateArr = []; + + this.funcReverse(newStateArr); + this.state = newStateArr; + + newStateArr = []; + this.moveLeft(); + + this.funcReverse(newStateArr); + this.state = newStateArr; + } + } + moveDown() { + if (this.status === Game.STATUS.playing) { + let newStateArr = []; + + this.funcReverse(newStateArr); + this.state = newStateArr; + + newStateArr = []; + this.moveRight(); + + this.funcReverse(newStateArr); + this.state = newStateArr; + } + } + + getScore() { + return this.score; + } + + getState() { + return this.state; + } + + getStatus() { + return this.status; + } + + restart() { + this.initialState = Game.INITIAL_STATE; + this.status = Game.STATUS.idle; + this.state = this.initialState.map((row) => row.slice()); + this.score = 0; + } + + start() { + this.status = Game.STATUS.playing; + this.randomNumber(); + this.randomNumber(); + } + + randomNumber() { + const emptyCells = []; + + for (let rowIndex = 0; rowIndex < this.state.length; rowIndex++) { + for ( + let colIndex = 0; + colIndex < this.state[rowIndex].length; + colIndex++ + ) { + if (this.state[rowIndex][colIndex] === 0) { + emptyCells.push({ row: rowIndex, col: colIndex }); + } + } + } + + if (emptyCells.length === 0) { + return; + } + + const randomIndex = Math.floor(Math.random() * emptyCells.length); + const { row, col } = emptyCells[randomIndex]; + + // Place a 2 or 4 in the selected cell + const newValue = Math.floor(Math.random() * 10) + 1 > 8 ? 4 : 2; + + this.state[row][col] = newValue; } - 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 + loseOrWin() { + let isZero = false; + let hasMove = false; + + for (let i = 0; i < this.state.length; i++) { + for (let j = 0; j < this.state[i].length; j++) { + if (this.state[i][j] === 2048) { + this.status = Game.STATUS.win; + + return; + } + + if (this.state[i][j] === 0) { + isZero = true; + } + + if ( + j < this.state[i].length - 1 && + this.state[i][j] === this.state[i][j + 1] + ) { + hasMove = true; + } + + if ( + i < this.state.length - 1 && + this.state[i][j] === this.state[i + 1][j] + ) { + hasMove = true; + } + } + } + + if (!isZero && !hasMove) { + this.status = Game.STATUS.lose; + } + } + + // AI helped to compare the Arrays + checkSameArr(firstArr, secondArr = this.state) { + if (firstArr.length !== secondArr.length) { + return false; + } + + for (let i = 0; i < firstArr.length; i++) { + if (Array.isArray(firstArr[i]) && Array.isArray(secondArr[i])) { + // Recursively check for nested arrays + if (!this.checkSameArr(firstArr[i], secondArr[i])) { + return false; + } + } else if (firstArr[i] !== secondArr[i]) { + return false; + } + } + + return true; + } + + funcReverse(newStateArr) { + for (let i = 0; i < this.state.length; i++) { + const newRowArr = []; + + for (let j = 0; j < this.state[i].length; j++) { + newRowArr.push(this.state[j][i]); + } + newStateArr.push(newRowArr); + } + } } module.exports = Game; diff --git a/src/scripts/main.js b/src/scripts/main.js index dc7f045a3..0d9e7a145 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,7 +1,89 @@ 'use strict'; -// Uncomment the next lines to use your game instance in the browser -// const Game = require('../modules/Game.class'); -// const game = new Game(); +// Module +const Game = require('../modules/Game.class'); +const game = new Game(); -// Write your code here +// All html veriables +const buttonStart = document.querySelector('.start'); +const gameScore = document.querySelector('.game-score'); +const fieldRow = [...document.querySelectorAll('.field-row')]; +const startMessage = document.querySelector('.message-start'); +const loseMessage = document.querySelector('.message-lose'); +const winMessage = document.querySelector('.message-win'); + +function loseOrWin(statusToCheck) { + if (statusToCheck === Game.STATUS.lose) { + loseMessage.classList.remove('hidden'); + } + + if (statusToCheck === Game.STATUS.win) { + winMessage.classList.remove('hidden'); + } +} + +function displayNumbers(state) { + for (let i = 0; i < fieldRow.length; i++) { + for (let j = 0; j < fieldRow[i].children.length; j++) { + fieldRow[i].children[j].textContent = ''; + fieldRow[i].children[j].className = `field-cell`; + + if (state[i][j] > 0) { + fieldRow[i].children[j].textContent = state[i][j]; + + fieldRow[i].children[j].className = + `field-cell field-cell--${state[i][j]}`; + } + } + } +} + +buttonStart.addEventListener('click', () => { + if (buttonStart.classList.contains('start')) { + buttonStart.textContent = 'Restart'; + + buttonStart.classList.remove('start'); + buttonStart.classList.add('restart'); + + game.start(); + displayNumbers(game.getState()); + + gameScore.textContent = game.getScore(); + + startMessage.classList.add('hidden'); + loseMessage.classList.add('hidden'); + } else { + buttonStart.textContent = 'Start'; + + buttonStart.classList.remove('restart'); + buttonStart.classList.add('start'); + + game.restart(); + displayNumbers(game.getState()); + + gameScore.textContent = game.getScore(); + + startMessage.classList.remove('hidden'); + loseMessage.classList.add('hidden'); + } +}); + +document.addEventListener('keydown', (e) => { + switch (e.key) { + case 'ArrowUp': + game.moveUp(); + break; + case 'ArrowDown': + game.moveDown(); + break; + case 'ArrowLeft': + game.moveLeft(); + break; + case 'ArrowRight': + game.moveRight(); + break; + } + displayNumbers(game.getState()); + gameScore.textContent = game.getScore(); + loseOrWin(game.getStatus()); +});