using System.Text;
using ActionBlocks.Game;
using ActionBlocks.Game.Core;
namespace ActionBlocks.Server.Dtos;
/// Converts game objects into compact network snapshots. Only the visible rows are sent.
public static class SnapshotMapper
{
public static MatchStateDto ToDto(Match match, IReadOnlyList events) => new(
match.Phase.ToString().ToLowerInvariant(),
Math.Round(match.CountdownRemaining, 2),
match.WinnerId,
match.Settings.BoardWidth,
match.Settings.VisibleRows,
match.Players.Select(p => ToDto(p, match)).ToList(),
events.Select(ToDto).ToList());
private static PlayerStateDto ToDto(PlayerGame player, Match match)
{
var board = player.Board;
var cells = new StringBuilder(board.Width * board.VisibleRows);
var items = new List();
for (var y = board.BufferRows; y < board.Height; y++)
{
for (var x = 0; x < board.Width; x++)
{
var cell = board[x, y];
cells.Append((char)('0' + cell.Color));
if (cell.ItemId is not null)
{
items.Add(new ItemCellDto(x, y - board.BufferRows, cell.ItemId));
}
}
}
return new PlayerStateDto(
player.PlayerId,
player.Name,
player.IsAlive,
match.Places.TryGetValue(player.PlayerId, out var place) ? place : null,
cells.ToString(),
items,
player.Active is { } active && player.IsAlive ? ToDto(active, board) : null,
player.IsAlive ? player.Ghost() is { } ghost ? ToCells(ghost, board) : null : null,
player.Hold is { } hold ? ToDto(hold) : null,
player.HoldUsed,
player.Next.Select(ToDto).ToList(),
player.Inventory.ToList(),
player.Score,
player.Lines,
player.Level,
player.PendingGarbage,
Math.Round(Math.Max(0, player.InvisibleUntil - match.Now), 2),
Math.Round(Math.Max(0, player.TransformedUntil - match.Now), 2));
}
private static ActivePieceDto ToDto(ActivePiece piece, Board board) =>
new((int)piece.Type, ToCells(piece, board), ToItems(piece.Spec));
private static int[][] ToCells(ActivePiece piece, Board board) =>
piece.Cells().Select(c => new[] { c.X, c.Y - board.BufferRows }).ToArray();
private static PieceDto ToDto(PieceSpec spec) => new((int)spec.Type, ToItems(spec));
private static string?[] ToItems(PieceSpec spec) => spec.Items?.ToArray() ?? new string?[PieceDefinitions.Minos(spec.Type, 0).Count];
private static MatchEventDto ToDto(MatchEvent e) => e switch
{
ItemUsedEvent used => new MatchEventDto("itemUsed", TargetId: used.TargetId, ItemId: used.ItemId, ItemName: used.ItemName),
PlayerEliminatedEvent eliminated => new MatchEventDto("eliminated", PlayerId: eliminated.PlayerId, Place: eliminated.Place),
_ => new MatchEventDto("unknown"),
};
}