Skip to content

Commit

Permalink
#17 Make it possible to use LucasChess.fns files or lines
Browse files Browse the repository at this point in the history
  • Loading branch information
Dumuzy committed Jan 16, 2023
1 parent 12607fa commit 01cdfb3
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 42 deletions.
145 changes: 125 additions & 20 deletions src/ChessSharp/ChessGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ public bool IsPromotionMove(Square? source, Square target)
public ChessGame(string fenOrLichessPuzzle)
{
if (fenOrLichessPuzzle.Contains(','))
// it's not a fen, it's a lichess puzzle
puzzle = new LichessPuzzle(fenOrLichessPuzzle);
// it's not a fen, it's a Lichess puzzle
puzzle = LichessPuzzle.Create(fenOrLichessPuzzle, PuzzleType.LichessPuzzle);
else if (fenOrLichessPuzzle.Contains('|'))
// it's not a fen, it's a LucasChess puzzle
puzzle = LichessPuzzle.Create(fenOrLichessPuzzle, PuzzleType.LucasChessPuzzle);
else
puzzle = new LichessPuzzle("," + fenOrLichessPuzzle + ",");
puzzle = LichessPuzzle.Create(fenOrLichessPuzzle, PuzzleType.FEN);

Moves = new Li<Move>();

Expand Down Expand Up @@ -251,7 +254,7 @@ internal bool PlayerWillBeInCheck(Move move)
ChessGame clone = DeepClone(); // Make the move on this board to keep original board as is.
Piece? piece = clone[move.Source];
clone.SetPiece(move.Source, null);
if (piece is Pawn)
if (piece is Pawn && clone.Moves.Any())
{
var lm = clone.Moves.Last();
// en passant handling
Expand Down Expand Up @@ -327,8 +330,8 @@ internal bool IsInsufficientMaterial() // TODO: Much allocations seem to happen
// King and bishop vs king and bishop
case 2 when blackPieces.Length == 2:
{
var whiteBishop = whitePieces.First(p => p.Piece is Bishop);
var blackBishop = blackPieces.First(p => p.Piece is Bishop);
var whiteBishop = whitePieces.FirstOrDefault(p => p.Piece is Bishop);
var blackBishop = blackPieces.FirstOrDefault(p => p.Piece is Bishop);
return whiteBishop != null && blackBishop != null &&
whiteBishop.SquareColor == blackBishop.SquareColor;
}
Expand Down Expand Up @@ -384,41 +387,143 @@ public ChessGame DeepClone()
protected LichessPuzzle puzzle;
}

public enum PuzzleType { FEN, LichessPuzzle, LucasChessPuzzle }

public class LichessPuzzle
{
public LichessPuzzle(string line)

public static LichessPuzzle Create(string line, PuzzleType lineType)
{
LichessPuzzle p;
if (lineType == PuzzleType.LichessPuzzle)
p = new LichessPuzzle(lineType, line);
else if (lineType == PuzzleType.FEN)
p = new LichessPuzzle(lineType, "," + line + ",");
else if (lineType == PuzzleType.LucasChessPuzzle)
{
var parts = line.Split('|');
p = new LichessPuzzle(lineType, "xxxxx," + parts[0] + "," + parts[2]);
}
else
throw new NotImplementedException();
return p;
}

private LichessPuzzle(PuzzleType lineType, string line)
{
this.PuzzleType = lineType;
var parts = line.Split(',');
Fen = parts[1];
var firstPlayer = Fen.Split()[1] == "w" ? Player.White : Player.Black;
SMoves = parts[2].Split().ToLiro();
SMoves = parts[2].SplitToWords().Where(sm => !sm.IsContainedIn("-+ +- 1-0 0-1".SplitToWords())).ToLiro();
Moves = new Li<Move>();
for (int i = 0; i < SMoves.Count; ++i)
Moves.Add(CreateMoveFromSMove(SMoves[i], firstPlayer, i));
Motifs = parts[7].SplitToWords().ToLiro();

if (SMoves != null && SMoves.Any())
{
ChessGame game = new ChessGame(Fen);
for (int i = 0; i < SMoves.Count; ++i)
Moves.Add(CreateMoveFromSMove(game, SMoves[i], firstPlayer, i));
}
if (parts.Length >= 8)
Motifs = parts[7].SplitToWords().ToLiro();
else
Motifs = new Liro<string>();
}
private static Move CreateMoveFromSMove(string sMove, Player firstPlayer, int nMove)

static readonly Liro<char> piecesChars = "N B R Q K S L T D".Split().Select(s => s[0]).ToLiro();
static readonly Liro<char> filesChars = "a b c d e f g h".Split().Select(s => s[0]).ToLiro();

private static Move CreateMoveFromSMove(ChessGame game, string sMove, Player firstPlayer, int nMove)
{
sMove = sMove.Replace(" ", "");
var from = Square.Create(sMove.Substring(0, 2));
var to = Square.Create(sMove.Substring(2, 2));
var player = nMove % 2 == 0 ? firstPlayer : ChessUtilities.Opponent(firstPlayer);
PawnPromotion? promoteTo = sMove.Length > 4 ? PawnPromotionHelper.Get(sMove[4]) : (PawnPromotion?)null;
var m = new Move(from, to, player, promoteTo);
var origMove = sMove;
sMove = sMove.Replace(" ", "").Replace("x", "").Replace("#", "").Replace("+", "")
.TrimStart("1234567890.".ToCharArray());
Square from, to;
PawnPromotion? promoteTo = (PawnPromotion?)null;
Move m = null;
if (sMove[0].IsContainedIn(piecesChars) || sMove.Length <= 3)
{
// Default. Die letzten 2 Zeichen sind das Zielfeld.
string toString = sMove.Substring(sMove.Length - 2, 2);
Li<Move> possMoves = null;
if (sMove.Length == 3 && sMove[0].IsContainedIn(piecesChars))
{
// Normaler Figurenzug oder Schlagzug
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game, Square.Create(toString),
ChessUtilities.PieceTypeFromChar(sMove[0]));
}
else if (sMove.StartsWith("-0"))
{
// Rochade, das erste 0 wurde oben getrimmt.
if (sMove == "-0")
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game,
game.WhoseTurn == Player.White ? Square.Create("g1") : Square.Create("g8"), typeof(King));
else if (sMove == "-0-0")
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game,
game.WhoseTurn == Player.White ? Square.Create("c1") : Square.Create("c8"), typeof(King));
}
else if (sMove.Length == 2 && !sMove[1].IsContainedIn(filesChars))
{
// Bauernzug 1 oder 2 vor
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game, Square.Create(toString), typeof(Pawn));
}
else if (sMove.Length == 2 && sMove[1].IsContainedIn(filesChars))
{
// Bauernzugschlagzug wie cd oder fe;
throw new NotImplementedException();
// var possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game, Square.Create(toString), typeof(Pawn));
}
else if (sMove[sMove.Length - 1].IsContainedIn(piecesChars))
{
// Bauernumwandlungszug
possMoves = possMoves.Where(m => m.Source.File == Parser.ParseFile(sMove[0])).ToLi();
}
else if (sMove.Length == 3 && sMove[0].IsContainedIn(filesChars))
{
// Bauernschlagzug
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game, Square.Create(toString), typeof(Pawn));
possMoves = possMoves.Where(m => m.Source.File == Parser.ParseFile(sMove[0])).ToLi();
}
else if (sMove.Length == 4 && sMove[0].IsContainedIn(piecesChars))
{
//Figurenzug wie Sbd2 oder The1 o.ä.
possMoves = ChessUtilities.GetValidMovesOfTargetSquare(game, Square.Create(toString),
ChessUtilities.PieceTypeFromChar(sMove[0]));
if (sMove[1].IsContainedIn(filesChars))
possMoves = possMoves.Where(m => m.Source.File == Parser.ParseFile(sMove[1])).ToLi();
else
possMoves = possMoves.Where(m => m.Source.Rank == Parser.ParseRank(sMove[1])).ToLi();
}
if (possMoves == null || possMoves.Count != 1)
throw new Exception("Cannot parse move:" + origMove);
m = possMoves[0];
}
else
{
from = Square.Create(sMove.Substring(0, 2));
to = Square.Create(sMove.Substring(2, 2));
promoteTo = sMove.Length > 4 ? PawnPromotionHelper.Get(sMove[4]) :
(PawnPromotion?)null;
var player = nMove % 2 == 0 ? firstPlayer : ChessUtilities.Opponent(firstPlayer);
m = new Move(from, to, player, promoteTo);
}
if (game != null)
game.MakeMove(m, true);
return m;
}

public readonly string Fen;
public readonly Liro<string> SMoves;
public readonly Li<Move> Moves;
public readonly Liro<string> Motifs;
public readonly PuzzleType PuzzleType;
}

public class PuzzleGame : ChessGame
{
public PuzzleGame(string fenOrLichessPuzzle) : base(fenOrLichessPuzzle)
{
if (puzzle.Moves != null && !puzzle.Moves.IsEmpty)
if (puzzle.Moves != null && !puzzle.Moves.IsEmpty && puzzle.PuzzleType != PuzzleType.LucasChessPuzzle)
MakeMove(CurrMove);
}

Expand Down Expand Up @@ -468,7 +573,7 @@ private void MakeMove(Action<Square, Square> formMakeMove, Move? tryMove)
{
// Lichess allows alternate solutions in mating puzzles as last move.
// Therefore, not always i CurrMove the move to make.
if(tryMove != null)
if (tryMove != null)
formMakeMove(tryMove.Source, tryMove.Destination);
else
formMakeMove(CurrMove.Source, CurrMove.Destination);
Expand Down
2 changes: 1 addition & 1 deletion src/ChessSharp/ChessSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Version>1.1.0</Version>
<Authors>Youssef Victor</Authors>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AwiUtils\AwiUtils.csproj" />
Expand Down
42 changes: 33 additions & 9 deletions src/ChessSharp/ChessUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ChessSharp.Pieces;
using AwiUtils;
using ChessSharp.Pieces;
using ChessSharp.SquareData;
using System;
using System.Collections.Generic;
Expand All @@ -23,16 +24,15 @@ from rank in Enum.GetValues(typeof(Rank)).Cast<Rank>()
/// <summary>Gets the valid moves of the given <see cref="ChessGame"/>.</summary>
/// <param name="board">The <see cref="ChessGame"/> that you want to get its valid moves.</param>
/// <returns>Returns a list of the valid moves.</returns>
public static List<Move> GetValidMoves(ChessGame board)
public static Li<Move> GetValidMoves(ChessGame board)
{
// Although nullable is enabled and board is non-nullable ref type, this check is needed
// because this is a public method that can be used by an application that doesn't have
// nullable enabled.
_ = board ?? throw new ArgumentNullException(nameof(board));


Player player = board.WhoseTurn;
var validMoves = new List<Move>();
var validMoves = new Li<Move>();

IEnumerable<Square> playerOwnedSquares = s_allSquares.Where(sq => board[sq.File, sq.Rank]?.Owner == player);
Square[] nonPlayerOwnedSquares = s_allSquares.Where(sq => board[sq.File, sq.Rank]?.Owner != player).ToArray(); // Converting to array to avoid "Possible multiple enumeration" as suggested by ReSharper.
Expand All @@ -43,7 +43,6 @@ public static List<Move> GetValidMoves(ChessGame board)
.Select(nonPlayerOwnedSquare => new Move(playerOwnedSquare, nonPlayerOwnedSquare, player))
.Where(move => ChessGame.IsValidMove(move, board)));
}

return validMoves;
}

Expand All @@ -52,17 +51,15 @@ public static List<Move> GetValidMoves(ChessGame board)
/// <param name="board">The <see cref="ChessGame"/> that you want to get its valid moves from the specified square.</param>
/// <returns>Returns a list of the valid moves that has the given source square.</returns>
///
public static List<Move> GetValidMovesOfSourceSquare(Square source, ChessGame board)
public static Li<Move> GetValidMovesOfSourceSquare(ChessGame board, Square source)
{
if (board == null || source == null)
throw new ArgumentNullException(nameof(board) + " or " + nameof(source));

var validMoves = new List<Move>();
var validMoves = new Li<Move>();
Piece? piece = board[source.File, source.Rank];
if (piece == null || piece.Owner != board.WhoseTurn)
{
return validMoves;
}

Player player = piece.Owner;
Square[] nonPlayerOwnedSquares = s_allSquares.Where(sq => board[sq.File, sq.Rank]?.Owner != player).ToArray();
Expand All @@ -73,6 +70,33 @@ public static List<Move> GetValidMovesOfSourceSquare(Square source, ChessGame bo
return validMoves;
}

public static Li<Move> GetValidMovesOfTargetSquare(ChessGame board, Square target, Type pieceType)
{
if (board == null || target == null)
throw new ArgumentNullException(nameof(board) + " or " + nameof(target));

var validMoves = GetValidMoves(board);
validMoves = validMoves.Where(m => m.Destination == target && board[m.Source]?.Owner == board.WhoseTurn
&& board[m.Source]?.GetType() == pieceType).ToLi();
return validMoves;
}

public static Type PieceTypeFromChar(char c)
{
switch (c)
{
case 'K': return typeof(King);
case 'D':
case 'Q': return typeof(Queen);
case 'T':
case 'R': return typeof(Rook);
case 'L':
case 'B': return typeof(Bishop);
case 'S':
case 'N': return typeof(Knight);
}
throw new NotImplementedException();
}

internal static bool IsPlayerInCheck(Player player, ChessGame board)
{
Expand Down
9 changes: 2 additions & 7 deletions src/ChessSharp/SquareData/Square.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,19 @@ namespace ChessSharp.SquareData
{
internal class Parser
{
private static Linie ParseFile(char file)
public static Linie ParseFile(char file)
{
// Culture doesn't really matter here, but to silence CA1304
file = char.ToUpper(file, CultureInfo.InvariantCulture);
if (file < 'A' || file > 'H')
{
throw new ArgumentOutOfRangeException(nameof(file));
}

return (Linie)(file - 'A');
}

private static Rank ParseRank(char rank)
public static Rank ParseRank(char rank)
{
if (rank < '1' || rank > '8')
{
throw new ArgumentOutOfRangeException(nameof(rank));
}
return (Rank)(rank - '1');
}

Expand Down
6 changes: 3 additions & 3 deletions src/ChessUI/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class Form1 : Form
private readonly Label[] _squareLabels;
private readonly Liro<Label> _sideLabels;
private Square? _selectedSourceSquare;
private PuzzleGame _gameBoard = new PuzzleGame("00143,r2q1rk1/5ppp/1np5/p1b5/2p1B3/P7/1P3PPP/R1BQ1RK1 b - - 1 17,d8f6 d1h5 h7h6 h5c5,1871,75,93,790,advantage middlegame short,https://lichess.org/jcuxlI63/black#34");
private PuzzleGame _gameBoard = null; // = new PuzzleGame("00143,r2q1rk1/5ppp/1np5/p1b5/2p1B3/P7/1P3PPP/R1BQ1RK1 b - - 1 17,d8f6 d1h5 h7h6 h5c5,1871,75,93,790,advantage middlegame short,https://lichess.org/jcuxlI63/black#34");
private PuzzleSet _puzzleSet;
string _currPuzzleSetName, iniDonated;
bool _isCurrPuzzleFinishedOk, shallIgnoreResizeEvent = true;
Expand Down Expand Up @@ -357,7 +357,7 @@ private void SquaresLabels_Click(Label selectedLabel, EventArgs e)
if ((selectedLabel.Tag as SquareTag).PieceCol != _gameBoard.WhoseTurn)
return;
_selectedSourceSquare = (selectedLabel.Tag as SquareTag).Square;
var validDestinations = ChessUtilities.GetValidMovesOfSourceSquare(_selectedSourceSquare.Value, _gameBoard).Select(m => m.Destination).ToArray();
var validDestinations = ChessUtilities.GetValidMovesOfSourceSquare(_gameBoard, _selectedSourceSquare.Value).Select(m => m.Destination).ToArray();
if (validDestinations.Length == 0)
{
_selectedSourceSquare = null;
Expand All @@ -369,7 +369,7 @@ private void SquaresLabels_Click(Label selectedLabel, EventArgs e)
{
// Second click
var targetSquare = (selectedLabel.Tag as SquareTag).Square;
var validDestinations = ChessUtilities.GetValidMovesOfSourceSquare(_selectedSourceSquare.Value, _gameBoard).
var validDestinations = ChessUtilities.GetValidMovesOfSourceSquare(_gameBoard, _selectedSourceSquare.Value).
Select(m => m.Destination).ToLi();
if (validDestinations.Contains(targetSquare))
{
Expand Down
7 changes: 5 additions & 2 deletions src/ChessUI/PuzzleSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using static System.Windows.Forms.LinkLabel;
using System.Windows.Forms;
using System.Net;
using System.Reflection.Metadata.Ecma335;

namespace PuzzlePecker
{
Expand Down Expand Up @@ -299,14 +300,16 @@ public Puzzle(string sLichessPuzzle)

public int NumTriedTotal { get; private set; }

public string LichessId => SLichessPuzzle.Split(',', 2)[0];
public string LichessId => SPuzzle.Split(',', 2)[0];

public string Rating => SLichessPuzzle.Split(',')[3];
public string Rating => SPuzzle.Split(',')[3];

public override string ToString() => $"TT={NumTriedInRound} C={NumCorrect} E= P={SLichessPuzzle}";

public string ToDbString() => $"TR={NumTriedInRound};C={NumCorrect};TT={NumTriedTotal};P={SLichessPuzzle}";

private string SPuzzle => SLichessPuzzle.Split(',').Length >= 4 ? SLichessPuzzle : ",,,,,";

public void Read(string dbString)
{
var parts = dbString.Split(';');
Expand Down

0 comments on commit 01cdfb3

Please sign in to comment.