create endgame play section

This commit is contained in:
2026-08-11 21:52:33 +02:00
parent ca51546be0
commit 88bb016bac
13 changed files with 594 additions and 26 deletions
+72
View File
@@ -36,6 +36,78 @@ defmodule Chesstrainer.Endgames.Endgame do
|> validate_format(:key, ~r/^(?=.{5,10}$)KQ*R*[NB]*P*\sv\sKQ*R*[NB]*P*$/,
message: "Key must follow pattern KQ.. v KQR.., max pieces 7"
)
|> validate_change(:fen, fn :fen, fen ->
case fen_issues(fen) do
[] -> []
issues -> [fen: "is not a valid FEN: #{Enum.join(issues, "; ")}"]
end
end)
|> unique_constraint(:fen, message: "FEN already exists")
end
# Strict structural validation. Mirrors chess.js rules so the FEN survives
# both the server-side parser (Chex, permissive) and the client-side parser
# (chess.js, strict).
defp fen_issues(fen) do
case String.split(fen, " ") do
[board, active, _castling, _en_passant, halfmove, fullmove] ->
issues = []
issues =
if valid_board?(board),
do: issues,
else: ["board must be 8 ranks of 8 squares each"]
issues =
if active in ["w", "b"],
do: issues,
else: ["active color must be 'w' or 'b'"]
issues =
if non_negative_integer?(halfmove),
do: issues,
else: ["halfmove clock must be a non-negative integer"]
issues =
if positive_integer?(fullmove),
do: issues,
else: ["fullmove number must be a positive integer"]
issues
_ ->
["must have 6 space-separated fields"]
end
end
defp valid_board?(board) do
ranks = String.split(board, "/")
length(ranks) == 8 and Enum.all?(ranks, &valid_rank?/1)
end
defp valid_rank?(rank) do
squares =
rank
|> String.graphemes()
|> Enum.reduce(0, fn
g, acc when byte_size(g) == 1 ->
c = :binary.first(g)
if c >= ?1 and c <= ?8 do
acc + (c - ?0)
else
acc + 1
end
end)
squares == 8
end
defp non_negative_integer?(str), do: integer_string?(str) and String.to_integer(str) >= 0
defp positive_integer?(str), do: integer_string?(str) and String.to_integer(str) >= 1
defp integer_string?(str) do
Regex.match?(~r/^[0-9]+$/, str)
end
end