chess_turn_engine/game/
simulation.rs

1use super::board::Board;
2use super::board_map::BoardMap;
3use super::castlinginfo;
4use super::enpassant::Enpassant;
5use super::king::{self, KingState};
6use super::movement::{self, PieceMove};
7use super::play;
8use super::state::State;
9use super::{AvailableTurn, Side};
10use chess_notation_parser::{
11    turn_castling, turn_move, Castling, CastlingType, Flag, FlagCheck, Move,
12    Piece, Square, Turn,
13};
14
15/// Square/Piece/Side - Basic info about chessboard pieces
16struct Sps {
17    square: Square,
18    piece: Piece,
19    side: Side,
20}
21
22/// Return list of all possible turns that are valid and can be played
23pub fn get_available_turns(board: &mut Board) -> Vec<AvailableTurn> {
24    let mut available_turns = Vec::<AvailableTurn>::with_capacity(128);
25    let side = board.active_player;
26
27    // Square/Piece/Side
28    let player: Vec<Sps> = scan_for_pieces(&board.map, side);
29
30    for sps in player.iter() {
31        let moves = &mut get_turns(sps, board);
32        available_turns.append(moves);
33    }
34
35    available_turns
36}
37
38/// Find info for every piece for a given player
39fn scan_for_pieces(map: &BoardMap, side: Side) -> Vec<Sps> {
40    map.into_iter()
41        .filter(|(_, (_, s))| side == *s)
42        .map(|(sq, (p, _))| Sps {
43            square: sq,
44            piece: p,
45            side,
46        })
47        .collect::<Vec<Sps>>()
48}
49
50/// Preparation for `AvailableTurn` struct
51struct TurnInfo {
52    captured: Option<Piece>,
53    turn: Turn,
54}
55
56/// Get all possible turns for a given piece
57fn get_turns(sps: &Sps, board: &mut Board) -> Vec<AvailableTurn> {
58    let unchecked_turns = get_unchecked_turns(sps, board);
59    let mut turns = get_check_checkmate_flags(unchecked_turns, sps, board);
60
61    set_correct_src(&mut turns, &board.map, sps);
62    gen_available_turns(turns, sps)
63}
64
65/// Generate available turns.
66/// Transform all `Turn' structs into `AvailableTurn` structs.
67fn gen_available_turns(turns: Vec<TurnInfo>, sps: &Sps) -> Vec<AvailableTurn> {
68    turns
69        .iter()
70        .map(|turn_info| {
71            let dst = match turn_info.turn {
72                Turn::Move(ref turn) => turn.dst,
73                Turn::Castling(castling) => {
74                    castlinginfo::get_path_king(sps.side, castling.r#type).dst
75                }
76            };
77
78            AvailableTurn::new(
79                sps.square.to_string(),
80                dst.to_string(),
81                sps.piece.to_string(),
82                turn_info.captured.map(|piece| piece.to_string()),
83                turn_info.turn.to_string(),
84            )
85        })
86        .collect::<Vec<AvailableTurn>>()
87}
88
89/// Decide what must be set as a `src` in given `Turn`
90fn set_correct_src(turns: &mut [TurnInfo], map: &BoardMap, sps: &Sps) {
91    for turn_info in turns.iter_mut() {
92        if let Turn::Move(ref mut move_turn) = turn_info.turn {
93            move_turn.src = match sps.piece {
94                Piece::Pawn => get_correct_pawn_src(move_turn, sps),
95                _ => get_correct_non_pawn_src(move_turn, map, sps),
96            };
97        }
98    }
99}
100
101/// Decide correct `src` for a pawn turn
102fn get_correct_pawn_src(t_move: &Move, sps: &Sps) -> Option<Vec<Square>> {
103    match t_move.check_flag(Flag::CAPTURE) {
104        // Pawns require annotated source only in case of capture
105        false => None,
106        _ => Some(Square::get_file(sps.square.get_file_char()).unwrap()),
107    }
108}
109
110/// Decide correct `src` for a non-pawn turn
111fn get_correct_non_pawn_src(
112    t_move: &Move,
113    map: &BoardMap,
114    sps: &Sps,
115) -> Option<Vec<Square>> {
116    let mut possible_src = movement::possible_squares_for_dst(
117        map,
118        t_move.dst,
119        sps.side,
120        PieceMove::from(sps.piece),
121    );
122
123    // We must always find at least one possible square which is sps.square
124    if possible_src.len() == 1 {
125        return None;
126    }
127
128    // Remove actual originating square
129    possible_src.retain(|s| *s != sps.square);
130
131    // TODO: Compress repetition below
132    let src_file = sps.square.get_file_char();
133    let mut use_file = true;
134    for square in possible_src.iter() {
135        if src_file == square.get_file_char() {
136            use_file = false;
137            break;
138        }
139    }
140
141    if use_file {
142        return Some(Square::get_file(src_file).unwrap());
143    }
144
145    let src_rank = sps.square.get_rank_char();
146    let mut use_rank = true;
147    for square in possible_src.iter() {
148        if src_rank == square.get_rank_char() {
149            use_rank = false;
150            break;
151        }
152    }
153
154    if use_rank {
155        return Some(Square::get_rank(src_rank).unwrap());
156    }
157
158    Some(vec![sps.square])
159}
160
161/// Return turns with dst, promotion and capture flag set
162fn get_unchecked_turns(sps: &Sps, board: &mut Board) -> Vec<Turn> {
163    let get_castling = |turn: &Turn| -> CastlingType {
164        match *turn {
165            Turn::Castling(castling) => castling.r#type,
166            _ => panic!("Castling expected"),
167        }
168    };
169
170    let active_side = board.active_player;
171    let mut unchecked_turns = match sps.piece {
172        // Delicate pawns require special handling attention
173        Piece::Pawn => return get_unchecked_pawn_turns(sps, board),
174
175        // Include castling turns along with the king
176        Piece::King => board
177            .castling_rights
178            .get()
179            .into_iter()
180            .filter(|(side, _)| *side == active_side)
181            .map(|(_, castling)| turn_castling!(castling))
182            .filter(|turn| {
183                play::verify_castling(board, get_castling(turn)).is_ok()
184            })
185            .collect::<Vec<Turn>>(),
186        _ => vec![],
187    };
188
189    unchecked_turns.append(&mut get_unchecked_non_pawn_turns(sps, board));
190    unchecked_turns
191}
192
193/// Return non-pawn turns with dst, promotion and and capture flag setup
194fn get_unchecked_non_pawn_turns(sps: &Sps, board: &Board) -> Vec<Turn> {
195    movement::possible_squares_for_src(
196        &board.map,
197        sps.square,
198        sps.side,
199        PieceMove::from(sps.piece),
200    )
201    .iter()
202    .map(|dst| {
203        turn_move!(
204            sps.piece,
205            *dst,
206            match board.map.get(dst).is_some() {
207                true => Flag::CAPTURE,
208                false => 0,
209            }
210        )
211    })
212    .collect::<Vec<Turn>>()
213}
214
215/// Return pawn turns with dst, promotion and and capture flag setup
216fn get_unchecked_pawn_turns(sps: &Sps, board: &Board) -> Vec<Turn> {
217    // Fetch normal turns first
218    let mut turns = movement::possible_squares_for_src(
219        &board.map,
220        sps.square,
221        sps.side,
222        PieceMove::PawnNormal,
223    )
224    .into_iter()
225    .filter(|square| board.map.get(square).is_none())
226    .map(|dst| turn_move!(Piece::Pawn, dst, Flag::NONE))
227    .collect::<Vec<Turn>>();
228
229    // Fetch capture turns
230    let mut capture_turns = movement::possible_squares_for_src(
231        &board.map,
232        sps.square,
233        sps.side,
234        PieceMove::PawnCapture,
235    )
236    .into_iter()
237    .filter(|square| match board.map.get(square) {
238        None => match board.enpassant {
239            None => false,
240            Some(enpassant) => *square == enpassant.capture_pos,
241        },
242        Some((p, s)) => s != board.active_player && p != Piece::King,
243    })
244    .map(|dst| turn_move!(Piece::Pawn, dst, Flag::CAPTURE))
245    .collect::<Vec<Turn>>();
246
247    turns.append(&mut capture_turns);
248    turns = get_promotion_for_unchecked_pawn_turns(turns, board.active_player);
249    turns
250}
251
252/// Generate promotion turns for pawns that reach it's final rank
253fn get_promotion_for_unchecked_pawn_turns(
254    mut turns: Vec<Turn>,
255    side: Side,
256) -> Vec<Turn> {
257    let mut promotion_moves: Vec<Move> = vec![];
258    let mut turns_iter = turns.iter_mut();
259
260    while let Some(Turn::Move(turn)) = turns_iter.next() {
261        if (turn.dst.get_rank_char() != '1' || side != Side::Black)
262            && (turn.dst.get_rank_char() != '8' || side != Side::White)
263        {
264            continue;
265        }
266
267        // Use current `Move` to promote to queen
268        turn.promotion = Some(Piece::Queen);
269
270        let mut gen_promoted_turn = |piece| {
271            let mut new_move: Move = turn.clone();
272            new_move.promotion = Some(piece);
273            promotion_moves.push(new_move);
274        };
275
276        // Generate rest of the promotion moves
277        gen_promoted_turn(Piece::Rook);
278        gen_promoted_turn(Piece::Bishop);
279        gen_promoted_turn(Piece::Knight);
280    }
281
282    let mut promotion_turns = promotion_moves
283        .into_iter()
284        .map(Turn::Move)
285        .collect::<Vec<Turn>>();
286
287    turns.append(&mut promotion_turns);
288    turns
289}
290
291/// Play the turn and check validity of the turn
292fn simulate_turn(
293    board: &mut Board,
294    sps: &Sps,
295    turn: &Turn,
296) -> Result<State, ()> {
297    let ret_ok = match turn {
298        Turn::Move(r#move) if sps.piece == Piece::Pawn => {
299            simulate_pawn_move(board, sps, r#move)
300        }
301        Turn::Move(r#move) => simulate_move(board, sps, r#move),
302        Turn::Castling(castling) => simulate_castling(board, castling),
303    }?;
304
305    board.hash_state_push();
306    board.active_player.switch_side();
307    Ok(ret_ok)
308}
309
310/// Simulate castling turn
311fn simulate_castling(board: &mut Board, turn: &Castling) -> Result<State, ()> {
312    let state = State::new(board, turn.to_string());
313
314    let side = board.active_player;
315    let castling = (side, turn.r#type);
316
317    // Something is quite wrong if castling is not available
318    assert!(
319        board.castling_rights.remove(&castling),
320        "Castling right not found"
321    );
322
323    let rook_path = castlinginfo::get_path_rook(side, turn.r#type);
324    board.move_piece(rook_path.dst, rook_path.src);
325
326    let king_path = castlinginfo::get_path_king(side, turn.r#type);
327    board.move_piece(king_path.dst, king_path.src);
328
329    // King safety have been ensured in `get_unchecked_turns` function,
330    // but let's double check
331    assert!(
332        king::is_safe(&board.map, board.get_king_pos(side), side),
333        "Castling: King is not safe"
334    );
335
336    // Remove any possibility for castling for this player
337    let castling_opposite = (side, turn.r#type.opposite());
338    board.castling_rights.remove(&castling_opposite);
339
340    board.enpassant = None;
341
342    Ok(state)
343}
344
345/// Simulate pawn move
346fn simulate_pawn_move(
347    board: &mut Board,
348    sps: &Sps,
349    turn: &Move,
350) -> Result<State, ()> {
351    let mut state = State::new(board, turn.to_string());
352    let side = board.active_player;
353
354    // I used to like pawns before doing all this bs :-)
355    // No need to check validity of the turn here, it has been done before
356    // when unchecked turns were fetched
357    state.captured = match board.move_piece(turn.dst, sps.square) {
358        None => match board.enpassant {
359            None => None,
360            Some(enpassant) => match turn.dst == enpassant.capture_pos {
361                true => Some((
362                    enpassant.pawn_src,
363                    board.map.remove(&enpassant.pawn_src).unwrap(),
364                )),
365                false => None,
366            },
367        },
368        Some(captured) => Some((turn.dst, captured)),
369    };
370
371    // Double check capturing logic due to en-passant and whole pawn complexity
372    assert!(!turn.check_flag(Flag::CAPTURE) ^ state.captured.is_some());
373
374    state.moving_piece_src = Some(sps.square);
375
376    // If our king is not safe, undo the simulated move
377    if !king::is_safe(&board.map, board.get_king_pos(side), side) {
378        // Undo will swap sides, so we don't want to change playing side here
379        board.undo(state);
380        return Err(());
381    }
382
383    if let Some(promotion) = turn.promotion {
384        board.map.insert(turn.dst, (promotion, sps.side));
385    }
386
387    board.fifty_move_rule = 0;
388
389    // Update new en-passant state
390    board.enpassant = match turn.check_flag(Flag::CAPTURE) {
391        true => None,
392        _ => Enpassant::try_from(sps.square, turn.dst, side),
393    };
394
395    Ok(state)
396}
397
398/// Move piece from src to dst if our king is not in check
399fn simulate_move(
400    board: &mut Board,
401    sps: &Sps,
402    turn: &Move,
403) -> Result<State, ()> {
404    let mut state = State::new(board, turn.to_string());
405    let side = board.active_player;
406
407    let captured = board.move_piece(turn.dst, sps.square);
408    state.captured = captured.map(|captured| (turn.dst, captured));
409
410    state.moving_piece_src = Some(sps.square);
411
412    // If our king is not safe, undo the simulated move
413    if !king::is_safe(&board.map, board.get_king_pos(side), side) {
414        // Undo will swap sides, so we don't want to change playing side here
415        board.undo(state);
416        return Err(());
417    }
418
419    // Post success actions
420    board.fifty_move_rule = 0;
421    board.enpassant = None;
422    play::handle_castling_status(board, sps.square, turn, &captured);
423
424    Ok(state)
425}
426
427/// Update check and checkmate flags and prepare `captured` piece
428fn get_check_checkmate_flags(
429    mut turns: Vec<Turn>,
430    sps: &Sps,
431    board: &mut Board,
432) -> Vec<TurnInfo> {
433    let mut to_be_removed = Vec::<usize>::new();
434    let mut captured = Vec::<Option<Piece>>::with_capacity(turns.len());
435
436    for (i, turn) in turns.iter_mut().enumerate() {
437        // `simulate_turn` swaps `active_player` side
438        let simulated_state = simulate_turn(board, sps, turn);
439        if simulated_state.is_err() {
440            captured.push(None);
441            to_be_removed.push(i);
442            continue;
443        }
444
445        match king::get_state(board, board.active_player) {
446            KingState::Safe => (),
447            KingState::Check => add_turn_flag(turn, Flag::CHECK),
448            // Check that king is really in checkmate
449            _ => add_turn_flag(
450                turn,
451                match confirm_checkmate(board) {
452                    true => Flag::CHECKMATE,
453                    _ => Flag::CHECK,
454                },
455            ),
456        }
457
458        let simulated_state = simulated_state.unwrap();
459        captured
460            .push(simulated_state.captured.map(|(_, (captured, _))| captured));
461
462        board.active_player.switch_side();
463        board.hash_state_pop();
464        board.undo(simulated_state);
465    }
466
467    // Remove those turns which endanger our king
468    while let Some(i) = to_be_removed.pop() {
469        turns.remove(i);
470        captured.remove(i);
471    }
472
473    turns
474        .into_iter()
475        .zip(captured.into_iter())
476        .map(|(turn, captured)| TurnInfo { turn, captured })
477        .collect::<Vec<TurnInfo>>()
478}
479
480/// Opponent's king is in check and has no safe moves available
481/// Check if any opponent pieces can remove the check
482fn confirm_checkmate(board: &mut Board) -> bool {
483    // Let's act like this is N+1th turn on the board
484    let opponent: Vec<Sps> = scan_for_pieces(&board.map, board.active_player);
485
486    for sps in opponent {
487        for turn in get_unchecked_turns(&sps, board) {
488            match simulate_turn(board, &sps, &turn) {
489                // If we can play any simulated turn, it means that our king is
490                // not in check after our turn. It implies the turn has removed
491                // king from the check, therefore king was never in checkmate
492                Ok(state) => {
493                    board.active_player.switch_side();
494                    board.hash_state_pop();
495                    board.undo(state);
496                    return false;
497                }
498                _ => continue,
499            }
500        }
501    }
502
503    true
504}
505
506/// Append turn flag
507fn add_turn_flag(turn: &mut Turn, flag: u8) {
508    match turn {
509        Turn::Castling(ref mut castling) => castling.flags |= flag,
510        Turn::Move(ref mut r#move) => r#move.flags |= flag,
511    }
512}