A Hearthstone-inspired digital card game built with Java/Spring Boot and React/TypeScript.
- Docker Desktop
- Java 21 + Maven 3.9
- Node.js 20+
docker-compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost |
| Backend | http://localhost:9000 |
| pgAdmin | http://localhost:5050 |
Subsequent runs (no code changes): docker-compose up.
Tear down: docker-compose down.
Reset database: docker-compose down -v.
docker-compose up -d postgres pgadmincd backend
mvn spring-boot:runAPI at http://localhost:8080.
cd frontend
npm install
npm run dev # http://localhost:5174cd backend
mvn test # 362 testscd backend
mvn spotless:apply # reformat
mvn spotless:check # CI checkcd frontend
npm run format
npm run format:check- Register an account at
/register - Account starts with 150 Lato Coins
- Buy packs in the Shop; open them to add cards to your collection
- Build a deck in Collection → My Decks
- Host or join a game in Play
Closely follows Hearthstone's rules:
- 30-card decks, 30 HP heroes, 7-slot shared board (minions + locations combined)
- 10 mana crystals max, gaining 1 per turn
- Mulligan phase: 3 cards first player, 4 + The Coin second
- Hand limit: 10 cards; deck size: 30
Implemented keywords: Battlecry, Deathrattle, Combo, Choose One, Discover, Overload, Taunt, Divine Shield, Stealth, Charge, Rush, Windfury, Lifesteal, Poisonous, Silence, Freeze, Secrets, Enrage, Spell Damage, Armor
- Buffs persist through zone transitions — a bounced minion keeps its attack/health buffs and damage taken. Aura buffs (Raid Leader, etc.) are stripped on bounce since they depend on board context.
- Rush vs Charge — Rush minions can only attack other minions on the turn they enter play; they may attack heroes from the next turn onward.
- Batched death processing — deathrattles fire after all simultaneous deaths are collected, rather than one at a time (simpler and more predictable).
- Locations share the board with minions in the same 7-slot row.
CardGaming/
├── docker-compose.yml
├── backend/ Spring Boot 3 + Java 21
│ └── src/main/java/com/cardgame/
│ ├── game/
│ │ ├── action/ Sealed action hierarchy (PlayCard, Attack, DiscoverChoice …)
│ │ ├── card/
│ │ │ ├── base/ Minion, Spell, Weapon, Location, HeroCard, Enchantment …
│ │ │ └── definitions/ Individual card classes (vanilla/, testing/, tokens/)
│ │ ├── discover/ DiscoverPool, DiscoverDestination, PendingDiscover
│ │ ├── engine/ GameEngine, GameState, PlayerState, GameEventBus
│ │ ├── entity/ Hero, GameEntity
│ │ ├── heropower/ Hero power implementations
│ │ ├── targeting/ TargetFilter, TargetFilters factory
│ │ └── util/ MinionCopier
│ ├── dto/game/ DTOs sent to frontend
│ ├── registry/ CardRegistry, PackDefinitionRegistry
│ ├── service/ GameService, ShopService, DeckService, AuthService
│ └── controller/ REST + WebSocket endpoints
└── frontend/src/
├── pages/Game/
│ ├── board/ GameBoard, BoardRow (minions + locations in one row)
│ ├── cards/ CardOnBoard, CardInHand, previews
│ └── overlays/ ChooseOneModal, DiscoverModal, SecretRevealedToast …
├── store/gameSlice.ts Redux state + animation queue
├── types/ TypeScript DTO types
└── websocket/ STOMP client + hooks
- Create a class in
backend/src/main/java/com/cardgame/game/card/definitions/<set>/<type>/extendingMinion,Spell,Weapon,Location, orHeroCard - Call
super(definitionId, name, text, manaCost, heroClass, rarity, category, ...)—definitionIdmust be unique (e.g."MAGE_FIREBALL") - Override only the lifecycle hooks you need:
onBattlecry,onDeathrattle,onMinionEnteredPlay,onSpellCast,onTurnEnded, etc. - For engine operations inside a card, use
GameEngineHolder.get()— never injectGameEnginedirectly - Register in
CardRegistry:register("MAGE_FIREBALL", Fireball::new)
See CLAUDE.md for full architecture notes, the two-UUID system, Discover usage, board structure, and coding conventions.
- Add a value to
CardCategory.java - Register a
PackDefinitioninPackDefinitionRegistry.java - Tag new cards with that category