2019-07-07 20:56:15 +00:00
|
|
|
#include <StdAfx.h>
|
2018-08-12 13:53:40 +12:00
|
|
|
#include "ChessMatch.h"
|
|
|
|
|
#include "Player.h"
|
|
|
|
|
#include "World.h"
|
|
|
|
|
#include "WeenieFactory.h"
|
|
|
|
|
#include "MathLib.h"
|
|
|
|
|
#include "RandomRange.h"
|
|
|
|
|
#include "GamePiece.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <iterator>
|
|
|
|
|
|
|
|
|
|
namespace GDLE::Chess
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
DEFINE_PACK(ChessPieceCoord)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
pWriter->Write<int32_t>(m_x);
|
|
|
|
|
pWriter->Write<int32_t>(m_y);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEFINE_UNPACK(ChessPieceCoord)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
m_x = pReader->Read<int32_t>();
|
|
|
|
|
m_y = pReader->Read<int32_t>();
|
|
|
|
|
return true;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEFINE_PACK(GameMoveData)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
pWriter->Write<ChessMoveType>(m_type);
|
|
|
|
|
pWriter->Write<ChessColour>(m_colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// there are other types to handle but client just ignores them
|
|
|
|
|
switch (m_type)
|
|
|
|
|
{
|
|
|
|
|
case MoveTypeFromTo:
|
|
|
|
|
{
|
|
|
|
|
m_from.Pack(pWriter);
|
|
|
|
|
m_to.Pack(pWriter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEFINE_UNPACK(GameMoveData)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return true;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BasePiece::CanAttackRaycast(ChessPieceCoord const& victim) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
int8_t const dx = m_coord.GetX() - victim.GetX();
|
|
|
|
|
int8_t const dy = m_coord.GetY() - victim.GetY();
|
|
|
|
|
return CanAttackRaycast(dx, dy);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PawnPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
bool const hasMoved = (m_colour ? Rank2 : Rank7) != dy;
|
|
|
|
|
uint8_t const ady = abs(dy);
|
|
|
|
|
return !dx && (ady == 1 || ady == 2 && !hasMoved);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PawnPiece::CanAttackRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return abs(dx) == 1 && dy == 1;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RookPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return (dy != 0) ^ (dx != 0);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool KnightPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
uint8_t const adx = abs(dx);
|
|
|
|
|
uint8_t const ady = abs(dy);
|
|
|
|
|
return adx == 1 && ady == 2 || adx == 2 && ady == 1;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BishopPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return abs(dy) == abs(dx);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QueenPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return !dx || !dy || abs(dx) == abs(dy);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool KingPiece::CanGoToRaycast(int8_t const dx, int8_t const dy) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return abs(dx) < 2 && abs(dy) < 2;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// executed in a seperate worker thread
|
|
|
|
|
ChessAiMoveResult ChessAiMove::operator()(ChessMatch* match) const
|
|
|
|
|
{
|
2019-03-08 03:38:46 +00:00
|
|
|
//using namespace std::chrono;
|
|
|
|
|
time_point const start = steady_clock::now();
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessAiMoveResult result;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
#ifdef DEBUG_SIMPLE_AI
|
|
|
|
|
match->AsyncMoveAiSimple({}, result);
|
|
|
|
|
#else
|
|
|
|
|
match->AsyncMoveAiComplex({}, result);
|
|
|
|
|
#endif
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2019-03-08 03:38:46 +00:00
|
|
|
time_point const end = steady_clock::now();
|
|
|
|
|
result.SetProfilingTime(std::chrono::duration_cast<milliseconds>(end - start).count());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
return result;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessLogic::ChessLogic()
|
2018-08-18 01:42:53 +12:00
|
|
|
: m_turn(White), m_move(), m_halfMove()
|
|
|
|
|
{
|
|
|
|
|
// setup white
|
|
|
|
|
AddPiece(White, Rook, 0, 0);
|
|
|
|
|
AddPiece(White, Knight, 1, 0);
|
|
|
|
|
AddPiece(White, Bishop, 2, 0);
|
|
|
|
|
AddPiece(White, Queen, 3, 0);
|
|
|
|
|
AddPiece(White, King, 4, 0);
|
|
|
|
|
AddPiece(White, Bishop, 5, 0);
|
|
|
|
|
AddPiece(White, Knight, 6, 0);
|
|
|
|
|
AddPiece(White, Rook, 7, 0);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < CHESS_BOARD_SIZE; i++)
|
|
|
|
|
AddPiece(White, Pawn, i, 1);
|
|
|
|
|
|
|
|
|
|
// setup black
|
|
|
|
|
AddPiece(Black, Rook, 0, 7);
|
|
|
|
|
AddPiece(Black, Knight, 1, 7);
|
|
|
|
|
AddPiece(Black, Bishop, 2, 7);
|
2018-09-07 18:13:17 -05:00
|
|
|
AddPiece(Black, King, 4, 7);
|
|
|
|
|
AddPiece(Black, Queen, 3, 7);
|
2018-08-18 01:42:53 +12:00
|
|
|
AddPiece(Black, Bishop, 5, 7);
|
|
|
|
|
AddPiece(Black, Knight, 6, 7);
|
|
|
|
|
AddPiece(Black, Rook, 7, 7);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < CHESS_BOARD_SIZE; i++)
|
|
|
|
|
AddPiece(Black, Pawn, i, 6);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessLogic::~ChessLogic()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
WalkPieces([](BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
delete piece;
|
|
|
|
|
});
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BasePiece* ChessLogic::AddPiece(ChessColour const colour, ChessPieceType const type, uint8_t const x, uint8_t const y)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessPieceCoord to(x, y);
|
|
|
|
|
assert(to.IsValid());
|
|
|
|
|
|
|
|
|
|
BasePiece* piece = nullptr;
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Pawn:
|
|
|
|
|
piece = new PawnPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
case Rook:
|
|
|
|
|
piece = new RookPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
case Knight:
|
|
|
|
|
piece = new KnightPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
case Bishop:
|
|
|
|
|
piece = new BishopPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
case Queen:
|
|
|
|
|
piece = new QueenPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
case King:
|
|
|
|
|
piece = new KingPiece(colour, to);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
assert(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_board[(y * CHESS_BOARD_SIZE) + x] = piece;
|
|
|
|
|
return piece;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BasePiece* ChessLogic::GetPiece(ChessColour colour, ChessPieceType type) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
auto const itr = std::find_if(std::begin(m_board), std::end(m_board),
|
|
|
|
|
[colour, type](BasePiece* piece) { return piece && piece->GetColour() == colour && piece->GetType() == type; });
|
|
|
|
|
return itr != std::end(m_board) ? *itr : nullptr;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-18 22:19:15 +12:00
|
|
|
BasePiece* ChessLogic::GetPiece(uint32_t const pieceGuid) const
|
|
|
|
|
{
|
|
|
|
|
auto const itr = std::find_if(std::begin(m_board), std::end(m_board),
|
|
|
|
|
[pieceGuid](BasePiece* piece) { return piece && piece->GetGuid() == pieceGuid; });
|
|
|
|
|
return itr != std::end(m_board) ? *itr : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-12 13:53:40 +12:00
|
|
|
void ChessLogic::RemovePiece(ChessPieceCoord const& victim)
|
|
|
|
|
{
|
2018-08-18 22:19:15 +12:00
|
|
|
if (BasePiece* piece = GetPiece(victim))
|
|
|
|
|
RemovePiece(piece);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::RemovePiece(BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
assert(piece);
|
|
|
|
|
|
|
|
|
|
ChessPieceCoord const coord = piece->GetCoord();
|
|
|
|
|
delete piece;
|
|
|
|
|
m_board[coord.GetOffset()] = nullptr;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::MovePiece(ChessPieceCoord const& from, ChessPieceCoord const& to)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BasePiece* fromPiece = GetPiece(from);
|
|
|
|
|
fromPiece->SetCoord(to);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
RemovePiece(to);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_board[to.GetOffset()] = m_board[from.GetOffset()];
|
|
|
|
|
m_board[from.GetOffset()] = nullptr;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::WalkPieces(std::function<void(BasePiece*)> const& func) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
for (uint8_t y = 0; y < CHESS_BOARD_SIZE; y++)
|
|
|
|
|
for (uint8_t x = 0; x < CHESS_BOARD_SIZE; x++)
|
|
|
|
|
if (BasePiece* piece = m_board[(y * CHESS_BOARD_SIZE) + x])
|
|
|
|
|
func(piece);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessMoveResult ChessLogic::Move(ChessColour const colour, ChessPieceCoord const& from, ChessPieceCoord const& to)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (!from.IsValid())
|
|
|
|
|
return BadMoveDestination;
|
|
|
|
|
if (!to.IsValid())
|
|
|
|
|
return BadMoveDestination;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_turn != colour)
|
|
|
|
|
return BadMoveNotYourTurn;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
BasePiece* fromPiece = GetPiece(from);
|
|
|
|
|
if (!fromPiece)
|
|
|
|
|
return BadMoveNoPiece;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (fromPiece->GetColour() != colour)
|
|
|
|
|
return BadMoveNotYours;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessMoveStore storage;
|
|
|
|
|
GenerateMoves(fromPiece, true, storage);
|
|
|
|
|
|
|
|
|
|
auto const itr = std::find_if(std::begin(storage), std::end(storage),
|
|
|
|
|
[from, to](ChessMove& move) { return move.GetFromCoord() == from && move.GetToCoord() == to; });
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// if this fails the client and server failed to find a common valid move
|
|
|
|
|
if (itr == std::end(storage))
|
|
|
|
|
return BadMoveDestination;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
return FinaliseMove(*itr);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessMoveResult ChessLogic::AsyncCalculateAiSimpleMove(ChessAiAsyncTurnKey, ChessPieceCoord& from, ChessPieceCoord& to)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessColour const colour = m_turn;
|
|
|
|
|
|
|
|
|
|
float bestBoardScore = 0;
|
|
|
|
|
std::optional<ChessMove> bestMove;
|
|
|
|
|
|
|
|
|
|
ChessMoveStore storage;
|
|
|
|
|
GenerateMoves(m_turn, storage);
|
|
|
|
|
|
|
|
|
|
for (ChessMove const& generatedMove : storage)
|
|
|
|
|
{
|
|
|
|
|
// no need to evaluate the board if the ai has checkmated the other player
|
|
|
|
|
ChessMoveResult const result = FinaliseMove(generatedMove);
|
|
|
|
|
if (result & OKMoveCheckmate)
|
|
|
|
|
{
|
|
|
|
|
from = generatedMove.GetFromCoord();
|
|
|
|
|
to = generatedMove.GetToCoord();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float const boardScore = EvaluateBoard();
|
|
|
|
|
if (boardScore > bestBoardScore)
|
|
|
|
|
{
|
|
|
|
|
bestMove.emplace(generatedMove);
|
|
|
|
|
bestBoardScore = boardScore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.has_value() && !storage.empty())
|
|
|
|
|
{
|
|
|
|
|
ChessMoveStore::iterator itr = std::begin(storage);
|
|
|
|
|
std::advance(itr, getRandomNumber(storage.size() - 1));
|
|
|
|
|
bestMove.emplace(*itr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(bestMove.has_value());
|
|
|
|
|
from = bestMove->GetFromCoord();
|
|
|
|
|
to = bestMove->GetToCoord();
|
|
|
|
|
return FinaliseMove(*bestMove);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessMoveResult ChessLogic::AsyncCalculateAiComplexMove(ChessAiAsyncTurnKey, ChessPieceCoord& from, ChessPieceCoord& to, uint32_t& counter)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
uint32_t const depth = 3;
|
|
|
|
|
bool const isMaximisingPlayer = true;
|
|
|
|
|
|
|
|
|
|
ChessMoveStore storage;
|
|
|
|
|
GenerateMoves(m_turn, storage);
|
|
|
|
|
|
|
|
|
|
float bestBoardScore = -9999;
|
|
|
|
|
std::optional<ChessMove> m_bestMove;
|
|
|
|
|
|
|
|
|
|
for (ChessMove const& generatedMove : storage)
|
|
|
|
|
{
|
|
|
|
|
// no need to evaluate the board if the ai has checkmated the other player
|
|
|
|
|
ChessMoveResult const result = FinaliseMove(generatedMove);
|
|
|
|
|
if (result & OKMoveCheckmate)
|
|
|
|
|
{
|
|
|
|
|
from = generatedMove.GetFromCoord();
|
|
|
|
|
to = generatedMove.GetToCoord();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float const boardScore = MinimaxAlphaBeta(depth - 1, -10000, 10000, !isMaximisingPlayer, counter);
|
|
|
|
|
UndoMove(1);
|
|
|
|
|
|
|
|
|
|
if (boardScore >= bestBoardScore)
|
|
|
|
|
{
|
|
|
|
|
m_bestMove.emplace(generatedMove);
|
|
|
|
|
bestBoardScore = boardScore;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// every generated move had the same board score, pick one at random
|
|
|
|
|
// this shouldn't happen, just here to prevent crash
|
|
|
|
|
if (!m_bestMove.has_value() && !storage.empty())
|
|
|
|
|
{
|
|
|
|
|
ChessMoveStore::iterator itr = std::begin(storage);
|
|
|
|
|
std::advance(itr, getRandomNumber(storage.size() - 1));
|
|
|
|
|
m_bestMove.emplace(*itr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(m_bestMove.has_value());
|
|
|
|
|
from = m_bestMove->GetFromCoord();
|
|
|
|
|
to = m_bestMove->GetToCoord();
|
|
|
|
|
return FinaliseMove(*m_bestMove);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float ChessLogic::MinimaxAlphaBeta(uint8_t const depth, float alpha, float beta, bool const isMaximisingPlayer, uint32_t& counter)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
counter++;
|
|
|
|
|
if (!depth)
|
|
|
|
|
return -EvaluateBoard();
|
|
|
|
|
|
|
|
|
|
ChessMoveStore storage;
|
|
|
|
|
GenerateMoves(m_turn, storage);
|
|
|
|
|
|
|
|
|
|
if (isMaximisingPlayer)
|
|
|
|
|
{
|
|
|
|
|
float bestBoardScore = -9999.f;
|
|
|
|
|
for (ChessMove const& move : storage)
|
|
|
|
|
{
|
|
|
|
|
FinaliseMove(move);
|
|
|
|
|
bestBoardScore = max(bestBoardScore, MinimaxAlphaBeta(depth - 1, alpha, beta, false, counter));
|
|
|
|
|
UndoMove(1);
|
|
|
|
|
|
|
|
|
|
alpha = max(alpha, bestBoardScore);
|
|
|
|
|
if (beta <= alpha)
|
|
|
|
|
return bestBoardScore;
|
|
|
|
|
}
|
|
|
|
|
return bestBoardScore;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
float bestBoardScore = 9999.f;
|
|
|
|
|
for (ChessMove const& move : storage)
|
|
|
|
|
{
|
|
|
|
|
FinaliseMove(move);
|
|
|
|
|
bestBoardScore = max(bestBoardScore, MinimaxAlphaBeta(depth - 1, alpha, beta, true, counter));
|
|
|
|
|
UndoMove(1);
|
|
|
|
|
|
|
|
|
|
beta = max(beta, bestBoardScore);
|
|
|
|
|
if (beta <= alpha)
|
|
|
|
|
return bestBoardScore;
|
|
|
|
|
}
|
|
|
|
|
return bestBoardScore;
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float ChessLogic::EvaluateBoard() const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
float boardScore = 0.f;
|
|
|
|
|
WalkPieces([&boardScore](BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
// the knight and queen only have a single shared table
|
|
|
|
|
ChessColour tableColour = piece->GetColour();
|
|
|
|
|
if (piece->GetType() == Knight || piece->GetType() == Queen)
|
|
|
|
|
tableColour = White;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
float value = 0.f;
|
|
|
|
|
value += PieceSquareTable[piece->GetType()][tableColour][piece->GetCoord().GetOffset()];
|
|
|
|
|
value += PieceWorth[piece->GetType()];
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
boardScore += piece->GetColour() ? value : -value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return boardScore;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::GenerateMoves(BasePiece* piece, bool const single, ChessMoveStore& storage) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessColour const colour = piece->GetColour();
|
|
|
|
|
if (piece->GetType() == Pawn)
|
|
|
|
|
{
|
|
|
|
|
// single
|
|
|
|
|
ChessPieceCoord const& from = piece->GetCoord();
|
|
|
|
|
ChessPieceCoord to = from;
|
|
|
|
|
to.MoveOffset(PawnOffsets[colour][0]);
|
|
|
|
|
|
|
|
|
|
if (!GetPiece(to))
|
|
|
|
|
{
|
|
|
|
|
BuildMove(storage, ChessMoveFlagNormal, colour, piece->GetType(), from, to);
|
|
|
|
|
|
|
|
|
|
// second
|
|
|
|
|
to = from;
|
|
|
|
|
to.MoveOffset(PawnOffsets[colour][1]);
|
|
|
|
|
|
|
|
|
|
if (!GetPiece(to) && (colour ? Rank2 : Rank7) == from.GetRank())
|
|
|
|
|
BuildMove(storage, ChessMoveFlagBigPawn, colour, piece->GetType(), from, to);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// capture
|
|
|
|
|
for (uint8_t i = 2; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
to = from;
|
|
|
|
|
to.MoveOffset(PawnOffsets[colour][i]);
|
|
|
|
|
if (!to.IsValid())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
BasePiece* toPiece = GetPiece(to);
|
|
|
|
|
if (toPiece && toPiece->GetColour() != colour)
|
|
|
|
|
BuildMove(storage, ChessMoveFlagCapture, colour, piece->GetType(), from, to);
|
|
|
|
|
else if (to == m_enPassantCoord)
|
|
|
|
|
BuildMove(storage, ChessMoveFlagEnPassantCapture, colour, piece->GetType(), from, *m_enPassantCoord);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto const range = PieceOffsets.equal_range(piece->GetType());
|
|
|
|
|
for (auto i = range.first; i != range.second; ++i)
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord const& from = piece->GetCoord();
|
|
|
|
|
ChessPieceCoord to = from;
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
to.MoveOffset((*i).second);
|
|
|
|
|
if (!to.IsValid())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (BasePiece* toPiece = GetPiece(to))
|
|
|
|
|
{
|
|
|
|
|
if (toPiece->GetColour() != colour)
|
|
|
|
|
BuildMove(storage, ChessMoveFlagCapture, colour, piece->GetType(), from, to);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuildMove(storage, ChessMoveFlagNormal, colour, piece->GetType(), from, to);
|
|
|
|
|
|
|
|
|
|
// Knights and Kings can't move more than once
|
|
|
|
|
if (piece->GetType() == Knight || piece->GetType() == King)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only check for castling during full board generation or for a single king
|
|
|
|
|
if (!single || piece->GetType() == King)
|
|
|
|
|
{
|
|
|
|
|
if (m_castling[colour] & (ChessMoveFlagKingSideCastle | ChessMoveFlagQueenSideCastle))
|
|
|
|
|
{
|
|
|
|
|
BasePiece* king = GetPiece(colour, King);
|
|
|
|
|
ChessPieceCoord const& kingCoord = king->GetCoord();
|
|
|
|
|
|
|
|
|
|
ChessColour const opColour = InverseColour(colour);
|
|
|
|
|
|
|
|
|
|
if (m_castling[colour] & ChessMoveFlagKingSideCastle)
|
|
|
|
|
{
|
2018-08-18 18:11:38 +12:00
|
|
|
ChessPieceCoord castlingToK = kingCoord; // destination king
|
|
|
|
|
castlingToK.MoveOffset(2, 0);
|
|
|
|
|
ChessPieceCoord castlingToR = kingCoord; // destination rook
|
|
|
|
|
castlingToR.MoveOffset(1, 0);
|
2018-08-18 01:42:53 +12:00
|
|
|
|
2018-08-18 18:11:38 +12:00
|
|
|
if (!GetPiece(castlingToR)
|
|
|
|
|
&& !GetPiece(castlingToK)
|
2018-08-18 01:42:53 +12:00
|
|
|
&& !CanAttack(opColour, kingCoord)
|
2018-08-18 18:11:38 +12:00
|
|
|
&& !CanAttack(opColour, castlingToR)
|
|
|
|
|
&& !CanAttack(opColour, castlingToK))
|
|
|
|
|
BuildMove(storage, ChessMoveFlagKingSideCastle, colour, King, kingCoord, castlingToK);
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_castling[colour] & ChessMoveFlagQueenSideCastle)
|
|
|
|
|
{
|
2018-08-18 18:11:38 +12:00
|
|
|
ChessPieceCoord castlingToK = kingCoord; // destination king
|
|
|
|
|
castlingToK.MoveOffset(-2, 0);
|
|
|
|
|
ChessPieceCoord castlingToR = kingCoord; // destination rook
|
|
|
|
|
castlingToR.MoveOffset(-1, 0);
|
|
|
|
|
ChessPieceCoord castlingToI = kingCoord; // intermediate
|
|
|
|
|
castlingToI.MoveOffset(-3, 0);
|
|
|
|
|
|
|
|
|
|
if (!GetPiece(castlingToR)
|
|
|
|
|
&& !GetPiece(castlingToK)
|
|
|
|
|
&& !GetPiece(castlingToI)
|
2018-08-18 01:42:53 +12:00
|
|
|
&& !CanAttack(opColour, kingCoord)
|
2018-08-18 18:11:38 +12:00
|
|
|
&& !CanAttack(opColour, castlingToR)
|
|
|
|
|
&& !CanAttack(opColour, castlingToK))
|
|
|
|
|
BuildMove(storage, ChessMoveFlagQueenSideCastle, colour, King, kingCoord, castlingToK);
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::GenerateMoves(ChessColour const colour, ChessMoveStore& storage) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
WalkPieces([this, colour, &storage](BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
if (piece->GetColour() != colour)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
GenerateMoves(piece, false, storage);
|
|
|
|
|
});
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ChessLogic::CanAttack(ChessColour const attacker, ChessPieceCoord const& victim) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
for (uint8_t x = 0; x < CHESS_BOARD_SIZE; x++)
|
|
|
|
|
{
|
|
|
|
|
for (uint8_t y = 0; y < CHESS_BOARD_SIZE; y++)
|
|
|
|
|
{
|
|
|
|
|
BasePiece* piece = m_board[(y * CHESS_BOARD_SIZE) + x];
|
|
|
|
|
if (!piece)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (piece->GetColour() != attacker)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (piece->CanAttackRaycast(victim))
|
|
|
|
|
{
|
|
|
|
|
// the knight can jump over other pieces and the pawn can only attack a single space
|
|
|
|
|
if (piece->GetType() == Knight || piece->GetType() == Pawn)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
auto const range = PieceOffsets.equal_range(piece->GetType());
|
|
|
|
|
for (auto i = range.first; i != range.second; ++i)
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord const& from = piece->GetCoord();
|
|
|
|
|
ChessPieceCoord to = from;
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
to.MoveOffset((*i).second);
|
|
|
|
|
if (!to.IsValid())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (GetPiece(to))
|
|
|
|
|
{
|
|
|
|
|
if (to == victim)
|
|
|
|
|
return true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ChessLogic::InCheck() const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BasePiece* king = GetPiece(m_turn, King);
|
|
|
|
|
assert(king);
|
|
|
|
|
return CanAttack(InverseColour(m_turn), king->GetCoord());
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ChessLogic::InCheckmate() const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessMoveStore storage;
|
2018-08-18 22:23:21 +12:00
|
|
|
GenerateMoves(m_turn, storage);
|
2018-08-18 01:42:53 +12:00
|
|
|
return InCheck() && storage.empty();
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::BuildMove(ChessMoveStore& storage, uint32_t result, ChessColour const colour,
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessPieceType const type, ChessPieceCoord const& from, ChessPieceCoord const& to) const
|
2018-08-12 13:53:40 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BasePiece* fromPiece = GetPiece(from);
|
|
|
|
|
BasePiece* toPiece = GetPiece(to);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// AC's Chess implementation doesn't support underpromotion
|
|
|
|
|
ChessPieceType promotion = Empty;
|
|
|
|
|
if (fromPiece->GetType() == Pawn
|
|
|
|
|
&& (to.GetRank() == Rank8 || to.GetRank() == Rank1))
|
2018-08-18 22:19:15 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
promotion = Queen;
|
|
|
|
|
result |= ChessMoveFlagPromotion;
|
2018-08-18 22:19:15 +12:00
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessPieceType captured = Empty;
|
|
|
|
|
if (toPiece)
|
|
|
|
|
captured = toPiece->GetType();
|
|
|
|
|
else if (result & ChessMoveFlagEnPassantCapture)
|
|
|
|
|
captured = Pawn;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
storage.emplace_back(static_cast<ChessMoveFlag>(result), colour, type, from, to, promotion,
|
|
|
|
|
captured, m_move, m_halfMove, m_castling, m_enPassantCoord, fromPiece->GetGuid(),
|
|
|
|
|
captured ? toPiece->GetGuid() : 0);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessMoveResult ChessLogic::FinaliseMove(ChessMove const& move)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
InternalMove(move);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
uint32_t result = move.GetFlags() & (ChessMoveFlagCapture | ChessMoveFlagEnPassantCapture) ? OKMoveToOccupiedSquare : OKMoveToEmptySquare;
|
|
|
|
|
if (move.GetFlags() & ChessMoveFlagPromotion)
|
|
|
|
|
result |= OKMovePromotion;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// win conditions
|
|
|
|
|
if (InCheck())
|
|
|
|
|
result |= OKMoveCheck;
|
|
|
|
|
if (InCheckmate())
|
|
|
|
|
result |= OKMoveCheckmate;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
return static_cast<ChessMoveResult>(result);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::InternalMove(ChessMove const& move)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessMoveFlag const flags = move.GetFlags();
|
|
|
|
|
ChessPieceCoord const to = move.GetToCoord();
|
|
|
|
|
ChessPieceCoord const from = move.GetFromCoord();
|
|
|
|
|
ChessColour const colour = move.GetColour();
|
|
|
|
|
ChessColour const opColour = InverseColour(colour);
|
|
|
|
|
|
|
|
|
|
MovePiece(from, to);
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagEnPassantCapture)
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord enPassantCoord = to;
|
|
|
|
|
enPassantCoord.MoveOffset(0, colour ? 2 : -2);
|
|
|
|
|
RemovePiece(enPassantCoord);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagPromotion)
|
|
|
|
|
{
|
2018-08-18 22:19:15 +12:00
|
|
|
BasePiece* pawnPiece = GetPiece(to);
|
|
|
|
|
uint32_t const guid = pawnPiece->GetGuid();
|
|
|
|
|
|
|
|
|
|
RemovePiece(pawnPiece);
|
|
|
|
|
|
|
|
|
|
BasePiece* queenPiece = AddPiece(colour, Queen, to.GetX(), to.GetY());
|
|
|
|
|
queenPiece->SetGuid(guid);
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (move.GetType() == King)
|
|
|
|
|
{
|
|
|
|
|
// if we castled, move the rook next to our king
|
|
|
|
|
if (flags & (ChessMoveFlagKingSideCastle | ChessMoveFlagQueenSideCastle))
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord castlingTo = move.GetToCoord();
|
|
|
|
|
ChessPieceCoord castlingFrom = castlingTo;
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagKingSideCastle)
|
|
|
|
|
{
|
|
|
|
|
castlingTo.MoveOffset(-1, 0);
|
|
|
|
|
castlingFrom.MoveOffset(1, 0);
|
|
|
|
|
}
|
|
|
|
|
if (flags & ChessMoveFlagQueenSideCastle)
|
|
|
|
|
{
|
|
|
|
|
castlingTo.MoveOffset(1, 0);
|
|
|
|
|
castlingFrom.MoveOffset(-2, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-18 18:11:38 +12:00
|
|
|
MovePiece(castlingFrom, castlingTo);
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// turn off castling, our king has moved
|
|
|
|
|
m_castling[colour] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::function<void(ChessColour)> const doCastleCheck = [this, from](ChessColour const colour)
|
|
|
|
|
{
|
|
|
|
|
auto const range = RookFlags.equal_range(colour);
|
|
|
|
|
for (auto i = range.first; i != range.second; ++i)
|
|
|
|
|
{
|
|
|
|
|
RookCastleFlag const rookCastleFlag = (*i).second;
|
|
|
|
|
if (from.GetX() == rookCastleFlag.GetVector().first
|
|
|
|
|
&& from.GetY() == rookCastleFlag.GetVector().second
|
|
|
|
|
&& m_castling[colour] & rookCastleFlag.GetFlag())
|
|
|
|
|
{
|
|
|
|
|
m_castling[colour] &= ~rookCastleFlag.GetFlag();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// turn off castling if we have move one of our rooks
|
|
|
|
|
if (m_castling[colour])
|
|
|
|
|
doCastleCheck(colour);
|
|
|
|
|
|
|
|
|
|
// turn off castling if we capture one of the opponents rooks
|
|
|
|
|
if (m_castling[opColour])
|
|
|
|
|
doCastleCheck(opColour);
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagBigPawn)
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord enPassantCoord = to;
|
|
|
|
|
enPassantCoord.MoveOffset(0, colour ? 2 : -2);
|
|
|
|
|
m_enPassantCoord = enPassantCoord;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
m_enPassantCoord.reset();
|
|
|
|
|
|
|
|
|
|
m_history.push(move);
|
|
|
|
|
|
|
|
|
|
if (colour == Black)
|
|
|
|
|
m_move++;
|
|
|
|
|
|
|
|
|
|
// reset 50 move rule counter if a pawn is moved or a piece is captured
|
|
|
|
|
if (move.GetType() == Pawn
|
|
|
|
|
|| flags & (ChessMoveFlagCapture | ChessMoveFlagEnPassantCapture))
|
|
|
|
|
m_halfMove = 0;
|
|
|
|
|
else
|
|
|
|
|
m_halfMove++;
|
|
|
|
|
|
|
|
|
|
m_turn = opColour;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessLogic::UndoMove(uint32_t count)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
while (!m_history.empty() && count)
|
|
|
|
|
{
|
|
|
|
|
ChessMove const& move = m_history.top();
|
|
|
|
|
|
|
|
|
|
// undo 'global' information
|
|
|
|
|
m_turn = InverseColour(move.GetColour());
|
|
|
|
|
m_castling = move.GetCastling();
|
|
|
|
|
m_enPassantCoord = move.GetEnPassantCoord();
|
|
|
|
|
m_halfMove = move.GetHalfMove();
|
|
|
|
|
m_move = move.GetMove();
|
|
|
|
|
|
|
|
|
|
MovePiece(move.GetToCoord(), move.GetFromCoord());
|
|
|
|
|
|
|
|
|
|
ChessMoveFlag const flags = move.GetFlags();
|
|
|
|
|
if (flags & ChessMoveFlagPromotion)
|
|
|
|
|
{
|
|
|
|
|
BasePiece* piece = AddPiece(m_turn, Pawn, move.GetToCoord());
|
|
|
|
|
piece->SetGuid(move.GetGuid());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagCapture)
|
|
|
|
|
{
|
|
|
|
|
BasePiece* piece = AddPiece(m_turn, move.GetCapture(), move.GetToCoord());
|
|
|
|
|
piece->SetGuid(move.GetGuid());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & (ChessMoveFlagKingSideCastle | ChessMoveFlagQueenSideCastle))
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord castlingTo = move.GetToCoord();
|
|
|
|
|
ChessPieceCoord castlingFrom = castlingTo;
|
|
|
|
|
|
|
|
|
|
if (flags & ChessMoveFlagKingSideCastle)
|
|
|
|
|
{
|
|
|
|
|
castlingTo.MoveOffset(1, 0);
|
|
|
|
|
castlingFrom.MoveOffset(-1, 0);
|
|
|
|
|
}
|
|
|
|
|
if (flags & ChessMoveFlagQueenSideCastle)
|
|
|
|
|
{
|
|
|
|
|
castlingTo.MoveOffset(-2, 0);
|
|
|
|
|
castlingFrom.MoveOffset(1, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MovePiece(castlingTo, castlingFrom);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_history.pop();
|
|
|
|
|
count--;
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CPlayerWeenie* ChessSide::GetPlayer() const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return g_pWorld->FindPlayer(m_guid);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChessMatch::ChessMatch(CWeenieObject* game)
|
2018-08-18 01:42:53 +12:00
|
|
|
: m_guid(game->GetID()), m_position(game->GetPosition()), m_state(), m_aiState(), m_moveResult(), m_waitingForWeenieMotion() { }
|
2018-08-12 13:53:40 +12:00
|
|
|
|
|
|
|
|
ChessMatch::~ChessMatch()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
for (ChessSide* side : m_side)
|
|
|
|
|
delete side;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ChessMatch::IsInMatch(uint32_t const guid) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
return GetColour(guid).has_value();
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<ChessColour> ChessMatch::GetColour(uint32_t const guid) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
for (ChessSide* side : m_side)
|
|
|
|
|
if (side && side->GetGuid() == guid)
|
|
|
|
|
return side->GetColour();
|
|
|
|
|
return std::nullopt;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::Update()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
switch (m_state)
|
|
|
|
|
{
|
|
|
|
|
case ChessStateInProgress:
|
|
|
|
|
{
|
|
|
|
|
switch (m_aiState)
|
|
|
|
|
{
|
|
|
|
|
case ChessAiStateWaitingToStart:
|
|
|
|
|
StartAiMove();
|
|
|
|
|
break;
|
|
|
|
|
case ChessAiStateWaitingForFinish:
|
|
|
|
|
FinishAIMove();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't handle any delayed actions while ai is working to prevent races
|
|
|
|
|
if (m_aiState != ChessAiStateNone)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// don't handle any delayed actions while weenie pieces are moving or attacking
|
|
|
|
|
if (m_waitingForWeenieMotion)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
while (!m_actions.empty())
|
|
|
|
|
{
|
|
|
|
|
ChessDelayedAction action = m_actions.front();
|
|
|
|
|
m_actions.pop();
|
|
|
|
|
|
|
|
|
|
switch (action.GetAction())
|
|
|
|
|
{
|
|
|
|
|
case DelayedActionTypeStart:
|
|
|
|
|
Start();
|
|
|
|
|
break;
|
|
|
|
|
case DelayedActionTypeMove:
|
|
|
|
|
MoveDelayed(action);
|
|
|
|
|
break;
|
|
|
|
|
case DelayedActionTypeMovePass:
|
|
|
|
|
MovePassDelayed(action);
|
|
|
|
|
break;
|
|
|
|
|
case DelayedActionTypeStalemate:
|
|
|
|
|
StalemateDelayed(action);
|
|
|
|
|
break;
|
|
|
|
|
case DelayedActionTypeQuit:
|
|
|
|
|
QuitDelayed(action.GetColour());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_nextRangeCheck)
|
|
|
|
|
{
|
2019-03-08 03:38:46 +00:00
|
|
|
time_point const now = steady_clock::now();
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_nextRangeCheck <= now)
|
|
|
|
|
{
|
|
|
|
|
for (ChessSide* side : m_side)
|
|
|
|
|
{
|
|
|
|
|
if (!side)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (side->IsAi())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
CPlayerWeenie* player = side->GetPlayer();
|
|
|
|
|
assert(player);
|
|
|
|
|
|
|
|
|
|
// arbitrary distance, should there be some warning before reaching leash range?
|
|
|
|
|
float const distanceToGame = m_position.distance(player->GetPosition());
|
|
|
|
|
if (abs(distanceToGame) > 40.f)
|
2018-08-18 18:15:35 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
QuitDelayed(side->GetColour());
|
2018-08-18 18:15:35 +12:00
|
|
|
return;
|
|
|
|
|
}
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_nextRangeCheck = now + seconds(5);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AddSide(uint32_t guid, ChessColour const colour)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(colour <= Black);
|
|
|
|
|
m_side[colour] = new ChessSide(guid, colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// spawn weenie pieces in the world for side
|
|
|
|
|
m_logic.WalkPieces([this, colour](BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
if (piece->GetColour() == colour)
|
|
|
|
|
AddWeeniePiece(piece);
|
|
|
|
|
});
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_side[InverseColour(colour)])
|
|
|
|
|
m_actions.emplace(DelayedActionTypeStart);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AddAi()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_state != ChessStateWaitingForPlayers)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> const colour = GetFreeColour();
|
|
|
|
|
if (!colour.has_value())
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
AddSide(0, *colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::Join(CPlayerWeenie* player)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> colour = GetFreeColour();
|
|
|
|
|
if (colour.has_value())
|
|
|
|
|
{
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
if (!m_nextRangeCheck)
|
|
|
|
|
m_nextRangeCheck = steady_clock::now() + seconds(5);
|
2018-08-18 00:41:26 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
AddSide(player->GetID(), *colour);
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
SendJoinGameResponse(player, m_guid, colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::MoveEnqueue(CPlayerWeenie* player, ChessPieceCoord const& from, ChessPieceCoord const& to)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_state != ChessStateInProgress)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> colour = GetColour(player->GetID());
|
|
|
|
|
assert(colour.has_value());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_actions.emplace(DelayedActionTypeMove, *colour, from, to);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::MovePassEnqueue(CPlayerWeenie* player)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_state != ChessStateInProgress)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> const colour = GetColour(player->GetID());
|
|
|
|
|
assert(colour.has_value());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_actions.emplace(DelayedActionTypeMovePass, *colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::QuitEnqueue(CPlayerWeenie* player)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_state != ChessStateWaitingForPlayers
|
|
|
|
|
&& m_state != ChessStateInProgress)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> const colour = GetColour(player->GetID());
|
|
|
|
|
assert(colour.has_value());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_actions.emplace(DelayedActionTypeQuit, *colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::StalemateEnqueue(CPlayerWeenie* player, bool const on)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_state != ChessStateInProgress)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
std::optional<ChessColour> const colour = GetColour(player->GetID());
|
|
|
|
|
assert(colour.has_value());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_actions.emplace(DelayedActionTypeStalemate, *colour);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AsyncMoveAiSimple(ChessAiAsyncTurnKey, ChessAiMoveResult& result)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_aiState == ChessAiStateWaitingForWorker);
|
|
|
|
|
m_aiState = ChessAiStateInProgress;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessPieceCoord from, to;
|
|
|
|
|
ChessMoveResult const moveResult = m_logic.AsyncCalculateAiSimpleMove({}, from, to);
|
|
|
|
|
result.SetResult(moveResult, from, to);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_aiState = ChessAiStateWaitingForFinish;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AsyncMoveAiComplex(ChessAiAsyncTurnKey, ChessAiMoveResult& result)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_aiState == ChessAiStateWaitingForWorker);
|
|
|
|
|
m_aiState = ChessAiStateInProgress;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessPieceCoord from, to;
|
|
|
|
|
uint32_t counter = 0;
|
|
|
|
|
ChessMoveResult const moveResult = m_logic.AsyncCalculateAiComplexMove({}, from, to, counter);
|
|
|
|
|
result.SetResult(moveResult, from, to);
|
|
|
|
|
result.SetProfilingCounter(counter);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_aiState = ChessAiStateWaitingForFinish;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-16 01:21:13 +12:00
|
|
|
void ChessMatch::PieceReady(uint32_t const pieceGuid)
|
|
|
|
|
{
|
2018-08-18 22:19:15 +12:00
|
|
|
if ((m_moveResult & OKMovePromotion) != 0)
|
|
|
|
|
{
|
|
|
|
|
BasePiece* piece = m_logic.GetPiece(pieceGuid);
|
|
|
|
|
assert(piece);
|
|
|
|
|
UpgradeWeeniePiece(piece);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_weenieMotion.erase(pieceGuid);
|
|
|
|
|
if (m_weenieMotion.empty())
|
|
|
|
|
{
|
|
|
|
|
m_waitingForWeenieMotion = false;
|
|
|
|
|
FinishTurn();
|
|
|
|
|
}
|
2018-08-16 01:21:13 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-12 13:53:40 +12:00
|
|
|
void ChessMatch::SendJoinGameResponse(CPlayerWeenie* player, uint32_t guid, std::optional<ChessColour> colour)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter joinGameResponse;
|
|
|
|
|
joinGameResponse.Write<uint32_t>(0x0281);
|
|
|
|
|
joinGameResponse.Write<uint32_t>(guid);
|
|
|
|
|
joinGameResponse.Write<int32_t>(colour.value_or(static_cast<ChessColour>(CHESS_COLOUR_INVALID)));
|
|
|
|
|
player->SendNetMessage(joinGameResponse.GetData(), joinGameResponse.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<ChessColour> ChessMatch::GetFreeColour() const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
for (uint8_t i = 0; i < CHESS_COLOUR_COUNT; i++)
|
|
|
|
|
if (!m_side[i])
|
|
|
|
|
return static_cast<ChessColour>(i);
|
|
|
|
|
return std::nullopt;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::Start()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_state == ChessStateWaitingForPlayers);
|
|
|
|
|
m_state = ChessStateInProgress;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
for (ChessSide* side : m_side)
|
|
|
|
|
{
|
|
|
|
|
if (side->IsAi())
|
|
|
|
|
continue;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
CPlayerWeenie* player = side->GetPlayer();
|
|
|
|
|
assert(player);
|
2018-09-01 17:19:59 +12:00
|
|
|
SendStartGame(player, m_logic.GetTurn());
|
2018-08-18 01:42:53 +12:00
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::Finish(int32_t const winner)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_state == ChessStateWaitingForPlayers || m_state == ChessStateInProgress);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
for (ChessSide* side : m_side)
|
|
|
|
|
{
|
|
|
|
|
if (!side)
|
|
|
|
|
continue;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (side->IsAi())
|
|
|
|
|
continue;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
CPlayerWeenie* player = side->GetPlayer();
|
|
|
|
|
assert(player);
|
2018-08-18 00:41:26 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
SendGameOver(player, winner);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (winner != CHESS_WINNER_END_GAME)
|
|
|
|
|
{
|
|
|
|
|
int32_t const total = player->m_Qualities.GetInt(CHESS_TOTALGAMES_INT, 0);
|
|
|
|
|
player->m_Qualities.SetInt(CHESS_TOTALGAMES_INT, total + 1);
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (winner)
|
|
|
|
|
{
|
|
|
|
|
ChessColour const winnerColour = static_cast<ChessColour>(winner);
|
|
|
|
|
if (winnerColour == side->GetColour())
|
|
|
|
|
{
|
|
|
|
|
int32_t const won = player->m_Qualities.GetInt(CHESS_GAMESWON_INT, 0);
|
|
|
|
|
player->m_Qualities.SetInt(CHESS_GAMESLOST_INT, won + 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int32_t const lost = player->m_Qualities.GetInt(CHESS_GAMESLOST_INT, 0);
|
|
|
|
|
player->m_Qualities.SetInt(CHESS_GAMESLOST_INT, lost + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
while (!m_actions.empty())
|
|
|
|
|
m_actions.pop();
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_logic.WalkPieces([this](BasePiece* piece)
|
|
|
|
|
{
|
|
|
|
|
RemoveWeeniePiece(piece);
|
|
|
|
|
});
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_state = ChessStateFinished;
|
|
|
|
|
m_nextRangeCheck.reset();
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::FinishTurn()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (ChessSide* side = m_side[InverseColour(m_logic.GetTurn())])
|
|
|
|
|
if (!side->IsAi())
|
|
|
|
|
SendMoveResponse(side->GetPlayer(), m_moveResult);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (ChessSide* side = m_side[m_logic.GetTurn()])
|
|
|
|
|
{
|
|
|
|
|
if (side->IsAi())
|
|
|
|
|
m_aiState = ChessAiStateWaitingToStart;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ChessMove const& move = m_logic.GetLastMove();
|
|
|
|
|
GameMoveData data(MoveTypeFromTo, move.GetColour(), move.GetFromCoord(), move.GetToCoord());
|
|
|
|
|
SendOpponentTurn(side->GetPlayer(), move.GetColour(), data);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::StartAiMove()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_aiState == ChessAiStateWaitingToStart);
|
|
|
|
|
m_aiState = ChessAiStateWaitingForWorker;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// execute ai work on a seperate thread
|
|
|
|
|
ChessAiMove aiMove;
|
|
|
|
|
m_aiFuture = std::async(std::launch::async, aiMove, this);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::FinishAIMove()
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(m_aiState == ChessAiStateWaitingForFinish);
|
|
|
|
|
m_aiState = ChessAiStateNone;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessAiMoveResult result = m_aiFuture.get();
|
|
|
|
|
m_moveResult = result.GetResult();
|
|
|
|
|
FinaliseWeenieMove(result.GetResult());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
LOG_PRIVATE(Data, Normal, "Calculated Chess AI move in %u ms with %u minimax calculations.\n",
|
|
|
|
|
result.GetProfilingTime(), result.GetProfilingCounter());
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-16 01:21:13 +12:00
|
|
|
void ChessMatch::FinaliseWeenieMove(ChessMoveResult const result)
|
2018-08-12 13:53:40 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessMove const& move = m_logic.GetLastMove();
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
// need to use destination coordinate as m_logic has already moved the piece
|
|
|
|
|
BasePiece* piece = m_logic.GetPiece(move.GetToCoord());
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if ((result & OKMoveToEmptySquare) != 0)
|
2018-08-18 18:11:38 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
MoveWeeniePiece(piece);
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 18:11:38 +12:00
|
|
|
ChessMoveFlag const flags = move.GetFlags();
|
|
|
|
|
if (flags & (ChessMoveFlagKingSideCastle | ChessMoveFlagQueenSideCastle))
|
|
|
|
|
{
|
|
|
|
|
ChessPieceCoord castlingTo = move.GetToCoord();
|
|
|
|
|
if (flags & ChessMoveFlagKingSideCastle)
|
|
|
|
|
castlingTo.MoveOffset(-1, 0);
|
|
|
|
|
if (flags & ChessMoveFlagQueenSideCastle)
|
|
|
|
|
castlingTo.MoveOffset(1, 0);
|
|
|
|
|
|
|
|
|
|
BasePiece* rookPiece = m_logic.GetPiece(castlingTo);
|
|
|
|
|
assert(rookPiece);
|
|
|
|
|
|
|
|
|
|
MoveWeeniePiece(rookPiece);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-18 22:19:15 +12:00
|
|
|
else if ((result & OKMoveToOccupiedSquare) != 0)
|
2018-08-18 01:42:53 +12:00
|
|
|
AttackWeeniePiece(piece, move.GetCapturedGuid());
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::MoveDelayed(ChessDelayedAction const& action)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
if (m_logic.GetTurn() != action.GetColour())
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
CPlayerWeenie* player = m_side[action.GetColour()]->GetPlayer();
|
|
|
|
|
assert(player);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessMoveResult const result = m_logic.Move(action.GetColour(), action.GetFromCoord(), action.GetToCoord());
|
|
|
|
|
if (result < NoMoveResult)
|
|
|
|
|
{
|
|
|
|
|
SendMoveResponse(player, result);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
m_moveResult = result;
|
|
|
|
|
FinaliseWeenieMove(result);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::MovePassDelayed(ChessDelayedAction const& action)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-18 00:41:26 +12:00
|
|
|
void ChessMatch::QuitDelayed(ChessColour const colour)
|
2018-08-12 13:53:40 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
switch (m_state)
|
|
|
|
|
{
|
|
|
|
|
case ChessStateWaitingForPlayers:
|
|
|
|
|
Finish(CHESS_WINNER_END_GAME);
|
|
|
|
|
break;
|
|
|
|
|
case ChessStateInProgress:
|
|
|
|
|
Finish(InverseColour(colour));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::StalemateDelayed(ChessDelayedAction const& action)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
ChessSide* side = m_side[action.GetColour()];
|
|
|
|
|
ChessSide const* opSide = m_side[InverseColour(action.GetColour())];
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
side->SetStalemate(action.GetStalemate());
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
if (action.GetStalemate() && opSide->GetStalemate())
|
|
|
|
|
Finish(CHESS_WINNER_STALEMATE);
|
|
|
|
|
else if (!opSide->IsAi())
|
|
|
|
|
SendOpponentStalemateState(opSide->GetPlayer(), side->GetColour(), action.GetStalemate());
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::CalculateWeeniePosition(ChessPieceCoord const& coord, ChessColour colour, Position& position) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
uint32_t heading = static_cast<uint32_t>(m_position.frame.get_heading());
|
|
|
|
|
heading += colour ? 180 : 0;
|
|
|
|
|
heading = heading % 360;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
position.frame.m_origin += Vector(-3.5f + (coord.GetX() * 1.f), -3.5f + (coord.GetY() * 1.f), 0.f);
|
|
|
|
|
position.frame.set_heading(heading);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AddWeeniePiece(BasePiece* piece) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
Position weeniePosition = m_position;
|
|
|
|
|
CalculateWeeniePosition(piece->GetCoord(), piece->GetColour(), weeniePosition);
|
|
|
|
|
|
|
|
|
|
CWeenieObject* weeniePiece = nullptr;
|
|
|
|
|
switch (piece->GetType())
|
|
|
|
|
{
|
|
|
|
|
case Pawn:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgepawn" : "mosswartpawn", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
case Rook:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgerook" : "mosswartrook", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
case Knight:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgeknight" : "mosswartknight", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
case Bishop:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgebishop" : "mosswartbishop", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
case Queen:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgequeen" : "mosswartqueen", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
case King:
|
|
|
|
|
weeniePiece = g_pWeenieFactory->CreateWeenieByName(piece->GetColour() ? "drudgeking" : "mosswartking", &weeniePosition, true);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(weeniePiece);
|
|
|
|
|
|
|
|
|
|
weeniePiece->AsGamePiece()->SetGuid(m_guid);
|
|
|
|
|
piece->SetGuid(weeniePiece->GetID());
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::MoveWeeniePiece(BasePiece* piece)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
CWeenieObject* gamePiece = g_pWorld->FindObject(piece->GetGuid());
|
|
|
|
|
assert(gamePiece);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
Position weeniePosition = m_position;
|
|
|
|
|
CalculateWeeniePosition(piece->GetCoord(), piece->GetColour(), weeniePosition);
|
|
|
|
|
gamePiece->AsGamePiece()->MoveEnqueue(weeniePosition);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
AddPendingWeenieMotion(piece);
|
2018-08-16 01:21:13 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::AttackWeeniePiece(BasePiece* piece, uint32_t const victim)
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
CWeenieObject* gamePiece = g_pWorld->FindObject(piece->GetGuid());
|
|
|
|
|
assert(gamePiece);
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
Position weeniePosition = m_position;
|
|
|
|
|
CalculateWeeniePosition(piece->GetCoord(), piece->GetColour(), weeniePosition);
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
gamePiece->AsGamePiece()->AttackEnqueue(weeniePosition, victim);
|
2018-08-16 01:21:13 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
AddPendingWeenieMotion(piece);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::RemoveWeeniePiece(BasePiece* piece) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
uint32_t const guid = piece->GetGuid();
|
|
|
|
|
if (!guid)
|
|
|
|
|
return;
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 22:19:15 +12:00
|
|
|
CWeenieObject* gamePiece = g_pWorld->FindObject(guid);
|
2018-08-18 01:42:53 +12:00
|
|
|
assert(gamePiece);
|
2018-08-12 13:53:40 +12:00
|
|
|
|
2018-08-18 01:42:53 +12:00
|
|
|
piece->SetGuid(0);
|
|
|
|
|
gamePiece->Remove();
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-18 22:19:15 +12:00
|
|
|
void ChessMatch::UpgradeWeeniePiece(BasePiece* piece) const
|
|
|
|
|
{
|
|
|
|
|
RemoveWeeniePiece(piece);
|
|
|
|
|
|
|
|
|
|
// AC's Chess implementation doesn't support underpromotion
|
|
|
|
|
piece->SetType(Queen);
|
|
|
|
|
AddWeeniePiece(piece);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 01:21:13 +12:00
|
|
|
void ChessMatch::AddPendingWeenieMotion(BasePiece* piece)
|
2018-08-12 13:53:40 +12:00
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
m_weenieMotion.insert(piece->GetGuid());
|
|
|
|
|
m_waitingForWeenieMotion = true;
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::SendStartGame(CPlayerWeenie* player, ChessColour colour) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter startGame;
|
|
|
|
|
startGame.Write<uint32_t>(0x0282);
|
|
|
|
|
startGame.Write<uint32_t>(m_guid);
|
|
|
|
|
startGame.Write<uint32_t>(colour);
|
|
|
|
|
player->SendNetMessage(startGame.GetData(), startGame.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::SendMoveResponse(CPlayerWeenie* player, ChessMoveResult const result) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter moveResponse;
|
|
|
|
|
moveResponse.Write<uint32_t>(0x0283);
|
|
|
|
|
moveResponse.Write<uint32_t>(m_guid);
|
|
|
|
|
moveResponse.Write<int32_t>(result);
|
|
|
|
|
player->SendNetMessage(moveResponse.GetData(), moveResponse.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::SendOpponentTurn(CPlayerWeenie* player, ChessColour const colour, GameMoveData& move) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter opponentTurn;
|
|
|
|
|
opponentTurn.Write<uint32_t>(0x0284);
|
|
|
|
|
opponentTurn.Write<uint32_t>(m_guid);
|
|
|
|
|
opponentTurn.Write<uint32_t>(colour);
|
|
|
|
|
opponentTurn.Write(&move);
|
|
|
|
|
player->SendNetMessage(opponentTurn.GetData(), opponentTurn.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::SendOpponentStalemateState(CPlayerWeenie* player, ChessColour const colour, bool const on) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter opponentStalemate;
|
|
|
|
|
opponentStalemate.Write<uint32_t>(0x0285);
|
|
|
|
|
opponentStalemate.Write<uint32_t>(m_guid);
|
|
|
|
|
opponentStalemate.Write<uint32_t>(colour);
|
|
|
|
|
opponentStalemate.Write<uint32_t>(on);
|
|
|
|
|
player->SendNetMessage(opponentStalemate.GetData(), opponentStalemate.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChessMatch::SendGameOver(CPlayerWeenie* player, int32_t winner) const
|
|
|
|
|
{
|
2018-08-18 01:42:53 +12:00
|
|
|
BinaryWriter gameOver;
|
|
|
|
|
gameOver.Write<uint32_t>(0x028C);
|
|
|
|
|
gameOver.Write<uint32_t>(m_guid);
|
|
|
|
|
gameOver.Write<int32_t>(winner);
|
|
|
|
|
player->SendNetMessage(gameOver.GetData(), gameOver.GetSize(), PRIVATE_MSG);
|
2018-08-12 13:53:40 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // GDLE::Chess
|