Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add task solution #1031

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/images/2048.ico
Binary file not shown.
45 changes: 11 additions & 34 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
content="width=device-width, initial-scale=1.0"
/>
<title>2048</title>
<link
rel="icon"
href="./images/2048.ico"
type="image/svg+xml"
sizes="any"
/>
<link
rel="stylesheet"
href="./styles/main.scss"
Expand All @@ -24,39 +30,7 @@ <h1>2048</h1>
<button class="button start">Start</button>
</div>
</div>

<table class="game-field">
<tbody>
<tr class="field-row">
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
</tr>

<tr class="field-row">
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
</tr>

<tr class="field-row">
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
</tr>

<tr class="field-row">
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
<td class="field-cell"></td>
</tr>
</tbody>
</table>

<div class="game-field"></div>
<div class="message-container">
<p class="message message-lose hidden">You lose! Restart the game?</p>
<p class="message message-win hidden">Winner! Congrats! You did it!</p>
Expand All @@ -65,6 +39,9 @@ <h1>2048</h1>
</p>
</div>
</div>
<script src="scripts/main.js"></script>
<script
src="./scripts/main.js"
type="module"
></script>
</body>
</html>
301 changes: 237 additions & 64 deletions src/modules/Game.class.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,241 @@
'use strict';
export default class Game {
static Status = {
IDLE: 'idle',
PLAYING: 'playing',
WIN: 'win',
LOSE: 'lose',
};

static Config = {
ROWS: 4,
COLUMNS: 4,
};

/**
* 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);
}

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
}
this.state = initialState;
this.score = 0;
this.status = Game.Status.IDLE;
}

renderBoard() {
document.querySelector('.game-field').innerHTML = '';

for (let r = 0; r < Game.Config.ROWS; r++) {
for (let c = 0; c < Game.Config.COLUMNS; c++) {
const tile = document.createElement('div');

tile.id = `${r}-${c}`;

const num = this.state[r][c];

this.updateTile(tile, num);
document.querySelector('.game-field').append(tile);
}
}
}

addRandomTile() {
const emptyCells = [];

for (let row = 0; row < Game.Config.ROWS; row++) {
for (let col = 0; col < Game.Config.COLUMNS; col++) {
if (this.state[row][col] === 0) {
emptyCells.push({ row, col });
}
}
}

if (emptyCells.length > 0) {
const randomCell =
emptyCells[Math.floor(Math.random() * emptyCells.length)];

this.state[randomCell.row][randomCell.col] = Math.random() < 0.9 ? 2 : 4;
}
}

filterZero(row) {
return row.filter((num) => num !== 0);
}

slide(row) {
let newRow = this.filterZero(row);

for (let i = 0; i < newRow.length - 1; i++) {
if (newRow[i] === newRow[i + 1]) {
newRow[i] *= 2;
newRow[i + 1] = 0;
this.score += newRow[i];
}
}

newRow = this.filterZero(newRow);

while (newRow.length < Game.Config.ROWS) {
newRow.push(0);
}

return newRow;
}

updateTile(tile, num) {
tile.innerText = '';
tile.classList.value = '';
tile.classList.add('field-cell');

if (num > 0) {
tile.innerText = num;
tile.classList.add(`field-cell--${num}`);
}
}

moveLeft() {
for (let r = 0; r < Game.Config.ROWS; r++) {
let row = this.state[r];

row = this.slide(row);
this.state[r] = row;

for (let c = 0; c < Game.Config.COLUMNS; c++) {
const tile = document.getElementById(r.toString() + '-' + c.toString());
const num = this.state[r][c];

this.updateTile(tile, num);
}
}
}

moveRight() {
for (let r = 0; r < Game.Config.ROWS; r++) {
let row = this.state[r];

row.reverse();
row = this.slide(row);
row.reverse();
this.state[r] = row;

for (let c = 0; c < Game.Config.COLUMNS; c++) {
const tile = document.getElementById(r.toString() + '-' + c.toString());
const num = this.state[r][c];

this.updateTile(tile, num);
}
}
}
moveUp() {
for (let c = 0; c < Game.Config.COLUMNS; c++) {
let row = [
this.state[0][c],
this.state[1][c],
this.state[2][c],
this.state[3][c],
];

row = this.slide(row);

for (let r = 0; r < Game.Config.ROWS; r++) {
this.state[r][c] = row[r];

const tile = document.getElementById(r.toString() + '-' + c.toString());
const num = this.state[r][c];

this.updateTile(tile, num);
}
}
}
moveDown() {
for (let c = 0; c < Game.Config.COLUMNS; c++) {
let row = [
this.state[0][c],
this.state[1][c],
this.state[2][c],
this.state[3][c],
];

row.reverse();
row = this.slide(row);
row.reverse();

for (let r = 0; r < Game.Config.ROWS; r++) {
this.state[r][c] = row[r];

const tile = document.getElementById(r.toString() + '-' + c.toString());
const num = this.state[r][c];

this.updateTile(tile, num);
}
}
}

getScore() {
return this.score;
}

getState() {
return this.state;
}

getStatus() {
return this.status;
}

module.exports = Game;
start() {
this.status = Game.Status.PLAYING;
this.addRandomTile();
this.addRandomTile();
}

restart() {
this.state = this.state.map((row) => row.map(() => 0));
this.score = 0;
this.start();
}

hasPossibleMoves() {
for (let r = 0; r < Game.Config.ROWS; r++) {
for (let c = 0; c < Game.Config.COLUMNS; c++) {
const current = this.state[r][c];

// Перевірка на порожні клітинки
if (current === 0) {
return true;
}

// Перевірка на можливість злиття по горизонталі
if (c < 3 && current === this.state[r][c + 1]) {
return true;
}

if (r < Game.Config.ROWs - 1 && current === this.state[r + 1][c]) {
return true;
}
}
}

return false;
}

isMoveMade(prevBoard) {
for (let r = 0; r < Game.Config.ROWS; r++) {
for (let c = 0; c < Game.Config.COLUMNS; c++) {
if (prevBoard[r][c] !== this.state[r][c]) {
return true;
}
}
}

return false;
}

hasEmptyTile() {
return this.state.some((row) => row.includes(0));
}

checkGameOver() {
if (!this.hasEmptyTile() && !this.hasPossibleMoves()) {
this.status = Game.Status.LOSE;
} else if (this.state.some((row) => row.includes(2048))) {
this.status = Game.Status.WIN;
}
}
}
Loading
Loading