A terminal-based C++ implementation of Isolation Chess with local play, three AI difficulty levels, replay save/load, a replay browser, and simple two-player netplay.
- Cecilia: replay implementation (playback, stepping, autoplay, etc.); README "Compliance with code requirements" section
- Emmanuel: initial concept; core architecture design; code integration among members; UI layer and netplay implementation
- Gino: core AI logic; AIPlayer interaction; GameSession and rules tweaks
- Haneef: UI prototype
- Noah: UI prototype
- Scott: GameRules section & related core tests; replay implementation (saving & reading); project video demo (recording & editing)
Isolation Chess is a terminal-based C++ strategy game built with ncurses. Players take turns moving their piece and then breaking a tile; a player loses when they cannot make a legal move.
The project is designed as both a playable terminal game and a modular C++ coursework project. It separates game rules, session state, player logic, UI rendering, replay persistence, and netplay transport into different source files.
Original concept reference: https://scratch.mit.edu/projects/241565591/
-
Local multiplayer: Human vs Human
-
Single-player: Human vs CPU with Easy, Medium, and Hard AI
-
Replay system:
-
Netplay:
-
Settings editor from the launcher:
-
Dynamic ncurses layout:
-
Colorized HUD log messages for player actions, help output, replay saves, and network status
The main entry point is src/main.cpp. The launcher currently supports:
- Human vs Human
- Human vs CPU - Easy
- Human vs CPU - Medium
- Human vs CPU - Hard
- Netplay
- Replay browser
- Edit settings
- Edit room code
- Exit
The main runtime path uses:
include/scenes/live_match_scene.hppinclude/scenes/netplay_scene.hppinclude/scenes/replay_scene.hppinclude/scenes/replay_browser.hppsrc/main.cpp
The launcher and browser menus are fully keyboard-driven.
WASDmove the selection cursorCconfirm selectionTABswitch focused window- replay browser uses:
WSmove cursor up / downADflip page
When the game board is focused:
WASDmove the selection cursorEnter/Cconfirm move or breakXcancel selection and snap cursor back to the active pieceQis not the main quit path here; use HUD commands instead
When the HUD is focused:
TaborEscswitches focus between board and HUDUp/Downscroll the logLeft/Rightmove the command cursorBackspacedeletes in the command boxEntersubmits a command or chat message
Supported shared commands in local play and netplay:
:hor:help— show command help:save [name]— save a replay into./replays:quit [name]— save a replay, then leave the match:quit!— leave without saving
Replay names are restricted to letters, digits, _, -, and ..
When the replay board is focused:
Astep backwardDstep forwardSpacetoggle autoplayRreset replay to the beginningQleave replay modeTaborEscswitches focus between board and HUD
When the HUD is focused, the command box accepts:
:qor:quit— leave replay mode:reset— reset replay:play— toggle autoplay:back— step backward:forward— step forward:speed— set playback speeed:goto [turn]— go to a specific turn
Netplay is built around a simple room relay server in server/relay_server.py.
python3 server/relay_server.pyThe bundled server listens on:
- host:
0.0.0.0 - port:
5050
The default settings also use localhost and port 5050, so local testing works without editing the port.
Client messages:
JOIN <room> <tag>MOVE <turn> <row> <col>BREAK <turn> <row> <col>CHAT <text>
Server messages:
WELCOME <1|2>WAITINGSTART <p1_tag> <p2_tag>MOVE <turn> <row> <col>BREAK <turn> <row> <col>CHAT <1|2> <text>INFO <free text>ERROR <free text>
Replay files are written into the replays/ directory using the .isor extension.
If no filename is provided, the game generates a timestamped replay name such as:
replay_YYYYMMDD_HHMMSS.isor
If a chosen filename already exists, the replay system appends _1, _2, and so on instead of overwriting the old file.
Stored replay data currently includes:
- initial board state
- board size
- player starting positions
- side/phase/status metadata
- winner
- player names
- full turn history
- stored UI messages
The AI implementation lives in include/players/ai_player.hpp and src/players/ai_player.cpp.
Current strategy mix:
- Easy: mostly random, sometimes greedy, rarely minimax
- Medium: balanced random/greedy/minimax mix, with more minimax as turns progress
- Hard: mostly minimax, and fully minimax in tighter endgames
The AI also includes:
- non-blocking think delays based on the game tick rate
- greedy move selection
- minimax with alpha-beta pruning
- stronger endgame behavior when the board becomes sparse
This is the current layout that matters for the active launcher path.
.
├─ include/
│ ├─ core/
│ ├─ misc/
│ ├─ players/
│ ├─ scenes/
│ ├─ sessions/
│ └─ ui/
├─ src/
│ ├─ core/
│ ├─ misc/
│ ├─ players/
│ ├─ sessions/
│ ├─ ui/
│ └─ main.cpp
├─ server/
│ └─ relay_server.py
├─ tests/
├─ replays/
├─ settings.cfg
├─ Makefile
└─ README.md
src/main.cpp— main app-layer controlinclude/scenes/live_match_scene.hpp— local match runtimeinclude/scenes/main_menu_scene.hpp— main menu runtimeinclude/scenes/netplay_scene.hpp— netplay runtime and waiting/connect flowinclude/scenes/replay_scene.hpp— replay runtimeinclude/scenes/scene_common.hpp— shared ncurses guard and command handlingsrc/ui/board_renderer.cpp— board rendering and viewport scrollingsrc/ui/game_hud.cpp— HUD panels, log view, and command boxsrc/sessions/match_session.cpp— live turn flow, history, and replay exportsrc/sessions/replay_session.cpp— replay stepping, autoplay, and visual statesrc/core/replay_io.cpp— replay file save/loadinclude/players/network_player.hpp— header-only network link and network player wrappers
-
Generation of random events
The project uses runtime-seeded pseudo-randomness in gameplay logic, not just fixed scripted branching. For example:
- AI strategy selection is probabilistic. The AI computes weighted strategy probabilities and samples one at runtime in
src/players/ai_player.cpp. - Easy/Medium/Hard use different base weights (random, greedy, minimax), defined in
src/players/ai_player.cpp. - Random legal move and break selection is implemented in
src/players/ai_player.cpp(findRandomMove,findRandomBreak). - The AI's PRNG is seeded at runtime using
std::random_devicein the AI constructor insrc/players/ai_player.cpp. - The launcher also includes randomized replay preview loading behavior in
src/main.cpp(loadRandom).
- AI strategy selection is probabilistic. The AI computes weighted strategy probabilities and samples one at runtime in
-
Data structures for storing data
The system uses structured containers across game state, session state, and persistence layers. For example:
- Board tiles are stored as a vector in
include/core/game_state.hpp(std::vector<TileState> m_tiles). - Live session UI messages and turn history are stored in vectors in
include/sessions/match_session.hpp. - Replay payload uses vectors for history and message storage in
include/core/replay_data.hpp. - AI search utilities build legal-action vectors in
src/players/ai_player.cpp.
- Board tiles are stored as a vector in
-
Dynamic memory management
Dynamic allocation is used to support polymorphism for different player types at runtime. For example:
- Player instances are created with
newdynamically via custom constructors ininclude/scenes/live_match_scene.hpp. - Ownership cleanup is explicitly handled in the
MatchSessiondestructor insrc/sessions/match_session.cpp. include/misc/blizzard_transition.hppuses manual allocation for itsBlizzardEffectpens, creating them withnewand releasing them in the effect destructor and update loop.
- Player instances are created with
-
File input/output (loading/saving)
Persistent I/O is implemented for both replay data and user settings. For example:
- Replay files are written using
std::ofstreaminsrc/core/replay_io.cpp(saveReplay). - Replay files are read using
std::ifstreaminsrc/core/replay_io.cpp(loadReplay). - Settings are loaded from
settings.cfgby default insrc/misc/settings_io.cpp(loadSettings). - Settings are saved back to
settings.cfgby default insrc/misc/settings_io.cpp(saveSettings). - The launcher uses these settings load/save paths during startup and settings edits in
src/main.cpp.
- Replay files are written using
-
Program code in multiple files
The project is clearly modularized and compiled from many translation units. For example:
- Core logic, UI, sessions, players, and misc utilities are split across
include/andsrc/. - The
launcherbuild target compiles many separate source files inMakefile. - This separation supports maintainability: AI logic is isolated in
src/players/ai_player.cpp, settings insrc/misc/settings_io.cpp, etc.
- Core logic, UI, sessions, players, and misc utilities are split across
-
Multiple difficulty levels
Difficulty levels are implemented through type definitions, runtime selection, and AI behavior. For example:
- Difficulty enum values
Easy,Medium,Hardare defined ininclude/core/enums.hpp. - Launcher menu routes each choice to the corresponding difficulty in
src/main.cpp. - AI behavior changes by difficulty via weight profiles in
src/players/ai_player.cpp. - Medium dynamically shifts toward minimax as turns progress in
src/players/ai_player.cpp. - Hard uses stronger minimax emphasis and deeper endgame search in
src/players/ai_player.cpp.
- Difficulty enum values
ncurseswfor the terminal UI and keyboard input handling- POSIX sockets (
arpa/inet.h,sys/socket.h,unistd.h) for netplay pthread/ C++ threads for the background network reader- Python 3 for the relay server in
server/relay_server.py(optional, not part of the main program)
g++with C++17 supportmakencurseswdevelopment package- Python 3 for the relay server
Example Debian/Ubuntu install:
sudo apt install g++ make libncursesw5-dev python3make launcher
./launchermake manual_curses_match
make manual_board_renderer
make manual_game_ui
make manual_replay_ui
make netplaymake clean removes the built binaries listed in the Makefile.
The launcher reads and writes settings.cfg using SettingsIO.
Default values:
server_ip=localhost
server_port=5050
game_tag=PlayerThe launcher will also create default settings if loading fails.
- The board uses a 4x2 tile style in ncurses.
- The HUD contains three sections:
- status box
- scrollable message log
- command input box
- Replay mode expands the HUD status section to show progress, playback speed, and autoplay status.
- The board viewport can scroll independently when the visible area is smaller than the logical board.
The current codebase is organized in layers rather than around one bundled game loop file. The easiest way to read it is from the outside in:
main.cpp / scene entry points
-> sessions
-> players + game rules + state
-> UI renderers
-> ncurses screen
Each frame follows the same general pattern:
- a scene reads input from
KeyQueue(a custom key input cache) - the scene forwards board input to a session
- the session updates the authoritative state
BoardRendererandGameHuddraw that state- the HUD may emit a command string back to the scene
- the scene handles high-level actions such as save, quit, chat, or replay browser navigation
The scene headers in include/scenes/ are thin runtime wrappers. They own the outer loop for a mode and coordinate input, layout, rendering, and top-level commands.
main_menu_scene.hppruntime for the main menu interfacelive_match_scene.hppruns local human-vs-human and human-vs-AI matchesnetplay_scene.hppadds connection setup, waiting-room flow, chat handling, and network error handlingreplay_scene.hppruns replay playback and replay HUD commandsreplay_browser.hppis the file picker for saved.isorreplaysscene_common.hppcontains shared helpers such as the ncurses guard and common save/quit command parsing
These files deliberately do not contain the detailed game rules.
The session classes are the heart of the runtime model. They sit between input/rendering and the lower-level rules/state code.
MatchSessionowns a live matchReplaySessionowns replay playback state
MatchSession tracks player names, UI messages, turn history, visual cursor state, and replay export data. ReplaySession keeps replay timeline state, playback settings, autoplay timing, and replay-only HUD information.
This is why the project keeps GameState and MatchSession separate. GameState is intentionally compact and easy to copy for rule checks and AI search, while MatchSession is the broader runtime container used by the UI and replay system.
The include/core/ and src/core/ files define the rule engine and persistent game data structures.
GameStateis the canonical board snapshotGameRulesvalidates and applies moves and breaksTurnRecordstores per-turn actions and timing dataReplayDatapackages everything needed to save or reload a sessionReplayIOserializes replay files to disk
A useful way to think about this layer is: it knows what the game is, but not how it should be drawn on screen.
All player types share the same Player interface, which lets MatchSession drive local, AI, and remote opponents in almost the same way.
HumanPlayerturns keyboard input into pending move/break selectionsAiPlayerchooses actions with random, greedy, or minimax-based logic depending on difficultyNetworkHumanPlayerandNetworkPlayeradapt the same turn model to remote play throughNetworkLink
That common interface keeps netplay from needing a separate copy of the game rules or turn engine.
The UI is split into two main renderers:
BoardRendererdraws the framed board, pieces, cursor overlays, and scrolling viewportGameHuddraws the status panel, log panel, and command input box
Both renderers are fed by sessions rather than mutating the game directly. This keeps rendering mostly one-way: sessions produce state, and the UI visualizes it.
ui_resize_helper.hpp sits beside them and computes the board/HUD size split on terminal resize.
For a normal match, the game selects a mode and creates the appropriate MatchSession object, which then constructs the appropriate players. The scene owns the ncurses lifetime, focus switching, input dispatch, layout refresh, and top-level commands, while the actual turn progression lives inside MatchSession::update(...).
Replay mode loads a ReplayData object through ReplayIO, builds a ReplaySession, and reuses the same board/HUD rendering approach as live play. This reuse means replay mode is backed by a different session type rather than a separate UI stack.
Netplay keeps the same match/session/rendering structure and swaps in a network-backed player layer. NetworkLink handles the socket connection, background reader thread, parsed inbound queues, and outbound text commands. The scene polls that link each frame, posts network messages into the HUD log, and lets MatchSession continue to run the same turn model as local play.
The network code therefore acts as a transport adapter, not as a second game engine.
This architecture makes the project easier to extend without rewriting the whole loop:
- new player types can plug into the
Playerinterface - replay support works because turn/session data is separated from rendering
- board and HUD rendering can evolve without rewriting move validation
- netplay can reuse the same match logic instead of duplicating game rules on the scene side
Scenes coordinate, sessions own runtime state, core files define the rules, and UI files only render.





