diff options
| -rw-r--r-- | CMakeLists.txt | 27 | ||||
| -rw-r--r-- | ChessRules.pdf | bin | 0 -> 300696 bytes | |||
| -rw-r--r-- | src/board.cpp | 4 | ||||
| -rw-r--r-- | src/board.hpp | 16 | ||||
| -rw-r--r-- | src/fen.cpp | 7 | ||||
| -rw-r--r-- | src/main.cpp | 14 | ||||
| -rw-r--r-- | src/moves.cpp | 76 | ||||
| -rw-r--r-- | src/moves.hpp | 2 | ||||
| -rw-r--r-- | src/repl.cpp | 124 |
9 files changed, 199 insertions, 71 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a970f50..2ecb495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,13 +3,10 @@ project(chess) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_C_STANDARD 23) -set(CMAKE_C_STANDARD_REQUIRED ON) - set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -file(GLOB_RECURSE SOURCE_FILES src/*.c src/*.cpp src/*.h src/*.hpp) +file(GLOB_RECURSE SOURCE_FILES src/*.cpp src/*.hpp) add_executable(${PROJECT_NAME} ${SOURCE_FILES}) @@ -23,6 +20,28 @@ else() -Wextra -Wpedantic -Werror + + -Wshadow + -Wconversion + -Wsign-conversion + -Wcast-align + -Wcast-qual + -Wformat=2 + -Wnull-dereference + -Wdouble-promotion + -Wimplicit-fallthrough + -Wmissing-declarations + -Wredundant-decls + -Wundef + -Wwrite-strings + -Wold-style-cast # C++ only + -Woverloaded-virtual # C++ only + -Wnon-virtual-dtor # C++ only + -Wduplicated-cond # GCC + -Wduplicated-branches # GCC + -Wlogical-op # GCC + -Wuseless-cast # C++ only + -Wextra-semi # Clang ) endif() diff --git a/ChessRules.pdf b/ChessRules.pdf Binary files differnew file mode 100644 index 0000000..5861575 --- /dev/null +++ b/ChessRules.pdf diff --git a/src/board.cpp b/src/board.cpp index 54df662..877e8c7 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -6,7 +6,7 @@ #include <print> #include <string> -char toChar(pieceType type) { +char toChar(PieceType type) { switch (type) { case NONE: return '.'; @@ -29,7 +29,7 @@ char toChar(pieceType type) { } } -Piece createPiece(pieceType type, bool color, bool moved) { +Piece createPiece(PieceType type, bool color, bool moved) { Piece piece; piece.type = type; piece.color = color; diff --git a/src/board.hpp b/src/board.hpp index 9e80025..05706ff 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -3,7 +3,7 @@ #include <cstdint> #include <string> -enum pieceType { +enum PieceType { NONE, PAWN, KNIGHT, @@ -13,11 +13,18 @@ enum pieceType { KING, }; -char toChar(pieceType type); +enum GameState { + TURN, + WHITE_WON, + BLACK_WON, + STALEMATE, + DRAW, +}; +char toChar(PieceType type); struct Piece { bool color; - pieceType type; + PieceType type; bool moved; }; @@ -27,6 +34,7 @@ struct Board { std::string castle; uint8_t halfMoveClock; uint16_t MoveClock; + GameState state; }; struct Position { @@ -43,7 +51,7 @@ struct Move { bool operator==(const Move &) const = default; }; void printBoard(Board *b); -Piece createPiece(pieceType type, bool color, bool moved = false); +Piece createPiece(PieceType type, bool color, bool moved = false); void PlayMove(Move move, Board *b); int PositionToIndex(Position i); diff --git a/src/fen.cpp b/src/fen.cpp index 9ddc795..2808a4c 100644 --- a/src/fen.cpp +++ b/src/fen.cpp @@ -2,6 +2,7 @@ #include "board.hpp" #include <cctype> #include <cstdio> +#include <cstdlib> #include <print> #include <string> @@ -81,8 +82,9 @@ void setBoardFen(std::string fen, Board *b) { if (isdigit(fen[i])) { int move = fen[i] - '0'; file += move; - if (file > 8) { + if (file >= 8) { std::println("Fatal: file was to big"); + exit(1); return; // malformed rank } continue; @@ -90,8 +92,9 @@ void setBoardFen(std::string fen, Board *b) { if (fen[i] == '/') { file = 0; rank++; - if (rank > 8) { + if (rank >= 8) { std::println("Fatal: rank was to big"); + exit(1); return; // malformed rank } continue; diff --git a/src/main.cpp b/src/main.cpp index 4b9e965..b8dd297 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,8 +2,6 @@ #include "fen.hpp" #include "repl.hpp" #include <cstdio> -#include <iostream> -#include <print> #include <string> using namespace std; @@ -25,6 +23,7 @@ void setAllPiecesToEmpty(Board *b) { }; Board initBoard(string startingFEN) { Board b; + b.state = TURN; b.turn = true; b.castle = ""; b.halfMoveClock = 0; @@ -32,6 +31,7 @@ Board initBoard(string startingFEN) { setBoardFen(startingFEN, &b); return b; }; +#define askMode = false int main(int argc, char **argv) { std::string startingFen = @@ -44,16 +44,8 @@ int main(int argc, char **argv) { if (argc == 2) { startingFen = argv[1]; } - println("ENTER which mode you want (REPL)"); - Mode mode = EXIT; - string modeString; - cin >> modeString; - std::transform(modeString.begin(), modeString.end(), modeString.begin(), - ::toupper); - if (modeString == "REPL") { - mode = REPL; - } + Mode mode = REPL; // ADD support for other modes here Board b = initBoard(startingFen); diff --git a/src/moves.cpp b/src/moves.cpp index 0059039..7efd00a 100644 --- a/src/moves.cpp +++ b/src/moves.cpp @@ -2,9 +2,38 @@ #include <cassert> #include <cstdint> #include <sys/types.h> +#include <vector> #include "board.hpp" #include "moves.hpp" +void GenerateKnightMoves(Board *b, int from, std::vector<Move> &moves) { + + Piece knight = b->pieces[from]; + if (knight.type != KNIGHT) { + assert(false && "Calling generate knight moves on non knight"); + return; + } + // Knight moves: https://www.chessprogramming.org/Knight_Pattern + constexpr std::array<int, 8> knight_moves{-10, 6, 15, 17, 10, -6, -15, -17}; + + for (int offset : knight_moves) { + int next = from + offset; + if (next >= 64 || next < 0) { + continue; + } + const int fileDelta = std::abs((next % 8) - (from % 8)); + if (fileDelta != 1 && fileDelta != 2) { + continue; + }; + if (b->pieces[next].type != NONE) { + if (b->pieces[next].color == b->turn) { + continue; + }; + } + + moves.push_back({IndexToPosition(from), IndexToPosition(next)}); + } +}; void GeneratePawnMoves(Board *b, int from, std::vector<Move> &moves) { Piece pawn = b->pieces[from]; if (pawn.type != PAWN) { @@ -49,7 +78,7 @@ void GeneratePawnMoves(Board *b, int from, std::vector<Move> &moves) { // the end } -std::vector<Move> GetLegalMoves(Board *b) { +std::vector<Move> GetPseudoLegalMoves(Board *b) { std::vector<Move> moves; moves.reserve(50); // almost all position dont have that many moves @@ -69,6 +98,9 @@ std::vector<Move> GetLegalMoves(Board *b) { if (piece.type == PAWN) { GeneratePawnMoves(b, i, moves); } + if (piece.type == KNIGHT) { + GenerateKnightMoves(b, i, moves); + }; if (piece.type == BISHOP) { GenerateSlidingMoves(b, i, bishop_Moves, moves); } @@ -86,6 +118,48 @@ std::vector<Move> GetLegalMoves(Board *b) { return moves; } +std::vector<Move> GetLegalMoves(Board *b) { + std::vector<Move> moves = GetPseudoLegalMoves(b); + std::vector<Move> legalMove; + for (Move move : moves) { + bool isLegal = true; + Board testBoard = *b; + PlayMove(move, &testBoard); + testBoard.turn = !testBoard.turn; + auto opponentResponse = GetPseudoLegalMoves(&testBoard); + for (Move move : opponentResponse) { + if (testBoard.pieces[PositionToIndex(move.To)].type == KING) { + isLegal = false; + } + } + if (isLegal) { + legalMove.push_back(move); + }; + } + if (legalMove.size() == 0) { + bool isCheck = false; + + Board testBoard = *b; + testBoard.turn = !testBoard.turn; + auto opponentResponse = GetPseudoLegalMoves(&testBoard); + for (Move move : opponentResponse) { + if (testBoard.pieces[PositionToIndex(move.To)].type == KING) { + isCheck = true; + } + } + + if (isCheck) { + if (b->turn) { + b->state = BLACK_WON; + } else { + b->state = WHITE_WON; + }; + } else { + b->state = STALEMATE; + } + } + return legalMove; +} Position IndexToPosition(int i) { uint8_t rank = i / 8; // 0-7 diff --git a/src/moves.hpp b/src/moves.hpp index ae26e29..a957d3a 100644 --- a/src/moves.hpp +++ b/src/moves.hpp @@ -5,11 +5,13 @@ #include <vector> std::vector<Move> GetLegalMoves(Board *b); +std::vector<Move> GetPseudoLegalMoves(Board *b); Position IndexToPosition(int i); void GenerateSlidingMoves(Board *b, int from, const std::array<int, 4> &directions, std::vector<Move> &moves); void GenerateKingMoves(Board *b, int from, std::vector<Move> &moves); +void GenerateKningtMoves(Board *b, int from, std::vector<Move> &moves); void GeneratePawnMoves(Board *b, int from, std::vector<Move> &moves); #endif /* SRC_MOVES_H_ */ diff --git a/src/repl.cpp b/src/repl.cpp index 642419a..ffa52b6 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -25,12 +25,36 @@ bool isValidChessFile(char rank) { } int startREPL(Board *b) { printBoard(b); + GetLegalMoves(b); while (true) { + if (b->state == STALEMATE) { + printBoard(b); + std::println(); + std::cout << "\033[1mStalemate\033[0m" << "\n"; + std::println(); + return 0; + } + if (b->state == WHITE_WON) { + printBoard(b); + std::cout << "\033[1mWhite won\033[0m" << "\n"; + return 0; + } + if (b->state == BLACK_WON) { + printBoard(b); + std::cout << "\033[1mBlack won\033[0m" << "\n"; + return 0; + } std::cout << "> "; std::string command; - std::cin >> command; + if (!(std::cin >> command)) { + std::println(); + std::cout << "Exiting...\n"; + return EXIT_SUCCESS; + } std::transform(command.begin(), command.end(), command.begin(), ::toupper); + + std::cout << command << "\n"; if (command == "EXIT") { std::println(); std::cout << "Exiting...\n"; @@ -40,61 +64,67 @@ int startREPL(Board *b) { printBoard(b); continue; } - - if (command.length() != 4) { - std::cout << "Unknown Command, use EXIT to exit"; - std::println(); + if (command == "MOVES" || command == "MOVE") { + auto moves = GetLegalMoves(b); + PrintMoves(moves); continue; } - if (!isValidChessFile(command[0])) { - std::cout << "ERROR: Expected valid file at first place"; + if (command.length() != 4) { + std::cout << "Unknown Command, use EXIT to exit"; std::println(); continue; } + if (b->state == TURN) { + if (!isValidChessFile(command[0])) { + std::cout << "ERROR: Expected valid file at first place"; + std::println(); + continue; + } - if (!isValidChessRank(command[1])) { - std::cout << "ERROR: Expected valid rank at second place"; - std::println(); - continue; - } + if (!isValidChessRank(command[1])) { + std::cout << "ERROR: Expected valid rank at second place"; + std::println(); + continue; + } - if (!isValidChessFile(command[2])) { - std::cout << "ERROR: Expected valid file at third place"; - std::println(); - continue; - } + if (!isValidChessFile(command[2])) { + std::cout << "ERROR: Expected valid file at third place"; + std::println(); + continue; + } - if (!isValidChessRank(command[3])) { - std::cout << "ERROR: Expected valid rank at four place"; - std::println(); - continue; - } + if (!isValidChessRank(command[3])) { + std::cout << "ERROR: Expected valid rank at four place"; + std::println(); + continue; + } - // we know its a valid chess pos - int fromFile = command[0] - 'A'; - int fromRank = '8' - command[1]; - int toFile = command[2] - 'A'; - int toRank = '8' - command[3]; - std::cout << fromFile << "\n"; - std::cout << fromRank << "\n"; - std::cout << toFile << "\n"; - std::cout << toRank << "\n"; - Move move = { - {static_cast<uint8_t>(fromRank), static_cast<uint8_t>(fromFile)}, - {static_cast<uint8_t>(toRank), static_cast<uint8_t>(toFile)}}; + // we know its a valid chess pos + int fromFile = command[0] - 'A'; + int fromRank = '8' - command[1]; + int toFile = command[2] - 'A'; + int toRank = '8' - command[3]; + std::cout << fromFile << "\n"; + std::cout << fromRank << "\n"; + std::cout << toFile << "\n"; + std::cout << toRank << "\n"; + Move move = { + {static_cast<uint8_t>(fromRank), static_cast<uint8_t>(fromFile)}, + {static_cast<uint8_t>(toRank), static_cast<uint8_t>(toFile)}}; - auto moves = GetLegalMoves(b); - if (!(std::ranges::find(moves, move) != moves.end())) { - std::cout << "Invalid move!"; + auto moves = GetLegalMoves(b); + if (!(std::ranges::find(moves, move) != moves.end())) { + std::cout << "Invalid move!"; + std::println(); + PrintMoves(moves); + continue; + } std::println(); - PrintMoves(moves); - continue; + PlayMove(move, b); + b->MoveClock++; + b->turn = !b->turn; + printBoard(b); } - std::println(); - PlayMove(move, b); - b->MoveClock++; - b->turn = !b->turn; - printBoard(b); } } @@ -102,8 +132,8 @@ void PrintMoves(const std::vector<Move> &moves) { std::cout << "Moves (" << moves.size() << "):\n"; for (const Move &move : moves) { - std::cout << "(" << (int)move.From.file << ", " << (int)move.From.rank - << ") -> (" << (int)move.To.file << ", " << (int)move.To.rank - << ")\n"; + std::cout << (char)((int)move.From.file + 'A') << 8 - (int)move.From.rank + << " -> " << (char)((int)move.To.file + 'A') + << 8 - (int)move.To.rank << "\n"; } } |
