Katkat

Design a 2048 Game

AsanaSoftware Engineer · Onsite

Last updated

This question evaluates object-oriented design, state management, deterministic randomness handling, and algorithmic reasoning for implementing game rules on a configurable N x N grid.

Design the object model and core game logic for a 2048 game on an N x N grid. This is an object-oriented design problem: focus on the classes, their responsibilities, the relationships between them, and the algorithm for a move — not on the UI or rendering.

For reference, 2048 is a sliding-tile puzzle. The player moves all tiles in one of four directions; tiles slide as far as they can, and two adjacent tiles of equal value merge into one tile of their sum. After every move that changes the board, a new tile spawns in a random empty cell.

Constraints & Assumptions

  • The grid is square, N x N, with N = 4 by default but configurable.
  • Tile values are powers of two; empty cells are represented by 0.
  • A new tile is a 2 with probability $0.9$ and a 4 with probability $0.1$ (configurable).
  • The game starts with two random tiles already placed.
  • The default win condition is reaching a 2048 tile; the game may continue after winning.
  • The game logic must be deterministically testable — randomness has to be injectable so tests are reproducible.
  • Assume a single local player; no networking or persistence is required (but the design should not preclude them).

The Problem

Produce a class model and the core logic that satisfies the following requirements:

  • Initialization — start a board with two random tiles.
  • Moves — support UP, DOWN, LEFT, and RIGHT.
  • Slide & merge — implement the standard rules: tiles slide toward the move direction, and each tile may merge at most once per move.
  • Scoring — increase the score by the value of each merged tile.
  • Tile spawn — spawn a new tile only when a move actually changes the board.
  • State detection — detect the win state and the game-over state.
  • Separation of concerns — keep UI/presentation strictly out of the game logic.

Also discuss how you would support these extensions without rewriting the merge logic: undo, custom board sizes, deterministic tests, and configurable spawn probabilities.

Clarifying Questions to Ask

  • Is the board always square, and is N fixed at 4 or configurable?
  • After reaching 2048, does the game end immediately or can the player keep playing for a higher tile?
  • What are the exact spawn probabilities for 2 vs 4, and must they be configurable?
  • Is undo a hard requirement, and if so, how many steps deep?
  • Do we need persistence (save/resume) or networking, or is this a single local session?
  • Should the score include only merge gains, or are there bonus/penalty rules?

Hints

Hint 1

Look for the repeated operation: Before writing four separate move handlers, ask whether `UP`/`DOWN`/`RIGHT` are genuinely different from `LEFT`, or whether they're the same operation viewed from a different angle. If you can reduce them to one case, what's the smallest unit of the board that operation acts on?

Hint 2

The merge rule has a trap: The "each tile merges at most once per move" rule is the easiest thing to get wrong. Pick a concrete line that exposes the ambiguity and decide its result *before* coding: what should `2 2 2` become when slid? Your answer here dictates exactly how your merge loop must advance.

Hint 3

Which pieces deserve to be pulled out?: Two parts of this system are the ones you'll most want to test in isolation and vary later: the rule that's easy to get wrong, and the part that's non-deterministic. Consider what the `Game` should depend on so that neither of those is hardwired into it.

Hint 4

Game over isn't "board full": Watch out: a completely full board can still have a legal move. Think about what actually has to be true for *every* direction to be blocked at once — that condition, not "no empty cells," is your game-over test.

Follow-up questions

  • How would you implement **undo** with minimal memory? Compare snapshotting the whole board vs. recording a reversible move delta, including how merges and the spawned tile complicate a pure inverse.
  • How would you detect game-over *efficiently* if `N` were large — can you maintain it incrementally instead of scanning the whole board after every move?
  • How would you adapt this design to a multiplayer or server-authoritative version where the server must validate moves and prevent cheating?
  • How would you generalize from "merge equal values into their sum" to a configurable rule (e.g., a different target tile, or Fibonacci-style merges) without touching the slide logic?