Skip to content

Tournament-strength Caro (Gomoku variant) AI with 7 advanced search optimizations (PVS, LMR, Quiescence, Enhanced Move Ordering, Transposition Table, History Heuristic, Aspiration Windows). Built with .NET 10 and SvelteKit 5. 10-50x faster than naive minimax, enabling Master difficulty (depth 7) in 5-30 seconds.

License

Notifications You must be signed in to change notification settings

lavantien/caro-ai-pvp

Repository files navigation

Caro AI PvP - Tournament-Strength Caro with Modern Web Stack

Table of Contents

A mobile-first real-time implementation of Caro (Gomoku variant) with grandmaster-level AI powered by 15+ advanced search optimizations.

Built with .NET 10 and SvelteKit 5, representing cutting-edge 2025 web development standards.


Table of Contents

  1. Overview
  2. Features
  3. AI Engine Deep Dive
  4. Concurrency Optimizations
  5. Architecture
  6. Tech Stack
  7. Testing
  8. Getting Started
  9. Tournament Mode
  10. Game Rules
  11. Project Structure
  12. Roadmap
  13. Achievements
  14. License

Back to Table of Contents


Overview

Caro is a sophisticated 15x15 board game implementation featuring:

  • Grandmaster-level AI with sequential search (depth 9+ capable in 7+5 time control)
  • Real-time multiplayer with WebSocket support (SignalR)
  • AI tournament mode with balanced scheduling and ELO tracking
  • Mobile-first UX with ghost stone positioning and haptic feedback
  • Comprehensive testing with 280+ automated tests including integration tests with snapshot verification
  • Production-ready concurrency with .NET 10 best practices and adversarial testing

Back to Table of Contents


Features

Grandmaster-Level AI Engine

Our AI employs state-of-the-art algorithms from computer chess, achieving 100-500x performance improvement over naive minimax.

Core Search Optimizations:

  • Principal Variation Search (PVS) - Null window searches for non-PV moves (20-40% speedup)
  • Late Move Reduction (LMR) - Reduce late moves, re-search if promising (30-50% speedup)
  • Quiescence Search - Extend search in tactical positions to prevent blunders
  • Enhanced Move Ordering - Tactical pattern detection (15-25% speedup)
  • Transposition Table - Lock-free 256MB Zobrist hashing cache (~8M entries, 2-5x speedup)
  • History Heuristic - Track moves causing cutoffs across all depths (10-20% speedup)
  • Aspiration Windows - Narrow search windows around estimated score (10-30% speedup)

Advanced Features:

  • Threat Space Search - Focus search on critical threats only
  • DFPN Solver - Depth-First Proof Number search for forced wins
  • BitBoard Representation - SIMD-accelerated board evaluation
  • Opening Book - Pre-computed strong opening positions
  • Pondering - AI thinks during opponent's turn
  • Adaptive Time Management - Smart time allocation per move

Difficulty Levels (D1-D11):

  • D1-D2: Beginner (randomness added for mercy)
  • D3-D4: Casual play
  • D5-D6: Intermediate challenge
  • D7-D8: Advanced
  • D9-D10: Expert (threat space + advanced pruning)
  • D11: Grandmaster (all optimizations + deep search)

Note: In 7+5 time control, D11 practically reaches depth 9, D10 reaches depth 8. Full depth capabilities require longer time controls.

Tournament Mode

  • 22 AI bots competing in balanced round-robin format
  • ELO tracking with standard rating calculation
  • Fair scheduling - each bot plays at most once per round
  • Live standings with win rates and rating changes
  • SQLite logging with FTS5 full-text search for game analysis
  • SignalR broadcasts via Channel-based async queues (no fire-and-forget)

Game Features

Core Gameplay:

  • 15x15 board with exact 5-in-row winning condition
  • Open Rule enforcement (second move restriction in center 3x3 zone)
  • Blocked ends detection (6+ or blocked lines don't win)
  • Chess clock with Fisher control (7min + 5sec increment)

Polish Features:

  • Sound Effects - Synthesized audio (no external files) with mute toggle
  • Move History - Scrollable chronological move display
  • Winning Line Animation - SVG stroke animation with color coding
  • Undo Functionality - Revert moves with time restoration
  • ELO/Ranking System - Standard ELO calculation with leaderboard

Back to Table of Contents


AI Engine Deep Dive

Sequential Search Architecture

graph TD
    A[Iterative Deepening] --> B[Depth 2, 3, 4, ... Target]
    A --> C[Transposition Table]
    A --> D[Time Management]

    C --> C1[Zobrist hashing cache]
    C --> C2[8M entries, 256MB]
    C --> C3[2-5x speedup]

    D --> D1[Adaptive time allocation]
    D --> D2[Difficulty-based multipliers]
    D --> D3[Emergency mode with VCF]
Loading

BitBoard Representation

graph TD
    A[BitBoard Layout 15x15 = 225 bits] --> B[Red BitBoard UInt128]
    A --> C[Blue BitBoard UInt128]
    A --> D[Occupied BitBoard Red OR Blue]
    A --> E[SIMD Operations]

    E --> E1[PopCount for stone counting]
    E --> E2[BitOps for line detection]
    E --> E3[Vectorized pattern matching]
Loading

Threat Detection

Threat Classification:

  • Threat Level 5: Five in row (WIN)
  • Threat Level 4: Open Four (unstoppable)
  • Threat Level 3: Closed Four / Open Three
  • Threat Level 2: Closed Three / Open Two
  • Threat Level 1: Closed Two / Open One

Threat Space Search:

  • Only search threat moves
  • Prune non-threat candidates
  • 10-100x reduction in search space

Performance Metrics

Difficulty Search Type Avg Time Positions/S TT Hit Rate Max Depth (7+5)
Easy (D1) Sequential <100ms ~100K N/A 2-3
Medium (D2-D3) Sequential <500ms ~50K 20% 4-5
Hard (D4-D5) Sequential <2s ~20K 35% 6
Expert (D6-D7) Sequential <5s ~50K 45% 7
Master (D8-D9) Sequential 5-30s ~500K 50%+ 8-9
Grandmaster (D10-D11) Sequential + TSS 15-60s ~1M 55%+ 9 (D11) / 8 (D10)

Note: Max depths shown are for 7+5 (Rapid) time control. Longer time controls enable deeper search.

Combined Optimization Impact: 100-500x faster than naive minimax.

Back to Table of Contents


Concurrency Optimizations

Overview

The codebase has been hardened with production-grade concurrency improvements following .NET 10 / C# 13 best practices. All concurrency decisions are validated by adversarial tests designed to surface race conditions, deadlocks, and data corruption under high contention.

Key Improvements

1. AsyncQueue Utility (System.Threading.Channels)

File: backend/src/Caro.Core/Concurrency/AsyncQueue.cs

Replaced all fire-and-forget Task.Run patterns with Channel-based async queues:

// Channel-based queue for fire-and-forget operations
public sealed class AsyncQueue<T> : IDisposable where T : notnull
{
    private readonly Channel<T> _channel;
    private readonly Func<T, ValueTask> _processAsync;

    // Bounded capacity provides backpressure
    // DropOldest mode prevents unbounded growth
    public AsyncQueue(Func<T, ValueTask> processAsync, int capacity = 100,
                     string queueName = "AsyncQueue", bool dropOldest = true)
    {
        _channel = Channel.CreateBounded<T>(new BoundedChannelOptions(capacity)
        {
            FullMode = dropOldest ? BoundedChannelFullMode.DropOldest
                                   : BoundedChannelFullMode.Wait
        });
    }
}

Benefits:

  • No unobserved task exceptions
  • Built-in backpressure via bounded channels
  • Graceful shutdown support
  • Exception handling with continuation

2. TournamentManager (Channel-Based Broadcasts)

File: backend/src/Caro.Api/TournamentManager.cs

Before:

// Fire-and-forget - unobserved exceptions!
Task.Run(async () =>
{
    await _hub.Clients.All.MoveMadeAsync(move);
});

After:

// Channel-based broadcast queue
private readonly AsyncQueue<MoveBroadcast> _moveQueue;

// Constructor
_moveQueue = new AsyncQueue<MoveBroadcast>(
    ProcessMoveBroadcastAsync,
    capacity: 100,
    queueName: "MoveBroadcast",
    dropOldest: true
);

// Enqueue without blocking
_moveQueue.TryEnqueue(new MoveBroadcast { ... });

Additional Improvements:

  • ReaderWriterLockSlim for GetState() - allows multiple concurrent readers
  • Interlocked.CompareExchange for atomic status transitions
  • No nested locks (prevents deadlock)

3. Program.cs (ConcurrentDictionary + Per-Game Locks)

File: backend/src/Caro.Api/Program.cs

Before:

// Single global lock blocks ALL game requests
var games = new Dictionary<string, GameState>();
var gamesLock = new object();

lock (gamesLock)
{
    // AI calculation here blocks everything for seconds
    var (x, y) = ai.GetBestMove(game.Board, ...);
}

After:

// ConcurrentDictionary allows concurrent game access
var games = new ConcurrentDictionary<string, GameSession>();

// Per-game locks - games don't block each other
public sealed class GameSession
{
    private readonly object _lock = new();

    // Extract data for AI calculation OUTSIDE lock
    public (Board BoardClone, Player CurrentPlayer, bool IsGameOver) ExtractForAI()
    {
        lock (_lock)
        {
            return (_game.Board.Clone(), _game.CurrentPlayer, _game.IsGameOver);
        }
    }
}

// AI calculation happens on cloned board, doesn't block other games
var (boardClone, currentPlayer, isGameOver) = session.ExtractForAI();
var (x, y) = ai.GetBestMove(boardClone, currentPlayer, difficulty);

Benefits:

  • 100+ concurrent games can proceed independently
  • AI calculation (which takes seconds) doesn't block other requests
  • No global lock bottleneck

4. ParallelMinimaxSearch (CancellationTokenSource)

File: backend/src/Caro.Core/GameLogic/ParallelMinimaxSearch.cs

Before:

// Volatile flag insufficient for cross-thread coordination
private volatile bool _searchShouldStop;

// Check-then-act pattern has race condition
if (_searchShouldStop) break;

After:

// CancellationTokenSource for proper cancellation
private CancellationTokenSource? _searchCts;

// Pass token to all tasks
var token = _searchCts.Token;
Task.Run(() => SearchWithIteration(..., token), token);

// Graceful exception handling
try
{
    Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae)
{
    // Filter expected TaskCanceledException
    var unexpected = ae.InnerExceptions.Where(e => e is not TaskCanceledException);
    if (unexpected.Any()) throw;
}

Benefits:

  • Coordinated cancellation across all threads
  • No race conditions in stop signal
  • Proper exception propagation

5. Ponderer (Lock-Only Synchronization)

File: backend/src/Caro.Core/GameLogic/Pondering/Ponderer.cs

Before:

// Mixed volatile/lock - race condition!
private volatile PonderState _state;
private readonly object _stateLock = new();

public PonderState State => _state;  // Volatile read without lock

After:

// Lock-only access - no mixed synchronization
private PonderState _state;
private readonly object _stateLock = new();

public PonderState State
{
    get
    {
        lock (_stateLock) { return _state; }
    }
}

Benefits:

  • No race between volatile read and lock-protected data
  • Consistent state visibility across threads
  • Proper memory semantics

Adversarial Testing

Test Files:

  • ConcurrencyStressTests.cs - 100+ concurrent operations
  • AdversarialConcurrencyTests.cs - CHESS-like delay injection
  • DeadlockDetectionTests.cs - Timeout-based deadlock detection
  • AsyncQueueTests.cs - Channel queue verification

Results:

  • 32/32 concurrency tests passing
  • Tests validate: no deadlocks, no data races, no data corruption

Best Practices Applied

Practice Implementation
Channel over Task.Run AsyncQueue for fire-and-forget
ReaderWriterLockSlim GetState() allows concurrent readers
ConcurrentDictionary Per-game locking for scalability
CancellationTokenSource Coordinated search cancellation
Lock-only (no mixed) Ponderer state consistency
Interlocked operations Atomic status transitions
ConfigureAwait(false) Avoid async deadlocks
Bounded channels Backpressure for producer-consumer

Back to Table of Contents


Architecture

System Design

graph TB
    subgraph Frontend["Frontend (SvelteKit)"]
        F1[Board.svelte]
        F2[GameStore Svelte 5 Runes]
        F3[SoundMgr Web Audio]
        F4[Tournament Dashboard]
        F5[SignalR Client]
        F6[Leaderboard]
    end

    subgraph Backend["Backend (ASP.NET Core 10)"]
        B1[TournamentHub SignalR]
        B2[MinimaxAI Lazy SMP + TSS]
        B3[ELOCalc Standard Formula]
        B4[TournamentMgr Channel Qs ReaderWriter Lock]
        B5[ThreatDetect Pattern Rec]
        B6[GameLogSvc SQLite+FTS]
        B7[BitBoardEval SIMD Acceler]
        B8[Transposition 256MB Lock-Free TT]
        B9[Validator Open Rule]
    end

    subgraph Database["Database (SQLite + EF Core)"]
        D1[Matches move history as JSON]
        D2[GameLogs FTS5 indexed search]
        D3[ActiveSessions board state]
        D4[Players ELO ratings]
    end

    Frontend -->|WebSocket| Backend
    Backend --> Database
Loading

Back to Table of Contents


Tech Stack

Frontend

  • SvelteKit 5 with TypeScript
  • Svelte 5 Runes ($state, $props, $derived) for modern reactivity
  • Skeleton UI v4 for accessible component library
  • TailwindCSS v4 for utility-first styling
  • SignalR client for real-time communication
  • Vitest v4 for unit testing

Backend

  • .NET 10 / C# 13 (LTS)
  • ASP.NET Core 10 Web API
  • SignalR for real-time WebSocket communication
  • System.Threading.Channels for async queues
  • ReaderWriterLockSlim for concurrent reads
  • ConcurrentDictionary for thread-safe collections
  • SQLite with FTS5 full-text search
  • xUnit v3.1 for testing

AI/ML

  • Custom Minimax with Lazy SMP parallel search
  • Zobrist hashing with lock-free transposition tables
  • BitBoard representation with SIMD operations
  • Threat space search and DFPN solver
  • Opening book with pre-computed positions
  • Pondering for optimal time utilization

Back to Table of Contents


Testing

Test Coverage Summary

Category Tests Focus
Backend Unit 200+ AI algorithms, board logic
Concurrency 32 Race conditions, deadlocks, data corruption
Integration 13 Tournament with snapshots
Frontend Unit 19+ Components, stores
E2E Tests 17+ Full user flows
TOTAL 280+ Full coverage

Concurrency Tests

The adversarial concurrency tests validate thread-safety under high contention:

Test Type Description Validates
Stress Tests 100+ concurrent operations No data loss, scalability
Adversarial CHESS-like delay injection Race conditions
Deadlock Detection Timeout-based detection No deadlocks
AsyncQueue Channel queue verification Producer-consumer

Integration Tests with Snapshots

Tests run real AI games and save JSON snapshots for regression detection:

Tournament/Snapshots/
├── RunSingleGame_BasicVsMedium_SavesSnapshot.json
├── RunThreeGames_EasyVsHard_LogsDepthStatistics.json
├── RunGame_VeryHardVsExpert_ParallelSearchReportsCorrectDepth.json
├── RunMiniTournament_FourBots_BalancedSchedule.json
└── RunGame_BeginnerVsBeginner_WithShortTimeControl.json

Each snapshot contains:

  • Per-move statistics (depth, nodes, NPS)
  • Game result metadata
  • Raw logs for inspection

Running Tests

# Backend
cd backend
dotnet test --verbosity quiet

# Concurrency tests only
dotnet test --filter "FullyQualifiedName~Concurrency"

# Integration tests only
dotnet test --filter "FullyQualifiedName~TournamentIntegration"

# Frontend
cd frontend
npm run test -- --run

Back to Table of Contents


Getting Started

Prerequisites

  • .NET 10 SDK
  • Node.js 20+
  • PowerShell or Bash

Installation

# Clone the repository
git clone https://github.com/yourusername/caro-ai-pvp.git
cd caro-ai-pvp

# Backend setup
cd backend
dotnet restore
dotnet build

# Frontend setup
cd ../frontend
npm install

Running the Application

Terminal 1 - Backend:

cd backend/src/Caro.Api
dotnet run

API runs on: http://localhost:5207

Terminal 2 - Frontend:

cd frontend
npm run dev

Frontend runs on: http://localhost:5173

Back to Table of Contents


Tournament Mode

AI vs AI tournaments with balanced scheduling:

Features

  • 22 AI bots across 11 difficulty levels (2 bots per level)
  • Round-robin format - each bot plays every other bot twice
  • Balanced scheduling - each bot plays at most once per round
  • ELO tracking - ratings update after each match
  • SQLite logging - all games logged with full statistics
  • SignalR broadcasts - Real-time updates via Channel-based async queues

Scheduling Algorithm

graph TD
    A[Balanced Round-Robin] --> B[Generate all pairings]
    B --> C[Greedy round assignment]
    C --> D[Each round maximizes unique bots]
    C --> E[No bot appears more than once per round]
    C --> F[Ensures fair distribution]
    F --> G[Total matches n x n-1 for n bots]
    G --> H[22 bots = 462 matches]
Loading

Back to Table of Contents


Game Rules

Board Setup

  • 15x15 grid (225 intersections)
  • Red (O) moves first
  • Blue (X) moves second

The Open Rule

The second Red move (move #3 overall) cannot be placed in the 3x3 zone surrounding the center intersection.

Winning Conditions

  • Exactly 5 stones in a row (horizontal, vertical, diagonal)
  • Neither end blocked
  • 6+ stones (overline) is not a win

Time Control

Fisher timing: 7 minutes initial + 5 seconds increment per move

Back to Table of Contents


Project Structure

caro-ai-pvp/
├── backend/
│   ├── src/Caro.Core/
│   │   ├── Entities/
│   │   │   ├── Board.cs              # 15x15 game board
│   │   │   ├── Cell.cs               # Intersection state
│   │   │   └── GameState.cs          # Game state + undo
│   │   ├── Concurrency/
│   │   │   └── AsyncQueue.cs         # Channel-based async queue
│   │   ├── GameLogic/
│   │   │   ├── MinimaxAI.cs          # Main AI engine
│   │   │   ├── ParallelMinimaxSearch.cs  # Lazy SMP
│   │   │   ├── BitBoard.cs           # Bit board rep
│   │   │   ├── BitBoardEvaluator.cs  # SIMD evaluation
│   │   │   ├── ThreatDetector.cs     # Threat detection
│   │   │   ├── ThreatSpaceSearch.cs  # TSS algorithm
│   │   │   ├── DFPNSearch.cs         # Proof number search
│   │   │   ├── OpeningBook.cs        # Opening positions
│   │   │   ├── Pondering/            # Think on opp time
│   │   │   ├── TimeManagement/       # Adaptive timing
│   │   │   ├── TranspositionTable.cs # Single-threaded TT (used by MinimaxAI)
│   │   │   ├── LockFreeTranspositionTable.cs  # Thread-safe TT (used by ParallelMinimaxSearch)
│   │   │   ├── BoardEvaluator.cs     # Static eval
│   │   │   ├── WinDetector.cs        # Win detection
│   │   │   └── AIDifficulty.cs       # D1-D11 levels
│   │   └── Tournament/
│   │       ├── TournamentEngine.cs   # Game runner
│   │       ├── TournamentMatch.cs    # Match scheduling
│   │       └── AIBot.cs              # Bot factory
│   ├── src/Caro.Api/
│   │   ├── TournamentHub.cs          # SignalR hub
│   │   ├── TournamentManager.cs      # Tournament state (Channel-based)
│   │   └── Logging/
│   │       └── GameLogService.cs     # SQLite + FTS5
│   └── tests/Caro.Core.Tests/
│       ├── Concurrency/              # 32 adversarial tests
│       │   ├── ConcurrencyStressTests.cs
│       │   ├── AdversarialConcurrencyTests.cs
│       │   ├── DeadlockDetectionTests.cs
│       │   └── AsyncQueueTests.cs
│       ├── Tournament/
│       │   ├── TournamentIntegrationTests.cs
│       │   ├── SavedLogVerifierTests.cs
│       │   ├── BalancedSchedulerTests.cs
│       │   └── TournamentLogCapture.cs
│       └── GameLogic/               # 200+ unit tests
├── frontend/
│   ├── src/routes/
│   │   └── tournament/              # Tournament UI
│   └── src/lib/
│       ├── stores/
│       │   └── tournamentStore.svelte.ts
│       └── components/
└── README.md

Back to Table of Contents


Roadmap

Completed

  • Core game logic (board, win detection, Open Rule)
  • Minimax AI with alpha-beta pruning
  • All 8+ search optimizations (PVS, LMR, Quiescence, Lazy SMP, etc.)
  • 11 difficulty levels (D1-D11)
  • Lazy SMP parallel search (4-8x speedup)
  • Threat detection and Threat Space Search
  • BitBoard with SIMD evaluation
  • Lock-free transposition table
  • Opening book
  • Pondering (think on opponent's time)
  • Adaptive time management
  • AI tournament mode with 22 bots
  • Balanced round-robin scheduling
  • SQLite logging with FTS5
  • Integration tests with snapshot verification
  • SignalR real-time updates
  • 280+ automated tests
  • Production-grade concurrency (.NET 10 best practices)
  • Channel-based async queues (no fire-and-forget)
  • Per-game locking (concurrent games)
  • Adversarial concurrency tests (32 tests)

In Progress

  • User authentication
  • Matchmaking system for PvP
  • Replay system (move history as JSON)

Planned

  • Progressive Web App (PWA)
  • Mobile app stores (iOS/Android)
  • Endgame tablebase
  • Machine learning evaluation function

Back to Table of Contents


Achievements

  • 100-500x AI speedup through advanced search optimizations
  • 280+ automated tests with snapshot-based regression detection
  • 32 concurrency tests validating thread-safety
  • Sequential search reaching depth 9+ in 7+5 time control
  • BitBoard with SIMD for accelerated evaluation
  • Threat Space Search for focused tactical calculation
  • 22 AI tournament bots with balanced scheduling
  • SQLite + FTS5 logging for game analysis
  • Grandmaster-level AI (depth 11 capable with longer time controls)
  • Production-ready concurrency with .NET 10 best practices
  • Channel-based broadcasts (no unobserved exceptions)
  • Per-game locking (100+ concurrent games)

Back to Table of Contents


License

This project is licensed under the MIT License.

Back to Table of Contents


Built with SvelteKit + .NET 10

Showcasing grandmaster-level AI with modern web development

About

Tournament-strength Caro (Gomoku variant) AI with 7 advanced search optimizations (PVS, LMR, Quiescence, Enhanced Move Ordering, Transposition Table, History Heuristic, Aspiration Windows). Built with .NET 10 and SvelteKit 5. 10-50x faster than naive minimax, enabling Master difficulty (depth 7) in 5-30 seconds.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •