Every move in 2048 operates on a single one-dimensional lane. For horizontal pushes, each row is a lane. For vertical pushes, each column is a lane. Once you realize that a lane behaves identically regardless of direction — as long as the wall it is sliding toward is treated as index 0 — you can implement the movement once and reuse it everywhere.
The core helper first compacts a lane by removing zeros while preserving the relative order of nonzero tiles. It then scans the compacted prefix from the front. When two adjacent values match, it writes their doubled value once and skips the next tile. That skip is what enforces "each tile merges at most once." For example, [2, 2, 2, 2] becomes [4, 4, 0, 0], never [8, 0, 0, 0].
Consider moving left on the lane [2, 4, 0, 4]. Compaction removes the zero to produce [2, 4, 4]. The merge scan sees the two adjacent 4s, writes 8, and leaves the 2 in front, producing [2, 8]. After padding with zeros, the final lane is [2, 8, 0, 0].
Mapping directions onto this helper is a small adapter. For left, rows are read and written directly. For right, rows are read from right to left, collapsed, and written back from the right. For up, columns are read top to bottom, collapsed, and written back top to bottom. For down, columns are read bottom to top, collapsed, and written back from the bottom. The implementation below uses two booleans: vertical for column processing and reverse for directions whose leading edge is the far end of the lane.
This implementation returns a fresh board as a pure function. If an in-place contract is required, copy the returned matrix back into the original board. A caller can detect whether the board changed by comparing the input and output matrices.
from typing import Listclass Solution: def move(self, board: List[List[int]], direction: str) -> List[List[int]]: if direction not in ("up", "down", "left", "right"): raise ValueError("Unknown direction: " + direction) n = len(board) result = [[0] * n for _ in range(n)] vertical = direction in ("up", "down") reverse = direction in ("right", "down") def read_lane(index: int) -> List[int]: lane = [0] * n for pos in range(n): row = (n - 1 - pos if reverse else pos) if vertical else index col = index if vertical else (n - 1 - pos if reverse else pos) lane[pos] = board[row][col] return lane def write_lane(index: int, lane: List[int]) -> None: for pos in range(n): row = (n - 1 - pos if reverse else pos) if vertical else index col = index if vertical else (n - 1 - pos if reverse else pos) result[row][col] = lane[pos] def collapse(lane: List[int]) -> List[int]: packed = [value for value in lane if value != 0] moved: List[int] = [] i = 0 while i < len(packed): if i + 1 < len(packed) and packed[i] == packed[i + 1]: moved.append(packed[i] * 2) i += 2 # both tiles in the pair are consumed else: moved.append(packed[i]) i += 1 moved.extend([0] * (n - len(moved))) return moved for index in range(n): write_lane(index, collapse(read_lane(index))) return resultclass Solution { public int[][] move(int[][] board, String direction) { if (!direction.equals("left") && !direction.equals("right") && !direction.equals("up") && !direction.equals("down")) { throw new IllegalArgumentException("Unknown direction: " + direction); } int n = board.length; int[][] result = new int[n][n]; boolean vertical = direction.equals("up") || direction.equals("down"); boolean reverse = direction.equals("right") || direction.equals("down"); for (int index = 0; index < n; index++) { int[] lane = readLane(board, vertical, reverse, index); int[] collapsed = collapse(lane); writeLane(result, collapsed, vertical, reverse, index); } return result; } private int[] readLane(int[][] board, boolean vertical, boolean reverse, int index) { int n = board.length; int[] lane = new int[n]; for (int pos = 0; pos < n; pos++) { int row = vertical ? (reverse ? n - 1 - pos : pos) : index; int col = vertical ? index : (reverse ? n - 1 - pos : pos); lane[pos] = board[row][col]; } return lane; } private void writeLane(int[][] board, int[] lane, boolean vertical, boolean reverse, int index) { int n = board.length; for (int pos = 0; pos < n; pos++) { int row = vertical ? (reverse ? n - 1 - pos : pos) : index; int col = vertical ? index : (reverse ? n - 1 - pos : pos); board[row][col] = lane[pos]; } } private int[] collapse(int[] lane) { int n = lane.length; int[] packed = new int[n]; int write = 0; for (int value : lane) { if (value != 0) { packed[write++] = value; } } int[] moved = new int[n]; int out = 0; for (int i = 0; i < write; i++) { if (i + 1 < write && packed[i] == packed[i + 1]) { moved[out++] = packed[i] * 2; i++; // both tiles in the pair are consumed } else { moved[out++] = packed[i]; } } return moved; }}