Basic Description
What is the program for?
This is just a hobby project for me to improve at coding, not a serious one.
What does the program do?
It can take a chess position (not including variations like chess960 etc.), and generate all legal moves from it. Draw and checkmates are not added yet.
File and function description
main.cpp/hpp
- Currently only used for testing
Note The functions here are just for debugging, thus don't matter much. Any feedback on how to write better code here is still appreciated though.
perft()/perftDivide()
Perft test to check the move generation function.printStateTest()
Only used for debugging; prints the gameState.
gameState.cpp/hpp
- Basic board handling
gameState
- Game state class.addPiece()/deletePiece()/movePieceFromPos()
Pretty self-explanatory.initWithFEN()
Initialize game state with FEN notation.isSameColor()/isSameType()
Compare the color/type of two pieces.oppositeColor()
Make the piece an opponent's piece.
piece.hpp
- Defines the enum value of the pieces
move.cpp/hpp
- Move handling
pieceMove
Move class.initMove()
Initialize pieceMove.applyMove()
Makes a move and updates the game state accordingly.cordAlgebraicToNum()
Converts an algebraic coordinate to the internal numeric format.cordNumToAlgebraic()
Opposite of the function above.moveToUCI()
Converts a piece move to UCI format.
moveGen.cpp/hpp
- Move generation
isInCheck()
Check if the selected side is in check. Doesn't use the function below, to increase performance .generatePseudoMoves()
Generate pseudo-legal moves.generateLegalMoves()
Filter legal moves from the function above.generateSlidingMoves()
Generate directional moves with specified depth.easyMask()
Generate move mask (see below).moveCanBeMade()
Testing if a move can be made without crossing the edge of the board.
What do I plan to add in the future?
- Positional evaluation
- AI engine
What do I wish to improve?
As I plan to implement more features to the code, I'm looking for ways to improve the structure and performance of it. (e.g. Better algorithms, code style...)
Any sort of help is appreciated, and thanks in advance!
The Code
main.cpp
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include "gameState.hpp"
#include "pieces.hpp"
#include "move.hpp"
#include "moveGen.hpp"
#include "main.hpp"
std::map<std::string, int> perftDivide (gameState currentState, int depth) {
std::map<std::string, int> output;
int sideToMove = currentState.whiteToMove ? COLOR_WHITE : COLOR_BLACK;
std::vector<pieceMove> moveList = generateLegalMoves(currentState, sideToMove);
for (int i = 0; i < moveList.size(); ++i) {
output[moveToUCI(moveList[i])] = perft(applyMove(currentState, moveList[i]), depth - 1);
}
return output;
}
int perft (gameState currentState, int depth) {
int sideToMove = currentState.whiteToMove ? COLOR_WHITE : COLOR_BLACK;
std::vector<pieceMove> moveList = generateLegalMoves(currentState, sideToMove);
int positionNum = 0;
if (depth == 0) {
return 1;
}
if (depth == 1) {
return moveList.size();
}
for (int i = 0; i < moveList.size(); ++i) {
positionNum += perft(applyMove(currentState, moveList[i]), depth - 1);
}
return positionNum;
}
void printStateTest(gameState test) { //Temp function for printing class gameState
std::map<int, char> pieces = {
{9, 'P'}, {10, 'N'}, {11,'B'}, {12, 'R'}, {13, 'Q'}, {14, 'K'},
{17, 'p'}, {18, 'n'}, {19,'b'}, {20, 'r'}, {21, 'q'}, {22, 'k'}
};
int temp;
std::cout << "Board:\n";
for (int i = 7; i >= 0; --i) {
for (int j = 0; j < 8; ++j) {
temp = test.board[i * 8 + j];
if (temp != 0) {
std::cout << pieces[temp];
} else {
std::cout << ".";
}
}
std::cout << std::endl;
}
std::cout << "White To Move: " << (test.whiteToMove == 1) << "\n";
std::cout << "Castling Right: {";
for (int i = 0; i < 4; ++ i) {
std::cout << test.castlingRights[i] << " ";
}
std::cout << "}" << std::endl;
std::cout << "En Passant: " << test.enPassant << "\n";
std::cout << "Move Clock: " << test.moveClock << "\n";
std::cout << "Moves: " << test.wholeTurn << "\n";
}
int main() {
//Example usage
gameState test = initWithFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8");
printStateTest(test);
std::cout << "Position " << perft(test, 4) << "\n";
return 0;
}
main.hpp
#ifndef main_h
#define main_h
#include "gameState.hpp"
int perft (gameState currentState, int depth);
std::map<std::string, int> perftDivide (gameState currentState, int depth);
void printStateTest(gameState test);
#endif /* main_h */
gameState.cpp
#include <string>
#include "gameState.hpp"
#include "pieces.hpp"
#include "move.hpp"
std::map<char, int>charToPiece = {
{'P', TYPE_PAWN},
{'N', TYPE_KNIGHT},
{'B', TYPE_BISHOP},
{'R', TYPE_ROOK},
{'Q', TYPE_QUEEN},
{'K', TYPE_KING},
};
bool isSameColor (int original, int reference) {
return (original & 24) == (reference & 24);
}
bool isSameType (int original, int reference) {
return (original & 7) == (reference & 7);
}
int oppsiteColor (int original) {
int output;
switch (original & 24) {
case 8:
output = (original & 7) | 16;
break;
case 16:
output = (original & 7) | 8;
break;
default:
output = original;
}
return output;
}
gameState addPiece (gameState currentState, int pieceType, int position) {
gameState newState = currentState;
newState.board[position] = pieceType;
return newState;
}
gameState deletePiece (gameState currentState, int position) {
gameState newState = currentState;
newState.board[position] = 0;
return newState;
}
gameState movePieceFromPos (gameState currentState, int startPos, int endPos) {
gameState newState = currentState;
newState = addPiece(newState, newState.board[startPos], endPos);
newState = deletePiece(newState, startPos);
return newState;
}
gameState initWithFEN (std::string FEN) {
gameState newState;
std::vector<std::string> parts(6);
//Seperate different parts
int temp = 0;
for (int i = 0; i < FEN.size(); ++i) {
if (FEN[i] == ' ') {
++temp;
} else {
parts[temp] += FEN[i];
}
}
//Setting up the board
int rank = 7, file = 0, piece;
std::string boardFEN = parts[0];
char current;
for (int i = 0; i < boardFEN.size(); ++i) {
current = boardFEN[i];
if (current == '/') {
file = 0;
--rank;
} else {
if (isdigit(current)) {
file += current - '0';
} else {
piece = charToPiece[toupper(current)];
piece += isupper(current) ? COLOR_WHITE : COLOR_BLACK;
newState.board[rank * 8 + file] = piece;
++file;
}
}
}
//Side to move
newState.whiteToMove = (parts[1] == "w");
//Castling rights
for (int i = 0; i < parts[2].size(); ++i) {
if (parts[2][i] == 'K') {
newState.castlingRights[0] = true;
}
if (parts[2][i] == 'Q') {
newState.castlingRights[1] = true;
}
if (parts[2][i] == 'k') {
newState.castlingRights[2] = true;
}
if (parts[2][i] == 'q') {
newState.castlingRights[3] = true;
}
}
//En passant
if (parts[3] == "-") {
newState.enPassant = -1;
} else {
newState.enPassant = cordAlgebraicToNum(parts[3]);
}
//Half move clock
newState.moveClock = std::stoi(parts[4]);
//Whole moves
newState.wholeTurn = std::stoi(parts[5]);
return newState;
}
#include "gameState.hpp"
gameState.hpp
#ifndef gameState_hpp
#define gameState_hpp
#include <stdio.h>
#include <vector>
#include <string>
class gameState {
public:
std::vector<int> board = std::vector<int>(64);
std::vector<bool> castlingRights = std::vector<bool>(4);
bool whiteToMove;
int enPassant, wholeTurn, moveClock;
};
bool isSameColor (int original, int reference);
bool isSameType (int original, int reference);
int oppsiteColor (int original);
gameState addPiece (gameState currentState, int pieceType, int position);
gameState deletePiece (gameState currentState, int position);
gameState movePieceFromPos (gameState currentState, int startPos, int endPos);
gameState initWithFEN (std::string FEN);
#endif /* gameState_hpp */
piece.hpp
#ifndef pieces_hpp
#define pieces_hpp
#include <stdio.h>
#include <string>
#include <map>
enum pieceType {
//EMPTY = 0,
TYPE_PAWN = 1,
TYPE_KNIGHT = 2,
TYPE_BISHOP = 3,
TYPE_ROOK = 4,
TYPE_QUEEN = 5,
TYPE_KING = 6
};
enum pieceColor {
COLOR_WHITE = 8,
COLOR_BLACK = 16,
COLOR_ANY = 24
};
#endif /* pieces_hpp */
move.cpp
#include "move.hpp"
#include "gameState.hpp"
#include "pieces.hpp"
pieceMove initMove (int start, int end, int flag) {
pieceMove output;
output.startSqr = start;
output.endSqr = end;
output.flag = flag;
return output;
}
gameState applyMove (gameState currentState, pieceMove move) { //Make move
gameState newState;
newState = movePieceFromPos(currentState, move.startSqr, move.endSqr);
newState.whiteToMove ^= 1;
if (newState.whiteToMove) {
++newState.wholeTurn;
}
if (isSameType(newState.board[move.startSqr], TYPE_PAWN) || newState.board[move.endSqr] != 0) {
newState.moveClock = 0;
} else {
++newState.moveClock;
}
int castlingSide, isKingSide;
for (int i = 0; i < 8; i += 7) {
for (int j = 0; j < 8; j += 7) {
if (move.startSqr == (i * 8 + j) || move.endSqr == (i * 8 + j)) {
castlingSide = i == 0 ? 0 : 2;
isKingSide = j == 0;
newState.castlingRights[castlingSide | isKingSide] = false;
}
}
if (move.startSqr == (i * 8 + 4) || move.endSqr == (i * 8 + 4)) {
castlingSide = i == 0 ? 0 : 2;
newState.castlingRights[castlingSide] = false;
newState.castlingRights[castlingSide + 1] = false;
}
}
switch (move.flag) {
case FLAG_DOUBLE_JUMP: {
newState.enPassant = move.endSqr;
break;
}
case FLAG_EN_PASSANT: {
newState.board[newState.enPassant] = 0;
newState.enPassant = -1;
break;
}
case FLAG_CASTLE: {
int rookStartPosition = (move.startSqr - move.endSqr == 2) ? move.startSqr - 4 : move.startSqr + 3;
int rookEndPosition = (move.startSqr - move.endSqr == 2) ? move.startSqr - 1 : move.startSqr + 1;
newState = movePieceFromPos(newState, rookStartPosition, rookEndPosition);
break;
}
case FLAG_PROMOTE_KNIGHT: {
newState.board[move.endSqr] = (newState.board[move.endSqr] & 24) | TYPE_KNIGHT;
break;
}
case FLAG_PROMOTE_BISHOP: {
newState.board[move.endSqr] = (newState.board[move.endSqr] & 24) | TYPE_BISHOP;
break;
}
case FLAG_PROMOTE_ROOK: {
newState.board[move.endSqr] = (newState.board[move.endSqr] & 24) | TYPE_ROOK;
break;
}
case FLAG_PROMOTE_QUEEN: {
newState.board[move.endSqr] = (newState.board[move.endSqr] & 24) | TYPE_QUEEN;
break;
}
}
if (move.flag != FLAG_DOUBLE_JUMP) {
newState.enPassant = -1;
}
return newState;
}
int cordAlgebraicToNum (std::string cordinate) {
return (cordinate[0] - 'a') + (cordinate[1] - '1') * 8;
}
std::string cordNumToAlgebraic (int cordinate) {
char rank = '1' + cordinate / 8;
char file = (cordinate % 8) + ((int) 'a');
return std::string() + file + rank;
}
std::string moveToUCI (pieceMove move) {
std::string output;
output = cordNumToAlgebraic(move.startSqr) + cordNumToAlgebraic(move.endSqr);
switch (move.flag) {
case FLAG_PROMOTE_KNIGHT: {
output += "n";
break;
}
case FLAG_PROMOTE_BISHOP: {
output += "b";
break;
}
case FLAG_PROMOTE_ROOK: {
output += "r";
break;
}
case FLAG_PROMOTE_QUEEN: {
output += "q";
break;
}
}
return output;
}
move.hpp
#ifndef move_hpp
#define move_hpp
#include <stdio.h>
#include "gameState.hpp"
class pieceMove {
public:
int startSqr;
int endSqr;
int flag;
};
enum moveFlag {
FLAG_NONE = 0,
FLAG_DOUBLE_JUMP = 1,
FLAG_EN_PASSANT = 2,
FLAG_CASTLE = 3,
FLAG_PROMOTE_KNIGHT = 4,
FLAG_PROMOTE_BISHOP = 5,
FLAG_PROMOTE_ROOK = 6,
FLAG_PROMOTE_QUEEN = 7
};
pieceMove initMove (int start, int end, int flag);
gameState applyMove (gameState currentState, pieceMove move);
int cordAlgebraicToNum (std::string cordinate);
std::string cordNumToAlgebraic (int cordinate);
std::string moveToUCI (pieceMove move);
#endif /* move_hpp */
moveGen.cpp
#include "moveGen.hpp"
#include "move.hpp"
#include "gameState.hpp"
#include "pieces.hpp"
#include <vector>
#include <iostream>
#include <math.h>
bool isInCheck (gameState currentState, int side) {
int kingPosition = 0;
for (int i = 0; i < 64; ++i) {
if (currentState.board[i] == (side | TYPE_KING)) {
kingPosition = i;
break;
}
}
//Pawn attacks
int opponentDirection;
opponentDirection = side == COLOR_WHITE ? 8 : -8;
for (int i = -1; i < 2; i += 2) {
if (moveCanBeMade(kingPosition, opponentDirection + i) && currentState.board[kingPosition + i + opponentDirection] == oppsiteColor(side | TYPE_PAWN)) {
return true;
}
}
//Knight attacks
std::vector<int> directions = {-17, -15, -10, -6, 6, 10, 15, 17};
int destination;
for (int i = 0; i < directions.size(); ++i) {
if (moveCanBeMade(kingPosition, directions[i])) {
destination = kingPosition + directions[i];
if (currentState.board[destination] == oppsiteColor(side | TYPE_KNIGHT)) {
return true;
}
}
}
//King opposition
directions = {-9, -8, -7, -1, 1, 7, 8, 9};
for (int i = 0; i < directions.size(); ++i) {
if (moveCanBeMade(kingPosition, directions[i])) {
destination = kingPosition + directions[i];
if (currentState.board[destination] == oppsiteColor(side | TYPE_KING)) {
return true;
}
}
}
//Rook and (non-diaganol) Queen attacks
directions = {-8, -1, 1, 8};
for (int i = 0; i < directions.size(); ++i) {
destination = kingPosition;
for (int j = 0; j < 7; ++j) {
//Can't move past the edge
if (!moveCanBeMade(destination, directions[i])) {
break;
}
destination += directions[i];
//Found an attack piece
if ((currentState.board[destination] == oppsiteColor(side | TYPE_ROOK)) || (currentState.board[destination] == oppsiteColor(side | TYPE_QUEEN))) {
return true;
}
//Obstructed by piece
if (currentState.board[destination]) {
break;
}
}
}
//Bishop and (diaganol) Queen attacks
directions = {-9, -7, 7, 9};
for (int i = 0; i < directions.size(); ++i) {
destination = kingPosition;
for (int j = 0; j < 7; ++j) {
//Can't move past the edge
if (!moveCanBeMade(destination, directions[i])) {
break;
}
destination += directions[i];
//Found an attack piece
if ((currentState.board[destination] == oppsiteColor(side | TYPE_BISHOP)) || (currentState.board[destination] == oppsiteColor(side | TYPE_QUEEN))) {
return true;
}
//Obstructed by piece
if (currentState.board[destination]) {
break;
}
}
}
return false;
}
std::vector<pieceMove> generatePseudoMoves (gameState currentState, int sideToMove) {
int piece, type;
pieceMove move;
std::vector<pieceMove> pseudoMoves;
for (int i = 0; i < 64; ++i) {
piece = currentState.board[i];
if (isSameColor(piece, sideToMove)) {
type = piece & 7;
move.startSqr = i;
switch (type) {
case TYPE_PAWN: {
bool isWhite = isSameColor(piece, COLOR_WHITE);
int startingRank = isWhite ? 1 : 6;
int promotionRank = isWhite ? 7 : 0;
int forwardDirection = isWhite ? 8 : -8;
//Forward move
if (currentState.board[i + forwardDirection] == 0 && moveCanBeMade(i, forwardDirection)) {
if ((i + forwardDirection) / 8 != promotionRank) {
pseudoMoves.push_back(initMove(i, i + forwardDirection, FLAG_NONE));
} else {
//Promotion
pseudoMoves.push_back(initMove(i, i + forwardDirection, FLAG_PROMOTE_KNIGHT));
pseudoMoves.push_back(initMove(i, i + forwardDirection, FLAG_PROMOTE_BISHOP));
pseudoMoves.push_back(initMove(i, i + forwardDirection, FLAG_PROMOTE_ROOK));
pseudoMoves.push_back(initMove(i, i + forwardDirection, FLAG_PROMOTE_QUEEN));
}
//Double jump
if (currentState.board[i + forwardDirection * 2] == 0 && (i / 8) == startingRank) {
pseudoMoves.push_back(initMove(i, i + forwardDirection * 2, FLAG_DOUBLE_JUMP));
}
}
//Diagonal capture
int neighbor;
for (int j = -1; j < 2; j += 2) {
neighbor = i + j;
//Normal capture
if (moveCanBeMade(i, j + forwardDirection) && isSameColor(currentState.board[i], oppsiteColor(currentState.board[neighbor + forwardDirection]))) {
if ((i + forwardDirection) / 8 != promotionRank) {
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_NONE));
} else {
//Promotion
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_PROMOTE_KNIGHT));
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_PROMOTE_BISHOP));
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_PROMOTE_ROOK));
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_PROMOTE_QUEEN));
}
}
//En passant
if (moveCanBeMade(i, j + forwardDirection) && isSameColor(currentState.board[i], oppsiteColor(currentState.board[neighbor])) && currentState.enPassant == neighbor) {
pseudoMoves.push_back(initMove(i, neighbor + forwardDirection, FLAG_EN_PASSANT));
}
}
break;
}
case TYPE_KNIGHT: {
std::vector<pieceMove> moveList = generateSlidingMoves(currentState, i, {-17, -15, -10, -6, 6, 10, 15, 17}, 1);
pseudoMoves.insert(std::end(pseudoMoves), std::begin(moveList), std::end(moveList));
break;
}
case TYPE_BISHOP: {
std::vector<pieceMove> moveList = generateSlidingMoves(currentState, i, {-9, -7, 7, 9}, 7);
pseudoMoves.insert(std::end(pseudoMoves), std::begin(moveList), std::end(moveList));
break;
}
case TYPE_ROOK: {
std::vector<pieceMove> moveList = generateSlidingMoves(currentState, i, {-8, -1, 1, 8}, 7);
pseudoMoves.insert(std::end(pseudoMoves), std::begin(moveList), std::end(moveList));
break;
}
case TYPE_QUEEN: {
std::vector<pieceMove> moveList = generateSlidingMoves(currentState, i, {-9, -8, -7, -1, 1, 7, 8, 9}, 7);
pseudoMoves.insert(std::end(pseudoMoves), std::begin(moveList), std::end(moveList));
break;
}
case TYPE_KING: {
std::vector<pieceMove> moveList = generateSlidingMoves(currentState, i, {-9, -8, -7, -1, 1, 7, 8, 9}, 1);
pseudoMoves.insert(std::end(pseudoMoves), std::begin(moveList), std::end(moveList));
int castlingRightsStart = sideToMove == COLOR_WHITE ? 0 : 2;
//Kingside castle
if (currentState.castlingRights[castlingRightsStart]) {
if (!(currentState.board[i + 1] | currentState.board[i + 2]) &&
!(isInCheck(currentState, sideToMove) || isInCheck(movePieceFromPos(currentState, i, i + 1), sideToMove))) {
pseudoMoves.push_back(initMove(i, i + 2, FLAG_CASTLE));
}
}
//Queenside castle
if (currentState.castlingRights[castlingRightsStart + 1]) {
if (!(currentState.board[i - 1] | currentState.board[i - 2] | currentState.board[i - 3]) &&
!(isInCheck(currentState, sideToMove) || isInCheck(movePieceFromPos(currentState, i, i - 1), sideToMove))) {
pseudoMoves.push_back(initMove(i, i - 2, FLAG_CASTLE));
}
}
break;
}
}
}
}
return pseudoMoves;
}
std::vector<pieceMove> generateLegalMoves (gameState currentState, int sideToMove) {
std::vector<pieceMove> moveList, legalMoves, pseudoMoves = generatePseudoMoves(currentState, sideToMove);
for (int i = 0; i < pseudoMoves.size(); ++i) {
if (!isInCheck(applyMove(currentState, pseudoMoves[i]), sideToMove)) {
legalMoves.push_back(pseudoMoves[i]);
}
}
return legalMoves;
}
std::vector<pieceMove> generateSlidingMoves (gameState currentState, int square, std::vector<int> directions, int maxDepth) { //Move generation for Knights, Rooks, Queens and Kings
std::vector<pieceMove> possibleMoves;
pieceMove move;
int destination;
for (int i = 0; i < directions.size(); ++i) {
destination = square;
for (int j = 0; j < maxDepth; ++j) {
//Can't move pass the edge
if (!moveCanBeMade(destination, directions[i])) {
break;
}
destination += directions[i];
//Can't capture own piece
if (isSameColor(currentState.board[square], currentState.board[destination])) {
break;
}
move.endSqr = destination;
possibleMoves.push_back(initMove(square, destination, FLAG_NONE));
//Can't move past captured pieces
if (isSameColor(currentState.board[square], oppsiteColor(currentState.board[destination]))) {
break;
}
}
}
return possibleMoves;
}
long long int easyMask (int direction) { //Prevent moving past the edge
long long int mask = 0;
int vertical, horizontal;
vertical = (int) lround((float) direction / 8);
horizontal = direction - vertical * 8;
if (horizontal != 0) {
if (horizontal > 0) {
for (int i = 0; i < horizontal; ++i) {
mask = mask | ( 0x101010101010101 << (7 - i));
}
} else {
for (int i = 0; i < (~horizontal + 1); ++i) {
mask = mask | ( 0x101010101010101 << i);
}
}
}
if (vertical != 0) {
if (vertical > 0) {
for (int i = 0; i < vertical; ++i) {
mask = mask | ( 0xff00000000000000 >> (i * 8));
}
} else {
for (int i = 0; i < (~vertical + 1); ++i) {
mask = mask | ( 0xff << (i * 8));
}
}
}
return mask;
}
bool moveCanBeMade (int square, int direction) {
return !((easyMask(direction) >> square) & 1);
}
moveGen.hpp
#ifndef moveGen_hpp
#define moveGen_hpp
#include <stdio.h>
#include <vector>
#include "move.hpp"
#include "gameState.hpp"
bool isInCheck (gameState currentState, int side);
std::vector<pieceMove> generateSlidingMoves (gameState currentState, int square, std::vector<int> directions, int maxDepth);
std::vector<pieceMove> generatePseudoMoves (gameState currentState, int sideToMove);
std::vector<pieceMove> generateLegalMoves (gameState currentState, int sideToMove);
long long int easyMask (int direction);
bool moveCanBeMade (int square, int direction);
#endif /* moveGen_hpp */
#include "gameState.hpp"
at the end of gameState.cpp? \$\endgroup\$