What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Build one left-oriented line-merge function, then reuse it for up, right, and down by transforming the board before and after each move. This keeps the merge-once-per-move rule in one place, makes the logic easier to test, and avoids four subtly different implementations.
How a 2048 move should work
Classic 2048 uses a 4×4 board of power-of-two tiles. A move slides tiles toward one edge; adjacent equal tiles combine into a tile equal to their sum, and that resulting value is added to the score. A tile created during a move cannot merge again until the next move. The original game implements that constraint by tracking merge provenance on destination tiles in its move logic.
Keep these rules independent of the DOM. A pure function that accepts a board and direction and returns a new board and score change can be tested without rendering tiles or handling input. After that logic reports a successful move, the game can spawn a tile and check for game-over. The original project is available under the MIT License.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC 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 & 11Write the canonical left-merge function
For a line moving left, compact nonzero values toward the beginning, merge equal neighbors once, and pad with zeroes to restore the line length. When a pair merges, advance past both source tiles; that prevents the new result from combining again in the same move.
#1 Best Overall
function slideAndMergeLine(line) {
const values = line.filter(value => value !== 0);
const result = [];
let scoreDelta = 0;
for (let i = 0; i < values.length; i++) {
if (values[i] === values[i + 1]) {
const merged = values[i] * 2;
result.push(merged);
scoreDelta += merged;
i++; // Consume both tiles; the new tile cannot merge again now.
} else {
result.push(values[i]);
}
}
while (result.length < line.length) result.push(0);
return { line: result, scoreDelta };
}
The function assumes zero represents an empty cell and that the line contains valid 2048 tile values. It returns a new line rather than changing the input, which makes move comparisons and tests straightforward.
Check the merge-once rule
[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 created 4 cannot absorb the original adjacent 4 on the same move.[0, 2, 0, 2]compacts to[2, 2, 0, 0]and then becomes[4, 0, 0, 0].
These examples capture both ordering requirements: empty cells close up before merging, and a created tile is unavailable for another merge until the next move.
Rank #2
Reuse the line operation for all four directions
Make every directional move look like a left move by orienting the board, applying the line function, then restoring the orientation. One convenient representation is an array of rows. Reversing every row changes left to right; transposing rows and columns changes horizontal movement to vertical movement.
function reverseRows(board) {
return board.map(row => [...row].reverse());
}
function transpose(board) {
return board[0].map((_, column) =>
board.map(row => row[column])
);
}
function move(board, direction) {
let oriented;
switch (direction) {
case "left": oriented = board.map(row => [...row]); break;
case "right": oriented = reverseRows(board); break;
case "up": oriented = transpose(board); break;
case "down": oriented = reverseRows(transpose(board)); break;
default: throw new Error(`Unknown direction: ${direction}`);
}
let scoreDelta = 0;
const slid = oriented.map(row => {
const result = slideAndMergeLine(row);
scoreDelta += result.scoreDelta;
return result.line;
});
let nextBoard;
switch (direction) {
case "left": nextBoard = slid; break;
case "right": nextBoard = reverseRows(slid); break;
case "up": nextBoard = transpose(slid); break;
case "down": nextBoard = transpose(reverseRows(slid)); break;
}
const changed = nextBoard.some((row, r) =>
row.some((value, c) => value !== board[r][c])
);
return { board: nextBoard, scoreDelta, changed };
}
This uses a square-board assumption, which matches classic 2048’s 4×4 grid. The transpose-based pattern is also used in a JavaScript 2048 example repository that derives all directions from one line primitive.
Understand the transforms
- Left: process each row as-is.
- Right: reverse each row, process it as left, then reverse it back.
- Up: transpose so each original column becomes a row, process left, then transpose back.
- Down: transpose, reverse those rows so the leading edge is down, process left, reverse, then transpose back.
The mapping from direction to transform is the part most likely to cause coordinate mistakes. Keep the reversal and transpose helpers small, and test that each transform reverses itself as expected.
Apply a move without spawning on a no-op
The move function should calculate board changes and score only. The game loop can then decide whether to perform follow-on effects. Spawn a tile only if the resulting board differs from the input; an attempted move into a blocked edge must not consume a turn or add a tile. The original game likewise performs spawn and game-over checks only after detecting movement.
Rank #4
- Call
move(board, direction). - If
changedis false, keep the current board and do not spawn a tile. - If
changedis true, use the returned board, addscoreDeltato the score, spawn one tile, then check for a win or game over.
For classic 2048, a newly spawned tile is 2 with 90% probability or 4 with 10% probability, as described by Szubert and Jaśkowski’s 2014 paper, Temporal Difference Learning of N-Tuple Networks for the Game 2048. Keep the random-number source injectable in the spawn function so tests can control which empty cell and value are selected; the move and merge rules themselves need no randomness.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Test the rules and directional symmetry
Test the line primitive directly, then test the board transforms and move pipeline separately. For example, a line test should assert both the resulting values and the score change; a board test should assert that a no-op reports changed: false.
Best Value
- Verify the three merge examples above, including that each created tile merges at most once per move.
- Verify that zeros compact toward the selected edge in all four directions.
- For an asymmetric board, compare right and vertical moves against the corresponding reversed or transposed left-oriented result; avoid relying only on symmetric boards, which can hide orientation bugs.
- Check that score increases by the value of each resulting merge tile. For example, merging two 2s adds 4, not 2.
- Check that unchanged moves neither spawn nor alter score, and changed moves spawn exactly one tile.
- Test win detection when a 2048 tile appears and loss detection when the board is full with no equal horizontal or vertical neighbors.
A useful separation is three small test targets: slideAndMergeLine for line rules, transpose/reverseRows for orientation, and move for board results and score. This localizes a failure to either the rule or the coordinate mapping.
Choose between directional branches and transformations
| Approach | Duplicated rule code | Testing and clarity | Main risk |
|---|---|---|---|
| Four explicit directional implementations | Merge and traversal logic is repeated across directions. | Each branch may initially feel direct because it follows board coordinates explicitly. | One direction can diverge in merge-once behavior or edge handling. |
| One line primitive plus transforms | The merge rule is implemented once and reused. | Line rules can be tested once; transform tests make the directional mapping explicit. | A mistaken transpose/reversal pairing can send tiles the wrong way. |
The shared primitive reduces duplicated rule code; it is a maintainability choice, not a claim that it runs faster. If you choose explicit branches for a first prototype, compare all four paths against the same line-rule examples so their behavior remains consistent.
Keep rendering separate from game rules
The merge function should not query or update HTML elements. Treat the board as data, render the returned board afterward, and keep input handling, animation, score display, and random spawning outside the pure line operation. This makes it possible to change the interface without changing the rules and to test the rules in a plain JavaScript runtime.
Quick 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.

