use crate::board::*;
const PAWN_VAL: i32 = 100;
const KNIGHT_VAL: i32 = 320;
const BISHOP_VAL: i32 = 330;
const ROOK_VAL: i32 = 500;
const QUEEN_VAL: i32 = 900;
const KING_VAL: i32 = 20000;
fn piece_value(pt: usize) -> i32 {
match pt {
PAWN => PAWN_VAL,
KNIGHT => KNIGHT_VAL,
BISHOP => BISHOP_VAL,
ROOK => ROOK_VAL,
QUEEN => QUEEN_VAL,
KING => KING_VAL,
_ => 0,
}
}
#[rustfmt::skip]
const PAWN_TABLE: [i32; 64] = [
0, 0, 0, 0, 0, 0, 0, 0,
5, 10, 10,-20,-20, 10, 10, 5,
5, -5,-10, 0, 0,-10, -5, 5,
0, 0, 0, 20, 20, 0, 0, 0,
5, 5, 10, 25, 25, 10, 5, 5,
10, 10, 20, 30, 30, 20, 10, 10,
50, 50, 50, 50, 50, 50, 50, 50,
0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const KNIGHT_TABLE: [i32; 64] = [
-50,-40,-30,-30,-30,-30,-40,-50,
-40,-20, 0, 5, 5, 0,-20,-40,
-30, 5, 10, 15, 15, 10, 5,-30,
-30, 0, 15, 20, 20, 15, 0,-30,
-30, 5, 15, 20, 20, 15, 5,-30,
-30, 0, 10, 15, 15, 10, 0,-30,
-40,-20, 0, 0, 0, 0,-20,-40,
-50,-40,-30,-30,-30,-30,-40,-50,
];
#[rustfmt::skip]
const BISHOP_TABLE: [i32; 64] = [
-20,-10,-10,-10,-10,-10,-10,-20,
-10, 5, 0, 0, 0, 0, 5,-10,
-10, 10, 10, 10, 10, 10, 10,-10,
-10, 0, 10, 10, 10, 10, 0,-10,
-10, 5, 5, 10, 10, 5, 5,-10,
-10, 0, 5, 10, 10, 5, 0,-10,
-10, 0, 0, 0, 0, 0, 0,-10,
-20,-10,-10,-10,-10,-10,-10,-20,
];
#[rustfmt::skip]
const ROOK_TABLE: [i32; 64] = [
0, 0, 0, 5, 5, 0, 0, 0,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
5, 10, 10, 10, 10, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const QUEEN_TABLE: [i32; 64] = [
-20,-10,-10, -5, -5,-10,-10,-20,
-10, 0, 5, 0, 0, 0, 0,-10,
-10, 5, 5, 5, 5, 5, 0,-10,
0, 0, 5, 5, 5, 5, 0, -5,
-5, 0, 5, 5, 5, 5, 0, -5,
-10, 0, 5, 5, 5, 5, 0,-10,
-10, 0, 0, 0, 0, 0, 0,-10,
-20,-10,-10, -5, -5,-10,-10,-20,
];
#[rustfmt::skip]
const KING_TABLE_MID: [i32; 64] = [
20, 30, 10, 0, 0, 10, 30, 20,
20, 20, 0, 0, 0, 0, 20, 20,
-10,-20,-20,-20,-20,-20,-20,-10,
-20,-30,-30,-40,-40,-30,-30,-20,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
];
fn pst_value(pt: usize, sq: u8, is_white: bool) -> i32 {
let idx = if is_white { sq as usize } else { (56 - (sq & !7) + (sq & 7)) as usize };
match pt {
PAWN => PAWN_TABLE[idx],
KNIGHT => KNIGHT_TABLE[idx],
BISHOP => BISHOP_TABLE[idx],
ROOK => ROOK_TABLE[idx],
QUEEN => QUEEN_TABLE[idx],
KING => KING_TABLE_MID[idx],
_ => 0,
}
}
fn evaluate(pos: &Position, personality: &Personality) -> i32 {
let mut score: i32 = 0;
for pt in 0..6 {
let mut white = pos.pieces[pt] & pos.colors[WHITE];
while white != 0 {
let sq = white.trailing_zeros() as u8;
score += piece_value(pt) + pst_value(pt, sq, true);
white &= white - 1;
}
let mut black = pos.pieces[pt] & pos.colors[BLACK];
while black != 0 {
let sq = black.trailing_zeros() as u8;
score -= piece_value(pt) + pst_value(pt, sq, false);
black &= black - 1;
}
}
let mobility_white = pos.legal_moves().len() as i32;
let mut flipped = pos.clone();
flipped.side_to_move = if pos.side_to_move == Color::White { Color::Black } else { Color::White };
let mobility_diff = mobility_white as i32 * personality.aggression / 10;
score += mobility_diff;
if pos.side_to_move == Color::White { score } else { -score }
}
fn alpha_beta(pos: &Position, depth: u8, mut alpha: i32, beta: i32, personality: &Personality) -> i32 {
if depth == 0 {
return evaluate(pos, personality);
}
let moves = pos.legal_moves();
if moves.is_empty() {
if pos.in_check(pos.side_to_move) {
return -KING_VAL + (10 - depth as i32); }
return 0; }
for mv in moves {
let new_pos = pos.make_move(mv);
let score = -alpha_beta(&new_pos, depth - 1, -beta, -alpha, personality);
if score >= beta {
return beta;
}
if score > alpha {
alpha = score;
}
}
alpha
}
#[derive(Clone)]
pub struct Personality {
pub name: &'static str,
pub description: &'static str,
pub depth: u8, pub aggression: i32, pub randomness: u8, }
pub const PERSONALITIES: &[Personality] = &[
Personality {
name: "Beginner",
description: "Just learning the rules. Makes random mistakes.",
depth: 1, aggression: 5, randomness: 40,
},
Personality {
name: "Casual",
description: "Plays for fun. Occasionally blunders.",
depth: 2, aggression: 8, randomness: 20,
},
Personality {
name: "Club Player",
description: "Solid fundamentals. Doesn't hang pieces.",
depth: 3, aggression: 10, randomness: 5,
},
Personality {
name: "Strong Player",
description: "Plays well. Finds tactical shots.",
depth: 4, aggression: 12, randomness: 2,
},
Personality {
name: "Expert",
description: "Tournament strength. Deep calculation.",
depth: 5, aggression: 15, randomness: 0,
},
Personality {
name: "Bobby Fischer",
description: "Aggressive, precise, no mercy.",
depth: 4, aggression: 18, randomness: 0,
},
Personality {
name: "Mikhail Tal",
description: "Wild sacrifices, chaotic attacks.",
depth: 3, aggression: 20, randomness: 10,
},
Personality {
name: "Anatoly Karpov",
description: "Positional squeeze, quiet suffocation.",
depth: 4, aggression: 5, randomness: 0,
},
Personality {
name: "Garry Kasparov",
description: "Dynamic, calculating, overwhelming force.",
depth: 5, aggression: 16, randomness: 0,
},
Personality {
name: "Magnus Carlsen",
description: "Universal, patient, grinds you down.",
depth: 5, aggression: 12, randomness: 0,
},
];
pub fn find_best_move(pos: &Position, personality: &Personality) -> Option<Move> {
let moves = pos.legal_moves();
if moves.is_empty() {
return None;
}
let mut scored: Vec<(Move, i32)> = moves.iter().map(|&mv| {
let new_pos = pos.make_move(mv);
let score = -alpha_beta(&new_pos, personality.depth.saturating_sub(1), -KING_VAL, KING_VAL, personality);
(mv, score)
}).collect();
scored.sort_by(|a, b| b.1.cmp(&a.1));
if personality.randomness > 0 && scored.len() > 1 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().subsec_nanos();
let mut hasher = DefaultHasher::new();
nanos.hash(&mut hasher);
let rand = (hasher.finish() % 100) as u8;
if rand < personality.randomness {
let range = (scored.len() / 2).max(2).min(scored.len());
let idx = (hasher.finish() / 100 % range as u64) as usize;
return Some(scored[idx].0);
}
}
Some(scored[0].0)
}