Compare commits

...

36 Commits

Author SHA1 Message Date
Karma Riuk
0145664567 minor fixes
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-16 09:30:59 +01:00
Karma Riuk
959ecdeb7d added the opponenent pawn attack map move better ordering 2025-02-16 09:29:50 +01:00
Karma Riuk
adf21ee021 minor stuff 2025-02-09 21:18:51 +01:00
Karma Riuk
989225c2d7 fixed bugs 2025-02-07 20:08:25 +01:00
Karma Riuk
aef6fc39e3 fixed slight bug 2025-02-07 19:39:52 +01:00
Karma Riuk
2260cd918a implemented promotion for white 2025-02-07 19:39:51 +01:00
Karma Riuk
3bc5b75f1e implemented v4 that resolves all the captures
after looking at the next two moves (all of them)
2025-02-07 19:14:09 +01:00
Karma Riuk
377e5dfdcc implemented move ordering to improve on alpha_beta
pruning
2025-02-07 18:24:57 +01:00
Karma Riuk
10de9828be implemented alpha beta pruning 2025-02-07 17:49:08 +01:00
Karma Riuk
9942832b18 caching check and nlm 2025-02-07 17:35:14 +01:00
Karma Riuk
5b45fc32c0 annoying 2025-02-07 16:18:11 +01:00
Karma Riuk
d28008d913 committing to stash and compare 2025-02-07 15:08:53 +01:00
Karma Riuk
86beb9cc85 renamed v1_simple to v1_pure_minimax 2025-02-07 15:08:10 +01:00
Karma Riuk
8edde1a8b1 caching checks and no legal moves 2025-02-07 14:15:10 +01:00
Karma Riuk
182d183247 committing to stash and compare 2025-02-07 14:02:56 +01:00
Karma Riuk
1b14ed693d made small opti 2025-02-07 12:04:20 +01:00
Karma Riuk
a80fc9482d base for testing optimization 2025-02-07 11:08:59 +01:00
Karma Riuk
88b2d01cf8 made v1_simple multithreaded again 2025-02-07 10:03:37 +01:00
Karma Riuk
979d44fad0 fixed small bug 2025-02-07 10:03:32 +01:00
Karma Riuk
db87c2ec8b minor stuff 2025-02-07 09:52:37 +01:00
Karma Riuk
f3e28d2646 found better terminal condition for v1_simple 2025-02-07 09:52:09 +01:00
Karma Riuk
7e888bba61 made v1_simple single threaded because it was
getting messy
2025-02-07 09:51:49 +01:00
Karma Riuk
1d83c066f6 made v0_random thinking longer 2025-02-07 09:51:22 +01:00
Karma Riuk
2764787e63 made v1_simple look 4 moves into the future, it's
parallelized
2025-02-07 00:34:22 +01:00
Karma Riuk
c71cabb3cd fixed tests 2025-02-07 00:29:49 +01:00
Karma Riuk
0525bd565e minor fixes 2025-02-07 00:29:39 +01:00
Karma Riuk
695cef33df fixed test execution 2025-02-07 00:29:17 +01:00
Karma Riuk
75b6c35cc5 if the ai found the move before the thinking_time
is over, stop the thread to return
2025-02-06 23:46:34 +01:00
Karma Riuk
74fa99fe7b faking some thought for the random player 2025-02-06 23:46:06 +01:00
Karma Riuk
0274f44647 minor fix 2025-02-06 23:46:00 +01:00
Karma Riuk
a5abe39aa4 added a position to perft 2025-02-06 23:37:55 +01:00
Karma Riuk
e056ef0805 minor modifications to ai v1 2025-02-06 22:57:06 +01:00
Karma Riuk
dc6631f79c made AIs aware what colour they were playing 2025-02-06 22:56:41 +01:00
Karma Riuk
d6aa977a15 added the 50-move draw rule 2025-02-06 22:39:11 +01:00
Karma Riuk
6e567f2f11 added the check for insufficient material 2025-02-06 22:34:48 +01:00
Karma Riuk
fced9757c2 made figuring whether the board is terminal easier 2025-02-06 22:10:47 +01:00
29 changed files with 825 additions and 197 deletions

View File

@ -1,4 +1,5 @@
CXXFLAGS += -O3 -Wall
# CXXFLAGS += -pg
# Add .d to Make's recognized suffixes.
SUFFIXES += .d
@ -28,7 +29,7 @@ obj/%.o:
$(CXX) $(CXXFLAGS) -o $@ -c $<
main: $(OBJFILES)
$(CXX) $(LDFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
$(CXX) $(CXXFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
clean:
rm -rf obj/* $(DEPFILES) test_bin/
@ -46,7 +47,7 @@ LIBS := $(filter-out obj/main.o,$(OBJFILES))
test_bin/%: tests/%.cpp $(LIBS)
@echo $(LIBS)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS)
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS) -lsfml-graphics -lsfml-window -lsfml-system
# The 'test' target builds all tests and then runs each one.
.PHONY: test

View File

@ -14,7 +14,8 @@ void AIvsAIController::start() {
ai::AI* current_player;
while (!board.is_terminal()) {
current_player = board.white_to_play ? &p1 : &p2;
Move move = current_player->search(board, board.white_to_play);
std::cout << typeid(*current_player).name() << " to play" << std::endl;
Move move = current_player->search(board);
make_move(move);
}
@ -23,13 +24,14 @@ void AIvsAIController::start() {
void AIvsAIController::make_move(Move move) {
board = board.make_move(move);
std::cout << "Made move: " << move << std::endl;
view.update_board(board, -1, {});
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate_for(current_colour))
if (board.is_checkmate())
view.notify_checkmate(current_colour);
if (board.is_stalemate_for(current_colour))
if (board.is_stalemate())
view.notify_stalemate(current_colour);
}

View File

@ -9,6 +9,7 @@ ManualController::ManualController(Board b, View& v): Controller(b, v) {
}
void ManualController::start() {
reset_selection();
view.show();
}
@ -50,13 +51,24 @@ void ManualController::show_legal_moves(Coords xy) {
}
void ManualController::make_move(Move move) {
// handle promotion before making the move
Colour colour = board.white_to_play ? White : Black;
Coords source = Coords::from_index(move.source_square);
if (board.piece_at(move.source_square) == Piece::Pawn
&& board.colour_at(move.source_square) == White && source.y == 6) {
Piece promotion_piece = (Piece) (colour | view.ask_about_promotion());
move.promoting_to = promotion_piece;
}
std::cout << "Move made: " << move << std::endl;
board = board.make_move(move);
reset_selection();
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate_for(current_colour))
if (board.is_checkmate())
view.notify_checkmate(current_colour);
if (board.is_stalemate_for(current_colour))
if (board.is_stalemate())
view.notify_stalemate(current_colour);
}

View File

@ -2,6 +2,8 @@
#include "controller/controller.hpp"
#include "controller/human_vs_ai.hpp"
#include "controller/manual.hpp"
#include "model/ais/ai.hpp"
#include "model/perft/perft.hpp"
#include "view/gui.hpp"
#include "view/noop.hpp"
#include "view/view.hpp"
@ -9,26 +11,31 @@
#include <chrono>
int main(int argc, char* argv[]) {
// std::string pos =
// "r2qkb1r/2p1pppp/p1n1b3/1p6/B2P4/2P1P3/P4PPP/R1BQK1NR w KQkq - 0 9 ";
// std::string pos = "8/6K1/5P2/8/1k6/8/8/8 w - - 0 1";
// pos for ai timing<
std::string pos =
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
"r3k2r/p1ppqpb1/Bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPB1PPP/R3K2R b KQkq - 0 3";
Board b = Board::setup_fen_position(pos);
ai::v0_random p1(std::chrono::milliseconds(500));
ai::v0_random p2(std::chrono::milliseconds(500));
ai::v0_random p1(true, std::chrono::milliseconds(1000));
// ai::v1_pure_minimax p2(false, std::chrono::milliseconds(20000));
// ai::v2_alpha_beta p2(false, std::chrono::milliseconds(20000));
ai::v3_AB_ordering p2(false, std::chrono::milliseconds(20000));
// ai::v4_search_captures p2(false, std::chrono::milliseconds(20000));
GUI gui;
// GUI gui;
NoOpView gui;
AIvsAIController manual(b, gui, p1, p2);
// HumanVsAIController manual(b, gui, p2);
Controller& controller = manual;
controller.start();
// perft();
// ai::v1_simple ai;
//
// Board b = Board::setup_fen_position(pos);
// Move move = ai.search(b, true);
// std::cout << move << std::endl;
return 0;
}

View File

@ -1,17 +1,44 @@
#include "ai.hpp"
#include <condition_variable>
#include <ostream>
#include <thread>
Move ai::AI::search(const Board& b, bool am_white) {
Move ai::AI::search(const Board& b) {
Move result;
std::condition_variable cv;
std::mutex cv_mutex;
double elapsed;
stop_computation = false;
// Start computation in a separate thread
std::thread computation_thread([&]() { result = _search(b, am_white); });
std::thread computation_thread([&]() {
auto start = std::chrono::steady_clock::now();
result = _search(b);
auto end = std::chrono::steady_clock::now();
elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
// Notify the timer thread that computation is done
{
std::lock_guard<std::mutex> lock(cv_mutex);
stop_computation = true;
cv.notify_one();
}
});
// Start a timer thread to stop computation after given time
std::thread timer_thread([&]() {
std::this_thread::sleep_for(thinking_time);
stop_computation = true;
std::unique_lock<std::mutex> lock(cv_mutex);
if (!cv.wait_for(lock, thinking_time, [&] {
return stop_computation.load();
})) {
// Timeout reached; set stop flag
stop_computation = true;
}
});
// Wait for computation thread to finish
@ -19,5 +46,7 @@ Move ai::AI::search(const Board& b, bool am_white) {
// Ensure timer thread is also stopped
timer_thread.join();
std::cout << "Took " << elapsed << " ms" << std::endl;
return result;
}

View File

@ -8,36 +8,72 @@
namespace ai {
class AI {
protected:
bool am_white;
std::chrono::milliseconds thinking_time;
virtual Move _search(const Board&, bool = false) = 0;
virtual Move _search(const Board&) = 0;
public:
AI(std::chrono::milliseconds tt): thinking_time(tt) {}
AI(bool am_white, std::chrono::milliseconds tt)
: am_white(am_white),
thinking_time(tt) {}
std::atomic<bool> stop_computation = false;
Move search(const Board& b, bool am_white = false);
Move search(const Board& b);
virtual int eval(const Board&) = 0;
};
struct v0_random : public AI {
v0_random(std::chrono::milliseconds tt): AI(tt) {}
v0_random(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&, bool) override;
Move _search(const Board&) override;
int eval(const Board&) override {
return 0;
};
};
class v1_simple : public AI {
class v1_pure_minimax : public AI { // looks two moves ahead
int _search(const Board&, int);
public:
v1_simple(std::chrono::milliseconds tt): AI(tt) {}
v1_pure_minimax(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&, bool) override;
Move _search(const Board&) override;
int eval(const Board&) override;
};
class v2_alpha_beta : public AI {
// looks two moves ahead, with alpha-beta pruning (no move ordering)
int _search(const Board&, int, int, int);
public:
v2_alpha_beta(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int eval(const Board&) override;
};
class v3_AB_ordering : public AI {
// looks two moves ahead, with alpha-beta pruning, with move ordering
virtual int _search(const Board&, int, int, int);
public:
v3_AB_ordering(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int eval(const Board&) override;
};
class v4_search_captures : public v3_AB_ordering {
// same as v3, but looking at only at captures when leaf is reached,
// until no captures are left
int _search(const Board&, int, int, int) override;
int _search_captures(const Board&, int, int);
public:
v4_search_captures(bool w, std::chrono::milliseconds tt)
: v3_AB_ordering(w, tt) {}
};
} // namespace ai

View File

@ -2,8 +2,12 @@
#include <thread>
Move ai::v0_random::_search(const Board& b, bool) {
Move ai::v0_random::_search(const Board& b) {
std::vector<Move> moves = b.all_legal_moves();
std::this_thread::sleep_for(std::chrono::milliseconds(thinking_time)
); // Simulate work
return moves[rand() % moves.size()];
}

View File

@ -0,0 +1,91 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
static int position_counter = 0;
Move ai::v1_pure_minimax::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v1_pure_minimax::_search(const Board& b, int depth) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
int best_evaluation = -INFINITY;
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1);
best_evaluation = std::max(best_evaluation, tmp_eval);
}
return best_evaluation;
}
int ai::v1_pure_minimax::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -1,95 +0,0 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "ai.hpp"
#include <future>
#include <map>
static int INFINITY = std::numeric_limits<int>::max();
Move ai::v1_simple::_search(const Board& b, bool am_white) {
ThreadPool pool(std::thread::hardware_concurrency());
std::vector<Move> moves = b.all_legal_moves();
std::map<Move, std::future<int>> futures;
for (int depth = 1; !stop_computation; depth++) {
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&]() {
return _search(tmp_board, depth - 1);
})});
}
}
Move best_move;
int best_eval = -INFINITY;
for (auto& [move, future] : futures) {
int eval = future.get();
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
return best_move;
}
int ai::v1_simple::_search(const Board& b, int depth) {
if (b.is_checkmate_for(b.white_to_play ? White : Black))
return -INFINITY;
if (depth == 0 || stop_computation)
return eval(b);
std::vector<Move> moves = b.all_legal_moves();
int best_evaluation = 0;
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1);
best_evaluation = std::max(best_evaluation, tmp_eval);
}
return best_evaluation;
}
static int PawnValue = 100;
static int KnightValue = 300;
static int BishopValue = 320;
static int RookValue = 500;
static int QueenValue = 900;
int count_material(const Board& b, int8_t colour) {
int ret = 0;
for (int i = 0; i < 64; i++) {
if (b.colour_at(i) == colour)
switch (b.piece_at(i)) {
case Piece::Pawn:
ret += PawnValue;
break;
case Piece::Knigt:
ret += KnightValue;
break;
case Piece::Bishop:
ret += BishopValue;
break;
case Piece::Rook:
ret += RookValue;
break;
case Piece::Queen:
ret += QueenValue;
break;
}
}
return ret;
}
int ai::v1_simple::eval(const Board& b) {
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,92 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
static int position_counter = 0;
Move ai::v2_alpha_beta::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3, -INFINITY, INFINITY);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v2_alpha_beta::_search(const Board& b, int depth, int alpha, int beta) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v2_alpha_beta::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,99 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#include <map>
#define MULTITHREADED 1
static int position_counter;
Move ai::v3_AB_ordering::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3, -INFINITY, INFINITY);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v3_AB_ordering::_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v3_AB_ordering::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,61 @@
#include "../pieces/piece.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#define MULTITHREADED 1
static int position_counter;
int ai::v4_search_captures::_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return _search_captures(b, alpha, beta);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v4_search_captures::_search_captures(
const Board& b, int alpha, int beta
) {
int evaluation = eval(b);
if (evaluation >= beta)
return beta;
alpha = std::max(evaluation, alpha);
std::vector<Move> moves = b.all_capturing_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search_captures(tmp_board, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}

View File

@ -5,9 +5,11 @@
#include "../utils/move.hpp"
#include "../utils/utils.hpp"
#include <SFML/Graphics/BlendMode.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <sstream>
#include <stdexcept>
Board Board::setup_fen_position(std::string fen) {
@ -92,6 +94,8 @@ Board Board::setup_fen_position(std::string fen) {
index = fen.find(' ', index) + 1;
board.n_full_moves = std::stoi(fen.substr(index));
board.check = board._is_check_for(board.white_to_play ? White : Black);
board.nlm = board._no_legal_moves_for(board.white_to_play ? White : Black);
return board;
}
@ -111,7 +115,7 @@ std::string Board::to_fen() const {
for (int rank = 7; rank >= 0; rank--) {
int empty_cell_counter = 0;
for (int file = 0; file < 8; file++) {
if (squares[rank * 8 + file] == Piece::None) {
if (piece_at({file, rank}) == Piece::None) {
empty_cell_counter++;
continue;
}
@ -169,7 +173,14 @@ std::string Board::to_fen() const {
return ret;
}
Board Board::make_move(Move move) const {
Board Board::skip_turn() const {
Board ret = *this;
ret.white_to_play = !ret.white_to_play;
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
return ret;
}
Board Board::make_move(Move move, bool recurse_call) const {
Board ret;
std::copy(
std::begin(this->squares),
@ -266,24 +277,64 @@ Board Board::make_move(Move move) const {
}
}
ret.n_half_moves = n_half_moves + 1;
if (is_capturing || piece_at(move.source_square) == Piece::Pawn)
ret.n_half_moves = 0;
if (!white_to_play)
ret.n_full_moves = n_full_moves + 1;
if (ret.n_half_moves > 150) {
std::cerr << "too many recursions" << std::endl;
exit(1);
}
if (recurse_call) {
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
ret.nlm = ret._no_legal_moves_for(ret.white_to_play ? White : Black);
}
return ret;
}
bool Board::insufficient_material_for(Colour current_colour) const {
int n_bishop = 0, n_knight = 0;
for (int i = 0; i < 64; i++) {
Colour colour = colour_at(i);
if (colour != current_colour)
continue;
Piece piece = piece_at(i);
if (piece == Piece::Pawn || piece == Piece::Queen
|| piece == Piece::Rook)
return false;
if (piece == Piece::Bishop)
n_bishop++;
if (piece == Piece::Knigt && colour == Colour::White)
n_knight++;
}
return (n_bishop == 0 && n_knight == 0) || (n_bishop == 1 && n_knight == 0)
|| (n_bishop == 0 && n_knight == 1);
}
int8_t Board::get_king_of(int8_t colour) const {
for (int i = 0; i < 64; i++)
if (squares[i] == (colour | Piece::King))
return i;
throw std::domain_error(
"Apparently there no kings of the such color in this board"
);
std::stringstream ss;
ss << "Apparently there no kings of the such color in this board: "
<< std::endl;
ss << to_fen();
throw std::domain_error(ss.str());
}
bool Board::is_check_for(int8_t colour) const {
bool Board::_is_check_for(Colour colour) const {
int8_t king_idx = this->get_king_of(colour);
for (int i = 0; i < 64; i++) {
std::vector<Move> all_moves;
all_moves.reserve(50);
for (int8_t i = 0; i < 64; i++) {
if (this->squares[i] == Piece::None || colour_at(i) == colour)
continue;
std::vector<int8_t> targets;
if (piece_at(i) == King) {
// special case for the king, because it creates infinite recursion
// (since he looks if he's walking into a check)
@ -292,7 +343,7 @@ bool Board::is_check_for(int8_t colour) const {
for (int dy = -1; dy <= 1; dy++) {
Coords c{king_pos.x + dx, king_pos.y + dy};
if (c.is_within_bounds())
targets.push_back(c.to_index());
all_moves.push_back(Move{i, c.to_index()});
}
}
} else {
@ -302,27 +353,51 @@ bool Board::is_check_for(int8_t colour) const {
Coords::from_index(i),
true
);
targets = to_target_square(moves);
all_moves.insert(all_moves.end(), moves.begin(), moves.end());
}
if (std::find(targets.begin(), targets.end(), king_idx)
!= targets.end())
return true;
for (const Move& move : all_moves)
if (move.target_square == king_idx)
return true;
all_moves.clear();
}
return false;
}
bool Board::no_legal_moves_for(int8_t colour) const {
bool Board::_no_legal_moves_for(Colour colour) const {
for (int i = 0; i < 64; i++) {
if (colour_at(i) == colour) {
std::vector<Move> moves =
legal_moves(squares[i], *this, Coords::from_index(i));
if (moves.size() > 0)
return false;
}
if (squares[i] == Piece::None || colour_at(i) != colour)
continue;
std::vector<Move> moves;
moves = legal_moves(squares[i], *this, Coords::from_index(i));
if (moves.size() > 0)
return false;
}
return true;
}
bool Board::no_legal_moves() const {
return nlm;
}
bool Board::is_check() const {
return check;
}
bool Board::is_checkmate() const {
return check && nlm;
}
bool Board::is_stalemate() const {
return !check && nlm;
}
bool Board::is_terminal() const {
return n_half_moves == 100 || insufficient_material() || is_checkmate()
|| is_stalemate();
}
std::vector<Move> Board::all_legal_moves() const {
std::vector<Move> ret;
for (int i = 0; i < 64; i++) {
@ -335,3 +410,28 @@ std::vector<Move> Board::all_legal_moves() const {
}
return ret;
}
std::vector<Move> Board::all_capturing_moves() const {
std::vector<Move> moves = all_legal_moves();
std::vector<Move> ret;
ret.reserve(moves.size());
for (const Move& move : moves)
if (piece_at(move.target_square) != Piece::None)
ret.push_back(move);
return ret;
}
std::vector<int8_t> Board::opponent_pawn_attack_map() const {
std::vector<int8_t> ret;
for (int i = 0; i < 64; i++) {
if (piece_at(i) == Piece::Pawn
&& ((colour_at(i) == White && !white_to_play)
|| (colour_at(i) == Black && white_to_play))) {
std::vector<int8_t> attack_map =
pawn_attack_map(*this, Coords::from_index(i));
ret.insert(ret.end(), attack_map.begin(), attack_map.end());
}
}
return ret;
}

View File

@ -9,7 +9,9 @@
struct Board {
private:
int8_t get_king_of(int8_t) const;
bool no_legal_moves_for(int8_t) const;
bool _no_legal_moves_for(Colour) const;
bool _is_check_for(Colour) const;
bool nlm = false, check = false;
public:
int8_t squares[64] = {Piece::None};
@ -17,43 +19,47 @@ struct Board {
int8_t w_castle_rights = CastleSide::NeitherSide;
int8_t b_castle_rights = CastleSide::NeitherSide;
int8_t en_passant_target = -1;
uint8_t n_half_moves = 0;
uint8_t n_full_moves = 0;
int n_half_moves = 0;
int n_full_moves = 0;
static Board setup_fen_position(std::string fen);
Board make_move(Move) const;
Board skip_turn() const;
Board make_move(Move, bool = true) const;
std::string to_fen() const;
bool is_check_for(int8_t) const;
bool no_legal_moves() const;
bool is_check() const;
bool insufficient_material_for(Colour) const;
bool insufficient_material() const {
return insufficient_material_for(White)
&& insufficient_material_for(Black);
};
std::vector<Move> all_legal_moves() const;
std::vector<Move> all_capturing_moves() const;
std::vector<int8_t> opponent_pawn_attack_map() const;
bool is_checkmate_for(int8_t colour) const {
return is_check_for(colour) && no_legal_moves_for(colour);
}
bool is_checkmate() const;
bool is_stalemate_for(int8_t colour) const {
return !is_check_for(colour) && no_legal_moves_for(colour);
}
bool is_stalemate() const;
bool is_terminal() const {
return is_checkmate_for(White) || is_checkmate_for(Black)
|| is_stalemate_for(White) || is_stalemate_for(Black);
}
bool is_terminal() const;
Piece piece_at(int8_t idx) const {
inline Piece piece_at(int8_t idx) const {
return (Piece) (squares[idx] & 0b00111);
}
Piece piece_at(Coords xy) const {
inline Piece piece_at(Coords xy) const {
return piece_at(xy.to_index());
}
Colour colour_at(int8_t idx) const {
inline Colour colour_at(int8_t idx) const {
return (Colour) (squares[idx] & 0b11000);
}
Colour colour_at(Coords xy) const {
inline Colour colour_at(Coords xy) const {
return colour_at(xy.to_index());
}
};

View File

@ -47,7 +47,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{3, 2812}, // 11
{4, 43238}, // 157
{5, 674624}, // 2199
// {6, 11030083},
{6, 11030083},
},
},
@ -55,11 +55,11 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{
"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 4",
{
{1, 6}, // 0
{2, 264}, // 1
{3, 9467}, // 69
{4, 422333}, // 3085
// {5, 15833292}, // 124452
{1, 6}, // 0
{2, 264}, // 1
{3, 9467}, // 69
{4, 422333}, // 3085
{5, 15833292}, // 124452
},
},
@ -67,11 +67,11 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{
"r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 5",
{
{1, 6}, // 0
{2, 264}, // 2
{3, 9467}, // 104
{4, 422333}, // 3742
// {5, 15833292}, // 136784
{1, 6}, // 0
{2, 264}, // 2
{3, 9467}, // 104
{4, 422333}, // 3742
{5, 15833292}, // 136784
},
},
@ -79,10 +79,10 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{
"rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 6",
{
{1, 44}, // 0
{2, 1486}, // 12
{3, 62379}, // 357
// {4, 2103487}, // 13804
{1, 44}, // 0
{2, 1486}, // 12
{3, 62379}, // 357
{4, 2103487}, // 13804
// {5, 89941194}, // 1230428
},
},
@ -92,10 +92,10 @@ static std::map<std::string, std::map<int, int>> pos2expected{
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 "
"7",
{
{1, 46}, // 0
{2, 2079}, // 16
{3, 89890}, // 602
// {4, 3894594}, // 26612
{1, 46}, // 0
{2, 2079}, // 16
{3, 89890}, // 602
{4, 3894594}, // 26612
// {5, 164075551}, // 1230428
},
},
@ -141,6 +141,7 @@ int move_generation_test(
} else {
// Regular sequential execution
for (const Move& move : moves) {
// std::cout << "Looking at " << move << std::endl;
Board tmp_board = b.make_move(move);
int n = move_generation_test(tmp_board, depth - 1, max_depth, pool);
if (depth == max_depth)

View File

@ -10,8 +10,9 @@ static bool is_clear_king_side(const Board& b, const Coords xy) {
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value());
if (board_after_move.is_check_for(b.colour_at(xy)))
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (board_after_move.is_check())
return false;
}
return true;
@ -24,8 +25,9 @@ static bool is_clear_queen_side(const Board& b, const Coords xy) {
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value());
if (dx < 3 && board_after_move.is_check_for(b.colour_at(xy)))
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (dx < 3 && board_after_move.is_check())
return false;
}
return true;
@ -46,7 +48,8 @@ std::vector<Move> king_moves(const Board& b, const Coords xy) {
}
}
if (b.is_check_for(b.colour_at(xy)))
if (b.is_check())
return keep_only_blocking(ret, b);
// -- Castles

View File

@ -19,7 +19,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
left.to_index(),
(int8_t) (my_colour | piece)
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), left.to_index()});
@ -39,7 +39,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
right.to_index(),
(int8_t) (my_colour | piece)
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), right.to_index()});
@ -68,7 +68,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
new_xy.to_index(),
.promoting_to = (int8_t) (my_colour | piece)
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{
@ -79,3 +79,24 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
return ret;
}
std::vector<int8_t> pawn_attack_map(const Board& b, Coords xy) {
std::vector<int8_t> ret{};
Colour my_colour = b.colour_at(xy);
// -- Capture to the left
if (xy.x > 0) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords left{xy.x - 1, xy.y + dy};
ret.push_back(left.to_index());
}
// -- Capture to the right
if (xy.x < 7) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords right{xy.x + 1, xy.y + dy};
ret.push_back(right.to_index());
}
return ret;
}

View File

@ -9,11 +9,12 @@ keep_only_blocking(const std::vector<Move> candidates, const Board& board) {
if (candidates.size() == 0)
return {};
Colour my_colour = board.colour_at(candidates[0].source_square);
std::vector<Move> ret;
for (Move move : candidates) {
Board board_after_move = board.make_move(move);
if (!board_after_move.is_check_for(my_colour))
Board board_after_move = board.make_move(move, false);
board_after_move = board_after_move.skip_turn();
if (!board_after_move.is_check())
ret.push_back(move);
}
return ret;

View File

@ -50,7 +50,12 @@ inline const char* to_string(Piece c) {
}
}
inline std::ostream& operator<<(std::ostream& os, const int8_t& i) {
inline std::ostream& operator<<(std::ostream& os, const Colour& i) {
os << std::to_string(i);
return os;
}
inline std::ostream& operator<<(std::ostream& os, const Piece& i) {
os << std::to_string(i);
return os;
}
@ -64,6 +69,7 @@ std::vector<Move> keep_only_blocking(const std::vector<Move>, const Board&);
std::optional<Move> move_for_position(const Board&, const Coords, const Coords);
std::vector<Move> look_direction(const Board&, const Coords, int, int);
std::vector<int8_t> pawn_attack_map(const Board&, const Coords);
std::vector<Move> pawn_moves(const Board&, const Coords);
std::vector<Move> rook_moves(const Board&, const Coords);
std::vector<Move> knight_moves(const Board&, const Coords);

View File

@ -0,0 +1,26 @@
#include "move.hpp"
#include "../board/board.hpp"
#include "utils.hpp"
#include <algorithm>
int Move::score_guess(const Board& b) const {
int ret = 0;
Piece me_piece = b.piece_at(source_square);
Piece captured_piece = b.piece_at(target_square);
if (captured_piece != Piece::None)
ret += 10 * piece_value(captured_piece) - piece_value(me_piece);
if (promoting_to != Piece::None)
ret += piece_value(promoting_to);
std::vector<int8_t> pawn_attack_map = b.opponent_pawn_attack_map();
if (std::find(pawn_attack_map.begin(), pawn_attack_map.end(), target_square)
!= pawn_attack_map.end())
ret -= me_piece;
return ret;
}

View File

@ -11,7 +11,9 @@ struct Move {
int8_t source_square;
int8_t target_square;
int8_t promoting_to = Piece::None;
Piece promoting_to = Piece::None;
int score_guess(const Board&) const;
std::string to_string() const {
std::stringstream ss;

View File

@ -1,8 +1,35 @@
#include "utils.hpp"
#include "../board/board.hpp"
std::vector<int8_t> to_target_square(std::vector<Move> moves) {
std::vector<int8_t> ret;
for (Move move : moves)
ret.push_back(move.target_square);
return ret;
}
int piece_value(Piece p) {
switch (p) {
case Piece::Pawn:
return PawnValue;
case Piece::Knigt:
return KnightValue;
case Piece::Bishop:
return BishopValue;
case Piece::Rook:
return RookValue;
case Piece::Queen:
return QueenValue;
default:
return 0;
}
}
int count_material(const Board& b, int8_t colour) {
int ret = 0;
for (int i = 0; i < 64; i++)
if (b.colour_at(i) == colour)
ret += piece_value(b.piece_at(i));
return ret;
}

View File

@ -3,6 +3,17 @@
#include "move.hpp"
#include <cstdint>
#include <limits>
#include <vector>
std::vector<int8_t> to_target_square(std::vector<Move>);
int count_material(const Board&, int8_t);
int piece_value(Piece);
const int INFINITY = std::numeric_limits<int>::max();
const int PawnValue = 100;
const int KnightValue = 300;
const int BishopValue = 320;
const int RookValue = 500;
const int QueenValue = 900;

View File

@ -134,6 +134,81 @@ void GUI::draw_pieces(const Board& board) {
}
}
int GUI::show_popup(
const std::string& message, const std::vector<std::string>& options
) {
sf::RenderWindow popup(sf::VideoMode(300, 200), "Choice");
sf::Font font;
if (!font.loadFromFile("res/arial.ttf")) {
std::cerr << "Error: Could not load font!" << std::endl;
return -1;
}
sf::Text text(message, font, 20);
text.setPosition(20, 20);
text.setFillColor(sf::Color::Black);
std::vector<sf::RectangleShape> buttonShapes;
std::vector<sf::Text> buttonTexts;
for (size_t i = 0; i < options.size(); ++i) {
sf::RectangleShape button(sf::Vector2f(200, 30));
button.setPosition(50, 70 + i * 40);
button.setFillColor(sf::Color(150, 150, 150));
buttonShapes.push_back(button);
sf::Text buttonText(options[i], font, 18);
buttonText.setPosition(60, 75 + i * 40);
buttonText.setFillColor(sf::Color::Black);
buttonTexts.push_back(buttonText);
}
while (popup.isOpen()) {
sf::Event event;
while (popup.pollEvent(event)) {
if (event.type == sf::Event::Closed)
popup.close();
else if (event.type == sf::Event::MouseButtonPressed) {
for (size_t i = 0; i < buttonShapes.size(); ++i) {
if (buttonShapes[i].getGlobalBounds().contains(
event.mouseButton.x,
event.mouseButton.y
)) {
popup.close();
return i;
}
}
}
}
popup.clear(sf::Color::White);
popup.draw(text);
for (size_t i = 0; i < buttonShapes.size(); ++i) {
popup.draw(buttonShapes[i]);
popup.draw(buttonTexts[i]);
}
popup.display();
}
return -1;
}
Piece GUI::ask_about_promotion() {
std::vector<std::string> options = {"Queen", "Rook", "Bishop", "Knight"};
int idx = show_popup("Please choose a promotion for your pawn", options);
switch (idx) {
case 0:
return Queen;
case 1:
return Rook;
case 2:
return Bishop;
case 3:
return Knigt;
};
return Piece::None;
}
void GUI::show() {
while (window.isOpen())
handle_events();

View File

@ -15,6 +15,7 @@ class GUI : public View {
GUI();
void show() override;
Piece ask_about_promotion();
void update_board(const Board&, int8_t, std::vector<int8_t>) override;
void notify_checkmate(Colour) override;
void notify_stalemate(Colour) override;
@ -27,6 +28,10 @@ class GUI : public View {
sf::Color colours[2] = {sf::Color(0xB88762FF), sf::Color(0xEDD6B0FF)};
sf::Color alt_colours[2] = {sf::Color(0xDCC34BFF), sf::Color(0xF6EB72FF)};
int show_popup(
const std::string& message, const std::vector<std::string>& options
);
void load_textures();
void handle_events();
void handle_click(int, int);

View File

@ -9,4 +9,8 @@ class NoOpView : public View {
void update_board(const Board&, int8_t, std::vector<int8_t>) override {};
void notify_checkmate(Colour) override{};
void notify_stalemate(Colour) override{};
Piece ask_about_promotion() override {
return Queen;
};
};

View File

@ -15,6 +15,7 @@ class View {
}
virtual void show() = 0;
virtual Piece ask_about_promotion() = 0;
virtual void update_board(const Board&, int8_t, std::vector<int8_t>) = 0;
virtual void notify_checkmate(Colour) = 0;
virtual void notify_stalemate(Colour) = 0;

View File

@ -1,4 +1,4 @@
#include "../src/board.hpp"
#include "../src/model/board/board.hpp"
#include "lib.hpp"
int main() {

View File

@ -1,4 +1,4 @@
#include "../src/coords.hpp"
#include "../src/model/utils/coords.hpp"
#include "lib.hpp"
int main() {