Skip to content

johnnyhchen/betrayal-house

Repository files navigation

Betrayal at House on the Hill — Web Edition

A faithful, fully-playable digital adaptation of Betrayal at House on the Hill (2nd Edition) for 3–6 players, built with ASP.NET Core 10 Blazor Server and SignalR.

Repo: https://github.com/johnnyhchen/betrayal-house


For Agents Picking This Up

This project was generated by the jc_attractor pipeline system using a 22-node DAG (betrayal.dot) that orchestrated multiple LLM agents (Claude Opus 4.6 + Sonnet 4.6) to build the entire codebase from an architecture doc through implementation.

Key files to read first

  • This README — project overview, structure, how to run
  • ARCHITECTURE.md — deep dive into data models, engine design, all 12 characters' stat arrays, SignalR hub methods, component tree
  • RULEBOOK.md — complete game rules for Betrayal at House on the Hill 2nd Edition

Current state (as of initial generation)

  • All 50 haunt scenarios implemented with behaviors, win conditions, and tests
  • All game content: 45 room tiles, 22 items, 22 omens, 44 events, 12 characters
  • Full multiplayer over SignalR with lobby/character select/game flow
  • Gothic-themed Blazor UI with board grid, dice roller, card reveals, combat overlays
  • ~1,200 unit tests across the engine
  • Known gaps: The scaffold node hit a tool-round limit (may have incomplete stubs in some files). The ui_cards_dice node had a transient network error. Both were compensated by later pipeline nodes but there may be rough edges in card reveal animations or dice UI.

README maintenance rule

When you make changes to this repo, update this README to reflect them. Specifically:

  • If you add/remove/rename files or directories, update the Project Structure section
  • If you change how to build or run the app, update Setup & Running
  • If you add new haunt scenarios or game content, update Data Verification counts
  • If you fix known issues listed above, remove them from the "Current state" section
  • If you add new features, document them in the appropriate section
  • If you change test counts, update the expected count in Testing
  • Add a brief changelog entry to the Changelog section at the bottom

Table of Contents

  1. Tech Stack
  2. Project Structure
  3. Setup & Running
  4. How to Play
  5. Data Verification
  6. Development Guide
  7. Testing
  8. Multiplayer & Networking
  9. Accessibility & UX Features
  10. Audio
  11. Architecture Notes
  12. Changelog

Tech Stack

Layer Technology
Backend ASP.NET Core 10 (Blazor Server + SignalR)
Game Engine Pure C# class library (BetrayalEngine)
Real-time ASP.NET Core SignalR
Frontend Blazor Server Components + Vanilla JS interop
Audio Web Audio API (procedural synthesis — no audio files)
CSS Custom CSS (no Tailwind/SASS dependency)
Fonts Google Fonts — Cinzel Decorative, Crimson Text, EB Garamond
Testing xUnit + .NET 10 Test SDK

Project Structure

BetrayalWeb.sln
├── src/
│   ├── BetrayalEngine/              # Pure game logic (no UI deps)
│   │   ├── Data/                    # Static data definitions
│   │   │   ├── CharacterData.cs     # All 12 characters with stat arrays
│   │   │   ├── EventCardData.cs     # 44 event cards
│   │   │   ├── HauntScenarioData.cs # 50 haunt scenario definitions
│   │   │   ├── ItemCardData.cs      # 22 item cards
│   │   │   ├── OmenCardData.cs      # 22 omen cards
│   │   │   └── RoomTileData.cs      # 45 room tiles + 5 starting tiles
│   │   ├── Engine/                  # Core game systems
│   │   │   ├── BoardGrid.cs         # 2D sparse grid, tile placement, pathfinding
│   │   │   ├── CardDeck.cs          # Generic deck: shuffle, draw, discard
│   │   │   ├── CardEffectResolver.cs# Applies card effects to game state
│   │   │   ├── CombatSystem.cs      # Physical + mental combat resolution
│   │   │   ├── DiceRoller.cs        # 0/1/2 dice via IRandom
│   │   │   ├── ExplorationEngine.cs # Tile discovery + card triggers
│   │   │   ├── GameEngine.cs        # Top-level orchestrator (IGameEngine)
│   │   │   ├── HauntEngine.cs       # Dispatches to haunt behaviors
│   │   │   ├── HauntRevelation.cs   # Omen count vs dice, haunt table lookup
│   │   │   ├── HauntTable.cs        # 990-entry cross-reference (omen × room → scenario)
│   │   │   ├── HauntTurnManager.cs  # Haunt-phase turn order (heroes then traitor)
│   │   │   ├── TurnManager.cs       # Exploration-phase turn sequencing
│   │   │   └── Haunts/              # 50 haunt behavior classes
│   │   │       ├── BaseHauntBehavior.cs
│   │   │       ├── Haunt01Behavior.cs  # The Mummy Walks
│   │   │       ├── ...
│   │   │       └── Haunt50Behavior.cs  # Treasure Hunt
│   │   ├── Models/                  # Domain models + enums
│   │   ├── Helpers/                 # Shuffle, door alignment utilities
│   │   └── Validation/              # Move/action/tile placement validators
│   └── BetrayalWeb/                 # Blazor Server web app
│       ├── Components/
│       │   ├── Pages/
│       │   │   ├── Home.razor       # Landing: create/join game
│       │   │   ├── GameLobby.razor  # Character selection + ready up
│       │   │   └── GameBoard.razor  # Main game view
│       │   ├── Layout/
│       │   │   ├── GothicLayout.razor   # Dark themed layout
│       │   │   ├── MainLayout.razor
│       │   │   └── ReconnectModal.razor # Auto-reconnect UI
│       │   └── App.razor
│       ├── Hubs/
│       │   └── GameHub.cs           # All SignalR methods (create/join/move/attack/etc.)
│       ├── Services/
│       │   ├── GameService.cs       # IGameService — validates + delegates to engine
│       │   ├── GameSessionStore.cs  # ConcurrentDictionary of active games
│       │   └── AudioService.cs      # Scoped JS interop for sound
│       ├── Shared/                  # Reusable Blazor components
│       │   ├── BoardGrid.razor      # Tile map with zoom/pan
│       │   ├── CardReveal.razor     # Card draw modal
│       │   ├── CombatOverlay.razor  # Attack/defend UI
│       │   ├── DiceRoller.razor     # Animated dice with 0/1/2 faces
│       │   ├── HauntReveal.razor    # Dramatic haunt announcement
│       │   ├── HauntDashboard.razor # Haunt-phase objectives + actions
│       │   ├── PlayerDashboard.razor# Stats, inventory, actions
│       │   ├── GameLog.razor        # Scrollable action log
│       │   ├── RulesReference.razor # Searchable rules sidebar
│       │   ├── TutorialOverlay.razor# First-time player walkthrough
│       │   ├── SettingsPanel.razor  # Volume, speed, color-blind mode
│       │   └── UndoToast.razor      # Undo last move
│       └── wwwroot/
│           ├── css/                 # Gothic theme, board, tiles, animations
│           └── js/
│               ├── audio-interop.js # Web Audio API procedural sound engine
│               └── board-interop.js # Board zoom/pan, localStorage persistence
├── tests/
│   └── BetrayalEngine.Tests/       # ~1,200 unit tests
├── ARCHITECTURE.md                  # Detailed architecture document
├── RULEBOOK.md                      # Complete game rules
└── GNUmakefile                      # Build/test/run shortcuts

Setup & Running

Prerequisites

  • .NET 10 SDK
  • A modern browser (Chrome 90+, Firefox 90+, Safari 15+, Edge 90+)

Run in Development

dotnet restore BetrayalWeb.sln
dotnet run --project src/BetrayalWeb/BetrayalWeb.csproj

Then open:

For hot-reload during development:

dotnet watch run --project src/BetrayalWeb/BetrayalWeb.csproj

Let Others Play (LAN)

# Find your IP
ipconfig getifaddr en0

# Bind to all interfaces
dotnet run --project src/BetrayalWeb/BetrayalWeb.csproj --urls "http://0.0.0.0:5109"

Other players on the same network go to http://<your-ip>:5109.

Let Others Play (Internet)

Use a tunnel:

ngrok http 5109

Share the generated public URL.

Run Tests

dotnet test BetrayalWeb.sln

Expected: ~1,200 tests — all passing.

Release Build

dotnet publish src/BetrayalWeb/BetrayalWeb.csproj -c Release --output ./publish
./publish/BetrayalWeb

How to Play

Lobby

  1. Host enters their name and clicks Host a Game — gets a game code (e.g. HOUSE-7X4M)
  2. Other players enter the code and click Join Game (3–6 players required)
  3. Each player picks a character from the selection grid (12 characters in 6 color pairs)
  4. When all players are ready, the host clicks Start Game

Exploration Phase

Action How
Move Click an open door on your current tile (up to Speed value in rooms)
Explore Step through an unexplored door to draw and place a new room tile
Draw a Card Automatic when entering a room with a symbol (spiral=Event, bull's-eye=Item, raven=Omen)
Use Item Click an item in your inventory panel
End Turn Click End Turn in your dashboard

The Haunt

Every Omen card drawn triggers a haunt check — roll 6 dice, if total < number of omens revealed, the haunt begins. One player becomes the Traitor with secret objectives opposing the heroes.

Combat

Both combatants roll their relevant stat dice. Higher total wins. Loser takes damage = difference, distributed across stats of their choice.

Winning

  • Heroes win by fulfilling the hero victory condition (varies per haunt)
  • Traitor wins by fulfilling the traitor victory condition

See RULEBOOK.md for complete rules.


Data Verification

The following counts match the official 2nd Edition component list:

Component Expected Actual
Haunt scenarios 50 50
Room tiles 45 drawable + 5 starting = 50 50
Item cards 22 22
Omen cards 22 22
Event cards 44 44
Characters 12 (6 pairs) 12
Haunt table entries 990 (22 omens x 45 rooms) 990

Verification is enforced by unit tests.


Development Guide

Key interfaces

Interface Purpose Implementation
IGameEngine Top-level game orchestration GameEngine
IHauntBehavior Per-scenario haunt logic Haunt01Behavior ... Haunt50Behavior
IGameService Web-layer game session management GameService
IRandom Abstraction over randomness for testability DeterministicRandom (tests), system random (prod)

Adding a new haunt

  1. Create src/BetrayalEngine/Engine/Haunts/HauntNNBehavior.cs extending BaseHauntBehavior
  2. Register in src/BetrayalWeb/Program.cs:
    builder.Services.AddSingleton<IHauntBehavior, HauntNNBehavior>();
  3. Add scenario definition to src/BetrayalEngine/Data/HauntScenarioData.cs
  4. Add omen x room entries to src/BetrayalEngine/Engine/HauntTable.cs
  5. Add tests in tests/BetrayalEngine.Tests/

Adding a sound effect

  1. Implement synthesis in wwwroot/js/audio-interop.js under the sounds object
  2. Add a public Task PlayXxxAsync() to AudioService.cs
  3. Call it from the relevant Blazor component

CSS theming

All colors, spacing, and typography values are CSS custom properties in gothic-theme.css (:root block). Edit these to retheme the entire app.


Testing

The test project (tests/BetrayalEngine.Tests/) covers:

  • Data integrity — all 50 haunts, 45 tiles, 22 items, 22 omens, 44 events loaded and valid
  • Haunt table — 990-entry cross-reference, all omens and rooms covered
  • Board grid — tile placement, door alignment, adjacency, floor restrictions
  • Card decks — shuffle, draw, discard, empty-deck reshuffling
  • Dice roller — distribution, stat-based count, seeded determinism via IRandom
  • Combat system — roll resolution, damage distribution, monster combat, death
  • Turn manager — phase sequencing, haunt turn order, condition effects
  • Exploration engine — door discovery, tile draw, card trigger logic
  • Haunt revelation — haunt number lookup, traitor assignment
  • All 50 haunt behaviors — setup, per-turn hooks, win conditions, scenario actions
  • Validators — move legality, tile placement, staircase rules
  • Multiplayer integration — lobby → game start → haunt trigger flow

Multiplayer & Networking

SignalR Hub Methods (GameHub.cs)

Method Direction Purpose
CreateGame(name) Client → Server Host creates a game, returns code
JoinGame(code, name) Client → Server Player joins by code
SelectCharacter(id) Client → Server Pick a character in lobby
ReadyUp() Client → Server Mark ready
StartGame() Client → Server Host starts (requires 3-6 ready)
MoveToTile(x, y) Client → Server Move explorer
ExploreDoor(direction) Client → Server Discover new room
DrawCard() Client → Server Resolve room card
RollDice(count, purpose) Client → Server Roll dice
UseItem(itemId, targetId?) Client → Server Use an item
Attack(targetId) Client → Server Initiate combat
EndTurn() Client → Server Pass turn
HauntAction(type, params) Client → Server Scenario-specific action

SignalR Groups

  • {code} — all players in a game (broadcasts state changes)
  • {code}-traitor — traitor only (secret objectives)
  • {code}-heroes — heroes only (secret objectives)

Reconnection

SignalR is configured with WithAutomaticReconnect(). On reconnect, the client re-syncs full game state from the server. A ReconnectModal.razor overlay shows connection status.


Accessibility & UX Features

Feature Implementation
Gothic horror theme Dark palette, CSS noise texture overlay, flickering candle animations
Tooltips data-tip CSS attribute on all interactive elements
Rules reference Slide-out sidebar with searchable quick-reference
Tutorial overlay 7-step first-time walkthrough; persisted to localStorage
Undo last move Toast bar after each move; dismissed on dice roll or turn end
Color-blind mode Distinct SVG patterns on player tokens (toggle in Settings)
Animation speed Slow / Normal / Fast — persisted to localStorage
Responsive layout Mobile / tablet / desktop grid; pinch-to-zoom on touch
ARIA labels All interactive elements labelled; live regions for log and alerts
Rate limiting JS-side sliding-window limiter per action; server-side ASP.NET rate limiter
Auto-reconnect SignalR reconnect + full state re-sync

Audio

All sounds are synthesised procedurally via the Web Audio API — no audio files bundled. The engine lives in wwwroot/js/audio-interop.js.

Key Description
ambience_exploration Low sawtooth drone loop
ambience_haunt_hero Tense triangle pad
ambience_haunt_traitor Sinister low sawtooth
sting_haunt_reveal Dramatic descending harmonic cascade
sfx_dice_roll Filtered noise bursts
sfx_card_draw High-frequency noise sweep + sine ping
sfx_tile_placed Low thud + filtered noise
sfx_combat_hit Sharp noise crack
sfx_player_death Descending sine tones
sfx_omen_found Eerie ascending chimes
sfx_victory Bright ascending arpeggio
sfx_defeat Slow descending minor tones

Volume and mute state persist to localStorage. Audio context initialises lazily on first user gesture (browser autoplay policy).


Architecture Notes

  • Server-authoritative — all game state lives on the server (Blazor Server model). No client-side cheating possible.
  • GameInstance — each active game gets its own instance wrapping shared engine services, with a per-session SemaphoreSlim(1,1) for thread safety.
  • IRandom abstraction — all randomness flows through a single interface. Tests use DeterministicRandom for reproducibility.
  • 50 haunt behaviors — each is a singleton implementing IHauntBehavior, dispatched by HauntEngine via scenario number.
  • Information hiding — during the haunt, traitor and hero objectives are sent only to their respective SignalR groups.
  • No database — game state is in-memory only. Games are lost on server restart. For persistence, add a backing store to GameSessionStore.

See ARCHITECTURE.md for the full design document including all character stat arrays, data model schemas, and component hierarchy.


Changelog

Date Change
2025-02-17 Initial generation via jc_attractor pipeline (22-node DAG, Opus 4.6 + Sonnet 4.6)
2025-02-18 Added RULEBOOK.md, .gitignore, removed build artifacts from tracking

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors