Problem statement
Design the object model and core APIs for a Connect Four game engine that runs a single two-player match from an empty grid to a decided result. The engine owns the rules of a token-drop game: players alternate dropping a disc into a column, the disc falls to the lowest free slot under gravity, and the first player to line up four of their own discs in a row wins. Whatever host drives it (a console loop, a GUI, a test harness) only feeds column drops in and reads state back.
Operating context. One in-process match at a time on a rectangular grid of columns and rows (the familiar setup is 7 columns by 6 rows, but the dimensions and the win length arrive as construction parameters — nothing is hard-coded to 7x6-connect-4). Two players alternate turns; each turn the mover names a column, and the disc settles on top of whatever is already stacked there. A drop is legal only when it is the mover's turn, the column index is in range, the column is not already full, and the match is still running. The match ends the instant a drop completes a straight run of the required length horizontally, vertically, or along either diagonal — or in a draw when every slot is filled and no such run exists. Input arrives as (player, column) calls on one thread; there is no clock, no network, and no concurrent access.
Out of scope. Rendering or animating the falling disc, any console I/O or input parsing, an AI opponent or move search, persistence and save/resume, and networking or matchmaking. Design only the in-process rules engine and its object model, not the UI or transport around it.
What to produce. The class hierarchy (entities such as Board, Column or Cell, Player, Move, Game, plus value objects like Disc or Position), the public API each class exposes, and the state transitions for the match (not-started, in-progress, won, drawn) and for a slot (empty, filled). Be explicit about: how gravity picks the landing row for a dropped disc; how a win is detected in all four directions right after a drop without rescanning the whole grid; how turn ownership is tracked and enforced; how a drop into a full column is rejected without corrupting state; and how the same core extends to a different board size or win length (say 8x8 with connect-5) without rewriting drop handling.
Functional requirements
- Drop a disc for the current player into a named column: validate it, settle the disc on the lowest free slot under gravity, and advance the turn.
- Reject an illegal drop (wrong turn, column out of range, column already full, or a finished match) without mutating the grid or turn.
- Detect a win immediately after a drop when the mover completes a run of the required length horizontally, vertically, or on either diagonal.
- Detect a draw when every slot is filled and no winning run exists.
- Expose read-only queries for the current grid contents, whose turn it is, which columns are still droppable, and the match outcome.
Non-functional requirements
- A drop resolves in O(rows) to find the landing slot and O(win-length) to check the four directions from it, never O(rows*columns) per move.
- Illegal-drop rejection is total: every rejected call leaves grid, turn, and status exactly as they were, with no partial mutation.
- The rules core is testable with no I/O, no wall clock, and no rendering — column drops in, state out.
- Board dimensions and win length are constructor parameters: supporting a different size or connect-K must not change drop or turn logic.
- The win check is a seam pluggable enough that a variant rule (a different win length or extra directions) swaps in without touching drop or grid mutation.
- Public state is immutable to callers: reading the grid or outcome cannot let a caller place a disc directly and bypass validation.
Topics
- System Design LLD
- Oop Solid
- Statemachine
- Extensibility
- Testability Clock