2026-04-10 14:58:03 +02:00
|
|
|
using Chessistics.Engine.Events;
|
|
|
|
|
|
|
|
|
|
namespace Chessistics.Engine.Model;
|
|
|
|
|
|
|
|
|
|
public class BoardState
|
|
|
|
|
{
|
|
|
|
|
public int Width { get; }
|
|
|
|
|
public int Height { get; }
|
|
|
|
|
public CellType[,] Grid { get; }
|
|
|
|
|
public Dictionary<Coords, ProductionDef> Productions { get; }
|
|
|
|
|
public Dictionary<Coords, DemandState> Demands { get; }
|
|
|
|
|
public List<PieceState> Pieces { get; }
|
2026-04-10 21:44:12 +02:00
|
|
|
public List<PieceState> DestroyedPieces { get; } = new();
|
|
|
|
|
public Dictionary<Coords, int> ProductionBuffers { get; }
|
2026-04-10 14:58:03 +02:00
|
|
|
public SimPhase Phase { get; set; }
|
|
|
|
|
public int TurnNumber { get; set; }
|
|
|
|
|
public int NextPieceId { get; set; }
|
|
|
|
|
public Dictionary<PieceKind, int> RemainingStock { get; }
|
|
|
|
|
public int MaxDeadline { get; }
|
|
|
|
|
|
|
|
|
|
// Tracks all cells ever occupied by a piece (for metrics)
|
|
|
|
|
public HashSet<Coords> OccupiedCells { get; }
|
|
|
|
|
|
|
|
|
|
private readonly LevelDef _levelDef;
|
|
|
|
|
private bool _isApplyingCommand;
|
|
|
|
|
|
|
|
|
|
private BoardState(LevelDef level)
|
|
|
|
|
{
|
|
|
|
|
_levelDef = level;
|
|
|
|
|
Width = level.Width;
|
|
|
|
|
Height = level.Height;
|
|
|
|
|
MaxDeadline = level.MaxDeadline;
|
|
|
|
|
|
|
|
|
|
Grid = new CellType[Width, Height];
|
|
|
|
|
Productions = new Dictionary<Coords, ProductionDef>();
|
|
|
|
|
Demands = new Dictionary<Coords, DemandState>();
|
|
|
|
|
Pieces = new List<PieceState>();
|
2026-04-10 21:44:12 +02:00
|
|
|
ProductionBuffers = new Dictionary<Coords, int>();
|
2026-04-10 14:58:03 +02:00
|
|
|
RemainingStock = new Dictionary<PieceKind, int>();
|
|
|
|
|
OccupiedCells = new HashSet<Coords>();
|
|
|
|
|
|
|
|
|
|
Phase = SimPhase.Edit;
|
|
|
|
|
TurnNumber = 0;
|
|
|
|
|
NextPieceId = 1;
|
|
|
|
|
|
|
|
|
|
// Initialize grid as empty
|
|
|
|
|
for (int c = 0; c < Width; c++)
|
|
|
|
|
for (int r = 0; r < Height; r++)
|
|
|
|
|
Grid[c, r] = CellType.Empty;
|
|
|
|
|
|
|
|
|
|
// Place walls
|
|
|
|
|
foreach (var wall in level.Walls)
|
|
|
|
|
Grid[wall.Col, wall.Row] = CellType.Wall;
|
|
|
|
|
|
|
|
|
|
// Place productions
|
|
|
|
|
foreach (var prod in level.Productions)
|
|
|
|
|
{
|
|
|
|
|
Grid[prod.Position.Col, prod.Position.Row] = CellType.Production;
|
|
|
|
|
Productions[prod.Position] = prod;
|
2026-04-10 21:44:12 +02:00
|
|
|
ProductionBuffers[prod.Position] = 0;
|
2026-04-10 14:58:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Place demands
|
|
|
|
|
foreach (var demand in level.Demands)
|
|
|
|
|
{
|
|
|
|
|
Grid[demand.Position.Col, demand.Position.Row] = CellType.Demand;
|
|
|
|
|
Demands[demand.Position] = new DemandState(demand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize stock
|
|
|
|
|
foreach (var stock in level.Stock)
|
|
|
|
|
RemainingStock[stock.Kind] = stock.Count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static BoardState FromLevel(LevelDef level) => new(level);
|
|
|
|
|
|
|
|
|
|
public CellType GetCell(Coords coords) => Grid[coords.Col, coords.Row];
|
|
|
|
|
|
|
|
|
|
public bool IsOnBoard(Coords coords) => coords.IsOnBoard(Width, Height);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns all cells currently occupied by any piece (both start and end during Edit, CurrentCell during sim).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public HashSet<Coords> GetOccupiedCells()
|
|
|
|
|
{
|
|
|
|
|
var occupied = new HashSet<Coords>();
|
|
|
|
|
foreach (var piece in Pieces)
|
|
|
|
|
{
|
|
|
|
|
if (Phase == SimPhase.Edit)
|
|
|
|
|
{
|
|
|
|
|
occupied.Add(piece.StartCell);
|
|
|
|
|
occupied.Add(piece.EndCell);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
occupied.Add(piece.CurrentCell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return occupied;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PieceState? GetPieceById(int pieceId) => Pieces.Find(p => p.Id == pieceId);
|
|
|
|
|
|
|
|
|
|
public void ApplyCommand(Action<BoardState, List<IWorldEvent>> apply, List<IWorldEvent> changeList)
|
|
|
|
|
{
|
|
|
|
|
if (_isApplyingCommand)
|
|
|
|
|
throw new InvalidOperationException("Nested command not allowed.");
|
|
|
|
|
|
|
|
|
|
_isApplyingCommand = true;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
apply(this, changeList);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_isApplyingCommand = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ResetFromLevel()
|
|
|
|
|
{
|
|
|
|
|
Pieces.Clear();
|
2026-04-10 21:44:12 +02:00
|
|
|
DestroyedPieces.Clear();
|
2026-04-10 14:58:03 +02:00
|
|
|
Productions.Clear();
|
|
|
|
|
Demands.Clear();
|
|
|
|
|
ProductionBuffers.Clear();
|
|
|
|
|
RemainingStock.Clear();
|
|
|
|
|
OccupiedCells.Clear();
|
|
|
|
|
|
|
|
|
|
Phase = SimPhase.Edit;
|
|
|
|
|
TurnNumber = 0;
|
|
|
|
|
NextPieceId = 1;
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < Width; c++)
|
|
|
|
|
for (int r = 0; r < Height; r++)
|
|
|
|
|
Grid[c, r] = CellType.Empty;
|
|
|
|
|
|
|
|
|
|
foreach (var wall in _levelDef.Walls)
|
|
|
|
|
Grid[wall.Col, wall.Row] = CellType.Wall;
|
|
|
|
|
|
|
|
|
|
foreach (var prod in _levelDef.Productions)
|
|
|
|
|
{
|
|
|
|
|
Grid[prod.Position.Col, prod.Position.Row] = CellType.Production;
|
|
|
|
|
Productions[prod.Position] = prod;
|
2026-04-10 21:44:12 +02:00
|
|
|
ProductionBuffers[prod.Position] = 0;
|
2026-04-10 14:58:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var demand in _levelDef.Demands)
|
|
|
|
|
{
|
|
|
|
|
Grid[demand.Position.Col, demand.Position.Row] = CellType.Demand;
|
|
|
|
|
Demands[demand.Position] = new DemandState(demand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var stock in _levelDef.Stock)
|
|
|
|
|
RemainingStock[stock.Kind] = stock.Count;
|
|
|
|
|
}
|
|
|
|
|
}
|