connect_four_solver/
lib.rs1mod bitboard;
2mod precalculated;
3mod solver;
4mod transposition_table;
5
6use self::bitboard::PlayerStones;
7use std::{fmt, io, str::FromStr};
8
9use bitboard::{heuristic, AllStones, NonLoosingMoves};
10pub use solver::{score, Solver};
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub struct Column(u8);
15
16impl Column {
17 pub const fn from_index(index: u8) -> Column {
19 assert!(index < 7);
20 Column(index)
21 }
22}
23
24impl FromStr for Column {
25 type Err = &'static str;
26 fn from_str(source: &str) -> Result<Column, Self::Err> {
27 match source.as_bytes().first() {
28 Some(v @ b'1'..=b'7') => Ok(Column(v - b'1')),
29 _ => Err("Only digits from 1 to 7 count as valid moves."),
30 }
31 }
32}
33
34impl fmt::Display for Column {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 write!(f, "{}", self.0 + 1)
37 }
38}
39
40#[derive(Clone, Copy, PartialEq, Eq)]
42enum Cell {
43 Empty,
45 PlayerOne,
47 PlayerTwo,
49}
50
51#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
56pub struct ConnectFour {
57 last: PlayerStones,
60 both: AllStones,
62}
63
64impl ConnectFour {
65 pub fn new() -> ConnectFour {
67 ConnectFour {
68 last: PlayerStones::new(),
69 both: AllStones::default(),
70 }
71 }
72
73 pub fn play(&mut self, column: Column) -> bool {
75 if self.both.is_full(column.0) {
77 return false;
78 }
79 self.both.insert(column.0);
81 self.last.flip(self.both);
83 true
84 }
85
86 pub fn is_legal_move(&self, column: Column) -> bool {
88 !self.both.is_full(column.0)
89 }
90
91 pub fn from_move_list(move_list: &str) -> ConnectFour {
94 let mut game = ConnectFour::new();
95 for c in move_list
96 .as_bytes()
97 .iter()
98 .map(|c| c - b'1')
99 .map(Column::from_index)
100 {
101 if !game.play(c) {
102 panic!("Illegal move in String describing Connect Four Game")
103 }
104 }
105 game
106 }
107
108 pub fn print_to(&self, mut out: impl io::Write) -> io::Result<()> {
110 write!(out, "{self}")
111 }
112
113 pub fn legal_moves(&self) -> impl Iterator<Item = Column> + use<'_>{
114 (0..7).map(Column::from_index).filter(move |&c| self.is_legal_move(c))
115 }
116
117 fn cell(&self, row: u8, column: u8) -> Cell {
120 let players = [Cell::PlayerOne, Cell::PlayerTwo];
121 if self.both.is_empty(row, column) {
122 Cell::Empty
123 } else if self.last.is_empty(row, column) {
124 players[self.both.stones() as usize % 2]
125 } else {
126 players[(self.both.stones() as usize + 1) % 2]
127 }
128 }
129
130 fn heuristic(&self) -> u32 {
133 heuristic(self.last, self.both)
134 }
135
136 pub fn stones(&self) -> u8 {
138 self.both.stones()
139 }
140
141 pub fn is_victory(&self) -> bool {
143 self.last.is_win()
144 }
145
146 pub fn encode(&self) -> u64 {
148 self.last.key(self.both)
149 }
150
151 pub fn can_win_in_next_move(&self) -> bool {
153 let mut current = self.last;
154 current.flip(self.both);
155 self.both.possible() & current.winning_positions() != 0
156 }
157
158 pub fn is_over(&self) -> bool {
160 self.stones() == 42 || self.is_victory()
161 }
162
163 pub fn non_loosing_moves(&self) -> impl Iterator<Item = Column> {
166 debug_assert!(!self.can_win_in_next_move());
167 let nlm = self.non_loosing_moves_impl();
168 (0..7).filter(move |&i| nlm.contains(i)).map(Column::from_index)
169 }
170
171 fn non_loosing_moves_impl(&self) -> NonLoosingMoves {
173 debug_assert!(!self.can_win_in_next_move());
174 NonLoosingMoves::new(self.last, self.both)
175 }
176}
177
178impl fmt::Display for ConnectFour {
179 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180 for row in (0..6).rev() {
181 for field in (0..7).map(|column| self.cell(row, column)) {
182 let c = match field {
183 Cell::PlayerOne => 'X',
184 Cell::PlayerTwo => 'O',
185 Cell::Empty => ' ',
186 };
187 write!(f, "|{}", c)?;
188 }
189 writeln!(f, "|")?;
190 }
191 writeln!(f, "---------------\n 1 2 3 4 5 6 7")
192 }
193}