Add some basic algorithms and eval functions for an AI
All checks were successful
Python tests (make) / test (push) Successful in 11s
All checks were successful
Python tests (make) / test (push) Successful in 11s
This commit is contained in:
4
engine/include/ai.h
Normal file
4
engine/include/ai.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "bitboard.h"
|
||||
|
||||
int ai_find_best_move_with_window(struct Board *b, int depth, int window_cp, struct Move *best_out);
|
||||
int ai_play(struct Board *b, int depth);
|
||||
123
engine/src/ai/negamax.c
Normal file
123
engine/src/ai/negamax.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
#include "ai.h"
|
||||
#include "bitboard.h"
|
||||
|
||||
#define INF 30000
|
||||
#define MATE 29000
|
||||
|
||||
// P N B R Q K
|
||||
int VAL[6] = {100, 300, 300, 500, 900, 0};
|
||||
|
||||
int popcount64(uint64_t bits) {
|
||||
int count = 0;
|
||||
while (bits) {
|
||||
// clear lowest set bit.
|
||||
bits &= (bits - 1);
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int eval(struct Board *board) {
|
||||
// White minus Black material (centipawns)
|
||||
// Ignore the King's material.
|
||||
int score = 0;
|
||||
score += VAL[0] * (popcount64(board->pieces[P]) - popcount64(board->pieces[p]));
|
||||
score += VAL[1] * (popcount64(board->pieces[N]) - popcount64(board->pieces[n]));
|
||||
score += VAL[2] * (popcount64(board->pieces[B]) - popcount64(board->pieces[b]));
|
||||
score += VAL[3] * (popcount64(board->pieces[R]) - popcount64(board->pieces[r]));
|
||||
score += VAL[4] * (popcount64(board->pieces[Q]) - popcount64(board->pieces[q]));
|
||||
|
||||
// Negamax convention: score from side-to-move POV
|
||||
return (board->side_to_move == WHITE) ? score : -score;
|
||||
}
|
||||
|
||||
static int search(struct Board *b, int depth, int alpha, int beta, int ply) {
|
||||
if (depth == 0) return eval(b);
|
||||
|
||||
struct Move moves[256];
|
||||
int n = get_legal_moves((struct Board*)b, moves);
|
||||
|
||||
if (n == 0) {
|
||||
// terminal: checkmate or stalemate
|
||||
// prefer quicker mates
|
||||
if (in_check(b, b->side_to_move)) {
|
||||
return -MATE + ply;
|
||||
}
|
||||
// Stalemate
|
||||
return 0;
|
||||
}
|
||||
|
||||
int best = -INF;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
struct Board after;
|
||||
if (!apply_move_on_copy((struct Board*)b, &after, moves[i])) continue;
|
||||
|
||||
int score = -search(&after, depth - 1, -beta, -alpha, ply + 1);
|
||||
if (score > best) best = score;
|
||||
if (score > alpha) alpha = score;
|
||||
if (alpha >= beta) break; // cutoff
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
int rand_uniform(int upper_bound) {
|
||||
if (upper_bound <= 0) return 0;
|
||||
// Rejection sampling to reduce modulo bias
|
||||
int limit = RAND_MAX - (RAND_MAX % upper_bound);
|
||||
int r;
|
||||
do { r = rand(); } while (r > limit);
|
||||
return r % upper_bound;
|
||||
}
|
||||
|
||||
int ai_find_best_move_with_window(struct Board *board, int depth, int window_cp, struct Move *best_out) {
|
||||
enum { MAX_MOVES = 256 };
|
||||
struct Move moves[MAX_MOVES];
|
||||
int n = get_legal_moves(board, moves);
|
||||
if (n <= 0) return 0;
|
||||
|
||||
int scores[MAX_MOVES];
|
||||
int best = -INF;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
struct Board after;
|
||||
if (!apply_move_on_copy(board, &after, moves[i])) return 0;
|
||||
|
||||
// Root child: depth-1; alpha=-INF, beta=+INF; ply starts at 1
|
||||
int s = -search(&after, depth - 1, -INF, INF, 1);
|
||||
scores[i] = s;
|
||||
if (s > best) best = s;
|
||||
}
|
||||
|
||||
// collect candidates within window
|
||||
int cand_idx[MAX_MOVES], cand_n = 0;
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (scores[i] >= best - window_cp)
|
||||
cand_idx[cand_n++] = i;
|
||||
|
||||
// fallback if window excludes all
|
||||
if (cand_n == 0) {
|
||||
int best_i = 0;
|
||||
for (int i = 1; i < n; ++i)
|
||||
if (scores[i] > scores[best_i]) best_i = i;
|
||||
*best_out = moves[best_i];
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Select a random candidate to prevent deterministic results.
|
||||
// I found that games were coming out with the same result each
|
||||
// time when the AI played itself.
|
||||
int pick = cand_idx[rand_uniform(cand_n)];
|
||||
*best_out = moves[pick];
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ai_play(struct Board *b, int depth) {
|
||||
struct Move m;
|
||||
struct Board after;
|
||||
if (!apply_move_on_copy(b, &after, m)) return 0;
|
||||
*b = after;
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user