Launching Godot with --automation=<dir> activates an AutomationHarness node that polls <dir>/inbox/ for JSON command files, executes them via a thin facade over existing public surfaces (GameSim, InputMapper, EventAnimator, ControlBar, PieceStockPanel), and writes results plus screenshots back to disk. The black-box simulation boundary is not crossed — every command routes through the same signals/methods a real player would trigger. A stdlib-only Python helper (tools/automation/harness.py) wraps the protocol for test scripts and interactive REPLs. Smoke test passes end-to-end: load mission, place a piece, step 10 turns, capture 14 1280x720 PNGs, handle rejections, quit cleanly. Existing 102 engine unit tests still green.
123 lines
4.2 KiB
C#
123 lines
4.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json.Nodes;
|
|
using Chessistics.Engine.Model;
|
|
|
|
namespace Chessistics.Scripts.Automation;
|
|
|
|
/// <summary>
|
|
/// Serializes BoardSnapshot into plain JSON so the agent can reason about game state.
|
|
/// </summary>
|
|
internal static class SnapshotSerializer
|
|
{
|
|
public static JsonObject Serialize(BoardSnapshot snap)
|
|
{
|
|
var root = new JsonObject
|
|
{
|
|
["phase"] = snap.Phase.ToString(),
|
|
["turn"] = snap.TurnNumber,
|
|
["width"] = snap.Width,
|
|
["height"] = snap.Height,
|
|
["grid"] = SerializeGrid(snap.Grid, snap.Width, snap.Height),
|
|
["productions"] = new JsonArray(snap.Productions.Select(SerializeProd).ToArray<JsonNode?>()),
|
|
["demands"] = new JsonArray(snap.Demands.Select(SerializeDemand).ToArray<JsonNode?>()),
|
|
["transformers"] = new JsonArray(snap.Transformers.Select(SerializeTransformer).ToArray<JsonNode?>()),
|
|
["pieces"] = new JsonArray(snap.Pieces.Select(SerializePiece).ToArray<JsonNode?>()),
|
|
["remainingStock"] = SerializeStock(snap.RemainingStock),
|
|
};
|
|
|
|
if (snap.Campaign != null)
|
|
{
|
|
root["campaign"] = new JsonObject
|
|
{
|
|
["name"] = snap.Campaign.Name,
|
|
["currentMissionIndex"] = snap.Campaign.CurrentMissionIndex,
|
|
["completedMissions"] = new JsonArray(snap.Campaign.CompletedMissions.Select(i => (JsonNode?)JsonValue.Create(i)).ToArray()),
|
|
["availablePieceKinds"] = new JsonArray(snap.Campaign.AvailablePieceKinds.Select(k => (JsonNode?)JsonValue.Create(k.ToString())).ToArray()),
|
|
["availableLevels"] = new JsonArray(snap.Campaign.AvailableLevels
|
|
.Select(u => (JsonNode?)new JsonObject { ["kind"] = u.Kind.ToString(), ["level"] = u.Level })
|
|
.ToArray()),
|
|
};
|
|
}
|
|
else
|
|
{
|
|
root["campaign"] = null;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
private static JsonArray SerializeGrid(CellType[,] grid, int w, int h)
|
|
{
|
|
var rows = new JsonArray();
|
|
for (int r = 0; r < h; r++)
|
|
{
|
|
var row = new JsonArray();
|
|
for (int c = 0; c < w; c++)
|
|
row.Add(grid[c, r].ToString());
|
|
rows.Add(row);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
private static JsonObject SerializeProd(ProductionSnapshot p) => new()
|
|
{
|
|
["pos"] = CoordsJson(p.Position),
|
|
["name"] = p.Name,
|
|
["cargo"] = p.Cargo.ToString(),
|
|
["amount"] = p.Amount,
|
|
["buffer"] = p.BufferCount,
|
|
};
|
|
|
|
private static JsonObject SerializeDemand(DemandSnapshot d) => new()
|
|
{
|
|
["pos"] = CoordsJson(d.Position),
|
|
["name"] = d.Name,
|
|
["cargo"] = d.Cargo.ToString(),
|
|
["required"] = d.Required,
|
|
["deadline"] = d.Deadline,
|
|
["received"] = d.ReceivedCount,
|
|
["satisfied"] = d.IsSatisfied,
|
|
["missionIndex"] = d.MissionIndex,
|
|
};
|
|
|
|
private static JsonObject SerializeTransformer(TransformerSnapshot t) => new()
|
|
{
|
|
["pos"] = CoordsJson(t.Position),
|
|
["name"] = t.Name,
|
|
["inputCargo"] = t.InputCargo.ToString(),
|
|
["inputRequired"] = t.InputRequired,
|
|
["outputCargo"] = t.OutputCargo.ToString(),
|
|
["outputAmount"] = t.OutputAmount,
|
|
["inputBuffer"] = t.InputBufferCount,
|
|
["outputBuffer"] = t.OutputBufferCount,
|
|
};
|
|
|
|
private static JsonObject SerializePiece(PieceSnapshot p) => new()
|
|
{
|
|
["id"] = p.Id,
|
|
["kind"] = p.Kind.ToString(),
|
|
["level"] = p.Level,
|
|
["start"] = CoordsJson(p.StartCell),
|
|
["end"] = CoordsJson(p.EndCell),
|
|
["current"] = CoordsJson(p.CurrentCell),
|
|
["cargo"] = p.Cargo?.ToString(),
|
|
["cargoFilter"] = p.CargoFilter?.ToString(),
|
|
["socialStatus"] = p.SocialStatus,
|
|
};
|
|
|
|
private static JsonObject SerializeStock(IReadOnlyDictionary<PieceKind, int> stock)
|
|
{
|
|
var obj = new JsonObject();
|
|
foreach (var (k, v) in stock) obj[k.ToString()] = v;
|
|
return obj;
|
|
}
|
|
|
|
private static JsonArray CoordsJson(Coords c)
|
|
{
|
|
var arr = new JsonArray();
|
|
arr.Add(c.Col);
|
|
arr.Add(c.Row);
|
|
return arr;
|
|
}
|
|
}
|