Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Build 2048’s movement around one left-oriented line function, then reuse it for all four directions by transforming the board. This keeps the rules for compaction and merging in one place, makes the “a tile can merge only once per move” rule explicit, and gives you a small, testable game engine separate from the DOM.
Model the board and move rules
Classic 2048 uses a 4×4 board. Each cell contains a power-of-two tile or zero for an empty cell. A move slides tiles in one direction; equal neighboring tiles combine into their sum, and the resulting tile’s value is added to the score. A tile created by a merge cannot merge again during that same move.
As an Amazon Associate I earn from qualifying purchases.
The original implementation is available in Gabriele Cirulli’s 2048 source repository, which is MIT-licensed. Its move logic records whether a destination tile has already merged so it cannot absorb another tile in the same turn. The classic rules, including the 2048 win condition, are also described in Szubert and Jaśkowski’s 2014 paper on 2048.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Write one line merge function
Make the left move the canonical operation. First remove zeros so tiles are packed toward the left. Then scan from left to right: when two adjacent values match, replace them with their sum and skip both inputs. Finally, pad the line with zeros to restore its original length.
#1 Best Overall
function slideAndMergeLine(line) {
const values = line.filter(value => value !== 0);
const result = [];
let scoreGained = 0;
for (let i = 0; i < values.length; i++) {
if (values[i] === values[i + 1]) {
const merged = values[i] * 2;
result.push(merged);
scoreGained += merged;
i++; // The consumed pair cannot merge again this move.
} else {
result.push(values[i]);
}
}
while (result.length < line.length) result.push(0);
return { line: result, scoreGained };
}
This implementation assumes the standard game’s powers-of-two values and uses zero for empty cells. It returns both the transformed line and the score increase, leaving the board and UI untouched.
Check merge-once-per-move cases
[2, 2, 2, 2]becomes[4, 4, 0, 0], not[8, 0, 0, 0].[2, 2, 4, 0]becomes[4, 4, 0, 0]. The newly formed 4 cannot merge with the adjacent 4 in the same move.[0, 2, 0, 2]compacts to[2, 2, 0, 0]before merging, producing[4, 0, 0, 0].
Reuse the line function for four directions
Orient the board so the chosen direction is left, run the same line function on each row, then restore the original orientation. A compact implementation can use transpose and row reversal:
Rank #2
function transpose(board) {
return board[0].map((_, column) => board.map(row => row[column]));
}
function reverseRows(board) {
return board.map(row => [...row].reverse());
}
function orientForMove(board, direction) {
switch (direction) {
case "left": return board.map(row => [...row]);
case "right": return reverseRows(board);
case "up": return transpose(board);
case "down": return reverseRows(transpose(board));
default: throw new Error(`Unknown direction: ${direction}`);
}
}
function restoreAfterMove(board, direction) {
switch (direction) {
case "left": return board;
case "right": return reverseRows(board);
case "up": return transpose(board);
case "down": return transpose(reverseRows(board));
default: throw new Error(`Unknown direction: ${direction}`);
}
}
function move(board, direction) {
const oriented = orientForMove(board, direction);
let scoreGained = 0;
const movedLines = oriented.map(row => {
const result = slideAndMergeLine(row);
scoreGained += result.scoreGained;
return result.line;
});
const nextBoard = restoreAfterMove(movedLines, direction);
const changed = nextBoard.some((row, r) =>
row.some((value, c) => value !== board[r][c])
);
return { board: nextBoard, scoreGained, changed };
}
Here, “right” reverses each row before and after the left operation. “Up” transposes the board, making each original column a row. “Down” transposes and reverses those rows, then applies the inverse transforms. This is the same single-primitive pattern used in Zoltan Dul’s 2048-Game example repository.
The code expects a rectangular board and treats it as immutable: the input rows are not modified. For a fixed 4×4 game this is straightforward; if you support other board sizes, ensure every row has the same length.
Connect moves to score, spawning, and game state
Keep move calculation pure: it should return a new board, the score gained, and whether anything changed. The surrounding game controller can then decide whether to update the score, spawn a tile, and check for the end of the game.
- Call
move(board, direction)and read its result. - If
changedis false, leave the board, score, and spawn state alone. A blocked move should not create a tile. - If
changedis true, addscoreGainedto the score and spawn one tile in an empty cell. - After the spawn, check whether the player has reached 2048 and whether any legal moves remain.
In the classic rules described by Szubert and Jaśkowski, a new tile is 2 with 90% probability or 4 with 10% probability. Keep the random-number source injectable into your spawn function so tests can select a known empty cell and tile value rather than depending on chance. These probabilities concern spawning, not merging.
Rank #4
The win condition is creating a 2048 tile. A board with no empty cells and no equal adjacent tiles has no legal move and is game over. For a general implementation, check equality only between horizontal and vertical neighbors; diagonal tiles cannot merge.
Free tools Windows power users keep installed
One-click scans. No signup required.
Keep game rules separate from rendering
The move function should not read buttons, update HTML, or inspect CSS classes. A small UI layer can translate a key press into a direction, call the game logic, then render the returned state. This separation makes merge behavior testable without a browser and avoids tying rules to a particular layout.
Best Value
When you later add animation, render the transition from the prior board to the returned board, but do not let animation state determine whether tiles are allowed to merge. The board calculation remains the authority for game rules.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Test the shared primitive and transforms
Test the line operation independently, then test each direction against a board whose expected result is easy to inspect. Include cases where tiles move without merging, multiple pairs merge, a merge cannot chain, and a move changes nothing.
- For the canonical left operation, verify the three edge cases above and confirm that score gained equals the sum of newly created tiles.
- For right, up, and down, use mirrored or transposed versions of the same inputs and check that results match the corresponding transform of the left result.
- Assert that a no-op move reports
changed: falseand does not cause the controller to spawn. - Use deterministic random values in spawn tests to check both a selected empty cell and the 2/4 choice.
- Check that the original board is unchanged after every move if your engine is intended to be immutable.
Choose shared logic or explicit directional branches
| Approach | Rule-code duplication | Testing and clarity | Main risk |
|---|---|---|---|
| One line merge function plus transforms | Low: merge behavior lives in one primitive. | Test the primitive once, then verify the four orientation mappings. | A mistaken inverse transform can put tiles in the wrong cells. |
| Separate logic for each direction | Higher: movement and merging are repeated across branches. | Each branch can read naturally at first, but all four must stay consistent. | Direction-specific bugs or rule drift between branches. |
The comparison is about code organization, not measured runtime performance. For this game, a canonical line operation is a useful default because merge rules change in one place; explicit branches can still be reasonable for a tiny prototype if you test all directions thoroughly.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




