rules: handle pawn promotion

This commit is contained in:
dece 2020-06-04 19:19:24 +02:00
parent 8310e0c1e7
commit 0daece72d3
5 changed files with 88 additions and 42 deletions

View file

@ -180,8 +180,8 @@ pub fn find_king(board: &Board, color: u8) -> Pos {
(0, 0) (0, 0)
} }
/// A movement, with before/after positions. /// A movement, with before/after positions and optional promotion.
pub type Move = (Pos, Pos); pub type Move = (Pos, Pos, Option<u8>);
/// Apply a move `m` to a copy of `board`. /// Apply a move `m` to a copy of `board`.
pub fn apply(board: &Board, m: &Move) -> Board { pub fn apply(board: &Board, m: &Move) -> Board {
@ -307,12 +307,12 @@ mod tests {
set_square(&mut b, &pos("d4"), SQ_WH_N); set_square(&mut b, &pos("d4"), SQ_WH_N);
set_square(&mut b, &pos("f4"), SQ_BL_N); set_square(&mut b, &pos("f4"), SQ_BL_N);
// Move white knight in a position attacked by black knight. // Move white knight in a position attacked by black knight.
apply_into(&mut b, &((pos("d4"), pos("e6")))); apply_into(&mut b, &(pos("d4"), pos("e6"), None));
assert_eq!(get_square(&b, &pos("d4")), SQ_E); assert_eq!(get_square(&b, &pos("d4")), SQ_E);
assert_eq!(get_square(&b, &pos("e6")), SQ_WH_N); assert_eq!(get_square(&b, &pos("e6")), SQ_WH_N);
assert_eq!(num_pieces(&b), 2); assert_eq!(num_pieces(&b), 2);
// Sack it with black knight // Sack it with black knight
apply_into(&mut b, &((pos("f4"), pos("e6")))); apply_into(&mut b, &(pos("f4"), pos("e6"), None));
assert_eq!(get_square(&b, &pos("e6")), SQ_BL_N); assert_eq!(get_square(&b, &pos("e6")), SQ_BL_N);
assert_eq!(num_pieces(&b), 1); assert_eq!(num_pieces(&b), 1);
} }

View file

@ -48,7 +48,7 @@ fn get_player_move() -> board::Move {
let from = if let Some(s) = get_input("From: ") { board::pos(&s) } else { continue }; let from = if let Some(s) = get_input("From: ") { board::pos(&s) } else { continue };
let to = if let Some(s) = get_input("To: ") { board::pos(&s) } else { continue }; let to = if let Some(s) = get_input("To: ") { board::pos(&s) } else { continue };
if board::is_valid_pos(from) && board::is_valid_pos(to) { if board::is_valid_pos(from) && board::is_valid_pos(to) {
return (from, to) return (from, to, None) // TODO this does not handle promotion.
} }
println!("Bad input."); println!("Bad input.");
} }

View file

@ -289,7 +289,7 @@ fn analyze(
let moves = rules::get_player_legal_moves(&state.board, state.color); let moves = rules::get_player_legal_moves(&state.board, state.color);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let best_move = moves.iter().choose(&mut rng).and_then(|m| Some(*m)); let best_move = moves.iter().choose(&mut rng).and_then(|m| Some(*m));
thread::sleep(time::Duration::from_millis(1000u64)); thread::sleep(time::Duration::from_millis(300u64));
tx.send(Cmd::TmpBestMove(best_move)).unwrap(); tx.send(Cmd::TmpBestMove(best_move)).unwrap();
// thread::sleep(time::Duration::from_secs(1)); // thread::sleep(time::Duration::from_secs(1));

View file

@ -8,11 +8,31 @@ pub fn move_to_string(m: &board::Move) -> String {
let mut move_string = String::new(); let mut move_string = String::new();
move_string.push_str(&board::pos_string(&m.0)); move_string.push_str(&board::pos_string(&m.0));
move_string.push_str(&board::pos_string(&m.1)); move_string.push_str(&board::pos_string(&m.1));
if let Some(prom) = m.2 {
move_string.push(match prom {
board::SQ_Q => 'q',
board::SQ_B => 'b',
board::SQ_N => 'n',
board::SQ_R => 'r',
_ => panic!("What are you doing? Promote to a legal piece.")
});
}
move_string move_string
} }
pub fn parse_move(m_str: &str) -> board::Move { pub fn parse_move(m_str: &str) -> board::Move {
(board::pos(&m_str[0..2]), board::pos(&m_str[2..4])) let prom = if m_str.len() == 5 {
Some(match m_str.as_bytes()[4] {
b'b' => board::SQ_B,
b'n' => board::SQ_N,
b'r' => board::SQ_R,
b'q' => board::SQ_Q,
_ => panic!("What is the opponent doing? This is illegal, I'm out."),
})
} else {
None
};
(board::pos(&m_str[0..2]), board::pos(&m_str[2..4]), prom)
} }
pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
@ -53,13 +73,17 @@ mod tests {
#[test] #[test]
fn test_move_to_string() { fn test_move_to_string() {
assert_eq!(move_to_string(&((0, 0), (3, 3))), "a1d4"); assert_eq!(move_to_string(&((0, 0), (3, 3), None)), "a1d4");
assert_eq!(move_to_string(&((7, 7), (0, 7))), "h8a8"); assert_eq!(move_to_string(&((7, 7), (0, 7), None)), "h8a8");
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(board::SQ_Q))), "h7h8q");
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(board::SQ_N))), "h7h8n");
} }
#[test] #[test]
fn test_parse_move() { fn test_parse_move() {
assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3))); assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3), None));
assert_eq!(parse_move("a7a8q"), ((0, 6), (0, 7), Some(board::SQ_Q)));
assert_eq!(parse_move("a7a8r"), ((0, 6), (0, 7), Some(board::SQ_R)));
} }
#[test] #[test]

View file

@ -40,21 +40,28 @@ pub fn get_piece_moves(board: &Board, at: &Pos) -> Vec<Move> {
fn get_pawn_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> { fn get_pawn_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
let (f, r) = *at; let (f, r) = *at;
let mut moves = vec!(); let mut moves = vec!();
let movement: i8 = if is_white(piece) { 1 } else { -1 }; // Direction: positive for white, negative for black.
let dir: i8 = if is_white(piece) { 1 } else { -1 };
// Check 1 or 2 square forward. // Check 1 or 2 square forward.
let move_len = if (is_white(piece) && r == 1) || (is_black(piece) && r == 6) { 2 } else { 1 }; let move_len = if (is_white(piece) && r == 1) || (is_black(piece) && r == 6) { 2 } else { 1 };
for i in 1..=move_len { for i in 1..=move_len {
let forward_r = r + movement * i; let forward_r = r + dir * i;
if movement > 0 && forward_r > POS_MAX { if dir > 0 && forward_r > POS_MAX {
return moves return moves
} }
if movement < 0 && forward_r < POS_MIN { if dir < 0 && forward_r < POS_MIN {
return moves return moves
} }
let forward: Pos = (f, forward_r); let forward: Pos = (f, forward_r);
// If forward square is empty (and we are not jumping over an occupied square), add it. // If forward square is empty (and we are not jumping over an occupied square), add it.
if is_empty(board, &forward) && (i == 1 || is_empty(board, &(f, forward_r - 1))) { if is_empty(board, &forward) && (i == 1 || is_empty(board, &(f, forward_r - dir))) {
moves.push((*at, forward)) // Pawns that get to the opposite rank automatically promote as queens.
let prom = if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
Some(SQ_Q)
} else {
None
};
moves.push((*at, forward, prom))
} }
// Check diagonals for pieces to attack. // Check diagonals for pieces to attack.
if i == 1 { if i == 1 {
@ -92,7 +99,7 @@ fn get_bishop_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
continue continue
} }
if is_empty(board, &p) { if is_empty(board, &p) {
moves.push((*at, p)); moves.push((*at, p, None));
} else { } else {
if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) { if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
moves.push(m); moves.push(m);
@ -113,7 +120,7 @@ fn get_knight_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
continue continue
} }
if is_empty(board, &p) { if is_empty(board, &p) {
moves.push((*at, p)); moves.push((*at, p, None));
} else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) { } else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
moves.push(m); moves.push(m);
} }
@ -135,7 +142,7 @@ fn get_rook_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
continue continue
} }
if is_empty(board, &p) { if is_empty(board, &p) {
moves.push((*at, p)); moves.push((*at, p, None));
} else { } else {
if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) { if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
moves.push(m); moves.push(m);
@ -164,7 +171,7 @@ fn get_king_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
continue continue
} }
if is_empty(board, &p) { if is_empty(board, &p) {
moves.push((*at, p)); moves.push((*at, p, None));
} else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) { } else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
moves.push(m); moves.push(m);
} }
@ -177,7 +184,16 @@ fn get_king_moves(board: &Board, at: &Pos, piece: u8) -> Vec<Move> {
fn move_on_enemy(piece1: u8, pos1: &Pos, piece2: u8, pos2: &Pos) -> Option<Move> { fn move_on_enemy(piece1: u8, pos1: &Pos, piece2: u8, pos2: &Pos) -> Option<Move> {
let color1 = get_color(piece1); let color1 = get_color(piece1);
if is_color(piece2, opposite(color1)) { if is_color(piece2, opposite(color1)) {
Some((*pos1, *pos2)) // Automatic queen promotion for pawns moving to the opposite rank.
let prom = if
is_piece(piece1, SQ_P) &&
((is_white(piece1) && pos2.1 == POS_MAX) || (is_black(piece1) && pos2.1 == POS_MIN))
{
Some(SQ_Q)
} else {
None
};
Some((*pos1, *pos2, prom))
} else { } else {
None None
} }
@ -231,20 +247,20 @@ mod tests {
// Check that a pawn (here white queen's pawn) can move forward if the road is free. // Check that a pawn (here white queen's pawn) can move forward if the road is free.
set_square(&mut b, &pos("d3"), SQ_WH_P); set_square(&mut b, &pos("d3"), SQ_WH_P);
let moves = get_piece_moves(&b, &pos("d3")); let moves = get_piece_moves(&b, &pos("d3"));
assert!(moves.len() == 1 && moves.contains( &(pos("d3"), pos("d4")) )); assert!(moves.len() == 1 && moves.contains( &(pos("d3"), pos("d4"), None) ));
// Check that a pawn (here white king's pawn) can move 2 square forward on first move. // Check that a pawn (here white king's pawn) can move 2 square forward on first move.
set_square(&mut b, &pos("e2"), SQ_WH_P); set_square(&mut b, &pos("e2"), SQ_WH_P);
let moves = get_piece_moves(&b, &pos("e2")); let moves = get_piece_moves(&b, &pos("e2"));
assert_eq!(moves.len(), 2); assert_eq!(moves.len(), 2);
assert!(moves.contains( &(pos("e2"), pos("e3")) )); assert!(moves.contains( &(pos("e2"), pos("e3"), None) ));
assert!(moves.contains( &(pos("e2"), pos("e4")) )); assert!(moves.contains( &(pos("e2"), pos("e4"), None) ));
// Check that a pawn cannot move forward if a piece is blocking its path. // Check that a pawn cannot move forward if a piece is blocking its path.
// 1. black pawn 2 square forward; only 1 square forward available from start pos. // 1. black pawn 2 square forward; only 1 square forward available from start pos.
set_square(&mut b, &pos("e4"), SQ_BL_P); set_square(&mut b, &pos("e4"), SQ_BL_P);
let moves = get_piece_moves(&b, &pos("e2")); let moves = get_piece_moves(&b, &pos("e2"));
assert!(moves.len() == 1 && moves.contains( &(pos("e2"), pos("e3")) )); assert!(moves.len() == 1 && moves.contains( &(pos("e2"), pos("e3"), None) ));
// 2. black pawn 1 square forward; no square available. // 2. black pawn 1 square forward; no square available.
set_square(&mut b, &pos("e3"), SQ_BL_P); set_square(&mut b, &pos("e3"), SQ_BL_P);
let moves = get_piece_moves(&b, &pos("e2")); let moves = get_piece_moves(&b, &pos("e2"));
@ -257,12 +273,18 @@ mod tests {
// Check that a pawn can take a piece diagonally. // Check that a pawn can take a piece diagonally.
set_square(&mut b, &pos("f3"), SQ_BL_P); set_square(&mut b, &pos("f3"), SQ_BL_P);
let moves = get_piece_moves(&b, &pos("e2")); let moves = get_piece_moves(&b, &pos("e2"));
assert!(moves.len() == 1 && moves.contains( &(pos("e2"), pos("f3")) )); assert!(moves.len() == 1 && moves.contains( &(pos("e2"), pos("f3"), None) ));
set_square(&mut b, &pos("d3"), SQ_BL_P); set_square(&mut b, &pos("d3"), SQ_BL_P);
let moves = get_piece_moves(&b, &pos("e2")); let moves = get_piece_moves(&b, &pos("e2"));
assert_eq!(moves.len(), 2); assert_eq!(moves.len(), 2);
assert!(moves.contains( &(pos("e2"), pos("f3")) )); assert!(moves.contains( &(pos("e2"), pos("f3"), None) ));
assert!(moves.contains( &(pos("e2"), pos("d3")) )); assert!(moves.contains( &(pos("e2"), pos("d3"), None) ));
// Check that a pawn moving to the last rank leads to queen promotion.
// 1. by simply moving forward.
set_square(&mut b, &pos("a7"), SQ_WH_P);
let moves = get_piece_moves(&b, &pos("a7"));
assert!(moves.len() == 1 && moves.contains( &(pos("a7"), pos("a8"), Some(SQ_Q)) ));
} }
#[test] #[test]
@ -274,22 +296,22 @@ mod tests {
let moves = get_piece_moves(&b, &pos("d4")); let moves = get_piece_moves(&b, &pos("d4"));
assert_eq!(moves.len(), 13); assert_eq!(moves.len(), 13);
// Going top-right. // Going top-right.
assert!(moves.contains( &(pos("d4"), pos("e5")) )); assert!(moves.contains( &(pos("d4"), pos("e5"), None) ));
assert!(moves.contains( &(pos("d4"), pos("f6")) )); assert!(moves.contains( &(pos("d4"), pos("f6"), None) ));
assert!(moves.contains( &(pos("d4"), pos("g7")) )); assert!(moves.contains( &(pos("d4"), pos("g7"), None) ));
assert!(moves.contains( &(pos("d4"), pos("h8")) )); assert!(moves.contains( &(pos("d4"), pos("h8"), None) ));
// Going bottom-right. // Going bottom-right.
assert!(moves.contains( &(pos("d4"), pos("e3")) )); assert!(moves.contains( &(pos("d4"), pos("e3"), None) ));
assert!(moves.contains( &(pos("d4"), pos("f2")) )); assert!(moves.contains( &(pos("d4"), pos("f2"), None) ));
assert!(moves.contains( &(pos("d4"), pos("g1")) )); assert!(moves.contains( &(pos("d4"), pos("g1"), None) ));
// Going bottom-left. // Going bottom-left.
assert!(moves.contains( &(pos("d4"), pos("c3")) )); assert!(moves.contains( &(pos("d4"), pos("c3"), None) ));
assert!(moves.contains( &(pos("d4"), pos("b2")) )); assert!(moves.contains( &(pos("d4"), pos("b2"), None) ));
assert!(moves.contains( &(pos("d4"), pos("a1")) )); assert!(moves.contains( &(pos("d4"), pos("a1"), None) ));
// Going top-left. // Going top-left.
assert!(moves.contains( &(pos("d4"), pos("c5")) )); assert!(moves.contains( &(pos("d4"), pos("c5"), None) ));
assert!(moves.contains( &(pos("d4"), pos("b6")) )); assert!(moves.contains( &(pos("d4"), pos("b6"), None) ));
assert!(moves.contains( &(pos("d4"), pos("a7")) )); assert!(moves.contains( &(pos("d4"), pos("a7"), None) ));
// When blocking sight to one square with friendly piece, lose 2 moves. // When blocking sight to one square with friendly piece, lose 2 moves.
set_square(&mut b, &pos("b2"), SQ_WH_P); set_square(&mut b, &pos("b2"), SQ_WH_P);
@ -375,7 +397,7 @@ mod tests {
set_square(&mut b, &pos("d6"), SQ_BL_R); set_square(&mut b, &pos("d6"), SQ_BL_R);
assert!(is_attacked(&b, &pos("d4"))); assert!(is_attacked(&b, &pos("d4")));
// Move the rook on another file, no more attack. // Move the rook on another file, no more attack.
apply_into(&mut b, &(pos("d6"), pos("e6"))); apply_into(&mut b, &(pos("d6"), pos("e6"), None));
assert!(!is_attacked(&b, &pos("d4"))); assert!(!is_attacked(&b, &pos("d4")));
} }
} }