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
180
181
182
183
184
185
186
187
mod bitboard;
mod solver;
mod transposition_table;

use self::bitboard::PlayerStones;
use std::{fmt, io, str::FromStr};

use bitboard::{heuristic, AllStones, NonLoosingMoves};
pub use solver::score;

/// An integer ranging from 0 to 6 representing a column of the connect four board.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Column(u8);

impl Column {
    /// Column index ranges from 0 to 6
    pub const fn from_index(index: u8) -> Column {
        assert!(index < 7);
        Column(index)
    }
}

impl FromStr for Column {
    type Err = &'static str;
    fn from_str(source: &str) -> Result<Column, Self::Err> {
        match source.as_bytes().first() {
            Some(v @ b'1'..=b'7') => Ok(Column(v - b'1')),
            _ => Err("Only digits from 1 to 7 count as valid moves."),
        }
    }
}

impl fmt::Display for Column {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Column: {}", self.0)
    }
}

/// State of a field in a four in a row board
#[derive(Clone, Copy, PartialEq, Eq)]
enum Cell {
    /// Field is not captured by either player
    Empty,
    /// Field contains a stone from Player 1
    PlayerOne,
    /// Field contains a stone from Player 2
    PlayerTwo,
}

#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ConnectFour {
    /// Bitborad encoding the stones of the player who did insert the last stone. Starts with Player
    /// two.
    last: PlayerStones,
    /// Bitboard encoding all cells containing stones, no matter the player.
    both: AllStones,
}

impl ConnectFour {
    /// Create an empty board
    pub fn new() -> ConnectFour {
        ConnectFour {
            last: PlayerStones::new(),
            both: AllStones::default(),
        }
    }

    /// Inserts a stone for the current player. `true` if move has been legal
    pub fn play(&mut self, column: Column) -> bool {
        // Let's check if the move is legal, otherwise return false.
        if self.both.is_full(column.0) {
            return false;
        }
        // Now we add a stone to the bitmask for both player.
        self.both.insert(column.0);
        // Flip players after adding the stone, so the stone is accounted for the last player
        self.last.flip(self.both);
        true
    }

    /// `true` if the column is not full.
    pub fn is_legal_move(&self, column: Column) -> bool {
        !self.both.is_full(column.0)
    }

    /// Create a game state from a sequence of moves. Each move represented as a number from 1 to 7
    /// standing for the column the player put in their stones.
    pub fn from_move_list(move_list: &str) -> ConnectFour {
        let mut game = ConnectFour::new();
        for c in move_list
            .as_bytes()
            .iter()
            .map(|c| c - b'1')
            .map(Column::from_index)
        {
            if !game.play(c) {
                panic!("Illegal move in String describing Connect Four Game")
            }
        }
        game
    }

    /// Prints out a text representation of a board to `out`
    pub fn print_to(&self, mut out: impl io::Write) -> io::Result<()> {
        for row in (0..6).rev() {
            for field in (0..7).map(|column| self.cell(row, column)) {
                let c = match field {
                    Cell::PlayerOne => 'X',
                    Cell::PlayerTwo => 'O',
                    Cell::Empty => ' ',
                };
                write!(out, "|{}", c)?;
            }
            writeln!(out, "|")?;
        }
        writeln!(out, "---------------\n 1 2 3 4 5 6 7")
    }

    /// Access any cell of the board and find out whether it is empty, or holding a stone of Player
    /// One or Two.
    fn cell(&self, row: u8, column: u8) -> Cell {
        let players = [Cell::PlayerOne, Cell::PlayerTwo];
        if self.both.is_empty(row, column) {
            Cell::Empty
        } else if self.last.is_empty(row, column) {
            players[self.both.stones() as usize % 2]
        } else {
            players[(self.both.stones() as usize + 1) % 2]
        }
    }

    /// Heurisitc used to decide which moves to explore first, in order to allow for better pruning
    /// of the search tree. Higher means better for the player which put in the last stone.
    fn heuristic(&self) -> u32 {
        heuristic(self.last, self.both)
    }

    /// Number of stones in the board
    pub fn stones(&self) -> u8 {
        self.both.stones()
    }

    /// `true` if the player which did insert the last stone has one the game.
    pub fn is_victory(&self) -> bool {
        self.last.is_win()
    }

    /// Uses the first 49 Bits to uniquely encode the board.
    pub fn encode(&self) -> u64 {
        self.last.key(self.both)
    }

    /// `true` if the current player has winning moves available
    pub fn can_win_in_next_move(&self) -> bool {
        let mut current = self.last;
        current.flip(self.both);
        self.both.possible() & current.winning_positions() != 0
    }

    /// `true` if game has a winner or is a draw.
    pub fn is_over(&self) -> bool {
        self.stones() == 42 || self.is_victory()
    }

    // Only valid to call if `can_win_in_next_move` is `false`.
    fn non_loosing_moves(&self) -> NonLoosingMoves {
        debug_assert!(!self.can_win_in_next_move());
        NonLoosingMoves::new(self.last, self.both)
    }
}

impl fmt::Display for ConnectFour {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for row in (0..6).rev() {
            for field in (0..7).map(|column| self.cell(row, column)) {
                let c = match field {
                    Cell::PlayerOne => 'X',
                    Cell::PlayerTwo => 'O',
                    Cell::Empty => ' ',
                };
                write!(f, "|{}", c)?;
            }
            writeln!(f, "|")?;
        }
        writeln!(f, "---------------\n 0 1 2 3 4 5 6")
    }
}