Skip to content

Repository files navigation

Unmatched — Card Game Engine

A digital implementation of the Unmatched board game in C++17, built around a data-driven architecture, a rule-based AI opponent, and raylib rendering.

The game currently includes Dracula, Sherlock Holmes, and The Invisible Man. Heroes, cards, maps, and effects are loaded from JSON rather than hardcoded into the C++ code.

Features

  • 🎮 Playable digital implementation of Unmatched
  • 🧩 Data-driven game content using JSON
  • 🤖 Rule-based AI opponent for Player 2
  • 💾 Save/load with 3 save slots
  • ↩️ Undo using serialized game-state snapshots
  • 🏭 Factory-based creation of heroes, cards, maps, effects, and more
  • 👀 Observer-based game events between the rules and rendering layers
  • ⚔️ Card combat, movement, maneuvers, abilities, and pending player choices
  • 🛡️ Custom exception hierarchy with contextual error propagation
  • 🖥️ raylib-based rendering

Architecture

The project is organized into five main layers:

Layer Responsibility
Model Game state and data classes such as Card, Hero, Sidekick, Deck, Map, and Tile.
Engine Reusable rule building blocks: Effect, Condition, Query, and shared game data.
Factory Converts JSON data into game objects and isolates the on-disk data format.
Controller Game rules, turn state, AI, scene management, and resource managers.
View raylib scenes and UI. Reads game state without containing game rules.

The main game-data flow is:

JSON → Factory → Model → Engine → GameManager → View

This keeps game content, rules, application control, and rendering separated from each other.

Design Patterns

Several design patterns are used where they fit the project:

  • Factory — creates data-driven objects from JSON. Adding a new hero or card can be done through data without changing the core game logic.
  • StrategyEffect, Condition, and Query provide interchangeable rule components selected from JSON.
  • ObserverIGameObserver allows the view to react to game events without coupling GameManager to rendering.
  • Memento — serialized game state is used to implement Undo as well as save/load.
  • Dependency Injection — application-owned managers are passed to the parts of the program that need them instead of relying on globals.
  • Singleton — limited to the player/load-selection state that needs to survive scene transitions.

AI Opponent

Player 2 can be controlled by a simple rule-based AI through AIController.

The AI interacts with the same public GameManager operations used by the human player. It can:

  • Resolve pending tile, fighter, card, and effect choices
  • Defend during combat
  • Attack reachable enemy fighters
  • Play useful event cards
  • Maneuver toward opponents
  • Manage its hand and end its turn

The AI intentionally uses greedy heuristics rather than lookahead or full board planning. It is designed as a playable starting point rather than a strong competitive opponent.

Save, Load & Undo

Save / Load

GameManager supports 3 save slots. The complete game state is serialized to JSON and restored when loading a slot.

Undo

Before meaningful actions, the game stores serialized state snapshots in an undo stack. Undo is disabled during intermediate states such as active combat or pending choices to prevent invalid state transitions.

Exception Handling

The project uses a custom exception hierarchy instead of relying on unexplained crashes:

AppException
├── FileException
├── JsonParseException
├── DataFormatException
└── FactoryException

Errors gain additional context as they move upward through the application. Scene construction can recover from application-level exceptions and return to the main menu, while main() acts as the final safety net.

Building

Requirements

  • CMake 3.16+
  • C++17-compatible compiler
  • Linux X11/OpenGL development headers required by raylib

raylib and nlohmann/json are fetched automatically through CMake FetchContent.

Build

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -j$(nproc)
./CardGameEngine

The build also copies the required assets/ and data/ directories next to the executable.

Project Structure

.
├── CMakeLists.txt
├── main.cpp
├── data/                      # Heroes, cards, maps, and other JSON data
├── include/
│   ├── controller/            # Application and game control
│   ├── engine/                # Effects, conditions, queries, game data
│   ├── factory/               # JSON → game objects
│   ├── model/                 # Core game data
│   ├── utility/               # File and exception utilities
│   └── view/                  # Scenes and UI
└── src/
    ├── controller/
    ├── factory/
    ├── model/
    ├── utility/
    └── view/

Current Content

  • 3 playable heroes: Dracula, Sherlock Holmes, The Invisible Man
  • 2 maps
  • Player vs. AI gameplay
  • Data-driven cards, effects, conditions, queries, and maps

Known Limitations

  • Only Player 2 can currently be controlled by the AI.
  • AI has no multi-turn lookahead or hero-specific strategy.
  • Only three heroes and two maps are currently implemented.
  • The Settings and Collection scenes/buttons are present but not implemented.
  • The How to Play scene is not implemented yet.
  • There is currently no automated test suite.

Future Improvements

Possible next steps include:

  • Smarter AI with deeper board evaluation and lookahead
  • More heroes, cards, and maps
  • Automated tests for game rules and data loading
  • Completing the remaining UI scenes
  • Improving extensibility and gameplay polish

Contributors

  • kiarash habibi

License

This project is developed for educational and portfolio purposes.

About

A C++17 implementation of the Unmatched board game with a data-driven architecture, rule-based AI opponent, and raylib rendering

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages