Models and parsers for board-related data for a Lichess client. Defines enums and DTOs for seek options, game info, player info, clock and state, chat and event types, plus helper methods to serialize seek options to form data and to parse JSON board events.
#nullable enable annotations
using System.Globalization;
using LichessNET.Entities.Enumerations;
using LichessNET.Extensions;
namespace LichessNET.Entities.Board;
public enum BoardColorPreference
{
Random,
White,
Black
}
public enum BoardChatRoom
{
Player,
Spectator
}
public sealed class BoardSeekOptions
{
public bool Rated { get; set; }
public double TimeMinutes { get; set; } = 5;
public int IncrementSeconds { get; set; }
public int? DaysPerTurn { get; set; }
public BoardColorPreference Color { get; set; } = BoardColorPreference.Random;
public ChessVariant Variant { get; set; } = ChessVariant.Standard;
public string RatingRange { get; set; }
internal Dictionary<string, string> ToFormData()
{
Validate();
var form = new Dictionary<string, string>
{
["rated"] = Rated.ToString().ToLowerInvariant(),
["variant"] = Variant.ToApiName(),
["color"] = ToApiName(Color)
};
if (DaysPerTurn.HasValue)
{
form["days"] = DaysPerTurn.Value.ToString(CultureInfo.InvariantCulture);
}
else
{
form["time"] = TimeMinutes.ToString(CultureInfo.InvariantCulture);
form["increment"] = IncrementSeconds.ToString(CultureInfo.InvariantCulture);
}
if (!string.IsNullOrWhiteSpace(RatingRange))
form["ratingRange"] = RatingRange;
return form;
}
private void Validate()
{
if (DaysPerTurn.HasValue)
{
if (DaysPerTurn.Value < 1 || DaysPerTurn.Value > 14)
throw new ArgumentOutOfRangeException(nameof(DaysPerTurn), "Days per turn must be between 1 and 14.");
return;
}
if (TimeMinutes <= 0 || TimeMinutes > 180)
throw new ArgumentOutOfRangeException(nameof(TimeMinutes), "Clock time must be between 0 and 180 minutes.");
if (IncrementSeconds < 0 || IncrementSeconds > 180)
throw new ArgumentOutOfRangeException(nameof(IncrementSeconds), "Increment must be between 0 and 180 seconds.");
}
private static string ToApiName(BoardColorPreference color)
{
return color switch
{
BoardColorPreference.White => "white",
BoardColorPreference.Black => "black",
_ => "random"
};
}
}
public sealed class BoardGameInfo
{
[JsonPropertyName("gameId")]
public string GameId { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("fullId")]
public string FullId { get; set; }
[JsonPropertyName("color")]
public string Color { get; set; }
[JsonPropertyName("fen")]
public string Fen { get; set; }
[JsonPropertyName("hasMoved")]
public bool? HasMoved { get; set; }
[JsonPropertyName("isMyTurn")]
public bool? IsMyTurn { get; set; }
[JsonPropertyName("lastMove")]
public string LastMove { get; set; }
[JsonPropertyName("source")]
public string Source { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; }
[JsonPropertyName("variant")]
public string Variant { get; set; }
[JsonPropertyName("speed")]
public string Speed { get; set; }
[JsonPropertyName("perf")]
public string Perf { get; set; }
[JsonPropertyName("rated")]
public bool? Rated { get; set; }
[JsonPropertyName("opponent")]
public BoardPlayerInfo Opponent { get; set; }
[JsonPropertyName("white")]
public BoardPlayerInfo White { get; set; }
[JsonPropertyName("black")]
public BoardPlayerInfo Black { get; set; }
[JsonPropertyName("state")]
public BoardGameState State { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> Extra { get; set; }
public string BestGameId => !string.IsNullOrWhiteSpace(GameId) ? GameId : Id;
}
public sealed class BoardPlayerInfo
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("rating")]
public int? Rating { get; set; }
[JsonPropertyName("provisional")]
public bool? Provisional { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> Extra { get; set; }
}
public sealed class BoardClockState
{
public int? WhiteTimeMilliseconds { get; set; }
public int? BlackTimeMilliseconds { get; set; }
public int? WhiteIncrementMilliseconds { get; set; }
public int? BlackIncrementMilliseconds { get; set; }
public TimeSpan? WhiteTime => WhiteTimeMilliseconds.HasValue
? TimeSpan.FromMilliseconds(WhiteTimeMilliseconds.Value)
: null;
public TimeSpan? BlackTime => BlackTimeMilliseconds.HasValue
? TimeSpan.FromMilliseconds(BlackTimeMilliseconds.Value)
: null;
public static BoardClockState FromState(BoardGameState state)
{
return new BoardClockState
{
WhiteTimeMilliseconds = state?.WhiteTimeMilliseconds,
BlackTimeMilliseconds = state?.BlackTimeMilliseconds,
WhiteIncrementMilliseconds = state?.WhiteIncrementMilliseconds,
BlackIncrementMilliseconds = state?.BlackIncrementMilliseconds
};
}
}
public sealed class BoardGameState
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("moves")]
public string Moves { get; set; }
[JsonPropertyName("wtime")]
public int? WhiteTimeMilliseconds { get; set; }
[JsonPropertyName("btime")]
public int? BlackTimeMilliseconds { get; set; }
[JsonPropertyName("winc")]
public int? WhiteIncrementMilliseconds { get; set; }
[JsonPropertyName("binc")]
public int? BlackIncrementMilliseconds { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; }
[JsonPropertyName("winner")]
public string Winner { get; set; }
[JsonPropertyName("wdraw")]
public bool? WhiteOfferingDraw { get; set; }
[JsonPropertyName("bdraw")]
public bool? BlackOfferingDraw { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> Extra { get; set; }
public BoardClockState Clock => BoardClockState.FromState(this);
public bool IsFinished => !string.IsNullOrWhiteSpace(Status)
&& !Status.Equals("started", StringComparison.OrdinalIgnoreCase)
&& !Status.Equals("created", StringComparison.OrdinalIgnoreCase);
public IReadOnlyList<string> MoveList => BoardEventParser.SplitMoves(Moves);
}
public sealed class BoardGameStartEvent
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("game")]
public BoardGameInfo Game { get; set; }
}
public sealed class BoardGameFinishEvent
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("game")]
public BoardGameInfo Game { get; set; }
}
public sealed class BoardGameFullEvent
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("initialFen")]
public string InitialFen { get; set; }
[JsonPropertyName("rated")]
public bool? Rated { get; set; }
[JsonPropertyName("variant")]
public string Variant { get; set; }
[JsonPropertyName("speed")]
public string Speed { get; set; }
[JsonPropertyName("perf")]
public string Perf { get; set; }
[JsonPropertyName("createdAt")]
public long? CreatedAt { get; set; }
[JsonPropertyName("white")]
public BoardPlayerInfo White { get; set; }
[JsonPropertyName("black")]
public BoardPlayerInfo Black { get; set; }
[JsonPropertyName("state")]
public BoardGameState State { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> Extra { get; set; }
}
public sealed class BoardChatLineEvent
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("room")]
public string Room { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("text")]
public string Text { get; set; }
}
public static class BoardEventParser
{
public static string GetEventType(JsonElement element)
{
return element.TryGetProperty("type", out var type)
? type.GetString()
: string.Empty;
}
public static BoardGameStartEvent ParseGameStart(JsonElement element)
{
return element.Deserialize<BoardGameStartEvent>(LichessJson.Options);
}
public static BoardGameFinishEvent ParseGameFinish(JsonElement element)
{
return element.Deserialize<BoardGameFinishEvent>(LichessJson.Options);
}
public static BoardGameFullEvent ParseGameFull(JsonElement element)
{
return element.Deserialize<BoardGameFullEvent>(LichessJson.Options);
}
public static BoardGameState ParseGameState(JsonElement element)
{
return element.Deserialize<BoardGameState>(LichessJson.Options);
}
public static BoardChatLineEvent ParseChatLine(JsonElement element)
{
return element.Deserialize<BoardChatLineEvent>(LichessJson.Options);
}
public static IReadOnlyList<string> SplitMoves(string moves)
{
if (string.IsNullOrWhiteSpace(moves))
return Array.Empty<string>();
return moves.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
}