ace/Source/ACE.Server/Entity/Chess/ChessLogic.cs
Mag-nus 12c138cb80
ThreadSafeRandom Cleanup (#2428)
This fixes the namespace for ThreadSafeRandom, from ACE -> ACE.Common.

It also adds better Summary descriptions.

The rest of the file changes are simply adding ACE.Common references.

This is mainly commenting and namespace preparation for adding a non-thread safe random class, in effort to reduce lock contention.
2019-11-21 07:22:20 -06:00

818 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ACE.Common;
using ACE.Entity;
using ACE.Entity.Enum;
namespace ACE.Server.Entity.Chess
{
// ported from Anahera's code
public class ChessLogic
{
public ChessColor Turn;
public uint Move;
public uint HalfMove;
public List<ChessMoveFlag> Castling = new List<ChessMoveFlag>()
{
{ ChessMoveFlag.KingSideCastle | ChessMoveFlag.QueenSideCastle },
{ ChessMoveFlag.KingSideCastle | ChessMoveFlag.QueenSideCastle },
};
public ChessPieceCoord EnPassantCoord;
public BasePiece[] Board = new BasePiece[Chess.BoardSize * Chess.BoardSize];
public Stack<ChessMove> History { get; set; }
public ChessLogic()
{
Turn = ChessColor.White;
// setup white
AddPiece(ChessColor.White, ChessPieceType.Rook, 0, 0);
AddPiece(ChessColor.White, ChessPieceType.Knight, 1, 0);
AddPiece(ChessColor.White, ChessPieceType.Bishop, 2, 0);
AddPiece(ChessColor.White, ChessPieceType.Queen, 3, 0);
AddPiece(ChessColor.White, ChessPieceType.King, 4, 0);
AddPiece(ChessColor.White, ChessPieceType.Bishop, 5, 0);
AddPiece(ChessColor.White, ChessPieceType.Knight, 6, 0);
AddPiece(ChessColor.White, ChessPieceType.Rook, 7, 0);
for (uint i = 0; i < Chess.BoardSize; i++)
AddPiece(ChessColor.White, ChessPieceType.Pawn, i, 1);
// setup black
AddPiece(ChessColor.Black, ChessPieceType.Rook, 0, 7);
AddPiece(ChessColor.Black, ChessPieceType.Knight, 1, 7);
AddPiece(ChessColor.Black, ChessPieceType.Bishop, 2, 7);
AddPiece(ChessColor.Black, ChessPieceType.Queen, 3, 7);
AddPiece(ChessColor.Black, ChessPieceType.King, 4, 7);
AddPiece(ChessColor.Black, ChessPieceType.Bishop, 5, 7);
AddPiece(ChessColor.Black, ChessPieceType.Knight, 6, 7);
AddPiece(ChessColor.Black, ChessPieceType.Rook, 7, 7);
for (uint i = 0; i < Chess.BoardSize; i++)
AddPiece(ChessColor.Black, ChessPieceType.Pawn, i, 6);
History = new Stack<ChessMove>();
}
public BasePiece AddPiece(ChessColor color, ChessPieceType type, uint x, uint y)
{
var to = new ChessPieceCoord((int)x, (int)y);
Debug.Assert(to.IsValid());
BasePiece piece = null;
switch (type)
{
case ChessPieceType.Pawn:
piece = new PawnPiece(color, to);
break;
case ChessPieceType.Rook:
piece = new RookPiece(color, to);
break;
case ChessPieceType.Knight:
piece = new KnightPiece(color, to);
break;
case ChessPieceType.Bishop:
piece = new BishopPiece(color, to);
break;
case ChessPieceType.Queen:
piece = new QueenPiece(color, to);
break;
case ChessPieceType.King:
piece = new KingPiece(color, to);
break;
default:
Debug.Assert(false);
break;
}
return Board[x + y * Chess.BoardSize] = piece;
}
public BasePiece AddPiece(ChessColor color, ChessPieceType type, ChessPieceCoord to)
{
return AddPiece(color, type, (uint)to.X, (uint)to.Y);
}
public BasePiece GetPiece(ChessPieceCoord from)
{
if (!from.IsValid())
return null;
return Board[from.X + from.Y * Chess.BoardSize];
}
public BasePiece GetPiece(ChessColor color, ChessPieceType type)
{
return Board.FirstOrDefault(i => i != null && i.Color == color && i.Type == type);
}
public BasePiece GetPiece(ObjectGuid guid)
{
return Board.FirstOrDefault(i => i != null && i.Guid == guid);
}
public void RemovePiece(ChessPieceCoord target)
{
var piece = GetPiece(target);
if (piece != null)
RemovePiece(piece);
}
public void RemovePiece(BasePiece piece)
{
Debug.Assert(piece != null);
var coord = piece.Coord;
Board[coord.Offset] = null;
}
public void MovePiece(ChessPieceCoord from, ChessPieceCoord to)
{
var fromPiece = GetPiece(from);
fromPiece.Coord = to;
RemovePiece(to);
Board[to.Offset] = Board[from.Offset];
Board[from.Offset] = null;
}
public void WalkPieces(Action<BasePiece> callback)
{
for (uint y = 0; y < Chess.BoardSize; y++)
{
for (uint x = 0; x < Chess.BoardSize; x++)
{
var piece = Board[x + y * Chess.BoardSize];
if (piece != null)
callback(piece);
}
}
}
public ChessMoveResult DoMove(ChessColor color, ChessPieceCoord from, ChessPieceCoord to)
{
if (!from.IsValid())
return ChessMoveResult.BadMoveDestination;
if (!to.IsValid())
return ChessMoveResult.BadMoveDestination;
if (Turn != color)
return ChessMoveResult.BadMoveNotYourTurn;
var fromPiece = GetPiece(from);
if (fromPiece == null)
return ChessMoveResult.BadMoveNoPiece;
if (fromPiece.Color != color)
return ChessMoveResult.BadMoveNotYours;
var storage = new List<ChessMove>();
GenerateMoves(fromPiece, true, storage);
ChessMove foundMove = null;
foreach (var move in storage)
{
if (move.From.Equals(from) && move.To.Equals(to))
{
foundMove = move;
break;
}
}
// if this fails the client and server failed to find a common valid move
if (foundMove == null)
//return ChessMoveResult.BadMoveDestination;
return ChessMoveResult.BadMoveInvalidCommand;
return FinalizeMove(foundMove);
}
public ChessMoveResult AsyncCalculateAiSimpleMove(ChessAiAsyncTurnKey key, ref ChessPieceCoord from, ref ChessPieceCoord to)
{
var color = Turn;
var bestBoardScore = 0.0f;
ChessMove bestMove = null;
var storage = new List<ChessMove>();
GenerateMoves(color, storage);
var nonCheckMoves = new List<ChessMove>();
foreach (var generatedMove in storage)
{
// no need to evaluate the board if the ai has checkmated the other player
var result = FinalizeMove(generatedMove);
if (result.HasFlag(ChessMoveResult.OKMoveCheckmate))
{
from = generatedMove.From;
to = generatedMove.To;
return result;
}
if (!InCheck(color))
{
var boardScore = EvaluateBoard();
if (boardScore > bestBoardScore)
{
bestMove = generatedMove;
bestBoardScore = boardScore;
}
nonCheckMoves.Add(generatedMove);
}
UndoMove(1);
}
// every generated move had the same board score, pick one at random
// this shouldn't happen, just here to prevent crash
if (bestMove == null && nonCheckMoves.Count > 0)
{
var rng = ThreadSafeRandom.Next(0, nonCheckMoves.Count - 1);
rng = 0; // easier debugging
bestMove = nonCheckMoves[rng];
}
// checkmate / stalemate
if (bestMove == null)
return ChessMoveResult.NoMoveResult;
from = bestMove.From;
to = bestMove.To;
return FinalizeMove(bestMove);
}
public ChessMoveResult AsyncCalculateAiComplexMove(ChessAiAsyncTurnKey key, ref ChessPieceCoord from, ref ChessPieceCoord to, ref uint counter)
{
uint depth = 3;
var isMaximizingPower = true;
var storage = new List<ChessMove>();
GenerateMoves(Turn, storage);
var bestBoardScore = -9999.0f;
ChessMove bestMove = null;
foreach (var generatedMove in storage)
{
// no need to evaluate the board if the ai has checkmated the other player
var result = FinalizeMove(generatedMove);
if (result.HasFlag(ChessMoveResult.OKMoveCheckmate))
{
from = generatedMove.From;
to = generatedMove.To;
return result;
}
var boardScore = MinimaxAlphaBeta(depth - 1, -10000, 10000, !isMaximizingPower, ref counter);
UndoMove(1);
if (boardScore >= bestBoardScore)
{
bestMove = generatedMove;
bestBoardScore = boardScore;
}
}
// ever generated move had the same board score, pick one at random
// this shouldn't happen, just here to prevent crash
if (bestMove == null && storage.Count > 0)
{
var rng = ThreadSafeRandom.Next(0, storage.Count - 1);
bestMove = storage[rng];
}
Debug.Assert(bestMove != null);
from = bestMove.From;
to = bestMove.To;
return FinalizeMove(bestMove);
}
public float MinimaxAlphaBeta(uint depth, float alpha, float beta, bool isMaximizingPower, ref uint counter)
{
counter++;
if (depth <= 0)
return -EvaluateBoard();
var storage = new List<ChessMove>();
GenerateMoves(Turn, storage);
if (isMaximizingPower)
{
var bestBoardScore = -9999.0f;
foreach (var move in storage)
{
FinalizeMove(move);
bestBoardScore = Math.Max(bestBoardScore, MinimaxAlphaBeta(depth - 1, alpha, beta, false, ref counter));
UndoMove(1);
alpha = Math.Max(alpha, bestBoardScore);
if (beta <= alpha)
return bestBoardScore;
}
return bestBoardScore;
}
else
{
var bestBoardScore = 9999.0f;
foreach (var move in storage)
{
FinalizeMove(move);
bestBoardScore = Math.Max(bestBoardScore, MinimaxAlphaBeta(depth - 1, alpha, beta, true, ref counter));
UndoMove(1);
beta = Math.Max(beta, bestBoardScore);
if (beta <= alpha)
return bestBoardScore;
}
return bestBoardScore;
}
}
public float EvaluateBoard()
{
var boardScore = 0.0f;
WalkPieces((piece) =>
{
// the knight and queen only have a single shared table
var tableColor = piece.Color;
if (piece.Type == ChessPieceType.Knight || piece.Type == ChessPieceType.Queen)
tableColor = ChessColor.White;
var value = 0.0f;
value += Chess.PieceSquareTable[(int)piece.Type, (int)tableColor, piece.Coord.Offset];
value += Chess.PieceWorth[(int)piece.Type];
boardScore += piece.Color == ChessColor.Black ? value : -value;
});
return boardScore;
}
public void GenerateMoves(BasePiece piece, bool single, List<ChessMove> storage)
{
var color = piece.Color;
if (piece.Type == ChessPieceType.Pawn)
{
// single
var from = new ChessPieceCoord(piece.Coord);
var to = new ChessPieceCoord(from);
to.MoveOffset(Chess.PawnOffsets[(int)color, 0]);
if (GetPiece(to) == null)
{
BuildMove(storage, ChessMoveFlag.Normal, color, piece.Type, from, to);
// second
to = new ChessPieceCoord(from);
to.MoveOffset(Chess.PawnOffsets[(int)color, 1]);
if (GetPiece(to) == null && from.Rank == (color == ChessColor.White ? 2 : 7))
BuildMove(storage, ChessMoveFlag.BigPawn, color, piece.Type, from, to);
}
// capture
for (uint i = 2; i < 4; i++)
{
to = new ChessPieceCoord(from);
to.MoveOffset(Chess.PawnOffsets[(int)color, i]);
if (!to.IsValid())
continue;
var toPiece = GetPiece(to);
if (toPiece != null && toPiece.Color != color)
BuildMove(storage, ChessMoveFlag.Capture, color, piece.Type, from, to);
else if (to.Equals(EnPassantCoord))
BuildMove(storage, ChessMoveFlag.EnPassantCapture, color, piece.Type, from, EnPassantCoord);
}
}
else
{
var range = Chess.PieceOffsets[piece.Type];
for (var i = 0; i < range.Count; i++)
{
var from = piece.Coord;
var to = new ChessPieceCoord(from);
while (true)
{
to.MoveOffset(range[i]);
if (!to.IsValid())
break;
var toPiece = GetPiece(to);
if (toPiece != null)
{
if (toPiece.Color != color)
BuildMove(storage, ChessMoveFlag.Capture, color, piece.Type, from, to);
break;
}
BuildMove(storage, ChessMoveFlag.Normal, color, piece.Type, from, to);
// Knights and Kings can't move more than once
if (piece.Type == ChessPieceType.Knight || piece.Type == ChessPieceType.King)
break;
}
}
}
// only check for castling during full board generation or for a single king
if (!single || piece.Type == ChessPieceType.King)
{
if ((Castling[(int)color] & ChessMoveFlag.KingSideCastle | ChessMoveFlag.QueenSideCastle) != 0)
{
var king = GetPiece(color, ChessPieceType.King);
var kingCoord = king.Coord;
var opColor = Chess.InverseColor(color);
if ((Castling[(int)color] & ChessMoveFlag.KingSideCastle) != 0)
{
var castlingToK = new ChessPieceCoord(kingCoord); // destination king
castlingToK.MoveOffset(2, 0);
var castlingToR = new ChessPieceCoord(kingCoord); // destination rook
castlingToR.MoveOffset(1, 0);
if (GetPiece(castlingToR) == null
&& GetPiece(castlingToK) == null
&& !CanAttack(opColor, kingCoord)
&& !CanAttack(opColor, castlingToR)
&& !CanAttack(opColor, castlingToK))
{
BuildMove(storage, ChessMoveFlag.KingSideCastle, color, ChessPieceType.King, kingCoord, castlingToK);
}
}
if ((Castling[(int)color] & ChessMoveFlag.QueenSideCastle) != 0)
{
var castlingToK = new ChessPieceCoord(kingCoord); // destination king
castlingToK.MoveOffset(-2, 0);
var castlingToR = new ChessPieceCoord(kingCoord); // destination rook
castlingToR.MoveOffset(-1, 0);
var castlingToI = new ChessPieceCoord(kingCoord); // intermediate
castlingToI.MoveOffset(-3, 0);
if (GetPiece(castlingToR) == null
&& GetPiece(castlingToK) == null
&& GetPiece(castlingToI) == null
&& !CanAttack(opColor, kingCoord)
&& !CanAttack(opColor, castlingToR)
&& !CanAttack(opColor, castlingToK))
{
BuildMove(storage, ChessMoveFlag.QueenSideCastle, color, ChessPieceType.King, kingCoord, castlingToK);
}
}
}
}
}
public void GenerateMoves(ChessColor color, List<ChessMove> storage)
{
WalkPieces((piece) =>
{
if (piece.Color != color)
return;
GenerateMoves(piece, false, storage);
});
}
public bool CanAttack(ChessColor attacker, ChessPieceCoord victim)
{
for (uint x = 0; x < Chess.BoardSize; x++)
{
for (uint y = 0; y < Chess.BoardSize; y++)
{
var piece = Board[x + y * Chess.BoardSize];
if (piece == null)
continue;
if (piece.Color != attacker)
continue;
if (piece.CanAttack(victim))
{
// the knight can jump over other pieces and the pawn can only attack a single space
if (piece.Type == ChessPieceType.Knight || piece.Type == ChessPieceType.Pawn)
return true;
var range = Chess.PieceOffsets[piece.Type];
for (var i = 0; i < range.Count; i++)
{
var from = piece.Coord;
var to = new ChessPieceCoord(from);
while (true)
{
to.MoveOffset(range[i]);
if (!to.IsValid())
break;
var toPiece = GetPiece(to);
if (toPiece != null)
{
if (to.Equals(victim))
return true;
break;
}
}
}
}
}
}
return false;
}
public bool InCheck(ChessColor color)
{
var king = GetPiece(color, ChessPieceType.King);
Debug.Assert(king != null);
return CanAttack(Chess.InverseColor(color), king.Coord);
}
public bool InCheckmate(ChessColor color, bool fullCheck = false)
{
var storage = new List<ChessMove>();
GenerateMoves(color, storage);
var hasMove = false;
if (fullCheck)
{
foreach (var generatedMove in storage)
{
var result = FinalizeMove(generatedMove);
if (!InCheck(color))
hasMove = true;
UndoMove(1);
if (hasMove)
break;
}
}
else
hasMove = storage.Count > 0;
return InCheck(color) && !hasMove;
}
public void BuildMove(List<ChessMove> storage, ChessMoveFlag result, ChessColor color, ChessPieceType type, ChessPieceCoord from, ChessPieceCoord to)
{
var fromPiece = GetPiece(from);
var toPiece = GetPiece(to);
// AC's Chess implementation doesn't support underpromotion
var promotion = ChessPieceType.Empty;
if (fromPiece.Type == ChessPieceType.Pawn && (to.Rank == 1 || to.Rank == 8))
{
promotion = ChessPieceType.Queen;
result |= ChessMoveFlag.Promotion;
}
var captured = ChessPieceType.Empty;
if (toPiece != null)
captured = toPiece.Type;
else if (result.HasFlag(ChessMoveFlag.EnPassantCapture))
{
captured = ChessPieceType.Pawn;
var enPassantCoord = new ChessPieceCoord(to);
enPassantCoord.MoveOffset(0, color == ChessColor.Black ? 1 : -1);
toPiece = GetPiece(enPassantCoord);
}
storage.Add(new ChessMove(result, color, type, from, to, promotion, captured, Move, HalfMove, Castling, EnPassantCoord, fromPiece.Guid, captured != ChessPieceType.Empty ? toPiece.Guid : new ObjectGuid(0)));
}
public ChessMoveResult FinalizeMove(ChessMove move)
{
InternalMove(move);
var result = (move.Flags & (ChessMoveFlag.Capture | ChessMoveFlag.EnPassantCapture)) != 0 ? ChessMoveResult.OKMoveToOccupiedSquare : ChessMoveResult.OKMoveToEmptySquare;
if (move.Flags.HasFlag(ChessMoveFlag.Promotion))
result |= ChessMoveResult.OKMovePromotion;
// win conditions
if (InCheck(Turn))
result |= ChessMoveResult.OKMoveCheck;
if (InCheckmate(Turn, false))
result |= ChessMoveResult.OKMoveCheckmate;
return result;
}
public void InternalMove(ChessMove move)
{
var flags = move.Flags;
var to = move.To;
var from = move.From;
var color = move.Color;
var opColor = Chess.InverseColor(color);
MovePiece(from, to);
if (flags.HasFlag(ChessMoveFlag.EnPassantCapture))
{
var enPassantCoord = new ChessPieceCoord(to);
enPassantCoord.MoveOffset(0, color == ChessColor.Black ? 1 : -1);
RemovePiece(enPassantCoord);
}
if (flags.HasFlag(ChessMoveFlag.Promotion))
{
var pawnPiece = GetPiece(to);
var guid = pawnPiece.Guid;
RemovePiece(pawnPiece);
var queenPiece = AddPiece(color, ChessPieceType.Queen, (uint)to.X, (uint)to.Y);
queenPiece.Guid = guid;
}
if (move.Type == ChessPieceType.King)
{
// if we castled, move the rook next to our king
if ((flags & (ChessMoveFlag.KingSideCastle | ChessMoveFlag.QueenSideCastle)) != 0)
{
var castlingTo = new ChessPieceCoord(move.To);
var castlingFrom = new ChessPieceCoord(castlingTo);
if (flags.HasFlag(ChessMoveFlag.KingSideCastle))
{
castlingTo.MoveOffset(-1, 0);
castlingFrom.MoveOffset(1, 0);
}
if (flags.HasFlag(ChessMoveFlag.QueenSideCastle))
{
castlingTo.MoveOffset(1, 0);
castlingFrom.MoveOffset(-2, 0);
}
MovePiece(castlingFrom, castlingTo);
}
// turn off castling, our king has moved
Castling[(int)color] = ChessMoveFlag.None;
}
// turn off castling if we have moved one of our rooks
if (Castling[(int)color] != ChessMoveFlag.None)
DoCastleCheck(color, from);
// turn off castling if we capture one of the opponent's rooks
if (Castling[(int)opColor] != ChessMoveFlag.None)
DoCastleCheck(opColor, from);
if (flags.HasFlag(ChessMoveFlag.BigPawn))
{
var enPassantCoord = new ChessPieceCoord(to);
//enPassantCoord.MoveOffset(0, color == ChessColor.Black ? 2 : -2);
enPassantCoord.MoveOffset(0, color == ChessColor.Black ? 1 : -1);
EnPassantCoord = enPassantCoord;
}
else
EnPassantCoord = null;
History.Push(move);
if (color == ChessColor.Black)
Move++;
// reset 50 move rule counter if a pawn is moved or a piece is captured
if (move.Type == ChessPieceType.Pawn
|| (flags & (ChessMoveFlag.Capture | ChessMoveFlag.EnPassantCapture)) != 0)
HalfMove = 0;
else
HalfMove++;
Turn = opColor;
}
public void UndoMove(uint count)
{
while (History.Count > 0 && count > 0)
{
var move = History.Peek();
// undo 'global' information
Turn = Chess.InverseColor(move.Color);
Castling = move.Castling;
EnPassantCoord = move.EnPassantCoord;
HalfMove = move.HalfMove;
Move = move.Move;
MovePiece(move.To, move.From);
var flags = move.Flags;
if (flags.HasFlag(ChessMoveFlag.Promotion))
{
var piece = AddPiece(Turn, ChessPieceType.Pawn, move.To);
piece.Guid = move.Guid;
}
if (flags.HasFlag(ChessMoveFlag.Capture))
{
var piece = AddPiece(Turn, move.Captured, move.To);
piece.Guid = move.CapturedGuid;
}
if (flags.HasFlag(ChessMoveFlag.EnPassantCapture))
{
var enPassantFrom = new ChessPieceCoord(move.To);
enPassantFrom.MoveOffset(0, move.Color == ChessColor.Black ? 1 : -1);
var piece = AddPiece(Turn, ChessPieceType.Pawn, enPassantFrom);
piece.Guid = move.CapturedGuid;
}
if ((flags & (ChessMoveFlag.KingSideCastle | ChessMoveFlag.QueenSideCastle)) != 0)
{
var castlingTo = new ChessPieceCoord(move.To);
var castlingFrom = new ChessPieceCoord(castlingTo);
if (flags.HasFlag(ChessMoveFlag.KingSideCastle))
{
castlingTo.MoveOffset(1, 0);
castlingFrom.MoveOffset(-1, 0);
}
if (flags.HasFlag(ChessMoveFlag.QueenSideCastle))
{
castlingTo.MoveOffset(-2, 0);
castlingFrom.MoveOffset(1, 0);
}
MovePiece(castlingTo, castlingFrom);
}
History.Pop();
count--;
}
}
public void DoCastleCheck(ChessColor color, ChessPieceCoord from)
{
var rookFlags = Chess.RookFlags[color];
foreach (var rookFlag in rookFlags)
{
if (from.X == rookFlag.Vector.X
&& from.Y == rookFlag.Vector.Y
&& (Castling[(int)color] & rookFlag.Flag) != 0)
{
Castling[(int)color] &= ~rookFlag.Flag;
break;
}
}
}
public ChessMove GetLastMove()
{
return History.Peek();
}
public void DebugBoard()
{
for (var y = 7; y >= 0; y--)
{
for (var x = 0; x <= 7; x++)
{
var piece = GetPiece(new ChessPieceCoord(x, y));
if (piece == null)
Console.Write(" ");
else if (piece is PawnPiece)
Console.Write("P");
else if (piece is KingPiece)
Console.Write("K");
else if (piece is QueenPiece)
Console.Write("Q");
else if (piece is BishopPiece)
Console.Write("B");
else if (piece is KnightPiece)
Console.Write("N");
else if (piece is RookPiece)
Console.Write("R");
}
Console.WriteLine();
}
}
}
}