Skip to content

Commit 12285b2

Browse files
committed
Slight improvement in chess bot
1 parent d87187f commit 12285b2

File tree

9 files changed

+251
-110
lines changed

9 files changed

+251
-110
lines changed

notebooks/benchmark.ipynb

Lines changed: 55 additions & 25 deletions
Large diffs are not rendered by default.

src/api/get/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod root;
22
pub mod get_eval;
3+
pub mod static_eval;

src/api/get/static_eval.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use axum::{ extract::Json, response::IntoResponse, http::StatusCode };
2+
use serde::{ Deserialize, Serialize };
3+
use std::str::FromStr;
4+
use chess::Board;
5+
6+
use crate::bot::algorithm::eval::evaluate_board;
7+
8+
#[derive(Debug, Deserialize)]
9+
pub struct EvalRequest {
10+
pub current_fen: String,
11+
}
12+
13+
#[derive(Debug, Serialize)]
14+
pub struct StaticEvalResponse {
15+
pub eval: i32,
16+
}
17+
18+
pub async fn static_eval_handler(Json(payload): Json<EvalRequest>) -> impl IntoResponse {
19+
let current_board = match Board::from_str(&payload.current_fen) {
20+
Ok(board) => board,
21+
Err(_) => {
22+
return (
23+
StatusCode::BAD_REQUEST,
24+
Json(StaticEvalResponse {
25+
eval: 0,
26+
}),
27+
);
28+
}
29+
};
30+
31+
let eval = evaluate_board(&current_board);
32+
33+
(
34+
StatusCode::OK,
35+
Json(StaticEvalResponse {
36+
eval,
37+
}),
38+
)
39+
}

src/bot/algorithm/ab.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
use chess::{ Board, ChessMove, MoveGen };
22
use std::time::Instant;
33
use crate::bot::algorithm::eval::evaluate_board;
4-
use crate::bot::algorithm::root::QUIET_SEARCH_DEPTH;
4+
use crate::bot::include::types::SpecialMove;
55
use crate::bot::util::board::BoardExt;
66
use crate::bot::util::piece::piece_value;
77
use crate::bot::{ include::types::{ EngineState } };
88

9-
pub fn get_prioritized_moves(board: &Board, is_capture: bool) -> Vec<(ChessMove, i32)> {
9+
pub fn get_prioritized_moves(board: &Board, only_noise: bool) -> Vec<(ChessMove, i32)> {
1010
let mut move_priority_pairs = Vec::new();
1111

1212
for mv in MoveGen::new_legal(board) {
13-
if is_capture {
13+
if only_noise {
14+
let move_tags = board.classify_move(mv);
15+
if
16+
!move_tags.contains(&SpecialMove::Promotion) &&
17+
!move_tags.contains(&SpecialMove::Capture)
18+
{
19+
continue;
20+
}
1421
if let Some((attacker, victim)) = board.capture_pieces(mv) {
1522
if piece_value(victim) < piece_value(attacker) {
1623
continue;
1724
}
18-
} else {
19-
continue;
2025
}
2126
}
2227

@@ -58,7 +63,8 @@ pub fn alpha_beta(
5863
match board.status() {
5964
chess::BoardStatus::Checkmate => {
6065
let eval = evaluate_board(board);
61-
let score = (eval * ((depth as i32) + 1)).clamp(-100_000, 100_000);
66+
let depth_weight = 20 - current_depth.min(20);
67+
let score = (eval * ((depth_weight as i32) + 1)).clamp(-500_000, 500_000);
6268
return (None, score);
6369
}
6470
chess::BoardStatus::Stalemate => {
@@ -76,7 +82,7 @@ pub fn alpha_beta(
7682
nodes,
7783
deadline,
7884
engine_state,
79-
QUIET_SEARCH_DEPTH,
85+
current_depth-1,
8086
true,
8187
current_depth + 1,
8288
max_depth_reached
@@ -112,7 +118,6 @@ pub fn alpha_beta(
112118
max_depth_reached
113119
);
114120

115-
116121
if let Some(count) = engine_state.history.get_mut(&new_hash) {
117122
*count -= 1;
118123
if *count == 0 {

src/bot/algorithm/eval.rs

Lines changed: 112 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,30 @@
1-
use chess::{ Board, Piece };
1+
use chess::{ Board, Color, File, Piece, Rank, Square };
22
use crate::bot::{ include::types::GlobalMap, util::{ board::BoardExt, piece::piece_value } };
33

4-
fn is_endgame(board: &Board) -> bool {
5-
// Simple heuristic: endgame if total material (excluding kings) is low
6-
let mut non_king_material = 0;
7-
8-
for sq in chess::ALL_SQUARES {
9-
if let Some(piece) = board.piece_on(sq) {
10-
if piece == Piece::King {
11-
continue;
12-
}
13-
14-
non_king_material += match piece {
15-
Piece::Pawn => 100,
16-
Piece::Knight => 320,
17-
Piece::Bishop => 330,
18-
Piece::Rook => 500,
19-
Piece::Queen => 900,
20-
_ => 0,
21-
};
22-
}
23-
}
24-
25-
non_king_material < 1600 // adjust threshold as needed
26-
}
27-
28-
fn square_distance(a: chess::Square, b: chess::Square) -> u8 {
29-
let file_dist = ((a.get_file().to_index() as i32) - (b.get_file().to_index() as i32)).abs();
30-
let rank_dist = ((a.get_rank().to_index() as i32) - (b.get_rank().to_index() as i32)).abs();
31-
(file_dist + rank_dist) as u8
4+
fn distance_between(a: Square, b: Square) -> u8 {
5+
let file_distance = ((a.get_file().to_index() as i8) - (b.get_file().to_index() as i8)).abs();
6+
let rank_distance = ((a.get_rank().to_index() as i8) - (b.get_rank().to_index() as i8)).abs();
7+
(file_distance + rank_distance) as u8
328
}
339

34-
fn evaluate_king_proximity(board: &Board) -> i32 {
35-
use chess::{ Color::*, Piece::King };
36-
37-
if !is_endgame(board) {
38-
return 0;
10+
fn evaluate_king_proximity(board: &Board, is_endgame: bool) -> i32 {
11+
if is_endgame {
12+
let white_king_sq = (board.pieces(Piece::King) & board.color_combined(Color::White))
13+
.into_iter()
14+
.next()
15+
.unwrap();
16+
let black_king_sq = (board.pieces(Piece::King) & board.color_combined(Color::Black))
17+
.into_iter()
18+
.next()
19+
.unwrap();
20+
21+
let proximity = distance_between(white_king_sq, black_king_sq) as i32;
22+
let score = 11 + (14 - proximity);
23+
24+
return score;
3925
}
4026

41-
let white_king_sq = (board.pieces(King) & board.color_combined(White))
42-
.into_iter()
43-
.next()
44-
.unwrap();
45-
let black_king_sq = (board.pieces(King) & board.color_combined(Black))
46-
.into_iter()
47-
.next()
48-
.unwrap();
49-
50-
let dist = square_distance(white_king_sq, black_king_sq);
51-
let proximity_score = 10 - (dist as i32); // Closer = better
52-
53-
proximity_score * 3
27+
0
5428
}
5529

5630
fn evaluate_connected_pawns(board: &Board) -> i32 {
@@ -66,21 +40,84 @@ fn evaluate_connected_pawns(board: &Board) -> i32 {
6640
let file = sq.get_file().to_index();
6741

6842
let connected = [-1, 1].iter().any(|&df| {
69-
let f = ((file as isize) + df) as usize;
70-
if f > 7 {
43+
let f = (file as isize) + df;
44+
if f < 0 || f > 7 {
7145
return false;
7246
}
73-
let adj_sq = chess::Square::make_square(
74-
chess::Rank::from_index(rank),
75-
chess::File::from_index(f)
76-
);
77-
board.piece_on(adj_sq) == Some(Pawn) && board.color_on(adj_sq) == Some(color)
47+
48+
// Check same rank, one ahead, and one behind
49+
[-1, 0, 1].iter().any(|&dr| {
50+
let r = (rank as isize) + dr;
51+
if r < 0 || r > 7 {
52+
return false;
53+
}
54+
55+
let adj_sq = chess::Square::make_square(
56+
chess::Rank::from_index(r as usize),
57+
chess::File::from_index(f as usize)
58+
);
59+
board.piece_on(adj_sq) == Some(Pawn) && board.color_on(adj_sq) == Some(color)
60+
})
7861
});
7962

8063
if connected {
8164
score += match color {
82-
White => 10,
83-
Black => -10,
65+
White => 15,
66+
Black => -15,
67+
};
68+
}
69+
}
70+
}
71+
72+
score
73+
}
74+
75+
pub fn evaluate_passed_pawns(board: &Board) -> i32 {
76+
use Color::{ White, Black };
77+
use Piece::Pawn;
78+
79+
let mut score = 0;
80+
81+
for &color in &[White, Black] {
82+
let pawns = board.pieces(Pawn) & board.color_combined(color);
83+
let opponent_color = match color {
84+
Color::White => Color::Black,
85+
Color::Black => Color::White,
86+
};
87+
88+
for sq in pawns {
89+
let rank_idx = sq.get_rank().to_index();
90+
let file_idx = sq.get_file().to_index();
91+
92+
// Check files: current, left, right
93+
let file_range = file_idx.saturating_sub(1)..=(file_idx + 1).min(7);
94+
95+
let is_passed = file_range.clone().all(|f| {
96+
let file = File::from_index(f);
97+
match color {
98+
White => {
99+
// Check ahead of current rank
100+
(rank_idx + 1..=7).all(|r| {
101+
let sq = Square::make_square(Rank::from_index(r), file);
102+
board.piece_on(sq) != Some(Pawn) ||
103+
board.color_on(sq) != Some(opponent_color)
104+
})
105+
}
106+
Black => {
107+
// Check behind current rank
108+
(0..rank_idx).all(|r| {
109+
let sq = Square::make_square(Rank::from_index(r), file);
110+
board.piece_on(sq) != Some(Pawn) ||
111+
board.color_on(sq) != Some(opponent_color)
112+
})
113+
}
114+
}
115+
});
116+
117+
if is_passed {
118+
score += match color {
119+
White => 15,
120+
Black => -15,
84121
};
85122
}
86123
}
@@ -93,15 +130,20 @@ pub fn evaluate_board(board: &Board) -> i32 {
93130
use chess::{ Piece::*, Color::* };
94131

95132
// Fifty-move rule draw
96-
if board.halfmove_clock() >= 50 {
133+
if board.halfmove_clock() >= 100 {
97134
return 0;
98135
}
99136

100137
// Check for checkmate
101138
if board.status() == chess::BoardStatus::Checkmate {
102139
return if board.side_to_move() == White { -10_000 } else { 10_000 };
140+
} else if board.status() == chess::BoardStatus::Stalemate {
141+
return 0;
103142
}
104143

144+
let white_base: i32 = board.material_score(chess::Color::White);
145+
let black_base = board.material_score(chess::Color::Black);
146+
105147
let mut white_total = 0;
106148
let mut black_total = 0;
107149
let mut white_bishops = 0;
@@ -160,9 +202,8 @@ pub fn evaluate_board(board: &Board) -> i32 {
160202
return 0;
161203
}
162204

163-
// Case 2: One side has only king+bishop or king+knight; ignore its score
164205
let mut score: i32 = 0;
165-
let is_endgame = is_endgame(board);
206+
let is_endgame = white_base + black_base < 1600;
166207

167208
for sq in chess::ALL_SQUARES {
168209
if let Some(piece) = board.piece_on(sq) {
@@ -189,22 +230,29 @@ pub fn evaluate_board(board: &Board) -> i32 {
189230
}
190231
};
191232

192-
let value = base + positional;
193-
233+
// Case 2: One side has only king+bishop or king+knight; ignore its base score
194234
if color == White {
195235
if !is_minor_or_lone(white_total, white_bishops, white_knights) {
196-
score += value;
236+
score += base;
197237
}
238+
score += positional;
198239
} else {
199240
if !is_minor_or_lone(black_total, black_bishops, black_knights) {
200-
score -= value;
241+
score -= base;
201242
}
243+
score -= positional;
202244
}
203245
}
204246
}
205247

248+
let proximity_score = evaluate_king_proximity(board, is_endgame);
206249
score += evaluate_connected_pawns(board);
207-
score += evaluate_king_proximity(board);
250+
score += evaluate_passed_pawns(board);
251+
if white_base > black_base {
252+
score += proximity_score;
253+
} else {
254+
score -= proximity_score;
255+
}
208256

209257
score
210258
}

src/bot/algorithm/root.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ use crate::bot::algorithm::eval::evaluate_board;
55
use crate::bot::util::lookup::lookup_opening_db;
66
use crate::bot::{ include::types::{ EngineState } };
77

8-
pub const QUIET_SEARCH_DEPTH: u8 = 4;
9-
108
pub fn search(
119
time_left_ms: u128,
1210
time_limit_ms: Option<u128>,

src/bot/include/map.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ fn read_opening_db() -> Result<Value, io::Error> {
3131
let tar = GzDecoder::new(tar_file);
3232
let mut archive = Archive::new(tar);
3333
archive.unpack(output_dir)?;
34-
34+
3535
// Clean up
3636
fs::remove_file(&compressed_path)?;
3737
println!("OpeningDB extracted to {:?}", file_path);
3838
}
39-
39+
4040
// Read and parse JSON
4141
let file_content = fs::read_to_string(&file_path)?;
4242
let json_data: Value = serde_json
@@ -123,13 +123,13 @@ impl GlobalMap {
123123
];
124124

125125
pub const KING_TABLE_END: [[i32; 8]; 8] = [
126-
[-50, -30, -30, -30, -30, -30, -30, -50],
127-
[-30, -30, 0, 0, 0, 0, -30, -30],
128-
[-30, -10, 20, 30, 30, 20, -10, -30],
129-
[-30, -10, 30, 40, 40, 30, -10, -30],
130-
[-30, -10, 30, 40, 40, 30, -10, -30],
131-
[-30, -10, 20, 30, 30, 20, -10, -30],
132-
[-30, -20, -10, 0, 0, -10, -20, -30],
133-
[-50, -40, -30, -20, -20, -30, -40, -50],
126+
[-25, -23, -20, -20, -20, -20, -23, -25],
127+
[-23, -15, -5, -5, -5, -5, -15, -23],
128+
[-20, -5, 15, 20, 20, 15, -5, -20],
129+
[-20, -5, 20, 25, 25, 20, -5, -20],
130+
[-20, -5, 20, 25, 25, 20, -5, -20],
131+
[-20, -5, 15, 20, 20, 15, -5, -20],
132+
[-23, -15, -5, -5, -5, -5, -15, -23],
133+
[-25, -23, -20, -20, -20, -20, -23, -25],
134134
];
135135
}

0 commit comments

Comments
 (0)