Skip to content

Commit 074a51d

Browse files
committed
Pawn Structure + tweaks
1 parent 029eaff commit 074a51d

File tree

7 files changed

+86
-14
lines changed

7 files changed

+86
-14
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ edition = "2021"
99
#bevy = { version = "0.11.2", features = ["dynamic_linking"] }
1010
chess = "3.2.0"
1111
rand = "0.8.5"
12+
rustc-hash = "1.1.0"
1213
tokio = { version = "1.34.0", features = ["full"] }
1314
# ordered_float = "4.1.1"

src/algorithms/the_algorithm.rs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::collections::HashMap;
1+
use std::{collections::HashMap, cmp};
22
use tokio::time::{Duration, Instant};
3+
use rustc_hash::FxHasher;
34

45
use chess::{Action, Board, BoardStatus, ChessMove, Color, MoveGen, Piece, BitBoard};
56

@@ -15,6 +16,7 @@ pub(crate) struct Algorithm {
1516
pub(crate) time_per_move: Duration,
1617
/// Number of times that a given board has been played
1718
pub(crate) board_played_times: HashMap<Board, u32>,
19+
pub(crate) pawn_hash: HashMap<BitBoard, f32>
1820
}
1921

2022
impl Algorithm {
@@ -24,6 +26,7 @@ impl Algorithm {
2426
transposition_table: HashMap::with_capacity(45),
2527
time_per_move,
2628
board_played_times: HashMap::new(),
29+
pawn_hash: HashMap::new()
2730
}
2831
}
2932

@@ -40,6 +43,8 @@ impl Algorithm {
4043
num_extensions: u32,
4144
board_played_times_prediction: &mut HashMap<Board, u32>,
4245
) -> Evaluation {
46+
47+
4348
if depth == 0 {
4449
stats.leaves_visited += 1;
4550
let eval = self.eval(board, board_played_times_prediction);
@@ -67,14 +72,17 @@ impl Algorithm {
6772

6873
// If we arrive at here and it is checkmate, then we know that the side playing
6974
// has been checkmated.
70-
75+
7176
best_evaluation.eval = Some(if board.side_to_move() == Color::White {
7277
f32::MIN
7378
} else {
7479
f32::MAX
7580
});
7681
return best_evaluation;
77-
}
82+
}
83+
84+
//best_evaluation.eval = Some(f32::MIN);
85+
7886

7987
let mut boards = legal_moves
8088
.map(|chess_move| {
@@ -114,6 +122,11 @@ impl Algorithm {
114122
(i.saturating_sub(1)) as f32 / num_legal_moves as f32;
115123
return best_evaluation;
116124
};
125+
126+
if depth > stats.max_depth {
127+
stats.max_depth = depth;
128+
}
129+
117130
if module_enabled(self.modules, SKIP_BAD_MOVES)
118131
&& i as f32 > num_legal_moves as f32 * 1.
119132
{
@@ -159,6 +172,7 @@ impl Algorithm {
159172
);
160173
evaluation
161174
};
175+
162176
stats.nodes_visited += 1;
163177

164178
// Replace best_eval if ours is better
@@ -309,7 +323,7 @@ impl Algorithm {
309323
}
310324

311325
pub(crate) fn eval(
312-
&self,
326+
&mut self,
313327
board: &Board,
314328
board_played_times_prediction: &HashMap<Board, u32>,
315329
) -> f32 {
@@ -351,10 +365,9 @@ impl Algorithm {
351365
//Essentially, gets the dot product between a "vector" of the bitboard (containing 64 0s and 1s) and the table with position bonus constants.
352366
let mut bonus: f32 = 0.;
353367
//Get's the bitboard with all piece positions, and runs bitwise and for the board having one's own colors.
354-
let mut piece_board: u64 = (piece_bitboard & color_bitboard).reverse_colors().to_size(0) as u64;
355368
for i in 0..63 {
356-
//I'm pretty sure the bitboard and position_table have opposite orientationns. Regardless, flipping the bitboard significantly increased performance.
357-
bonus += (piece_board >> i & 1) as f32 * position_table[i];
369+
//The position table and bitboard are flipped vertically, hence .reverse_colors(). Reverse colors is for some reason faster than replacing i with 56-i+2*(i%8).
370+
bonus += ((piece_bitboard & color_bitboard).reverse_colors().to_size(0) >> i & 1) as f32 * position_table[i];
358371
}
359372
return bonus;
360373
}
@@ -375,12 +388,54 @@ impl Algorithm {
375388
}
376389
}
377390

378-
let evaluation: f32 = controlled_squares as f32 / 20. + diff_material as f32 + position_bonus;
391+
let mut pawn_structure: f32 = 0.;
392+
if utils::module_enabled(self.modules, modules::PAWN_STRUCTURE) {
393+
fn pawn_structure_calc(all_pawn_bitboard: &BitBoard, color_bitboard: &BitBoard, all_king_bitboard: &BitBoard) -> f32 {
394+
let mut bonus: f32 = 0.;
395+
let pawn_bitboard: usize = (all_pawn_bitboard & color_bitboard).to_size(0);
396+
let king_bitboard: usize = (all_king_bitboard & color_bitboard).to_size(0);
397+
//pawn chain, awarding 0.5 eval for each pawn protected by another pawn.
398+
bonus += 0.5*((pawn_bitboard & (pawn_bitboard << 7)).count_ones() + (pawn_bitboard & (pawn_bitboard << 9)).count_ones()) as f32;
399+
400+
//stacked pawns. -0.5 points per rank containing >1 pawns. By taking the pawn bitboard and operating bitwise and for another bitboard (integer) where the leftmost rank is filled. This returns
401+
for i in 0..7 {
402+
//constant 9259542123273814144 = 0x8080808080808080, or the entire first rank.
403+
bonus -= 0.5*cmp::max((pawn_bitboard & (0x8080808080808080 >> i)).count_ones() as i64 - 1, 0) as f32;
404+
}
405+
406+
//king safety. Outer 3 pawns get +1 eval bonus per pawn if king is behind them. King position required is either ..X..... or ......X.
407+
if (king_bitboard & 0x2).count_ones() > 0 {
408+
bonus += (pawn_bitboard & 0x7).count_ones() as f32;
409+
}
410+
if (king_bitboard & 0x20).count_ones() > 0 {
411+
bonus += (pawn_bitboard & 0xE000).count_ones() as f32;
412+
}
413+
return bonus;
414+
}
415+
416+
//Because pawn moves (according to chessprogramming.org) are rarely performed, hashing them is useful.
417+
if board.side_to_move() == Color::White {
418+
let pawn_bitboard: BitBoard = board.pieces(Piece::Pawn) & board.color_combined(Color::White);
419+
if !self.pawn_hash.contains_key(&pawn_bitboard) {
420+
self.pawn_hash.insert(pawn_bitboard, pawn_structure_calc(board.pieces(Piece::Pawn), board.color_combined(Color::White), board.pieces(Piece::King)));
421+
}
422+
pawn_structure = *self.pawn_hash.get(&pawn_bitboard).unwrap();
423+
} else {
424+
let pawn_bitboard: BitBoard = board.pieces(Piece::Pawn) & board.color_combined(Color::Black);
425+
if !self.pawn_hash.contains_key(&pawn_bitboard) {
426+
self.pawn_hash.insert(pawn_bitboard, pawn_structure_calc(board.pieces(Piece::Pawn), board.color_combined(Color::Black), board.pieces(Piece::King)));
427+
}
428+
pawn_structure = *self.pawn_hash.get(&pawn_bitboard).unwrap();
429+
}
430+
}
431+
432+
let evaluation: f32 = controlled_squares as f32 / 20. + diff_material as f32 + position_bonus + pawn_structure;
379433
return evaluation
380434
}
381435

382436
pub(crate) fn reset(&mut self) {
383437
self.transposition_table = HashMap::new();
384438
self.board_played_times = HashMap::new();
439+
self.pawn_hash = HashMap::new();
385440
}
386441
}

src/common/constants.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub(crate) mod modules {
66
pub(crate) const SQUARE_CONTROL_METRIC: u32 = 0b10000;
77
pub(crate) const SKIP_BAD_MOVES: u32 = 0b100000;
88
pub(crate) const POSITION_BONUS: u32 = 0b1000000;
9+
pub(crate) const PAWN_STRUCTURE: u32 = 0b10000000;
910
}
1011

1112
//POSITION_BONUS TABLES
@@ -73,6 +74,6 @@ pub(crate) const position_bonus_table_king: [f32; 64] = [
7374
0., 0., 0., 0., 0., 0., 0., 0.,
7475
0., 0., 0., 0., 0., 0., 0., 0.,
7576
0., 0., 0., 0., 0., 0., 0., 0.,
76-
0.1, 0.3, 0.3, 0., 0., 0.3, 0.3, 0.1
77+
0.1, 0.3, 0.3, 0., 0., 0., 0.3, 0.1
7778
];
7879
}

src/common/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub(crate) use vector_push_debug;
8080
pub(crate) struct Stats {
8181
pub(crate) alpha_beta_breaks: u32,
8282
pub(crate) depth: u32,
83+
pub(crate) max_depth: u32,
8384
pub(crate) leaves_visited: u32,
8485
pub(crate) nodes_visited: u32,
8586
pub(crate) num_plies: u32,
@@ -95,6 +96,7 @@ impl AddAssign for Stats {
9596
fn add_assign(&mut self, rhs: Self) {
9697
self.nodes_visited += rhs.nodes_visited;
9798
self.depth += rhs.depth;
99+
self.max_depth += rhs.max_depth;
98100
self.leaves_visited += rhs.leaves_visited;
99101
self.alpha_beta_breaks += rhs.alpha_beta_breaks;
100102
self.num_plies += rhs.num_plies;
@@ -112,6 +114,7 @@ impl Div<u32> for Stats {
112114
StatsAverage {
113115
alpha_beta_breaks: self.alpha_beta_breaks as f32 / rhs as f32,
114116
depth: self.depth as f32 / rhs as f32,
117+
max_depth: self.max_depth as f32 / rhs as f32,
115118
leaves_visited: self.leaves_visited as f32 / rhs as f32,
116119
nodes_visited: self.nodes_visited as f32 / rhs as f32,
117120
num_plies: self.num_plies as f32 / rhs as f32,
@@ -132,6 +135,7 @@ impl Div<u32> for Stats {
132135
pub(crate) struct StatsAverage {
133136
pub(crate) alpha_beta_breaks: f32,
134137
pub(crate) depth: f32,
138+
pub(crate) max_depth: f32,
135139
pub(crate) leaves_visited: f32,
136140
pub(crate) nodes_visited: f32,
137141
pub(crate) num_plies: f32,

src/main.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::time::Duration;
22

33
use crate::algorithms::the_algorithm::Algorithm;
4+
#[allow(dead_code)]
45
use crate::common::constants::modules::{
56
ALPHA_BETA, ANALYZE, SEARCH_EXTENSIONS, SKIP_BAD_MOVES, SQUARE_CONTROL_METRIC,
6-
TRANSPOSITION_TABLE, POSITION_BONUS
7+
TRANSPOSITION_TABLE, POSITION_BONUS, PAWN_STRUCTURE
78
};
89

910
use self::pitter::logic::{Competition, GameOutcome};
@@ -14,10 +15,11 @@ mod pitter;
1415

1516
#[tokio::main]
1617
async fn main() {
17-
let modules1 = ALPHA_BETA | POSITION_BONUS;
18+
//ALPHA_BETA | ANALYZE | SEARCH_EXTENSIONS | SKIP_BAD_MOVES | SQUARE_CONTROL_METRIC | TRANSPOSITION_TABLE | POSITION_BONUS | PAWN_STRUCTURE
19+
let modules1 = ALPHA_BETA | PAWN_STRUCTURE;
1820
let modules2 = ALPHA_BETA;
19-
let time_per_move1 = Duration::from_micros(500);
20-
let time_per_move2 = Duration::from_micros(500);
21+
let time_per_move1 = Duration::from_micros(12000);
22+
let time_per_move2 = Duration::from_micros(12000);
2123

2224
let competition = Competition::new(
2325
Algorithm::new(modules1, time_per_move1),
@@ -27,6 +29,6 @@ async fn main() {
2729
// competition.analyze_algorithm_choices(|(game_info, _), _| {
2830
// game_info.outcome == GameOutcome::InconclusiveTooLong
2931
// });
30-
let results = competition.start_competition(2000).await;
32+
let results = competition.start_competition(500).await;
3133
dbg!(results);
3234
}

src/pitter/logic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,10 @@ impl Competition {
240240
game_pair_info.1.outcome,
241241
);
242242

243+
//Whether the game just played should be printed in console.
243244
//println!("Game pair played. Outcome: {:?}", combined_outcome);
244245
//println!("{}", utils::to_pgn(&game_pair_info.0.game.unwrap()));
246+
245247
results.lock().await.register_game_outcome(combined_outcome);
246248

247249
let mut locked_stats = sum_stats.lock().await;

0 commit comments

Comments
 (0)