Implement 2048 Game Logic
Company: Glean
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates array and matrix manipulation, stateful simulation of game mechanics, and handling edge cases such as merge constraints, single-merge-per-move rules, and empty-cell management.
Constraints
- The board is square, n x n, and the solution must work for any n >= 1 (not just n = 4).
- Tile values are positive powers of two; 0 denotes empty. The algorithm only relies on value equality for merges, so arbitrary positive integers also work.
- direction is exactly one of "up", "down", "left", "right".
- Each tile participates in at most one merge per move.
- Target O(n^2) time and O(n) or O(1) extra space per move.
Examples
Input: ([[2, 0, 2, 0], [2, 2, 2, 0], [2, 2, 2, 2], [4, 4, 2, 2]], 'left')
Expected Output: [[4, 0, 0, 0], [4, 2, 0, 0], [4, 4, 0, 0], [8, 4, 0, 0]]
Explanation: The four worked single-row examples as rows of one board moved left: [2,0,2,0]->[4,0,0,0]; [2,2,2,0]->[4,2,0,0]; [2,2,2,2]->[4,4,0,0] (two separate merges, not one chain); [4,4,2,2]->[8,4,0,0].
Input: ([[2, 0, 0, 2], [0, 0, 0, 0], [2, 2, 0, 0], [4, 2, 2, 4]], 'right')
Expected Output: [[0, 0, 0, 4], [0, 0, 0, 0], [0, 0, 0, 4], [0, 4, 4, 4]]
Explanation: Right slides toward the right wall. [2,0,0,2]->[..,4]; an all-zero row stays zero; [2,2,..]->[..,4]; [4,2,2,4]: the inner 2,2 merge to 4 while the outer 4s stay separate, giving [0,4,4,4].
Input: ([[2, 0, 0, 0], [2, 4, 0, 0], [4, 4, 2, 0], [0, 0, 2, 8]], 'up')
Expected Output: [[4, 8, 4, 8], [4, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
Explanation: Up collapses each column toward the top. col0 [2,2,4,0]->[4,4,0,0]; col1 [0,4,4,0]->[8,0,0,0]; col2 [0,0,2,2]->[4,0,0,0]; col3 [0,0,0,8]->[8,0,0,0]. Reassembled by rows.
Input: ([[2, 0, 0, 0], [2, 4, 0, 0], [4, 4, 2, 0], [0, 0, 2, 8]], 'down')
Expected Output: [[0, 0, 0, 0], [0, 0, 0, 0], [4, 0, 0, 0], [4, 8, 4, 8]]
Explanation: Same board moved down: each column collapses toward the bottom, so the merged tiles settle in the last rows — the mirror of the 'up' result.
Input: ([[5]], 'left')
Expected Output: [[5]]
Explanation: Edge case n = 1: a single cell cannot slide or merge, so the board is unchanged regardless of direction (value need not be a power of two).
Input: ([[0, 0, 0, 0]], 'left')
Expected Output: [[0, 0, 0, 0]]
Explanation: All-zero line: compacting yields an empty list, padded back to all zeros — nothing moves.
Input: ([[2, 4, 8, 16], [4, 8, 16, 32]], 'left')
Expected Output: [[2, 4, 8, 16], [4, 8, 16, 32]]
Explanation: No two adjacent tiles are equal and there are no gaps, so a left move leaves the board untouched (also shows a non-square 2x4 grid works).
Input: ([[2, 2, 2, 2, 2]], 'left')
Expected Output: [[4, 4, 2, 0, 0]]
Explanation: Odd-length line of five 2s: merge the first pair (4), the next pair (4), and the lone trailing 2 stays — [4,4,2,0,0]. Confirms 'at most one merge per tile' on an odd count.
Hints
- Every move is the same 1-D collapse applied to each row or column. Write 'collapse one line toward index 0' once, then transform the board so the chosen direction becomes 'left'.
- Collapse a single line in two passes to avoid double-merging: (1) drop all zeros so nonzero values pack to the front, then (2) scan front-to-back merging line[i] == line[i+1] into 2*line[i] and skipping the next index. The skip is what enforces 'at most one merge per tile'.
- left = the line function directly; right = reverse each row, collapse, reverse back; up/down = transpose so columns become rows (down also reverses), collapse, then transpose back. This keeps all merge correctness in exactly one place.