oecs-sharp/Game.TicTacToe.Tests/TestHelpers.cs

126 lines
3.9 KiB
C#
Raw Permalink Normal View History

using Game.TicTacToe;
using OECS;
using System.Text;
namespace Game.TicTacToe.Tests;
internal static class TestHelpers
{
/// <summary>
/// Creates a fresh TicTacToe world with 9 empty cells, GameState singleton, and WinCheckSystem.
/// </summary>
public static (World World, SystemGroup Group) SetupGame()
{
var world = new World();
var group = new SystemGroup(world);
group.Add(new WinCheckSystem());
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
world.AddComponent(world.CreateEntity(), new Cell { Row = r, Col = c });
world.SetSingleton(new GameState
{
CurrentPlayer = Player.X,
Status = GameStatus.Playing,
MoveCount = 0
});
world.PostChanges();
return (world, group);
}
/// <summary>
/// Enqueues and runs a sequence of moves.
/// </summary>
public static void PlayMoves(World world, SystemGroup group, params (int Row, int Col)[] moves)
{
foreach (var (row, col) in moves)
{
world.Commands.Enqueue(new PlaceMarkCommand { Row = row, Col = col });
group.RunLogical();
}
}
/// <summary>
/// Returns a human-readable textual snapshot of the world state.
/// </summary>
public static string SnapshotWorld(World world)
{
var sb = new StringBuilder();
var state = world.ReadSingleton<GameState>();
var statusText = state.Status switch
{
GameStatus.Playing => $"{(state.CurrentPlayer == Player.X ? 'X' : 'O')}'s turn, {state.MoveCount} moves",
GameStatus.XWon => "XWon",
GameStatus.OWon => "OWon",
GameStatus.Draw => "Draw",
_ => "Unknown"
};
sb.AppendLine($"GameState: {statusText}");
var grid = new char?[3, 3];
foreach (var it in world.Select<Cell, Mark>())
grid[it.Val1.Row, it.Val1.Col] =
it.Val2.Player == Player.X ? 'X' : 'O';
int totalCells = 0;
int markedCells = 0;
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 3; c++)
{
totalCells++;
var mark = grid[r, c];
if (mark.HasValue)
{
markedCells++;
sb.AppendLine($" ({r},{c}): {mark.Value}");
}
else
{
sb.AppendLine($" ({r},{c}): .");
}
}
}
sb.AppendLine($"Cells: {totalCells} total, {markedCells} marked");
return sb.ToString().TrimEnd();
}
/// <summary>
/// Returns the (Row, Col) of all empty cells.
/// </summary>
public static List<(int Row, int Col)> GetEmptyCells(World world)
{
var empty = new List<(int, int)>();
foreach (var it in world.Select(new Query<Cell>().Without<Mark>()))
empty.Add((it.Val1.Row, it.Val1.Col));
return empty;
}
/// <summary>
/// Checks whether placing a mark at (row, col) for the given player would win the game.
/// </summary>
public static bool WouldWin(World world, int row, int col, Player player)
{
var grid = new Player[3, 3];
foreach (var it in world.Select<Cell, Mark>())
grid[it.Val1.Row, it.Val1.Col] = it.Val2.Player;
grid[row, col] = player;
for (int r = 0; r < 3; r++)
if (grid[r, 0] == player && grid[r, 1] == player && grid[r, 2] == player)
return true;
for (int c = 0; c < 3; c++)
if (grid[0, c] == player && grid[1, c] == player && grid[2, c] == player)
return true;
if (grid[0, 0] == player && grid[1, 1] == player && grid[2, 2] == player)
return true;
if (grid[0, 2] == player && grid[1, 1] == player && grid[2, 0] == player)
return true;
return false;
}
}