120 lines
4.2 KiB
C#
120 lines
4.2 KiB
C#
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<string>();
|
||
|
||
// 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<Mark>().Subscribe(change =>
|
||
{
|
||
reactivityLog.Add($"[react] {change}");
|
||
});
|
||
|
||
// 3. Log every GameState singleton change.
|
||
world.ObserveComponentChanges<GameState>().Subscribe(change =>
|
||
{
|
||
reactivityLog.Add($"[react] {change}");
|
||
});
|
||
|
||
// 4. Log changes matching a query: cells that have a Mark.
|
||
var markedQuery = world.Query().With<Cell>().With<Mark>().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<GameState>();
|
||
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<string> log)
|
||
{
|
||
if (log.Count == 0) return;
|
||
Console.WriteLine("── reactivity log ──");
|
||
foreach (var entry in log)
|
||
Console.WriteLine(entry);
|
||
Console.WriteLine();
|
||
log.Clear();
|
||
}
|
||
}
|