1use crate::bitboard::{self, Bitboard};
11use crate::coretypes::{Color, Cp, CpKind, PieceKind, SquareIndexable, NUM_RANKS, NUM_SQUARES};
12use crate::coretypes::{Color::*, PieceKind::*};
13use crate::movegen as mg;
14use crate::position::Position;
15
16impl PieceKind {
17 pub const fn centipawns(&self) -> Cp {
19 Cp(match self {
20 Pawn => 100, Knight => 305, Bishop => 310, Rook => 510,
24 Queen => 950,
25 King => 10_000,
26 })
27 }
28}
29
30const MOBILITY_CP: Cp = Cp(1);
32
33pub fn terminal(position: &Position) -> Cp {
38 if position.is_checkmate() {
40 -Cp::CHECKMATE
41 } else {
42 Cp::STALEMATE
43 }
44}
45
46pub fn draw(is_engine: bool, contempt: Cp) -> Cp {
49 Cp::STALEMATE
50 + match is_engine {
51 true => -contempt,
52 false => contempt,
53 }
54}
55
56pub fn evaluate(position: &Position) -> Cp {
59 evaluate_abs(position) * position.player.sign()
60}
61
62pub fn terminal_abs(position: &Position) -> Cp {
67 if position.is_checkmate() {
68 match position.player {
69 White => -Cp::CHECKMATE,
70 Black => Cp::CHECKMATE,
71 }
72 } else {
73 Cp::STALEMATE
74 }
75}
76
77pub fn evaluate_abs(position: &Position) -> Cp {
80 let cp_material = material(position);
81 let cp_piece_sq = piece_square_lookup(position);
82 let cp_pass_pawns = pass_pawns(position);
83 let cp_xray_king = xray_king_attacks(position);
84 let cp_mobility = mobility(position);
85 let cp_king_safety = king_safety(position);
86
87 let cp_total =
88 cp_material + cp_piece_sq + cp_pass_pawns + cp_xray_king + cp_mobility + cp_king_safety;
89 cp_total
90}
91
92pub fn material(position: &Position) -> Cp {
96 let w_piece_cp: Cp = PieceKind::iter()
97 .map(|pk| pk.centipawns() * position.pieces[(White, pk)].count_squares())
98 .fold(Cp::default(), |acc, value| acc + value);
99
100 let b_piece_cp: Cp = PieceKind::iter()
101 .map(|pk| pk.centipawns() * position.pieces[(Black, pk)].count_squares())
102 .fold(Cp::default(), |acc, value| acc + value);
103
104 w_piece_cp - b_piece_cp
105}
106
107pub fn king_safety(position: &Position) -> Cp {
108 let mut cp = Cp(0);
109
110 let occupied = position.pieces.occupied();
111 let w_sliding = position.pieces[(White, Queen)]
113 | position.pieces[(White, Rook)]
114 | position.pieces[(White, Bishop)];
115 let b_sliding = position.pieces[(Black, Queen)]
116 | position.pieces[(Black, Rook)]
117 | position.pieces[(Black, Bishop)];
118 let w_num_sliding = w_sliding.count_squares();
119 let b_num_sliding = b_sliding.count_squares();
120 let w_king = position.pieces[(White, King)];
121 let b_king = position.pieces[(Black, King)];
122
123 let w_king_open_squares = mg::queen_attacks(w_king, occupied).count_squares();
124 let b_king_open_squares = mg::queen_attacks(b_king, occupied).count_squares();
125
126 let w_value = b_king_open_squares * w_num_sliding / 2;
128 let b_value = w_king_open_squares * b_num_sliding / 2;
129
130 let value_diff = Cp(w_value as CpKind - b_value as CpKind);
131 cp += value_diff;
132
133 cp
134}
135
136pub fn mobility(position: &Position) -> Cp {
138 let w_attacks = position.attacks(White, position.pieces().occupied());
139 let b_attacks = position.attacks(Black, position.pieces().occupied());
140
141 let attack_surface_area_diff =
142 w_attacks.count_squares() as CpKind - b_attacks.count_squares() as CpKind;
143
144 Cp(attack_surface_area_diff) * MOBILITY_CP
145}
146
147pub fn pass_pawns(position: &Position) -> Cp {
149 const SCALAR: Cp = Cp(20);
151 const RANK_CP: [CpKind; NUM_RANKS] = [0, 0, 1, 2, 10, 50, 250, 900];
153 let w_passed: Bitboard = pass_pawns_bb(position, White);
154 let b_passed: Bitboard = pass_pawns_bb(position, Black);
155 let w_num_passed = w_passed.count_squares() as CpKind;
156 let b_num_passed = b_passed.count_squares() as CpKind;
157
158 let w_rank_bonus = w_passed
160 .into_iter()
161 .map(|sq| sq.rank())
162 .fold(Cp(0), |acc, rank| acc + Cp(RANK_CP[rank as usize]));
163 let b_rank_bonus = b_passed
164 .into_iter()
165 .map(|sq| sq.rank().flip())
166 .fold(Cp(0), |acc, rank| acc + Cp(RANK_CP[rank as usize]));
167
168 Cp(w_num_passed - b_num_passed) * SCALAR + w_rank_bonus - b_rank_bonus
169}
170
171pub fn xray_king_attacks(position: &Position) -> Cp {
173 const SCALAR: Cp = Cp(8);
175 let w_king = position.pieces[(White, King)].get_lowest_square().unwrap();
176 let b_king = position.pieces[(Black, King)].get_lowest_square().unwrap();
177 let w_king_ortho = Bitboard::from(w_king.file()) | Bitboard::from(w_king.rank());
178 let b_king_ortho = Bitboard::from(b_king.file()) | Bitboard::from(b_king.rank());
179 let w_king_diags = mg::bishop_pattern(w_king);
180 let b_king_diags = mg::bishop_pattern(b_king);
181
182 let w_diags = position.pieces[(White, Queen)] | position.pieces[(White, Bishop)];
183 let b_diags = position.pieces[(Black, Queen)] | position.pieces[(Black, Bishop)];
184 let w_ortho = position.pieces[(White, Queen)] | position.pieces[(White, Rook)];
185 let b_ortho = position.pieces[(Black, Queen)] | position.pieces[(Black, Rook)];
186
187 let w_xray_attackers_bb = (b_king_diags & w_diags) | (b_king_ortho & w_ortho);
188 let b_xray_attackers_bb = (w_king_diags & b_diags) | (w_king_ortho & b_ortho);
189
190 let w_xray_attackers: CpKind = w_xray_attackers_bb.count_squares() as CpKind;
191 let b_xray_attackers: CpKind = b_xray_attackers_bb.count_squares() as CpKind;
192
193 Cp(w_xray_attackers - b_xray_attackers) * SCALAR
194}
195
196pub fn piece_square_lookup(position: &Position) -> Cp {
198 let mut w_values = Cp(0);
199 position.pieces[(White, Pawn)]
200 .into_iter()
201 .for_each(|sq| w_values += Cp(MG_PAWN_TABLE[sq.idx()]));
202 position.pieces[(White, Knight)]
203 .into_iter()
204 .for_each(|sq| w_values += Cp(MG_KNIGHT_TABLE[sq.idx()]));
205 position.pieces[(White, Bishop)]
206 .into_iter()
207 .for_each(|sq| w_values += Cp(MG_BISHOP_TABLE[sq.idx()]));
208 position.pieces[(White, King)]
209 .into_iter()
210 .for_each(|sq| w_values += Cp(MG_KING_TABLE[sq.idx()]));
211
212 let mut b_values = Cp(0);
213 position.pieces[(Black, Pawn)]
214 .into_iter()
215 .for_each(|sq| b_values += Cp(MG_PAWN_TABLE[sq.flip_rank().idx()]));
216 position.pieces[(Black, Knight)]
217 .into_iter()
218 .for_each(|sq| b_values += Cp(MG_KNIGHT_TABLE[sq.flip_rank().idx()]));
219 position.pieces[(Black, Bishop)]
220 .into_iter()
221 .for_each(|sq| b_values += Cp(MG_BISHOP_TABLE[sq.flip_rank().idx()]));
222 position.pieces[(Black, King)]
223 .into_iter()
224 .for_each(|sq| b_values += Cp(MG_KING_TABLE[sq.flip_rank().idx()]));
225
226 w_values - b_values
227}
228
229#[inline]
232fn pass_pawns_bb(position: &Position, player: Color) -> Bitboard {
233 use Bitboard as Bb;
234
235 let opponent_pawns = position.pieces[(!player, Pawn)];
236
237 let spans = opponent_pawns
238 .into_iter()
239 .map(|sq| {
240 let file = sq.file();
241 let mut span = Bb::from(file);
242 match player {
244 Color::White => span.clear_square_and_above(sq),
245 Color::Black => span.clear_square_and_below(sq),
246 };
247
248 span | span.to_east() | span.to_west()
249 })
250 .fold(Bitboard::EMPTY, |acc, bb| acc | bb);
251
252 position.pieces[(player, Pawn)] & !spans
254}
255
256#[rustfmt::skip]
268const MG_PAWN_TABLE: [CpKind; NUM_SQUARES] = [
269 0, 0, 0, 0, 0, 0, 0, 0,
270 5, 1, 0, -20, -20, 0, 1, 5,
271 5, -2, 0, 0, 0, 0, -2, 5,
272 0, 0, 0, 20, 20, 0, 0, 0,
273 2, 2, 2, 21, 21, 2, 2, 2,
274 3, 3, 3, 22, 22, 3, 3, 3,
275 4, 4, 4, 23, 23, 4, 4, 4,
276 0, 0, 0, 0, 0, 0, 0, 0,
277];
278
279#[rustfmt::skip]
282const MG_KNIGHT_TABLE: [CpKind; NUM_SQUARES] = [
283 -50, -30, -20, -20, -20, -20, -30, -50,
284 -20, 0, 0, 5, 5, 0, 0, -20,
285 -10, 0, 10, 15, 15, 10, 0, -10,
286 -10, 0, 15, 20, 20, 15, 0, -10,
287 -10, 0, 15, 20, 20, 15, 0, -10,
288 -10, 0, 10, 15, 15, 10, 0, -10,
289 -20, 0, 0, 0, 0, 0, 0, -20,
290 -50, -10, -10, -10, -10, -10, -10, -50,
291];
292
293#[rustfmt::skip]
296const MG_BISHOP_TABLE: [CpKind; NUM_SQUARES] = [
297 -20, -8, -10, -8, -8, -10, -8, -20,
298 -8, 5, 0, 0, 0, 0, 5, -8,
299 -8, 10, 10, 10, 10, 10, 10, -8,
300 -8, 0, 10, 10, 10, 10, 0, -8,
301 -8, 0, 10, 10, 10, 10, 0, -8,
302 -8, 0, 10, 10, 10, 10, 0, -8,
303 -8, 0, 0, 0, 0, 0, 0, -8,
304 -20, -8, -8, -8, -8, -8, -8, -20,
305];
306
307#[rustfmt::skip]
310const MG_KING_TABLE: [CpKind; NUM_SQUARES] = [
311 20, 30, 10, 0, 0, 10, 30, 20,
312 20, 20, 0, 0, 0, 0, 20, 20,
313 -10, -10, -15, -15, -15, -15, -10, -10,
314 -10, -10, -10, -10, -10, -10, -10, -10,
315 0, 0, 0, 0, 0, 0, 0, 0,
316 0, 0, 0, 0, 0, 0, 0, 0,
317 0, 0, 0, 0, 0, 0, 0, 0,
318 0, 0, 0, 0, 0, 0, 0, 0,
319];
320
321pub const PASS_PAWN_SIZE: usize = (NUM_SQUARES - 24) * 2;
325pub const PASS_PAWN_PATTERN: [Bitboard; PASS_PAWN_SIZE] = generate_pass_pawn_pattern();
326
327macro_rules! w_repeat_for_each {
331 ($array:ident, $func:ident, $($numbers:literal),+) => {
332 {
333 $($array[$numbers - 8] = $func($numbers);)*
334 }
335 };
336}
337
338const fn generate_pass_pawn_pattern() -> [Bitboard; PASS_PAWN_SIZE] {
348 let mut array = [Bitboard::EMPTY; PASS_PAWN_SIZE];
349
350 #[rustfmt::skip]
351 w_repeat_for_each!(
352 array,
353 w_pass_pawn_pattern_idx,
354 8, 9, 10, 11, 12, 13, 14, 15,
355 16, 17, 18, 19, 20, 21, 22, 23,
356 24, 25, 26, 27, 28, 29, 30, 31,
357 32, 33, 34, 35, 36, 37, 38, 39,
358 40, 41, 42, 43, 44, 45, 46, 47
359 );
360
361 array
362}
363
364const fn w_pass_pawn_pattern_idx(square: usize) -> Bitboard {
365 use Bitboard as Bb;
366 let square_bb: bitboard::BitboardKind = 1u64 << square;
367
368 if square_bb & Bitboard::FILE_A.0 > 0 {
369 let mut pass_pawn_pat = Bitboard::FILE_A.0 | Bitboard::FILE_B.0;
371 pass_pawn_pat &= !square_bb; pass_pawn_pat &= !(square_bb << 1); if square != 0 {
374 pass_pawn_pat &= !(square_bb - 1);
375 }
376 Bitboard(pass_pawn_pat)
377 } else if square_bb & Bitboard::FILE_H.0 > 0 {
378 let mut pass_pawn_pat = Bitboard::FILE_G.0 | Bitboard::FILE_H.0;
380 pass_pawn_pat &= !(square_bb ^ (square_bb - 1)); Bitboard(pass_pawn_pat)
382 } else {
383 let mut pass_pawn_pat = match square_bb {
385 bb if bb & Bb::FILE_B.0 > 0 => Bb::FILE_A.0 | Bb::FILE_B.0 | Bb::FILE_C.0,
386 bb if bb & Bb::FILE_C.0 > 0 => Bb::FILE_B.0 | Bb::FILE_C.0 | Bb::FILE_D.0,
387 bb if bb & Bb::FILE_D.0 > 0 => Bb::FILE_C.0 | Bb::FILE_D.0 | Bb::FILE_E.0,
388 bb if bb & Bb::FILE_E.0 > 0 => Bb::FILE_D.0 | Bb::FILE_E.0 | Bb::FILE_F.0,
389 bb if bb & Bb::FILE_F.0 > 0 => Bb::FILE_E.0 | Bb::FILE_F.0 | Bb::FILE_G.0,
390 bb if bb & Bb::FILE_G.0 > 0 => Bb::FILE_F.0 | Bb::FILE_G.0 | Bb::FILE_H.0,
391 _ => 0,
392 };
393 pass_pawn_pat &= !(square_bb ^ (square_bb - 1)); pass_pawn_pat &= !(square_bb << 1);
396
397 Bitboard(pass_pawn_pat)
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use crate::Fen;
405
406 #[test]
407 fn start_pos_equal_eval() {
408 let mut start = Position::start_position();
411 let w_eval = evaluate(&start);
412 start.player = Black;
413 let b_eval = evaluate(&start);
414 assert_eq!(w_eval, b_eval);
415
416 assert_eq!(w_eval, evaluate(&start.color_flip()));
417 }
418
419 #[test]
420 fn cp_min_and_max() {
421 let min = Cp::MIN;
422 let max = Cp::MAX;
423 assert_eq!(min.signum(), -1);
424 assert_eq!(max.signum(), 1);
425
426 assert_eq!((-min).signum(), 1);
428 assert_eq!((-max).signum(), -1);
429 }
430
431 #[test]
432 fn large_eval_in_score_range() {
433 let pos = Position::parse_fen("4k3/8/8/8/8/8/QQQQ1QQQ/QQQQKQQQ w - - 0 1").unwrap();
436 let score = evaluate(&pos);
437 assert!(score.is_score());
438 assert!(score.is_legal());
439 assert!(!score.is_mate());
440 println!("MAX POSSIBLE SCORE: {}", score);
441 }
442}