126 lines
3.9 KiB
C#
126 lines
3.9 KiB
C#
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;
|
|
}
|
|
} |