rules: handle pawn promotion
This commit is contained in:
parent
8310e0c1e7
commit
0daece72d3
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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]
|
||||||
|
|
86
src/rules.rs
86
src/rules.rs
|
@ -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")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue