Skip to content

Commit

Permalink
add solution
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleh Chernov committed Jan 14, 2025
1 parent a6d3781 commit 0c6a2d5
Show file tree
Hide file tree
Showing 23 changed files with 813 additions and 258 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test

on:
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm start & sleep 5 && npm test
- name: Upload tests report(cypress mochaawesome merged HTML report)
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: report
path: reports
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ You can change the HTML/CSS layout if you need it.
## Deploy and Pull Request
1. Replace `<your_account>` with your Github username in the link
- [DEMO LINK](https://<your_account>.github.io/js_2048_game/)
- [DEMO LINK](https://nineuito.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;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@mate-academy/eslint-config": "latest",
"@mate-academy/jest-mochawesome-reporter": "^1.0.0",
"@mate-academy/linthtml-config": "latest",
"@mate-academy/scripts": "^1.8.5",
"@mate-academy/scripts": "^1.9.12",
"@mate-academy/stylelint-config": "latest",
"@parcel/transformer-sass": "^2.12.0",
"cypress": "^13.13.0",
Expand Down
12 changes: 9 additions & 3 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<!doctype html>
<html lang="en">
<html
lang="en"
class="page"
>
<head>
<meta charset="UTF-8" />
<meta
Expand All @@ -12,7 +15,7 @@
href="./styles/main.scss"
/>
</head>
<body>
<body class="page__body">
<div class="container">
<div class="game-header">
<h1>2048</h1>
Expand Down Expand Up @@ -65,6 +68,9 @@ <h1>2048</h1>
</p>
</div>
</div>
<script src="scripts/main.js"></script>
<script
src="scripts/main.js"
type="module"
></script>
</body>
</html>
296 changes: 234 additions & 62 deletions src/modules/Game.class.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,240 @@
'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.
*/
export default class Game {
constructor(initialState) {
// eslint-disable-next-line no-console
console.log(initialState);
this.initialState = initialState || [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
];
this.state = structuredClone(this.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() {
if (this.status !== 'playing') {
return;
}

const prevState = structuredClone(this.state);

for (let i = 0; i < 4; i++) {
const row = this.state[i].filter((cell) => cell !== 0);

for (let j = 0; j < row.length - 1; j++) {
if (row[j] === row[j + 1]) {
row[j] *= 2;
this.score += row[j];
row.splice(j + 1, 1);
}
}

while (row.length < 4) {
row.push(0);
}
this.state[i] = row;
}

if (prevState !== structuredClone(this.state)) {
this.addRandomTile();
this.updateGameStatus();
}
}

moveRight() {
if (this.status !== 'playing') {
return;
}

const prevState = structuredClone(this.state);

for (let i = 0; i < 4; i++) {
const row = this.state[i].filter((cell) => cell !== 0);

for (let j = row.length - 1; j > 0; j--) {
if (row[j] === row[j - 1]) {
row[j] *= 2;
this.score += row[j];
row.splice(j - 1, 1);
j--;
}
}

while (row.length < 4) {
row.unshift(0);
}
this.state[i] = row;
}

if (prevState !== structuredClone(this.state)) {
this.addRandomTile();
this.updateGameStatus();
}
}

moveUp() {
if (this.status !== 'playing') {
return;
}

const prevState = structuredClone(this.state);

for (let j = 0; j < 4; j++) {
const column = [];

for (let i = 0; i < 4; i++) {
if (this.state[i][j] !== 0) {
column.push(this.state[i][j]);
}
}

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

while (column.length < 4) {
column.push(0);
}

for (let i = 0; i < 4; i++) {
this.state[i][j] = column[i];
}
}

if (prevState !== structuredClone(this.state)) {
this.addRandomTile();
this.updateGameStatus();
}
}

moveDown() {
if (this.status !== 'playing') {
return;
}

const prevState = structuredClone(this.state);

for (let j = 0; j < 4; j++) {
const column = [];

for (let i = 0; i < 4; i++) {
if (this.state[i][j] !== 0) {
column.push(this.state[i][j]);
}
}

for (let i = column.length - 1; i > 0; i--) {
if (column[i] === column[i - 1]) {
column[i] *= 2;
this.score += column[i];
column.splice(i - 1, 1);
i--;
}
}

module.exports = Game;
while (column.length < 4) {
column.unshift(0);
}

for (let i = 0; i < 4; i++) {
this.state[i][j] = column[i];
}
}

if (prevState !== structuredClone(this.state)) {
this.addRandomTile();
this.updateGameStatus();
}
}

getScore() {
return this.score;
}

getState() {
return this.state;
}

getStatus() {
return this.status;
}

start() {
this.state = structuredClone(this.initialState);
this.status = 'playing';
this.addRandomTile();
this.addRandomTile();
}

restart() {
this.state = structuredClone(this.initialState);
this.score = 0;
this.status = 'idle';
this.addRandomTile();
this.addRandomTile();
}

addRandomTile() {
const arrayForEmptyCells = [];

for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.state[i][j] === 0) {
arrayForEmptyCells.push([i, j]);
}
}
}

if (arrayForEmptyCells.length > 0) {
const [row, col] =
arrayForEmptyCells[
Math.floor(Math.random() * arrayForEmptyCells.length)
];

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

hasWon() {
return this.state.some((row) => row.some((cell) => cell === 2048));
}

canMove() {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.state[i][j] === 0) {
return true;
}
}
}

for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
const current = this.state[i][j];

if (
(i < 3 && current === this.state[i + 1][j]) ||
(j < 3 && current === this.state[i][j + 1])
) {
return true;
}
}
}

return false;
}

updateGameStatus() {
if (this.hasWon()) {
this.status = 'win';
} else if (!this.canMove()) {
this.status = 'lose';
}
}
}
Loading

0 comments on commit 0c6a2d5

Please sign in to comment.