aboutsummaryrefslogtreecommitdiff
path: root/src/board/io.rs
blob: cb73c5204df60714e022c66adf90eedc6b0df5a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::io::{stdin, stdout, Write};
use rand::{rngs::StdRng,SeedableRng,Rng};
use crate::{bitboard::Bitboard, attacks::Attacks, moves::Move, square::Square};


use super::{Board, PieceType, ttable::TTABLE_SIZE};

const PIECE_CHARS: [&str; 12] = [
    "♟︎", "♞", "♝", "♜", "♛", "♚",
    "♙", "♘", "♗", "♖", "♕", "♔",
];

/// Input/Output operations with Board
pub trait IO {
    fn print(&self) -> ();

    #[allow(non_snake_case)]
    fn from_FEN(fen: String) -> Self;
    #[allow(non_snake_case)]
    fn to_FEN(&self) -> String;

    fn read_move(&self) -> Result<Move, String>;
}

impl IO for Board {
    fn print(&self) {
        println!();
        for rank in (0..8).rev() {
            print!("{}|", rank + 1);
            for file in 0..8 {
                let index = rank * 8 + file;
                let position: Bitboard = 1 << index;
                let mut found = false;
                for (piece_type, piece_bitboard) in self.piece_sets.iter().enumerate() {
                    if (piece_bitboard & position) > 0 {
                        found = true;
                        print!("{} ", PIECE_CHARS[piece_type]);
                    }
                }
                if !found {
                    print!(". ");
                }
            }
            println!();
        }
        println!("  a b c d e f g h");
    }

    fn from_FEN(fen: String) -> Self {
        let mut piece_sets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

        let mut rank = 7;
        let mut file = 0i32;

        for character in fen.chars() {
            let index = rank * 8 + file;
            let position = 1 << index.clamp(0, 63);

            if character.is_numeric() {
                let digit = match character.to_digit(10) {
                    None => todo!("What to do here?"),
                    Some(digit) => digit,
                };
                if digit > 0 && digit <= 8 {
                    file += digit as i32;
                }
            } else {
                match character {
                    'P' => piece_sets[PieceType::Pawn as usize] |= position,
                    'N' => piece_sets[PieceType::Knight as usize] |= position,
                    'B' => piece_sets[PieceType::Bishop as usize] |= position,
                    'R' => piece_sets[PieceType::Rook as usize] |= position,
                    'Q' => piece_sets[PieceType::Queen as usize] |= position,
                    'K' => piece_sets[PieceType::King as usize] |= position,
                    'p' => piece_sets[PieceType::PawnBlack as usize] |= position,
                    'n' => piece_sets[PieceType::KnightBlack as usize] |= position,
                    'b' => piece_sets[PieceType::BishopBlack as usize] |= position,
                    'r' => piece_sets[PieceType::RookBlack as usize] |= position,
                    'q' => piece_sets[PieceType::QueenBlack as usize] |= position,
                    'k' => piece_sets[PieceType::KingBlack as usize] |= position,
                    '/' => {
                        rank -= 1;
                        file = -1; // So it becomes 0
                    },
                    ' ' => { break }, // TODO: break for now, parse everything else later
                    '-' => {}, // TODO
                    'w' => {}, // TODO
                    _ => todo!("Unexpected character!"),
                }
                file += 1;
            }
        }

        let mut rng = StdRng::seed_from_u64(228);
        let zobrist_seed = [(); 781].map(|_| rng.gen());

        let mut board = Self {
            piece_sets,
            occupancy: 0,
            ply: 0,
            attacks: Attacks::new(),
            castling_rights: [[true; 2]; 2], // TODO: actualy parse from FEN
            ep_target: None, // TODO: parse from FEN
            hash: 0,
            transposition_table: vec![None; TTABLE_SIZE as usize],
            zobrist_seed,
        };
        board.update_occupancy();
        board.update_zobrist_hash();
        board
    }

    #[allow(non_snake_case)]
    fn to_FEN(&self) -> String {
        todo!()
    }

    fn read_move(&self) -> Result<Move, String> {
        print!("\nEnter a move: ");
        stdout().flush().unwrap();
        let mut s = String::new();
        stdin().read_line(&mut s).unwrap();
        let chars = &mut s.chars();

        let source = match Square::from_notation(chars) {
            Ok(s) => s,
            Err(e) => return Err(e),
        };
        let target = match Square::from_notation(chars) {
            Ok(s) => s,
            Err(e) => return Err(e),
        };

        let moves = self.generate_pseudolegal_moves();

        let mov = match moves.iter().find(|m| m.source == source && m.target == target) {
            Some(m) => *m,
            None => return Err(String::from("Move is not valid")),
        };

        Ok(mov)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{bitboard::BitboardFns, square::Square, board::Color};

    #[test]
    fn new_from_default_fen() {
        let board = Board::new();

        board.print();
        board.empty().print("Empty squares");

        assert_eq!(board.piece_sets[PieceType::Pawn as usize].pop_count(), 8);
        assert_eq!(board.piece_sets[PieceType::Knight as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::Bishop as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::Rook as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::Queen as usize].pop_count(), 1);
        assert_eq!(board.piece_sets[PieceType::King as usize].pop_count(), 1);

        assert_eq!(board.piece_sets[PieceType::PawnBlack as usize].pop_count(), 8);
        assert_eq!(board.piece_sets[PieceType::KnightBlack as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::BishopBlack as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::RookBlack as usize].pop_count(), 2);
        assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].pop_count(), 1);
        assert_eq!(board.piece_sets[PieceType::KingBlack as usize].pop_count(), 1);

        assert_eq!(board.piece_sets[PieceType::King as usize].bitscan(), Square::E1);
        assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].bitscan(), Square::D8);

        assert_eq!(board.occupancy.pop_count(), 32);
        assert_eq!(board.empty().pop_count(), 32);
        assert_eq!(board.color_occupancy(Color::White).pop_count(), 16);
        assert_eq!(board.color_occupancy(Color::Black).pop_count(), 16);
    }
}