Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin-seb/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanheath committed Jul 26, 2023
2 parents cf46df4 + fed725d commit 4b837c4
Show file tree
Hide file tree
Showing 15 changed files with 444 additions and 164 deletions.
18 changes: 18 additions & 0 deletions Chess-Challenge/src/API/BitboardHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

namespace ChessChallenge.API
{
using ChessChallenge.Application.APIHelpers;
using ChessChallenge.Chess;

/// <summary>
Expand Down Expand Up @@ -99,6 +100,23 @@ public static ulong GetPawnAttacks(Square square, bool isWhite)
{
return isWhite ? Bits.WhitePawnAttacks[square.Index] : Bits.BlackPawnAttacks[square.Index];
}

/// <summary>
/// A debug function for visualizing bitboards.
/// Highlights the squares that are set to 1 in the given bitboard with a red colour.
/// Highlights the squares that are set to 0 in the given bitboard with a blue colour.
/// </summary>
public static void VisualizeBitboard(ulong bitboard)
{
BitboardDebugState.BitboardDebugVisualizationRequested = true;
BitboardDebugState.BitboardToVisualize = bitboard;
}

/// <summary>
/// Clears the bitboard debug visualization
/// </summary>
public static void StopVisualizingBitboard() => BitboardDebugState.BitboardDebugVisualizationRequested = false;

static ulong GetRookAttacks(Square square, ulong blockers)
{
ulong mask = Magic.RookMask[square.Index];
Expand Down
122 changes: 96 additions & 26 deletions Chess-Challenge/src/API/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace ChessChallenge.API
using ChessChallenge.Chess;
using System;
using System.Collections.Generic;
using System.Linq;

public sealed class Board
{
Expand All @@ -24,9 +25,23 @@ public sealed class Board
/// Create a new board. Note: this should not be used in the challenge,
/// use the board provided in the Think method instead.
/// </summary>
public Board(Chess.Board board)
public Board(Chess.Board boardSource)
{
this.board = board;
// Clone board and create game move history
board = new Chess.Board();
board.LoadPosition(boardSource.StartPositionInfo);
GameMoveHistory = new Move[boardSource.AllGameMoves.Count];

for (int i = 0; i < boardSource.AllGameMoves.Count; i ++)
{
Chess.Move move = boardSource.AllGameMoves[i];
int movePieceType = PieceHelper.PieceType(board.Square[move.StartSquareIndex]);
int capturePieceType = move.IsEnPassant ? PieceHelper.Pawn : PieceHelper.PieceType(board.Square[move.TargetSquareIndex]);
GameMoveHistory[i] = new Move(move, movePieceType, capturePieceType);
board.MakeMove(move, false);
}

// Init move gen
moveGen = new APIMoveGen();
cachedLegalMoves = Array.Empty<Move>();
cachedLegalCaptureMoves = Array.Empty<Move>();
Expand All @@ -47,8 +62,9 @@ public Board(Chess.Board board)

// Init rep history
repetitionHistory = new HashSet<ulong>(board.RepetitionPositionHistory);
GameRepetitionHistory = repetitionHistory.ToArray();
repetitionHistory.Remove(board.ZobristKey);
}
}

/// <summary>
/// Updates the board state with the given move.
Expand Down Expand Up @@ -81,9 +97,10 @@ public void UndoMove(Move move)
}

/// <summary>
/// Try skip the current turn
/// This will fail and return false if in check
/// Note: skipping a turn is not allowed in the game, but it can be used as a search technique
/// Try skip the current turn.
/// This will fail and return false if in check.
/// Note: skipping a turn is not allowed in the game, but it can be used as a search technique.
/// Skipped turns can be undone with UndoSkipTurn()
/// </summary>
public bool TrySkipTurn()
{
Expand All @@ -97,10 +114,25 @@ public bool TrySkipTurn()
return true;
}

/// <summary>
/// Undo a turn that was succesfully skipped with the TrySkipTurn method
/// </summary>
public void UndoSkipTurn()
/// <summary>
/// Forcibly skips the current turn.
/// Unlike TrySkipTurn(), this will work even when in check, which has some dangerous side-effects if done:
/// 1) Generating 'legal' moves will now include the illegal capture of the king.
/// 2) If the skipped turn is undone, the board will now incorrectly report that the position is not check.
/// Note: skipping a turn is not allowed in the game, but it can be used as a search technique.
/// Skipped turns can be undone with UndoSkipTurn()
/// </summary>
public void ForceSkipTurn()
{
hasCachedMoves = false;
hasCachedCaptureMoves = false;
board.MakeNullMove();
}

/// <summary>
/// Undo a turn that was succesfully skipped with TrySkipTurn() or ForceSkipTurn()
/// </summary>
public void UndoSkipTurn()
{
hasCachedMoves = false;
hasCachedCaptureMoves = false;
Expand Down Expand Up @@ -164,25 +196,38 @@ Move[] GetLegalCaptureMoves()
/// </summary>
public bool IsInCheckmate() => IsInCheck() && GetLegalMoves().Length == 0;

/// <summary>
/// Test if the current position is a draw due stalemate,
/// 3-fold repetition, insufficient material, or 50-move rule.
/// </summary>
public bool IsDraw()
/// <summary>
/// Test if the current position is a draw due stalemate, repetition, insufficient material, or 50-move rule.
/// Note: this function will return true if the same position has occurred twice on the board (rather than 3 times,
/// which is when the game is actually drawn). This quirk is to help bots avoid repeating positions unnecessarily.
/// </summary>
public bool IsDraw()
{
return IsFiftyMoveDraw() || Arbiter.InsufficentMaterial(board) || IsInStalemate() || IsRepetition();
return IsFiftyMoveDraw() || IsInsufficientMaterial() || IsInStalemate() || IsRepeatedPosition();

bool IsInStalemate() => !IsInCheck() && GetLegalMoves().Length == 0;
bool IsFiftyMoveDraw() => board.currentGameState.fiftyMoveCounter >= 100;
bool IsRepetition() => repetitionHistory.Contains(board.ZobristKey);
}

/// <summary>
/// Does the given player still have the right to castle kingside?
/// Note that having the right to castle doesn't necessarily mean castling is legal right now
/// (for example, a piece might be in the way, or player might be in check, etc).
/// Test if the current position has occurred at least once before on the board.
/// This includes both positions in the actual game, and positions reached by
/// making moves while the bot is thinking.
/// </summary>
public bool IsRepeatedPosition() => repetitionHistory.Contains(board.ZobristKey);

/// <summary>
/// Test if there are sufficient pieces remaining on the board to potentially deliver checkmate.
/// If not, the game is automatically a draw.
/// </summary>
public bool HasKingsideCastleRight(bool white) => board.currentGameState.HasKingsideCastleRight(white);
public bool IsInsufficientMaterial() => Arbiter.InsufficentMaterial(board);

/// <summary>
/// Does the given player still have the right to castle kingside?
/// Note that having the right to castle doesn't necessarily mean castling is legal right now
/// (for example, a piece might be in the way, or player might be in check, etc).
/// </summary>
public bool HasKingsideCastleRight(bool white) => board.currentGameState.HasKingsideCastleRight(white);

/// <summary>
/// Does the given player still have the right to castle queenside?
Expand Down Expand Up @@ -246,11 +291,11 @@ public bool SquareIsAttackedByOpponent(Square square)
/// </summary>
public string GetFenString() => FenUtility.CurrentFen(board);

/// <summary>
/// 64-bit number where each bit that is set to 1 represents a
/// square that contains a piece of the given type and colour.
/// </summary>
public ulong GetPieceBitboard(PieceType pieceType, bool white)
/// <summary>
/// 64-bit number where each bit that is set to 1 represents a
/// square that contains a piece of the given type and colour.
/// </summary>
public ulong GetPieceBitboard(PieceType pieceType, bool white)
{
return board.pieceBitboards[PieceHelper.MakePiece((int)pieceType, white)];
}
Expand All @@ -277,11 +322,36 @@ public ulong GetPieceBitboard(PieceType pieceType, bool white)
/// </summary>
public int PlyCount => board.plyCount;

/// <summary>
/// Number of ply (a single move by either white or black) since the last pawn move or capture.
/// If this value reaches a hundred (meaning 50 full moves without a pawn move or capture), the game is drawn.
/// </summary>
public int FiftyMoveCounter => board.currentGameState.fiftyMoveCounter;

/// <summary>
/// 64-bit hash of the current position
/// </summary>
public ulong ZobristKey => board.ZobristKey;

/// <summary>
/// Zobrist keys for all the positions played in the game so far. This is reset whenever a
/// pawn move or capture is made, as previous positions are now impossible to reach again.
/// Note that this is not updated when your bot makes moves on the board while thinking,
/// but rather only when moves are actually played in the game.
/// </summary>
public ulong[] GameRepetitionHistory { get; private set; }

/// <summary>
/// FEN representation of the game's starting position.
/// </summary>
public string GameStartFenString => board.GameStartFen;

/// <summary>
/// All the moves played in the game so far.
/// This only includes moves played in the actual game, not moves made on the board while the bot is thinking.
/// </summary>
public Move[] GameMoveHistory { get; private set; }

/// <summary>
/// Creates a board from the given fen string. Please note that this is quite slow, and so it is advised
/// to use the board given in the Think function, and update it using MakeMove and UndoMove instead.
Expand Down
35 changes: 29 additions & 6 deletions Chess-Challenge/src/API/Timer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,45 @@ namespace ChessChallenge.API
public sealed class Timer
{
/// <summary>
/// Amount of time left on clock for current player (in milliseconds)
/// The amount of time (in milliseconds) that each player started the game with
/// </summary>
public int MillisecondsRemaining => Math.Max(0, initialMillisRemaining - (int)sw.ElapsedMilliseconds);
public readonly int GameStartTimeMilliseconds;

/// <summary>
/// Amount of time elapsed since current player started thinking (in milliseconds)
/// Amount of time elapsed since the current player started thinking (in milliseconds)
/// </summary>
public int MillisecondsElapsedThisTurn => (int)sw.ElapsedMilliseconds;

System.Diagnostics.Stopwatch sw;
readonly int initialMillisRemaining;
/// <summary>
/// Amount of time left on the clock for the current player (in milliseconds)
/// </summary>
public int MillisecondsRemaining => Math.Max(0, millisRemainingAtStartOfTurn - MillisecondsElapsedThisTurn);

/// <summary>
/// Amount of time left on the clock for the other player (in milliseconds)
/// </summary>
public readonly int OpponentMillisecondsRemaining;

readonly System.Diagnostics.Stopwatch sw;
readonly int millisRemainingAtStartOfTurn;

public Timer(int millisRemaining)
{
initialMillisRemaining = millisRemaining;
millisRemainingAtStartOfTurn = millisRemaining;
sw = System.Diagnostics.Stopwatch.StartNew();
}

public Timer(int millisRemaining, int opponentMillisRemaining, int startingTimeMillis)
{
millisRemainingAtStartOfTurn = millisRemaining;
sw = System.Diagnostics.Stopwatch.StartNew();
GameStartTimeMilliseconds = startingTimeMillis;
OpponentMillisecondsRemaining = opponentMillisRemaining;
}

public override string ToString()
{
return $"Game start time: {GameStartTimeMilliseconds} ms. Turn elapsed time: {MillisecondsElapsedThisTurn} ms. My time remaining: {MillisecondsRemaining} Opponent time remaining: {OpponentMillisecondsRemaining} ms.";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,10 @@ void BotThinkerThread()

Move GetBotMove()
{
// Board b = new Board();
// b.LoadPosition(FenUtility.CurrentFen(board));
API.Board botBoard = new(new(board));
API.Board botBoard = new(board);
try
{
API.Timer timer = new(PlayerToMove.TimeRemainingMs);
API.Timer timer = new(PlayerToMove.TimeRemainingMs, PlayerNotOnMove.TimeRemainingMs, GameDurationMilliseconds);
API.Move move = PlayerToMove.Bot.Think(botBoard, timer);
return new Move(move.RawValue);
}
Expand Down Expand Up @@ -416,6 +414,8 @@ public void StartNewBotMatch(PlayerType botTypeA, PlayerType botTypeB)


ChessPlayer PlayerToMove => board.IsWhiteToMove ? PlayerWhite : PlayerBlack;
ChessPlayer PlayerNotOnMove => board.IsWhiteToMove ? PlayerBlack : PlayerWhite;

public int TotalGameCount => botMatchStartFens.Length * 2;
public int CurrGameNumber => Math.Min(TotalGameCount, botMatchGameIndex + 1);
public string AllPGNs => pgns.ToString();
Expand Down
2 changes: 1 addition & 1 deletion Chess-Challenge/src/Framework/Application/Core/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace ChessChallenge.Application
{
public static class Settings
{
public const string Version = "1.13";
public const string Version = "1.15";

// Game settings
public const int GameDurationMilliseconds = 60 * 1000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ void Init()
API.Move CreateAPIMove(int startSquare, int targetSquare, int flag)
{
int movePieceType = PieceHelper.PieceType(board.Square[startSquare]);
int capturePieceType = PieceHelper.PieceType(board.Square[targetSquare]);
API.Move apiMove = new(new Move(startSquare, targetSquare, flag), movePieceType, capturePieceType);
return apiMove;
return CreateAPIMove(startSquare, targetSquare, flag, movePieceType);
}

API.Move CreateAPIMove(int startSquare, int targetSquare, int flag, int movePieceType)
{
int capturePieceType = PieceHelper.PieceType(board.Square[targetSquare]);
if (flag == Move.EnPassantCaptureFlag)
{
capturePieceType = PieceHelper.Pawn;
}
API.Move apiMove = new(new Move(startSquare, targetSquare, flag), movePieceType, capturePieceType);
return apiMove;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ChessChallenge.Application.APIHelpers
{
public static class BitboardDebugState
{
public static bool BitboardDebugVisualizationRequested { get; set; }
public static ulong BitboardToVisualize {get; set;}
}
}
Loading

0 comments on commit 4b837c4

Please sign in to comment.