20-add-negamax-eval-function #31
@@ -28,6 +28,9 @@ FILES = {c:i for i,c in enumerate("abcdefgh")}
|
|||||||
WHITE, BLACK, BOTH = 0, 1, 2
|
WHITE, BLACK, BOTH = 0, 1, 2
|
||||||
P, N, B, R, Q, K, p, n, b, r, q, k = range(12)
|
P, N, B, R, Q, K, p, n, b, r, q, k = range(12)
|
||||||
|
|
||||||
|
PIECE_CH = ('P','N','B','R','Q','K','p','n','b','r','q','k')
|
||||||
|
FILES_STR = "a b c d e f g h"
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Register C function bindings and interfaces in a dictionary.
|
Register C function bindings and interfaces in a dictionary.
|
||||||
@@ -58,6 +61,9 @@ FFI_SPEC = {
|
|||||||
"perft": ((C.POINTER(Board), C.c_int), C.c_uint64),
|
"perft": ((C.POINTER(Board), C.c_int), C.c_uint64),
|
||||||
"load_fen": ((C.POINTER(Board), C.c_char_p), C.c_int),
|
"load_fen": ((C.POINTER(Board), C.c_char_p), C.c_int),
|
||||||
"gen_pseudo_moves": ((C.POINTER(Board), C.POINTER(Move), C.c_bool), C.c_int),
|
"gen_pseudo_moves": ((C.POINTER(Board), C.POINTER(Move), C.c_bool), C.c_int),
|
||||||
|
"apply_move_on_copy": ((C.POINTER(Board), C.POINTER(Board), Move), C.c_bool),
|
||||||
|
# AI Methods
|
||||||
|
"ai_find_best_move_with_window": ((C.POINTER(Board), C.c_int, C.c_int, C.POINTER(Move)), C.c_int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -82,6 +88,28 @@ def draw_bb(mask, origin=None):
|
|||||||
print(lines, "\n")
|
print(lines, "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def print_board(board):
|
||||||
|
"""
|
||||||
|
Prints the current position using piece letters from board.pieces.
|
||||||
|
highlight_sq: optional 0..63 square index to mark with 'O'.
|
||||||
|
"""
|
||||||
|
def piece_at(sq: int) -> str:
|
||||||
|
for i, ch in enumerate(PIECE_CH):
|
||||||
|
if (int(board.pieces[i]) >> sq) & 1:
|
||||||
|
return ch
|
||||||
|
return '.'
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for r in range(7, -1, -1):
|
||||||
|
row = []
|
||||||
|
for f in range(8):
|
||||||
|
sq = r * 8 + f
|
||||||
|
row.append(piece_at(sq))
|
||||||
|
lines.append(f"{r+1} " + " ".join(row))
|
||||||
|
lines.append(" " + FILES_STR)
|
||||||
|
print("\n" + "\n".join(lines) + "\n")
|
||||||
|
|
||||||
|
|
||||||
def sq_to_coord(sq):
|
def sq_to_coord(sq):
|
||||||
return chr(ord('a') + (sq % 8)) + chr(ord('1') + (sq // 8))
|
return chr(ord('a') + (sq % 8)) + chr(ord('1') + (sq // 8))
|
||||||
|
|
||||||
@@ -131,6 +159,10 @@ class ChessFFI:
|
|||||||
return buf, n
|
return buf, n
|
||||||
|
|
||||||
|
|
||||||
|
def apply_move_on_copy(self, board, out_board, move):
|
||||||
|
return bool(self._c_apply_move_on_copy(C.byref(board), C.byref(out_board), move))
|
||||||
|
|
||||||
|
|
||||||
def get_legal_moves(self, board, out):
|
def get_legal_moves(self, board, out):
|
||||||
return self._c_get_legal_moves(board, out)
|
return self._c_get_legal_moves(board, out)
|
||||||
|
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
56
makefile
56
makefile
@@ -2,46 +2,64 @@ CC := gcc
|
|||||||
CFLAGS := -O3 -fPIC -Wall -Wextra
|
CFLAGS := -O3 -fPIC -Wall -Wextra
|
||||||
LDFLAGS := -shared
|
LDFLAGS := -shared
|
||||||
|
|
||||||
SRCDIR := engine/src
|
# ---- paths ----
|
||||||
INCDIR := engine/include
|
SRCDIRS := engine/src engine/src/ai
|
||||||
|
INCDIRS := engine/include
|
||||||
BUILDDIR := build
|
BUILDDIR := build
|
||||||
LIBNAME := libchess
|
LIBNAME := libchess
|
||||||
|
LIB := $(BUILDDIR)/$(LIBNAME).so
|
||||||
|
|
||||||
# Exclude test.c from the shared lib build
|
# ---- sources/objects ----
|
||||||
SRC := $(filter-out $(SRCDIR)/test.c,$(wildcard $(SRCDIR)/*.c))
|
EXCLUDE := engine/src/test.c
|
||||||
OBJ := $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRC))
|
SRCS := $(foreach d,$(SRCDIRS),$(wildcard $(d)/*.c))
|
||||||
LIB := $(BUILDDIR)/$(LIBNAME).so
|
SRCS := $(filter-out $(EXCLUDE),$(SRCS))
|
||||||
|
OBJS := $(patsubst %.c,$(BUILDDIR)/%.o,$(SRCS))
|
||||||
|
|
||||||
|
# ---- includes ----
|
||||||
|
INCLUDES := $(addprefix -I,$(INCDIRS))
|
||||||
|
|
||||||
# ---- test executable (engine/src/test.c) ----
|
# ---- test executable (engine/src/test.c) ----
|
||||||
TESTSRC := $(SRCDIR)/test.c
|
TESTSRC := engine/src/test.c
|
||||||
TESTOBJ := $(BUILDDIR)/test.o
|
TESTOBJ := $(BUILDDIR)/engine/src/test.o
|
||||||
TESTBIN := $(BUILDDIR)/print_board
|
TESTBIN := $(BUILDDIR)/print_board
|
||||||
|
|
||||||
.PHONY: all clean test test-exe run-c-test
|
# ---- runner (python) ----
|
||||||
|
PYTHON := python3
|
||||||
|
RUN_SCRIPT := scripts/simulation.py
|
||||||
|
ARGS ?=
|
||||||
|
|
||||||
|
.PHONY: all clean test test-exe run-c-test run-engine
|
||||||
|
|
||||||
all: $(LIB)
|
all: $(LIB)
|
||||||
|
|
||||||
$(BUILDDIR):
|
# generic object rule: build/<path>/file.o from <path>/file.c
|
||||||
@mkdir -p $(BUILDDIR)
|
$(BUILDDIR)/%.o: %.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||||
|
|
||||||
$(BUILDDIR)/%.o: $(SRCDIR)/%.c | $(BUILDDIR)
|
$(LIB): $(OBJS)
|
||||||
$(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@
|
@mkdir -p $(dir $@)
|
||||||
|
|
||||||
$(LIB): $(OBJ) | $(BUILDDIR)
|
|
||||||
$(CC) $(LDFLAGS) -o $@ $^
|
$(CC) $(LDFLAGS) -o $@ $^
|
||||||
|
|
||||||
# ---- test exe rules ----
|
# ---- test exe rules ----
|
||||||
test-exe: $(TESTBIN)
|
test-exe: $(TESTBIN)
|
||||||
|
|
||||||
$(TESTOBJ): $(TESTSRC) | $(BUILDDIR)
|
$(TESTOBJ): $(TESTSRC)
|
||||||
$(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@
|
@mkdir -p $(dir $@)
|
||||||
|
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||||
|
|
||||||
$(TESTBIN): $(TESTOBJ) $(LIB) | $(BUILDDIR)
|
$(TESTBIN): $(TESTOBJ) $(LIB)
|
||||||
$(CC) -O2 -o $@ $(TESTOBJ) -L$(BUILDDIR) -lchess -Wl,-rpath,'$$ORIGIN'
|
$(CC) -O2 -o $@ $(TESTOBJ) -L$(BUILDDIR) -lchess -Wl,-rpath,'$$ORIGIN'
|
||||||
|
|
||||||
run-c-test: $(TESTBIN)
|
run-c-test: $(TESTBIN)
|
||||||
LD_LIBRARY_PATH=$(BUILDDIR) $(TESTBIN) $(FEN)
|
LD_LIBRARY_PATH=$(BUILDDIR) $(TESTBIN) $(FEN)
|
||||||
|
|
||||||
|
# ---- run engine (Python) ----
|
||||||
|
# Usage: make run-engine ARGS="--fen '...' --depth 5"
|
||||||
|
run-engine: $(LIB)
|
||||||
|
@echo "Running engine via $(RUN_SCRIPT)"
|
||||||
|
LD_LIBRARY_PATH=$(BUILDDIR) $(PYTHON) $(RUN_SCRIPT) $(ARGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning build and Python caches..."
|
@echo "Cleaning build and Python caches..."
|
||||||
@rm -rf $(BUILDDIR)
|
@rm -rf $(BUILDDIR)
|
||||||
|
|||||||
62
scripts/evaluation.py
Normal file
62
scripts/evaluation.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import random
|
||||||
|
import ctypes as C
|
||||||
|
from binding.python_c_ffi import ChessFFI
|
||||||
|
from binding.python_c_ffi import Move
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEvaluation:
|
||||||
|
def __init__(self, chess_ffi=None):
|
||||||
|
if chess_ffi is None:
|
||||||
|
chess_ffi = ChessFFI()
|
||||||
|
self.chess_ffi = chess_ffi
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_move(self, board, legal_moves):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
We will use a random move evaluation as our base AI. This
|
||||||
|
is expected to be the worst performing strategy. We can
|
||||||
|
play our other AI evaluation methods against this one to
|
||||||
|
confirm if our other strategies are at least better than
|
||||||
|
a simplistic approach.
|
||||||
|
"""
|
||||||
|
class RandomEval(BaseEvaluation):
|
||||||
|
def __init__(self, chess_ffi=None):
|
||||||
|
super().__init__(chess_ffi)
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_move(self, board, legal_moves):
|
||||||
|
return random.choice(legal_moves)
|
||||||
|
|
||||||
|
|
||||||
|
class NegaMaxEval(BaseEvaluation):
|
||||||
|
def __init__(self, depth=6, cp_window=20, chess_ffi=None):
|
||||||
|
super().__init__(chess_ffi)
|
||||||
|
self.depth = depth
|
||||||
|
self.window = cp_window
|
||||||
|
|
||||||
|
|
||||||
|
def _same(self, a, b):
|
||||||
|
return (int(getattr(a, "from")) == int(getattr(b, "from"))
|
||||||
|
and int(a.to) == int(b.to)
|
||||||
|
and int(getattr(a, "promo", 0) or 0) == int(getattr(b, "promo", 0) or 0))
|
||||||
|
|
||||||
|
def get_best_move(self, board, legal_moves):
|
||||||
|
if not legal_moves:
|
||||||
|
raise RuntimeError("No legal moves")
|
||||||
|
|
||||||
|
best = Move()
|
||||||
|
ok = self.chess_ffi._c_ai_find_best_move_with_window(
|
||||||
|
board,
|
||||||
|
self.depth,
|
||||||
|
self.window,
|
||||||
|
best
|
||||||
|
)
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
for m in legal_moves:
|
||||||
|
if self._same(m, best):
|
||||||
|
return m
|
||||||
|
return legal_moves[0]
|
||||||
149
scripts/simulation.py
Normal file
149
scripts/simulation.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
from binding.python_c_ffi import Board
|
||||||
|
from binding.python_c_ffi import Move
|
||||||
|
from binding.python_c_ffi import ChessFFI
|
||||||
|
from binding.python_c_ffi import WHITE
|
||||||
|
from binding.python_c_ffi import print_board
|
||||||
|
from binding.python_c_ffi import sq_to_coord
|
||||||
|
from scripts.evaluation import RandomEval
|
||||||
|
from scripts.evaluation import NegaMaxEval
|
||||||
|
|
||||||
|
|
||||||
|
YMD_HM = "%Y-%m-%d-%H-%M"
|
||||||
|
MAX_MOVES = 256
|
||||||
|
DATA_PATH = "./data"
|
||||||
|
|
||||||
|
|
||||||
|
class Engine:
|
||||||
|
def __init__(self, strat_white=None, strat_black=None, max_plys=500):
|
||||||
|
self.chess_ffi = ChessFFI()
|
||||||
|
self.board = Board()
|
||||||
|
self.max_plys = max_plys
|
||||||
|
self.moves = []
|
||||||
|
self._load_attack_cache()
|
||||||
|
self._seed_engine()
|
||||||
|
|
||||||
|
random_strat = RandomEval(chess_ffi=self.chess_ffi)
|
||||||
|
nega_strat = NegaMaxEval(chess_ffi=self.chess_ffi, depth=2, cp_window=30)
|
||||||
|
if not strat_white:
|
||||||
|
self.strat_white = random_strat
|
||||||
|
else:
|
||||||
|
self.strat_white = strat_white
|
||||||
|
|
||||||
|
if not strat_black:
|
||||||
|
self.strat_black = random_strat
|
||||||
|
else:
|
||||||
|
self.strat_black = strat_black
|
||||||
|
|
||||||
|
|
||||||
|
def run(self, fen):
|
||||||
|
self.chess_ffi.load_fen(self.board, fen)
|
||||||
|
self._clear_moves()
|
||||||
|
plys = 0
|
||||||
|
|
||||||
|
while plys <= self.max_plys:
|
||||||
|
print_board(self.board)
|
||||||
|
|
||||||
|
moves_buf = (Move * MAX_MOVES)()
|
||||||
|
n_legal = self.chess_ffi.get_legal_moves(self.board, moves_buf)
|
||||||
|
|
||||||
|
game_over, ending = self._is_game_over(n_legal)
|
||||||
|
if game_over:
|
||||||
|
print(ending)
|
||||||
|
break
|
||||||
|
|
||||||
|
legal = [moves_buf[i] for i in range(n_legal)]
|
||||||
|
if self.board.side_to_move == WHITE:
|
||||||
|
best_move = self.strat_white.get_best_move(self.board, legal)
|
||||||
|
else:
|
||||||
|
best_move = self.strat_black.get_best_move(self.board, legal)
|
||||||
|
|
||||||
|
|
||||||
|
new_board = Board()
|
||||||
|
if not self.chess_ffi.apply_move_on_copy(self.board, new_board, best_move):
|
||||||
|
print("ERROR: apply_move_on_copy failed")
|
||||||
|
break
|
||||||
|
|
||||||
|
self.board = new_board
|
||||||
|
plys += 1
|
||||||
|
|
||||||
|
move = self.to_uci(best_move)
|
||||||
|
self.moves.append(move)
|
||||||
|
|
||||||
|
if self.board.halfmove_clock >= 100:
|
||||||
|
print("draw (50-move rule)")
|
||||||
|
break
|
||||||
|
|
||||||
|
for move in self.moves:
|
||||||
|
# print(move)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
print("done")
|
||||||
|
print(f"winner: {self.side_tag(not self.board.side_to_move)}")
|
||||||
|
print(len(self.moves))
|
||||||
|
|
||||||
|
|
||||||
|
def side_tag(self, side):
|
||||||
|
return 'w' if side == WHITE else 'b'
|
||||||
|
|
||||||
|
|
||||||
|
def load_fen(self, fen):
|
||||||
|
self.chess_ffi.load_fen(self.board, fen)
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_moves(self):
|
||||||
|
self.moves = []
|
||||||
|
|
||||||
|
|
||||||
|
def to_uci(self, move):
|
||||||
|
fr = sq_to_coord(getattr(move, "from"))
|
||||||
|
to = sq_to_coord(move.to)
|
||||||
|
promo = getattr(move, "promo", 0) or ""
|
||||||
|
|
||||||
|
if promo:
|
||||||
|
MAP = {
|
||||||
|
1:"n",
|
||||||
|
2:"b",
|
||||||
|
3:"r",
|
||||||
|
4:"q",
|
||||||
|
"n":"n",
|
||||||
|
"b":"b",
|
||||||
|
"r":"r",
|
||||||
|
"q":"q"
|
||||||
|
}
|
||||||
|
promo = MAP.get(promo, "").lower()
|
||||||
|
return f"{fr}{to}{promo}"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_attack_cache(self):
|
||||||
|
self.chess_ffi.init_attack_cache()
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_engine(self):
|
||||||
|
import ctypes as C, time
|
||||||
|
|
||||||
|
seed = time.time_ns()
|
||||||
|
s32 = int(seed) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
libc = C.CDLL("libc.so.6") # on Ubuntu
|
||||||
|
libc.srand.argtypes = (C.c_uint,)
|
||||||
|
libc.srand.restype = None
|
||||||
|
libc.srand(C.c_uint(s32))
|
||||||
|
return s32
|
||||||
|
|
||||||
|
|
||||||
|
def _is_game_over(self, legal_moves):
|
||||||
|
if legal_moves == 0:
|
||||||
|
if self.chess_ffi.in_check(self.board, self.board.side_to_move):
|
||||||
|
ending = "checkmate"
|
||||||
|
else:
|
||||||
|
ending = "stalemate"
|
||||||
|
return True, ending
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
engine = Engine()
|
||||||
|
|
||||||
|
fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||||
|
engine.run(fen)
|
||||||
Reference in New Issue
Block a user