test: add play tests for Blackjack and TicTacToe
Implement play tests for Blackjack and TicTacToe that simulate games using various AI agents (Basic Strategy, Random, Greedy) and record the results, decisions, and reactivity into playlogs for debugging and LLM analysis. Update documentation to reflect the purpose of these tests.
This commit is contained in:
parent
343bcd5b2e
commit
f8d0fe314c
|
|
@ -0,0 +1,307 @@
|
|||
using FluentAssertions;
|
||||
using Game.Blackjack;
|
||||
using OECS;
|
||||
using R3;
|
||||
using Xunit;
|
||||
|
||||
namespace Game.Blackjack.Tests;
|
||||
|
||||
public class PlayTests
|
||||
{
|
||||
[Fact]
|
||||
public void Play_BasicStrategy()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
var (world, group) = SetupGame(seed: 42);
|
||||
|
||||
world.ObserveComponentChanges<Card>().Subscribe(change =>
|
||||
{
|
||||
var card = world.ReadComponent<Card>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Card = {card} on {change.Entity}");
|
||||
});
|
||||
world.ObserveComponentChanges<GameState>().Subscribe(change =>
|
||||
{
|
||||
var state = world.ReadComponent<GameState>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} GameState = {state}");
|
||||
});
|
||||
|
||||
log.Header = "Blackjack: Basic Strategy (seed=42)";
|
||||
|
||||
var agent = new BasicStrategyAgent();
|
||||
RunRound(world, group, agent, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("blackjack_basic_strategy.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Play_RandomAgent()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
var (world, group) = SetupGame(seed: 123);
|
||||
|
||||
world.ObserveComponentChanges<Card>().Subscribe(change =>
|
||||
{
|
||||
var card = world.ReadComponent<Card>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Card = {card} on {change.Entity}");
|
||||
});
|
||||
world.ObserveComponentChanges<GameState>().Subscribe(change =>
|
||||
{
|
||||
var state = world.ReadComponent<GameState>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} GameState = {state}");
|
||||
});
|
||||
|
||||
log.Header = "Blackjack: Random Agent (seed=123)";
|
||||
|
||||
var agent = new RandomBlackjackAgent();
|
||||
RunRound(world, group, agent, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("blackjack_random_agent.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Play_WeightedPool()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
var (world, group) = SetupGame(seed: 77);
|
||||
|
||||
world.ObserveComponentChanges<Card>().Subscribe(change =>
|
||||
{
|
||||
var card = world.ReadComponent<Card>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Card = {card} on {change.Entity}");
|
||||
});
|
||||
world.ObserveComponentChanges<GameState>().Subscribe(change =>
|
||||
{
|
||||
var state = world.ReadComponent<GameState>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} GameState = {state}");
|
||||
});
|
||||
|
||||
var pool = new WeightedAgentPool();
|
||||
pool.Add(new BasicStrategyAgent(), 7);
|
||||
pool.Add(new RandomBlackjackAgent(), 3);
|
||||
|
||||
log.Header = $"Blackjack: Weighted Pool (seed=77, agent={pool.LastPicked})";
|
||||
|
||||
var agent = pool.Pick();
|
||||
RunRound(world, group, agent, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("blackjack_weighted_pool.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
// ── Play Log ──────────────────────────────────────────────────────
|
||||
|
||||
private sealed class PlayLog
|
||||
{
|
||||
public string Header { get; set; } = "";
|
||||
public readonly List<string> Decisions = new();
|
||||
public readonly List<string> Reactivity = new();
|
||||
public string FinalSnapshot { get; set; } = "";
|
||||
|
||||
public string Build()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine(Header);
|
||||
sb.AppendLine(new string('=', Header.Length));
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Decisions ---");
|
||||
for (int i = 0; i < Decisions.Count; i++)
|
||||
sb.AppendLine($" {i + 1}. {Decisions[i]}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Reactivity ---");
|
||||
foreach (var entry in Reactivity)
|
||||
sb.AppendLine($" {entry}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Final State ---");
|
||||
sb.AppendLine(FinalSnapshot);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Round Runner ──────────────────────────────────────────────────
|
||||
|
||||
private static (World, SystemGroup) SetupGame(uint seed)
|
||||
{
|
||||
var world = new World();
|
||||
var group = new SystemGroup(world);
|
||||
group.Add(new DeckSetupSystem());
|
||||
group.Add(new DealSystem());
|
||||
group.Add(new PlayerBustCheckSystem());
|
||||
group.Add(new DealerSystem());
|
||||
|
||||
world.SetSingleton(new GameState
|
||||
{
|
||||
Phase = GamePhase.Betting,
|
||||
Result = RoundResult.None,
|
||||
Chips = 100,
|
||||
CurrentBet = 0,
|
||||
RoundNumber = 1,
|
||||
Seed = seed
|
||||
});
|
||||
world.PostChanges();
|
||||
|
||||
return (world, group);
|
||||
}
|
||||
|
||||
private static void RunRound(World world, SystemGroup group, IBlackjackAgent agent, PlayLog log)
|
||||
{
|
||||
world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 });
|
||||
group.RunLogical();
|
||||
|
||||
while (world.ReadSingleton<GameState>().Phase == GamePhase.PlayerTurn)
|
||||
{
|
||||
var total = HandUtil.CalculateHand(world, new PlayerHand());
|
||||
var decision = agent.Decide(world);
|
||||
log.Decisions.Add($"Hand={total}, Decision={decision}");
|
||||
|
||||
if (decision == BlackjackDecision.Hit)
|
||||
world.Commands.Enqueue(new HitCommand());
|
||||
else
|
||||
world.Commands.Enqueue(new StandCommand());
|
||||
|
||||
group.RunLogical();
|
||||
}
|
||||
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
log.Header += $" — Result: {state.Result}";
|
||||
}
|
||||
|
||||
// ── Snapshot ──────────────────────────────────────────────────────
|
||||
|
||||
private static string SnapshotWorld(World world)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
sb.AppendLine($"Phase: {state.Phase}");
|
||||
sb.AppendLine($"Chips: {state.Chips}, Bet: {state.CurrentBet}");
|
||||
sb.AppendLine($"Result: {state.Result}");
|
||||
|
||||
var playerHand = FindEntity<PlayerHand>(world);
|
||||
if (playerHand != Entity.Null)
|
||||
{
|
||||
sb.AppendLine($"Player ({HandUtil.CalculateHand(world, new PlayerHand())}):");
|
||||
foreach (var card in GetHandCards(world, playerHand))
|
||||
sb.AppendLine($" {card}");
|
||||
}
|
||||
|
||||
var dealerHand = FindEntity<DealerHand>(world);
|
||||
if (dealerHand != Entity.Null)
|
||||
{
|
||||
sb.AppendLine($"Dealer ({HandUtil.CalculateHand(world, new DealerHand())}):");
|
||||
foreach (var card in GetHandCards(world, dealerHand))
|
||||
sb.AppendLine($" {card}");
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
private static Entity FindEntity<T>(World world) where T : struct
|
||||
{
|
||||
using var iter = world.Select<T>();
|
||||
while (iter.MoveNext())
|
||||
if (iter.CurrentEntity != World.SingletonEntity)
|
||||
return iter.CurrentEntity;
|
||||
return Entity.Null;
|
||||
}
|
||||
|
||||
private static List<string> GetHandCards(World world, Entity hand)
|
||||
{
|
||||
var cards = new List<string>();
|
||||
foreach (var cardEntity in world.GetSources<Holds>(hand))
|
||||
{
|
||||
var card = world.ReadComponent<Card>(cardEntity);
|
||||
cards.Add($"{card.Rank} of {card.Suit}");
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
// ── File I/O ──────────────────────────────────────────────────────
|
||||
|
||||
private static string SavePlayLog(string filename, PlayLog log)
|
||||
{
|
||||
var dir = Path.Combine(AppContext.BaseDirectory, "playlogs");
|
||||
Directory.CreateDirectory(dir);
|
||||
var path = Path.Combine(dir, filename);
|
||||
File.WriteAllText(path, log.Build());
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void ReadAndVerify(string path)
|
||||
{
|
||||
var content = File.ReadAllText(path);
|
||||
|
||||
content.Should().Contain("--- Decisions ---");
|
||||
content.Should().Contain("--- Reactivity ---");
|
||||
content.Should().Contain("--- Final State ---");
|
||||
content.Should().Contain("Result:");
|
||||
}
|
||||
|
||||
// ── Agents ────────────────────────────────────────────────────────
|
||||
|
||||
private enum BlackjackDecision { Hit, Stand }
|
||||
|
||||
private interface IBlackjackAgent
|
||||
{
|
||||
BlackjackDecision Decide(World world);
|
||||
}
|
||||
|
||||
private sealed class BasicStrategyAgent : IBlackjackAgent
|
||||
{
|
||||
public BlackjackDecision Decide(World world)
|
||||
{
|
||||
var total = HandUtil.CalculateHand(world, new PlayerHand());
|
||||
return total < 17 ? BlackjackDecision.Hit : BlackjackDecision.Stand;
|
||||
}
|
||||
public override string ToString() => "BasicStrategy";
|
||||
}
|
||||
|
||||
private sealed class RandomBlackjackAgent : IBlackjackAgent
|
||||
{
|
||||
private static readonly Random _rng = new();
|
||||
public BlackjackDecision Decide(World world)
|
||||
{
|
||||
var total = HandUtil.CalculateHand(world, new PlayerHand());
|
||||
if (total >= 21) return BlackjackDecision.Stand;
|
||||
return _rng.Next(2) == 0 ? BlackjackDecision.Hit : BlackjackDecision.Stand;
|
||||
}
|
||||
public override string ToString() => "Random";
|
||||
}
|
||||
|
||||
private sealed class WeightedAgentPool
|
||||
{
|
||||
private readonly List<(IBlackjackAgent Agent, int Weight)> _agents = new();
|
||||
private int _totalWeight;
|
||||
private static readonly Random _rng = new();
|
||||
public string LastPicked { get; private set; } = "";
|
||||
|
||||
public void Add(IBlackjackAgent agent, int weight)
|
||||
{
|
||||
_agents.Add((agent, weight));
|
||||
_totalWeight += weight;
|
||||
}
|
||||
|
||||
public IBlackjackAgent Pick()
|
||||
{
|
||||
int roll = _rng.Next(_totalWeight);
|
||||
int cumulative = 0;
|
||||
foreach (var (agent, weight) in _agents)
|
||||
{
|
||||
cumulative += weight;
|
||||
if (roll < cumulative)
|
||||
{
|
||||
LastPicked = agent.ToString()!;
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
LastPicked = _agents[^1].Agent.ToString()!;
|
||||
return _agents[^1].Agent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
using FluentAssertions;
|
||||
using Game.TicTacToe;
|
||||
using OECS;
|
||||
using R3;
|
||||
using Xunit;
|
||||
|
||||
namespace Game.TicTacToe.Tests;
|
||||
|
||||
public class PlayTests
|
||||
{
|
||||
[Fact]
|
||||
public void Play_GreedyVsRandom()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
|
||||
var (world, group) = SetupGame();
|
||||
var agentX = new GreedyAgent(GreedyAgent.Strategy.WinOrBlock);
|
||||
var agentO = new RandomAgent();
|
||||
|
||||
// Subscribe reactivity.
|
||||
world.ObserveComponentChanges<Mark>().Subscribe(change =>
|
||||
{
|
||||
var mark = world.ReadComponent<Mark>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Mark = {mark} on {change.Entity}");
|
||||
});
|
||||
|
||||
log.Header = "TicTacToe: Greedy X vs Random O";
|
||||
|
||||
RunGame(world, group, agentX, agentO, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("tic_tac_toe_greedy_vs_random.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Play_RandomVsRandom()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
|
||||
var (world, group) = SetupGame();
|
||||
var agentX = new RandomAgent();
|
||||
var agentO = new RandomAgent();
|
||||
|
||||
world.ObserveComponentChanges<Mark>().Subscribe(change =>
|
||||
{
|
||||
var mark = world.ReadComponent<Mark>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Mark = {mark} on {change.Entity}");
|
||||
});
|
||||
|
||||
log.Header = "TicTacToe: Random X vs Random O";
|
||||
|
||||
RunGame(world, group, agentX, agentO, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("tic_tac_toe_random_vs_random.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Play_GreedyVsGreedy()
|
||||
{
|
||||
var log = new PlayLog();
|
||||
|
||||
var (world, group) = SetupGame();
|
||||
var agentX = new GreedyAgent(GreedyAgent.Strategy.WinOrBlock);
|
||||
var agentO = new GreedyAgent(GreedyAgent.Strategy.WinOrBlock);
|
||||
|
||||
world.ObserveComponentChanges<Mark>().Subscribe(change =>
|
||||
{
|
||||
var mark = world.ReadComponent<Mark>(change.Entity);
|
||||
log.Reactivity.Add($"{change.Kind} Mark = {mark} on {change.Entity}");
|
||||
});
|
||||
|
||||
log.Header = "TicTacToe: Greedy X vs Greedy O";
|
||||
|
||||
RunGame(world, group, agentX, agentO, log);
|
||||
|
||||
log.FinalSnapshot = SnapshotWorld(world);
|
||||
|
||||
var path = SavePlayLog("tic_tac_toe_greedy_vs_greedy.playlog", log);
|
||||
ReadAndVerify(path);
|
||||
}
|
||||
|
||||
// ── Play Log ──────────────────────────────────────────────────────
|
||||
|
||||
private sealed class PlayLog
|
||||
{
|
||||
public string Header { get; set; } = "";
|
||||
public readonly List<string> Moves = new();
|
||||
public readonly List<string> Reactivity = new();
|
||||
public string FinalSnapshot { get; set; } = "";
|
||||
|
||||
public string Build()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine(Header);
|
||||
sb.AppendLine(new string('=', Header.Length));
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Moves ---");
|
||||
for (int i = 0; i < Moves.Count; i++)
|
||||
sb.AppendLine($" {i + 1}. {Moves[i]}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Reactivity ---");
|
||||
foreach (var entry in Reactivity)
|
||||
sb.AppendLine($" {entry}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("--- Final Board ---");
|
||||
sb.AppendLine(FinalSnapshot);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Game Runner ───────────────────────────────────────────────────
|
||||
|
||||
private static (World, SystemGroup) 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);
|
||||
}
|
||||
|
||||
private static void RunGame(World world, SystemGroup group, IAgent agentX, IAgent agentO, PlayLog log)
|
||||
{
|
||||
while (world.ReadSingleton<GameState>().Status == GameStatus.Playing)
|
||||
{
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
var agent = state.CurrentPlayer == Player.X ? agentX : agentO;
|
||||
var cmd = agent.Decide(world);
|
||||
|
||||
world.Commands.Enqueue(cmd);
|
||||
group.RunLogical();
|
||||
|
||||
log.Moves.Add($"{state.CurrentPlayer} → ({cmd.Row},{cmd.Col})");
|
||||
}
|
||||
|
||||
var final = world.ReadSingleton<GameState>();
|
||||
log.Header += $" — Result: {final.Status}";
|
||||
}
|
||||
|
||||
// ── Snapshot ──────────────────────────────────────────────────────
|
||||
|
||||
private static string SnapshotWorld(World world)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
var grid = new char?[3, 3];
|
||||
using (var iter = world.Select<Cell, Mark>())
|
||||
{
|
||||
while (iter.MoveNext())
|
||||
grid[iter.Current1.Row, iter.Current1.Col] =
|
||||
iter.Current2.Player == Player.X ? 'X' : 'O';
|
||||
}
|
||||
|
||||
for (int r = 0; r < 3; r++)
|
||||
{
|
||||
sb.Append(" ");
|
||||
for (int c = 0; c < 3; c++)
|
||||
sb.Append(grid[r, c]?.ToString() ?? ".");
|
||||
sb.AppendLine();
|
||||
}
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
// ── File I/O ──────────────────────────────────────────────────────
|
||||
|
||||
private static string SavePlayLog(string filename, PlayLog log)
|
||||
{
|
||||
var dir = Path.Combine(AppContext.BaseDirectory, "playlogs");
|
||||
Directory.CreateDirectory(dir);
|
||||
var path = Path.Combine(dir, filename);
|
||||
File.WriteAllText(path, log.Build());
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void ReadAndVerify(string path)
|
||||
{
|
||||
var content = File.ReadAllText(path);
|
||||
|
||||
// Verify the play log is well-formed.
|
||||
content.Should().Contain("--- Moves ---");
|
||||
content.Should().Contain("--- Reactivity ---");
|
||||
content.Should().Contain("--- Final Board ---");
|
||||
|
||||
// The game must have finished.
|
||||
content.Should().Contain("Result:");
|
||||
content.Should().NotContain("Result: Playing");
|
||||
}
|
||||
|
||||
// ── Agents ────────────────────────────────────────────────────────
|
||||
|
||||
private interface IAgent
|
||||
{
|
||||
PlaceMarkCommand Decide(World world);
|
||||
}
|
||||
|
||||
private sealed class RandomAgent : IAgent
|
||||
{
|
||||
private static readonly Random _rng = new();
|
||||
public PlaceMarkCommand Decide(World world)
|
||||
{
|
||||
var empty = GetEmptyCells(world);
|
||||
var (row, col) = empty[_rng.Next(empty.Count)];
|
||||
return new PlaceMarkCommand { Row = row, Col = col };
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class GreedyAgent : IAgent
|
||||
{
|
||||
public enum Strategy { WinOrBlock }
|
||||
private readonly Strategy _strategy;
|
||||
private static readonly Random _rng = new();
|
||||
public GreedyAgent(Strategy strategy) => _strategy = strategy;
|
||||
|
||||
public PlaceMarkCommand Decide(World world)
|
||||
{
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
var me = state.CurrentPlayer;
|
||||
var empty = GetEmptyCells(world);
|
||||
|
||||
foreach (var (r, c) in empty)
|
||||
if (WouldWin(world, r, c, me))
|
||||
return new PlaceMarkCommand { Row = r, Col = c };
|
||||
|
||||
var opponent = me == Player.X ? Player.O : Player.X;
|
||||
foreach (var (r, c) in empty)
|
||||
if (WouldWin(world, r, c, opponent))
|
||||
return new PlaceMarkCommand { Row = r, Col = c };
|
||||
|
||||
if (empty.Any(c => c.Row == 1 && c.Col == 1))
|
||||
return new PlaceMarkCommand { Row = 1, Col = 1 };
|
||||
|
||||
var (rr, cc) = empty[_rng.Next(empty.Count)];
|
||||
return new PlaceMarkCommand { Row = rr, Col = cc };
|
||||
}
|
||||
}
|
||||
|
||||
private static List<(int Row, int Col)> GetEmptyCells(World world)
|
||||
{
|
||||
var empty = new List<(int, int)>();
|
||||
var query = world.Query().With<Cell>().Without<Mark>().Build();
|
||||
using var iter = world.Select<Cell>(query);
|
||||
while (iter.MoveNext())
|
||||
empty.Add((iter.Current1.Row, iter.Current1.Col));
|
||||
return empty;
|
||||
}
|
||||
|
||||
private static bool WouldWin(World world, int row, int col, Player player)
|
||||
{
|
||||
var grid = new Player[3, 3];
|
||||
using (var iter = world.Select<Cell, Mark>())
|
||||
while (iter.MoveNext())
|
||||
grid[iter.Current1.Row, iter.Current1.Col] = iter.Current2.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
## Play tests
|
||||
|
||||
Playtests are games executed with AI players who give unexpected play commands.
|
||||
The goal of playtests is to execute a game with AI players and potentially random seeds. Then the log/snapshots are read by the LLM agent to spot issues.
|
||||
|
||||
There is no failure in play tests; all tests pass but the agent should read the log for reference.
|
||||
|
||||
AI players are not LLMs here; they are typically common game AI implementations.
|
||||
|
||||
|
|
@ -17,3 +19,5 @@ Create static functions that analyze the game state and return a command. Each s
|
|||
Agents to consider:
|
||||
- Random: evenly chooses every move randomly.
|
||||
- Greedy: uses a specific way to score actions. Always chooses the onewith highest score. There can be multiple Greedy agents wtih different scoring.
|
||||
|
||||
For each playtest attempt, generate a full game log, analyze and report what you find from the log.
|
||||
|
|
|
|||
Loading…
Reference in New Issue