oecs-sharp/examples/TicTacToe/Program.cs

153 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using OECS;
using R3;
namespace TicTacToe;
public static class Program
{
private static readonly string SavePath = Path.Combine(
AppContext.BaseDirectory, "save.bin");
public static void Main()
{
var world = new World();
var reactivityLog = new List<string>();
SetupReactivityLogging(world, reactivityLog);
// ── Try to load a saved game ──────────────────────────────────
bool loaded = false;
if (File.Exists(SavePath))
{
Console.Write("Saved game found. Load it? (y/n): ");
if (Console.ReadLine()?.Trim().ToLower() == "y")
{
using var stream = File.OpenRead(SavePath);
WorldSerializer.Load(world, stream);
loaded = true;
Console.WriteLine("Game loaded!");
Console.WriteLine();
}
}
// ── Load fresh board from CSV (if not loaded) ─────────────────
if (!loaded)
{
var csvPath = Path.Combine(AppContext.BaseDirectory, "Data", "board.csv");
CsvLoader.LoadCells(world, csvPath);
world.PostChanges();
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 ─────────────────────────────────────────────────
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;
// "save" command.
if (input.Trim().ToLower() == "save")
{
SaveGame(world);
Console.WriteLine(" Game saved.");
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\" or \"save\".");
continue;
}
if (row < 0 || row > 2 || col < 0 || col > 2)
{
Console.WriteLine(" Row and col must be 02.");
continue;
}
world.Commands.Enqueue(new PlaceMarkCommand { Row = row, Col = col });
group.RunLogical();
PrintReactivityLog(reactivityLog);
// Auto-save after every move.
SaveGame(world);
}
// ── Final state ───────────────────────────────────────────────
Console.WriteLine();
Console.WriteLine("=== Game over ===");
PrintReactivityLog(reactivityLog);
// Clean up save file on game over.
if (File.Exists(SavePath))
File.Delete(SavePath);
}
private static void SaveGame(World world)
{
using var stream = File.Create(SavePath);
WorldSerializer.Save(world, stream);
}
private static void SetupReactivityLogging(World world, List<string> log)
{
world.ObserveEntityChanges().Subscribe(change =>
{
if (change.ComponentType != null) return;
log.Add($"[react] {change}");
});
world.ObserveComponentChanges<Mark>().Subscribe(change =>
{
log.Add($"[react] {change}");
});
world.ObserveComponentChanges<GameState>().Subscribe(change =>
{
log.Add($"[react] {change}");
});
var markedQuery = world.Query().With<Cell>().With<Mark>().Build();
world.ObserveQuery(markedQuery).Subscribe(change =>
{
log.Add($"[react:query] {change}");
});
}
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();
}
}