using OECS; using R3; namespace TicTacToe; public static class Program { public static void Main() { var world = new World(); // ── Setup reactivity logging ────────────────────────────────── // // Each subscription appends to a shared log buffer. The log is // printed after the board render so Console.Clear() doesn't wipe it. var reactivityLog = new List(); // 1. Log every entity-level change (creates, destroys). world.ObserveEntityChanges().Subscribe(change => { if (change.ComponentType != null) return; reactivityLog.Add($"[react] {change}"); }); // 2. Log every Mark component change (placed on a cell). world.ObserveComponentChanges().Subscribe(change => { reactivityLog.Add($"[react] {change}"); }); // 3. Log every GameState singleton change. world.ObserveComponentChanges().Subscribe(change => { reactivityLog.Add($"[react] {change}"); }); // 4. Log changes matching a query: cells that have a Mark. var markedQuery = world.Query().With().With().Build(); world.ObserveQuery(markedQuery).Subscribe(change => { reactivityLog.Add($"[react:query] {change}"); }); // ── Load initial state from CSV ─────────────────────────────── var csvPath = Path.Combine(AppContext.BaseDirectory, "Data", "board.csv"); CsvLoader.LoadCells(world, csvPath); world.PostChanges(); // flush entity-created notifications // ── Initialize singleton ────────────────────────────────────── world.SetSingleton(new GameState { CurrentPlayer = Player.X, Status = GameStatus.Playing, MoveCount = 0 }); world.PostChanges(); // ── Wire up systems ─────────────────────────────────────────── var group = new SystemGroup(world); group.Add(new WinCheckSystem(world)); group.Add(new RenderSystem(world)); // ── Game loop ───────────────────────────────────────────────── // Initial render. group.RunLogical(); PrintReactivityLog(reactivityLog); while (true) { ref var state = ref world.GetSingleton(); if (state.Status != GameStatus.Playing) break; var input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; var parts = input.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2 || !int.TryParse(parts[0], out var row) || !int.TryParse(parts[1], out var col)) { Console.WriteLine(" Invalid input. Try \"1 2\"."); continue; } if (row < 0 || row > 2 || col < 0 || col > 2) { Console.WriteLine(" Row and col must be 0–2."); continue; } world.Commands.Enqueue(new PlaceMarkCommand { Row = row, Col = col }); group.RunLogical(); PrintReactivityLog(reactivityLog); } // ── Final state ─────────────────────────────────────────────── Console.WriteLine(); Console.WriteLine("=== Game over ==="); PrintReactivityLog(reactivityLog); } private static void PrintReactivityLog(List log) { if (log.Count == 0) return; Console.WriteLine("── reactivity log ──"); foreach (var entry in log) Console.WriteLine(entry); Console.WriteLine(); log.Clear(); } }