rustenginelib/
state.rs

1use crate::bitboard::*;
2use crate::constants::*;
3use crate::piece::*;
4use crate::square::*;
5
6/// MoveBuffItem stores a move with meta information
7#[derive(Clone)]
8pub struct MoveBuffItem {
9    pub mv: Move,
10    uci: String,
11}
12
13/// CastlingRight represents a castling right
14#[derive(Copy, Clone)]
15pub struct CastlingRight {
16    pub can_castle: bool,
17}
18
19/// ColorCastlingRights represents castling rights for a color
20#[derive(Copy, Clone)]
21pub struct ColorCastlingRights {
22    pub rights: [CastlingRight; 2],
23}
24
25/// CastlingRights represents all castling rights
26#[derive(Copy, Clone)]
27pub struct CastlingRigths {
28    rights: [ColorCastlingRights; 2],
29}
30
31/// State records the state of a chess game
32#[derive(Clone)]
33pub struct State {
34    variant: Variant,
35    rep: [Piece; BOARD_AREA],
36    pub turn: Color,
37    ep_square: Square,
38    halfmove_clock: usize,
39    fullmove_number: usize,
40    has_disabled_move: bool,
41    disable_from_sq: Square,
42    disable_to_sq: Square,
43    by_figure: [[Bitboard; FIGURE_ARRAY_SIZE]; 2],
44    by_color: [Bitboard; 2],
45    castling_rights: CastlingRigths,
46    pub move_buff: Vec<MoveBuffItem>,
47}
48
49/// Variant type records the index of the variant
50pub type Variant = usize;
51
52trait VariantTrait {
53    /// returns name of variant
54    fn string(self) -> String;
55}
56
57impl VariantTrait for Variant {
58    /// returns name of variant
59    fn string(self) -> String {
60        let name = match self {
61            VARIANT_STANDARD => "Standard",
62            VARIANT_EIGHTPIECE => "Eightpiece",
63            VARIANT_ATOMIC => "Atomic",
64            _ => "Unknownvariant",
65        };
66        name.to_string()
67    }
68}
69
70/// VariantInfo records variant information
71pub struct VariantInfo {
72    pub start_fen: &'static str,
73    pub display_name: &'static str,
74}
75
76/// State implementation
77impl State {
78    /// parses piece placement
79    pub fn parse_piece_placement(&mut self, fen: &str) {
80        self.by_figure = [EMTPY_FIGURE_BITBOARDS, EMTPY_FIGURE_BITBOARDS];
81        self.by_color = [0, 0];
82        let mut rank: Rank = 0;
83        let mut file: File = 0;
84        let mut lancer_color = 0;
85        let mut lancer_has_north = false;
86        let mut lancer_index = 0;
87        for i in 0..fen.len() {
88            let c = &fen[i..i + 1];
89            if file > LAST_FILE && c != "/" && c != " " {
90                panic!("invalid piece placement file");
91            }
92            let mut examine_c = true;
93            let sq: Square = (LAST_RANK - rank) * NUM_FILES + file;
94            if c == "l" {
95                lancer_color = BLACK;
96                lancer_index = 1;
97            } else if c == "L" {
98                lancer_color = WHITE;
99                lancer_index = 1;
100            } else if lancer_index == 1 {
101                if c == "n" {
102                    lancer_has_north = true;
103                    lancer_index = 2;
104                } else if c == "s" {
105                    lancer_has_north = false;
106                    lancer_index = 2;
107                } else if c == "e" {
108                    self.put(sq, color_figure(lancer_color, LANCERE));
109                    file += 1;
110                    lancer_index = 0;
111                    examine_c = false;
112                } else if c == "w" {
113                    self.put(sq, color_figure(lancer_color, LANCERW));
114                    file += 1;
115                    lancer_index = 0;
116                    examine_c = false;
117                } else {
118                    panic!("invalid lancer")
119                }
120            } else if lancer_index == 2 {
121                if c == "e" {
122                    if lancer_has_north {
123                        self.put(sq, color_figure(lancer_color, LANCERNE));
124                        file += 1;
125                        lancer_index = 0;
126                        examine_c = false;
127                    } else {
128                        self.put(sq, color_figure(lancer_color, LANCERSE));
129                        file += 1;
130                        lancer_index = 0;
131                        examine_c = false;
132                    }
133                } else if c == "w" {
134                    if lancer_has_north {
135                        self.put(sq, color_figure(lancer_color, LANCERNW));
136                        file += 1;
137                        lancer_index = 0;
138                        examine_c = false;
139                    } else {
140                        self.put(sq, color_figure(lancer_color, LANCERSW));
141                        file += 1;
142                        lancer_index = 0;
143                        examine_c = false;
144                    }
145                } else {
146                    if lancer_has_north {
147                        self.put(sq, color_figure(lancer_color, LANCERN));
148                        file += 1;
149                        lancer_index = 0;
150                    } else {
151                        self.put(sq, color_figure(lancer_color, LANCERS));
152                        file += 1;
153                        lancer_index = 0;
154                    }
155                }
156            }
157            if lancer_index == 0 && examine_c {
158                if c == " " {
159                    return;
160                } else if c == "/" {
161                    file = 0;
162                    rank += 1;
163                } else if c >= "1" && c <= "8" {
164                    for _ in 0..c.parse().expect("should not happen") {
165                        if file > LAST_FILE {
166                            panic!("invalid piece placement file");
167                        }
168                        self.remove((LAST_RANK - rank) * NUM_FILES + file);
169                        file += 1;
170                    }
171                } else {
172                    let p = fen_symbol_to_piece(c);
173                    if p != NO_PIECE {
174                        self.put(sq, p);
175                        file += 1;
176                    } else {
177                        panic!("invalid fen symbol")
178                    }
179                }
180            }
181        }
182    }
183
184    /// creates a new empty State
185    pub fn new() -> State {
186        State {
187            variant: DEFAULT_VARIANT,
188            rep: EMPTY_REP,
189            turn: WHITE,
190            ep_square: SQUARE_A1,
191            halfmove_clock: 0,
192            fullmove_number: 1,
193            has_disabled_move: false,
194            disable_from_sq: SQUARE_A1,
195            disable_to_sq: SQUARE_A1,
196            by_figure: [EMTPY_FIGURE_BITBOARDS, EMTPY_FIGURE_BITBOARDS],
197            by_color: [0, 0],
198            castling_rights: CastlingRigths {
199                rights: [EMPTY_COLOR_CASTLING_RIGHTS, EMPTY_COLOR_CASTLING_RIGHTS],
200            },
201            move_buff: Vec::new(),
202        }
203    }
204
205    /// sets state from fen
206    pub fn set_from_fen(&mut self, fen: &str) {
207        let parts: Vec<&str> = fen.split(" ").collect();
208
209        let l = parts.len();
210
211        if l != 4 && l != 6 && l != 7 {
212            panic!("invalid number of fen fields {}", l);
213        }
214
215        self.parse_piece_placement(parts[0]);
216
217        match parts[1] {
218            "w" => self.turn = WHITE,
219            "b" => self.turn = BLACK,
220            _ => panic!("invalid turn {}", parts[1]),
221        }
222
223        self.castling_rights = CastlingRigths {
224            rights: [EMPTY_COLOR_CASTLING_RIGHTS, EMPTY_COLOR_CASTLING_RIGHTS],
225        };
226
227        if parts[2] == "-" {
228            // no castling rights
229        } else {
230            for i in 0..parts[2].len() {
231                let r = &parts[2][i..i + 1];
232                match r {
233                    "K" => self.castling_rights.rights[WHITE].rights[KING_SIDE].can_castle = true,
234                    "Q" => self.castling_rights.rights[WHITE].rights[QUEEN_SIDE].can_castle = true,
235                    "k" => self.castling_rights.rights[BLACK].rights[KING_SIDE].can_castle = true,
236                    "q" => self.castling_rights.rights[BLACK].rights[QUEEN_SIDE].can_castle = true,
237                    _ => panic!("invalid castling right {}", r),
238                }
239            }
240        }
241
242        self.ep_square = SQUARE_A1;
243
244        if parts[3] != "-" {
245            self.ep_square = Square::from_uci(parts[3].to_string()).0;
246        }
247
248        self.halfmove_clock = parts[4].parse().expect("invalid halfmove clock");
249        self.fullmove_number = parts[5].parse().expect("invalid fullmove number");
250
251        self.has_disabled_move = false;
252
253        if self.variant == VARIANT_EIGHTPIECE {
254            if parts.len() > 6 {
255                if parts[6] != "-" {
256                    if parts[6].len() != 4 {
257                        panic!("invalid disabled mvoe {:?}", parts[6]);
258                    }
259                    let df = Square::from_uci(parts[6][0..2].to_string());
260                    let dt = Square::from_uci(parts[6][2..4].to_string());
261                    if df.1 && dt.1 {
262                        self.disable_from_sq = df.0;
263                        self.disable_to_sq = dt.0;
264                    } else {
265                        panic!("invalid disabled move {}", parts[6]);
266                    }
267                    self.has_disabled_move = true;
268                }
269            }
270        }
271    }
272
273    // puts a piece on a square
274    pub fn put(&mut self, sq: Square, p: Piece) {
275        if p == NO_PIECE {
276            return;
277        }
278        self.rep[sq] = p;
279        let bb = sq.bitboard();
280        self.by_figure[p.color()][p.figure()] |= bb;
281        self.by_color[p.color()] |= bb;
282    }
283
284    // removes the piece from a square
285    pub fn remove(&mut self, sq: Square) {
286        let p = self.piece_at_square(sq);
287        if p == NO_PIECE {
288            return;
289        }
290        self.rep[sq] = NO_PIECE;
291        let bb = sq.bitboard();
292        self.by_figure[p.color()][p.figure()] &= !bb;
293        self.by_color[p.color()] &= !bb;
294    }
295
296    /// initializes state to variant
297    pub fn init(&mut self, variant: Variant) {
298        self.variant = variant;
299        self.set_from_fen(VARIANT_INFOS[self.variant].start_fen);
300    }
301
302    /// returns the piece at a square
303    pub fn piece_at_square(&self, sq: Square) -> Piece {
304        self.rep[sq]
305    }
306
307    /// reports the state as fen
308    pub fn report_fen(&self) -> String {
309        let mut buff = "".to_string();
310        let mut acc = 0;
311        for rank in 0..NUM_RANKS {
312            for file in 0..NUM_FILES {
313                let sq: Square = (LAST_RANK - rank) * NUM_FILES + file;
314                let p = self.piece_at_square(sq);
315                let mut should_flush = false;
316                if p == NO_PIECE {
317                    acc += 1;
318                } else {
319                    should_flush = true;
320                    buff = format!("{}{}", buff, p.fen_symbol());
321                }
322                if acc > 0 && (should_flush || file == LAST_FILE) {
323                    buff = format!("{}{}", buff, acc);
324                    acc = 0;
325                }
326                if file == LAST_FILE && rank < LAST_RANK {
327                    buff = format!("{}/", buff);
328                }
329            }
330        }
331        buff = format!("{} {}", buff, self.turn.turn_fen());
332        let mut cfen = "".to_string();
333        if self.castling_rights.rights[WHITE].rights[KING_SIDE].can_castle {
334            cfen = format!("{}{}", cfen, "K")
335        }
336        if self.castling_rights.rights[WHITE].rights[QUEEN_SIDE].can_castle {
337            cfen = format!("{}{}", cfen, "Q")
338        }
339        if self.castling_rights.rights[BLACK].rights[KING_SIDE].can_castle {
340            cfen = format!("{}{}", cfen, "k")
341        }
342        if self.castling_rights.rights[BLACK].rights[QUEEN_SIDE].can_castle {
343            cfen = format!("{}{}", cfen, "q")
344        }
345        if cfen == "" {
346            cfen = "-".to_string();
347        }
348        let mut epfen = "-".to_string();
349        if self.ep_square != SQUARE_A1 {
350            epfen = self.ep_square.uci();
351        }
352        buff = format!(
353            "{} {} {} {} {}",
354            buff, cfen, epfen, self.halfmove_clock, self.fullmove_number
355        );
356        if self.variant == VARIANT_EIGHTPIECE {
357            let mut dfen = "-".to_string();
358            if self.has_disabled_move {
359                dfen = format!("{}{}", self.disable_from_sq.uci(), self.disable_to_sq.uci());
360            }
361            buff = format!("{} {}", buff, dfen);
362        }
363        buff
364    }
365
366    /// prints bitboards
367    pub fn print_bitboards(&self) {
368        for col in BLACK..WHITE + 1 {
369            for fig in FIG_MIN..FIG_MAX {
370                println!(
371                    "{} {} {}",
372                    col.turn_fen(),
373                    fig.symbol(),
374                    self.by_figure[col][fig].pretty_print_string()
375                );
376            }
377        }
378    }
379
380    /// returns mobility of color figure at square
381    pub fn color_figure_mobility_at_square(
382        &self,
383        sq: Square,
384        gen_mode: MoveGenMode,
385        col: Color,
386        fig: Figure,
387    ) -> Bitboard {
388        match fig.base_figure() {
389            KNIGHT => knight_mobility(
390                sq,
391                gen_mode,
392                self.by_color[col],
393                self.by_color[col.inverse()],
394            ),
395            BISHOP => bishop_mobility(
396                sq,
397                gen_mode,
398                self.by_color[col],
399                self.by_color[col.inverse()],
400            ),
401            SENTRY => bishop_mobility(
402                sq,
403                gen_mode,
404                self.by_color[col],
405                self.by_color[col.inverse()],
406            ),
407            ROOK => rook_mobility(
408                sq,
409                gen_mode,
410                self.by_color[col],
411                self.by_color[col.inverse()],
412            ),
413            JAILER => jailer_mobility(
414                sq,
415                gen_mode,
416                self.by_color[col],
417                self.by_color[col.inverse()],
418            ),
419            QUEEN => queen_mobility(
420                sq,
421                gen_mode,
422                self.by_color[col],
423                self.by_color[col.inverse()],
424            ),
425            LANCER => lancer_mobility(
426                sq,
427                gen_mode,
428                self.by_color[col],
429                self.by_color[col.inverse()],
430                LANCER_ATTACKS[fig.lancer_direction()][sq],
431            ),
432            KING => king_mobility(
433                sq,
434                gen_mode,
435                self.by_color[col],
436                self.by_color[col.inverse()],
437            ),
438            _ => 0,
439        }
440    }
441
442    /// generates pseudo legal moves for turn
443    pub fn generate_pseudo_legal_moves(&mut self, gen_mode: MoveGenMode) -> Vec<Move> {
444        self.generate_pseudo_legal_moves_for_color(gen_mode, self.turn)
445    }
446
447    /// generates pseudo legal moves for color
448    pub fn generate_pseudo_legal_moves_for_color(
449        &self,
450        gen_mode: MoveGenMode,
451        col: Color,
452    ) -> Vec<Move> {
453        let mut moves: Vec<Move> = vec![0; 0];
454        let mut bb = self.by_color[col];
455        loop {
456            let (sq, ok) = bb.pop_square();
457            if ok {
458                let p = self.piece_at_square(sq);
459                let fig = p.figure();
460                match fig {
461                    PAWN => {
462                        let pi: &PawnInfo = &PAWN_INFOS[col][sq];
463                        let mut do_pawn_moves = |pushes, captures| {
464                            if pushes {
465                                for to_sq in pi.pushes.iter() {
466                                    if self.piece_at_square(*to_sq) == NO_PIECE {
467                                        moves.push(Move::ft(sq, *to_sq));
468                                    }
469                                }
470                            }
471                            if captures {
472                                for to_sq in pi.captures.iter() {
473                                    let cp: Piece = self.piece_at_square(*to_sq);
474                                    if cp != NO_PIECE && cp.color() == col.inverse() {
475                                        moves.push(Move::ft(sq, *to_sq));
476                                    }
477                                }
478                            }
479                        };
480                        match gen_mode {
481                            MoveGenMode::Violent => do_pawn_moves(false, true),
482                            MoveGenMode::Quiet => do_pawn_moves(true, false),
483                            MoveGenMode::All => do_pawn_moves(true, true),
484                        }
485                    }
486                    _ => {
487                        let mut mob =
488                            self.color_figure_mobility_at_square(sq, gen_mode, col, p.figure());
489                        loop {
490                            let (to_sq, ok) = mob.pop_square();
491                            if ok {
492                                moves.push(Move::ft(sq, to_sq));
493                            } else {
494                                break;
495                            }
496                        }
497                    }
498                }
499            } else {
500                break;
501            }
502        }
503        moves
504    }
505
506    /// returns the state as pretty printable string
507    pub fn pretty_print_string(&mut self) -> String {
508        let mut buff = "".to_string();
509        for rank in 0..NUM_RANKS {
510            for file in 0..NUM_FILES {
511                let sq = rank_file(LAST_RANK - rank, file);
512                let p = self.piece_at_square(sq);
513                buff = format!("{}{:^3}", buff, p.fen_symbol());
514                if file == LAST_FILE {
515                    buff += "\n";
516                }
517            }
518        }
519        buff = format!(
520            "{}\nvariant {} fen {}\n",
521            buff,
522            self.variant.string(),
523            self.report_fen()
524        );
525        format!("{}\n{}\n", buff, self.gen_move_buff())
526    }
527
528    /// generates moves with meta information
529    pub fn gen_move_buff(&mut self) -> String {
530        let moves = self.generate_pseudo_legal_moves_for_color(MoveGenMode::All, self.turn);
531        let mut move_buff = "".to_string();
532        self.move_buff = Vec::new();
533        for i in 0..moves.len() {
534            let mv = moves[i];
535            self.move_buff.push(MoveBuffItem {
536                mv: mv,
537                uci: mv.uci(),
538            });
539        }
540        self.move_buff.sort_by(|a, b| a.uci.cmp(&b.uci));
541        for i in 0..self.move_buff.len() {
542            let fromp = self.piece_at_square(self.move_buff[i].mv.from_sq());
543            let mut san_letter = fromp.san_letter();
544            if fromp.figure() == PAWN {
545                san_letter = "";
546            }
547            let move_str = format!("{}. {}{}", i + 1, san_letter, self.move_buff[i].uci);
548            move_buff = format!("{}{:16}", move_buff, move_str);
549            if i % 6 == 5 {
550                move_buff = format!("{}\n", move_buff);
551            }
552        }
553        move_buff
554    }
555
556    /// initialize from variant
557    pub fn init_variant(&self) {}
558
559    /// returns the start fen for the variant of the state
560    pub fn variant_start_fen(&self) -> &str {
561        VARIANT_INFOS[self.variant].start_fen
562    }
563
564    /// makes a move
565    pub fn make_move(&mut self, mv: Move) {
566        let from_sq = mv.from_sq();
567        let to_sq = mv.to_sq();
568        let fromp: Piece = self.piece_at_square(from_sq);
569        //let top: Piece = self.piece_at_square(to_sq);
570        self.remove(from_sq);
571        self.put(to_sq, fromp);
572
573        self.turn = self.turn.inverse();
574    }
575}