Skip to content

Commit a28b01f

Browse files
committed
fixed the ui if the 2048 game
1 parent bbb7bb2 commit a28b01f

3 files changed

Lines changed: 342 additions & 181 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Welcome! This guide will help you make your first contribution. No experience ne
3131
- Save your work and run:
3232
```bash
3333
git add .
34-
git commit -m "Describe your change here"
34+
git commit -m "Describe your change here
3535
git push
3636
```
3737

projects/game-2048/script.js

Lines changed: 207 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,246 @@
1+
// Smooth 2048 with stable tile IDs (drop-in replacement)
12
let board = [];
23
let score = 0;
34
let best = 0;
45
const size = 4;
6+
let nextId = 1; // unique id generator
7+
const tilesMap = new Map(); // id -> DOM element
58

9+
// ---------- init / new game ----------
610
function init() {
7-
board = Array(size).fill().map(() => Array(size).fill(0));
8-
score = 0;
9-
updateScore();
10-
addRandomTile();
11-
addRandomTile();
12-
render();
11+
board = Array(size).fill().map(() => Array(size).fill(null));
12+
score = 0;
13+
nextId = 1;
14+
tilesMap.clear();
15+
document.getElementById('grid').querySelectorAll('.tile').forEach(t => t.remove());
16+
updateScore();
17+
addRandomTile();
18+
addRandomTile();
19+
render(true);
1320
}
1421

1522
function newGame() {
16-
document.getElementById('gameOver').classList.remove('show');
17-
init();
23+
document.getElementById('gameOver').classList.remove('show');
24+
init();
25+
}
26+
27+
// ---------- helpers ----------
28+
function makeCell(value) {
29+
return { v: value, id: nextId++ };
1830
}
1931

2032
function addRandomTile() {
21-
let empty = [];
22-
for (let r = 0; r < size; r++) {
23-
for (let c = 0; c < size; c++) {
24-
if (board[r][c] === 0) empty.push({r, c});
25-
}
26-
}
27-
if (empty.length > 0) {
28-
let {r, c} = empty[Math.floor(Math.random() * empty.length)];
29-
board[r][c] = Math.random() < 0.9 ? 2 : 4;
33+
const empty = [];
34+
for (let r = 0; r < size; r++) {
35+
for (let c = 0; c < size; c++) {
36+
if (board[r][c] === null) empty.push({ r, c });
3037
}
38+
}
39+
if (empty.length === 0) return;
40+
const { r, c } = empty[Math.floor(Math.random() * empty.length)];
41+
board[r][c] = makeCell(Math.random() < 0.9 ? 2 : 4);
3142
}
3243

33-
function render() {
44+
// ---------- rendering ----------
45+
function render(animated = true) {
3446
const grid = document.getElementById('grid');
35-
const tiles = grid.querySelectorAll('.tile');
36-
tiles.forEach(tile => tile.remove());
37-
47+
const gridRect = grid.getBoundingClientRect();
48+
const gap = 10; // same as CSS
49+
const cellSize = (gridRect.width - gap * (size - 1) - 20) / size;
50+
51+
const existingTiles = {};
52+
grid.querySelectorAll('.tile').forEach(tile => {
53+
existingTiles[String(tile.dataset.id)] = tile;
54+
});
55+
const newIds = new Set();
56+
3857
for (let r = 0; r < size; r++) {
39-
for (let c = 0; c < size; c++) {
40-
if (board[r][c] !== 0) {
41-
const tile = document.createElement('div');
42-
tile.className = `tile tile-${board[r][c]}`;
43-
if (board[r][c] > 2048) tile.className = 'tile tile-super';
44-
tile.textContent = board[r][c];
45-
tile.style.left = `${c * 100 + 10}px`;
46-
tile.style.top = `${r * 100 + 10}px`;
47-
grid.appendChild(tile);
48-
}
58+
for (let c = 0; c < size; c++) {
59+
const cell = board[r][c];
60+
if (!cell) continue;
61+
62+
const value = cell.v;
63+
const idStr = String(cell.id);
64+
newIds.add(idStr);
65+
66+
let tile = existingTiles[idStr];
67+
if (!tile) {
68+
tile = document.createElement('div');
69+
tile.className = `tile tile-${value}`;
70+
tile.textContent = value;
71+
tile.dataset.id = idStr;
72+
grid.appendChild(tile);
73+
74+
tile.style.transform = "scale(0)";
75+
requestAnimationFrame(() => {
76+
tile.style.transform = "scale(1)";
77+
});
78+
} else if (parseInt(tile.textContent, 10) !== value) {
79+
tile.textContent = value;
80+
tile.className = `tile tile-${value} merged`;
81+
tile.addEventListener('animationend', () => tile.classList.remove('merged'), { once: true });
4982
}
83+
84+
// RESPONSIVE POSITIONING
85+
const left = c * (cellSize + gap) + 10; // +10 for grid padding
86+
const top = r * (cellSize + gap) + 10;
87+
tile.style.width = `${cellSize}px`;
88+
tile.style.height = `${cellSize}px`;
89+
tile.style.left = `${left}px`;
90+
tile.style.top = `${top}px`;
91+
92+
if (animated) {
93+
tile.style.transition = "top 0.15s ease, left 0.15s ease, transform 0.15s, opacity 0.15s";
94+
} else {
95+
tile.style.transition = "none";
96+
}
97+
98+
if (value > 2048) tile.classList.add("tile-super");
99+
}
100+
}
101+
102+
for (const idStr in existingTiles) {
103+
if (!newIds.has(idStr)) {
104+
const tile = existingTiles[idStr];
105+
tile.style.transform = "scale(0)";
106+
tile.style.opacity = "0";
107+
tile.addEventListener("transitionend", () => tile.remove(), { once: true });
108+
setTimeout(() => { if (tile.parentNode) tile.remove(); }, 300);
109+
}
50110
}
111+
}
112+
113+
114+
115+
116+
function findCellById(id) {
117+
for (let r = 0; r < size; r++)
118+
for (let c = 0; c < size; c++)
119+
if (board[r][c] && board[r][c].id === id) return { r, c };
120+
return null;
51121
}
52122

123+
// ---------- score ----------
53124
function updateScore() {
54-
document.getElementById('score').textContent = score;
55-
if (score > best) {
56-
best = score;
57-
document.getElementById('best').textContent = best;
125+
document.getElementById('score').textContent = score;
126+
if (score > best) {
127+
best = score;
128+
document.getElementById('best').textContent = best;
129+
}
130+
}
131+
132+
// ---------- movement utilities ----------
133+
// slide & merge a line of cell objects (keeps ids appropriately)
134+
function slideAndMergeLine(line) {
135+
// line: array of cell objects or null, length = size
136+
const comps = line.filter(x => x !== null); // compacted left
137+
const merged = [];
138+
for (let i = 0; i < comps.length; i++) {
139+
if (i + 1 < comps.length && comps[i].v === comps[i + 1].v) {
140+
// merge into comps[i]; keep comps[i].id (so DOM element of first tile remains)
141+
comps[i].v *= 2;
142+
score += comps[i].v;
143+
// mark the second tile's id for removal by not carrying it forward
144+
// (we simply skip the next item)
145+
merged.push(comps[i].id); // for optional UI if needed
146+
comps.splice(i + 1, 1);
58147
}
148+
}
149+
while (comps.length < size) comps.push(null);
150+
return comps;
59151
}
60152

153+
// ---------- moves ----------
61154
function move(direction) {
62-
let moved = false;
63-
let newBoard = board.map(row => [...row]);
64-
65-
if (direction === 'left' || direction === 'right') {
66-
for (let r = 0; r < size; r++) {
67-
let row = newBoard[r].filter(val => val !== 0);
68-
if (direction === 'right') row.reverse();
69-
70-
for (let i = 0; i < row.length - 1; i++) {
71-
if (row[i] === row[i + 1]) {
72-
row[i] *= 2;
73-
score += row[i];
74-
row.splice(i + 1, 1);
75-
}
76-
}
77-
78-
while (row.length < size) row.push(0);
79-
if (direction === 'right') row.reverse();
80-
81-
if (JSON.stringify(newBoard[r]) !== JSON.stringify(row)) moved = true;
82-
newBoard[r] = row;
83-
}
84-
} else {
85-
for (let c = 0; c < size; c++) {
86-
let col = [];
87-
for (let r = 0; r < size; r++) {
88-
if (newBoard[r][c] !== 0) col.push(newBoard[r][c]);
89-
}
90-
if (direction === 'down') col.reverse();
91-
92-
for (let i = 0; i < col.length - 1; i++) {
93-
if (col[i] === col[i + 1]) {
94-
col[i] *= 2;
95-
score += col[i];
96-
col.splice(i + 1, 1);
97-
}
98-
}
99-
100-
while (col.length < size) col.push(0);
101-
if (direction === 'down') col.reverse();
102-
103-
for (let r = 0; r < size; r++) {
104-
if (newBoard[r][c] !== col[r]) moved = true;
105-
newBoard[r][c] = col[r];
106-
}
155+
let moved = false;
156+
let newBoard = Array(size).fill().map(() => Array(size).fill(null));
157+
158+
if (direction === 'left' || direction === 'right') {
159+
for (let r = 0; r < size; r++) {
160+
const line = board[r].slice(); // row
161+
// map to objects or null already
162+
let working = line.slice();
163+
if (direction === 'right') working = working.reverse();
164+
const compact = working.filter(x => x !== null);
165+
// create shallow copies to avoid mutating original objects except value changes
166+
const mergedLine = slideAndMergeLine(compact.map(x => x ? { v: x.v, id: x.id } : null));
167+
// place back
168+
let final = mergedLine;
169+
if (direction === 'right') final = final.reverse();
170+
for (let c = 0; c < size; c++) {
171+
// if final[c] is an object keep its id and updated value; else null
172+
newBoard[r][c] = final[c] ? { v: final[c].v, id: final[c].id } : null;
173+
}
174+
if (!arraysRowEqual(board[r], newBoard[r])) moved = true;
175+
}
176+
} else { // up / down
177+
for (let c = 0; c < size; c++) {
178+
const col = [];
179+
for (let r = 0; r < size; r++) col.push(board[r][c]);
180+
let working = col.slice();
181+
if (direction === 'down') working = working.reverse();
182+
const compact = working.filter(x => x !== null);
183+
const mergedCol = slideAndMergeLine(compact.map(x => x ? { v: x.v, id: x.id } : null));
184+
let final = mergedCol;
185+
if (direction === 'down') final = final.reverse();
186+
for (let r = 0; r < size; r++) {
187+
newBoard[r][c] = final[r] ? { v: final[r].v, id: final[r].id } : null;
188+
}
189+
// compare original column with new column
190+
for (let r = 0; r < size; r++) {
191+
const a = board[r][c];
192+
const b = newBoard[r][c];
193+
if ((a === null && b !== null) || (a !== null && b === null) || (a && b && (a.v !== b.v || a.id !== b.id))) {
194+
moved = true;
195+
break;
107196
}
197+
}
108198
}
199+
}
109200

110-
if (moved) {
111-
board = newBoard;
112-
addRandomTile();
113-
updateScore();
114-
render();
115-
116-
if (checkGameOver()) {
117-
setTimeout(() => {
118-
document.getElementById('gameOver').classList.add('show');
119-
}, 300);
120-
}
201+
if (moved) {
202+
board = newBoard;
203+
addRandomTile();
204+
updateScore();
205+
render();
206+
if (checkGameOver()) {
207+
setTimeout(() => {
208+
document.getElementById('gameOver').classList.add('show');
209+
}, 300);
121210
}
211+
}
122212
}
123213

214+
// helper to compare row arrays of cell objects/null by value+id
215+
function arraysRowEqual(oldRow, newRow) {
216+
for (let i = 0; i < size; i++) {
217+
const a = oldRow[i], b = newRow[i];
218+
if (a === null && b === null) continue;
219+
if ((a === null) !== (b === null)) return false;
220+
if (a.v !== b.v || a.id !== b.id) return false;
221+
}
222+
return true;
223+
}
224+
225+
// ---------- game over ----------
124226
function checkGameOver() {
125-
for (let r = 0; r < size; r++) {
126-
for (let c = 0; c < size; c++) {
127-
if (board[r][c] === 0) return false;
128-
if (c < size - 1 && board[r][c] === board[r][c + 1]) return false;
129-
if (r < size - 1 && board[r][c] === board[r + 1][c]) return false;
130-
}
227+
for (let r = 0; r < size; r++) {
228+
for (let c = 0; c < size; c++) {
229+
if (board[r][c] === null) return false;
230+
if (c < size - 1 && board[r][c].v === board[r][c + 1].v) return false;
231+
if (r < size - 1 && board[r][c].v === board[r + 1][c].v) return false;
131232
}
132-
return true;
233+
}
234+
return true;
133235
}
134236

237+
// ---------- keyboard ----------
135238
document.addEventListener('keydown', (e) => {
136-
if (e.key === 'ArrowLeft') move('left');
137-
else if (e.key === 'ArrowRight') move('right');
138-
else if (e.key === 'ArrowUp') move('up');
139-
else if (e.key === 'ArrowDown') move('down');
239+
if (e.key === 'ArrowLeft') move('left');
240+
else if (e.key === 'ArrowRight') move('right');
241+
else if (e.key === 'ArrowUp') move('up');
242+
else if (e.key === 'ArrowDown') move('down');
140243
});
141244

142-
init();
245+
// ---------- start ----------
246+
init();

0 commit comments

Comments
 (0)