arimaa_engine_step/lib.rs
1/*!
2A game engine for the board game [Arimaa](http://arimaa.com/arimaa/). This library
3provides the functionality to parse and display board states, generate sets of valid
4step based actions as well as take actions on those states.
5
6# Example: Initialize a new game state.
7
8A new game state can be initialized from the GameState
9
10```
11use arimaa_engine_step::GameState;
12
13let game_state = GameState::initial();
14
15println!("{}", game_state);
16```
17# Example: Parsing game states and actions.
18
19A game state can also be read from a string as well as actions.
20
21```
22use arimaa_engine_step::{action, board};
23
24let game_state = board!(
25 "2g
26 +-----------------+
27 8| h c d m e d c h |
28 7| r r r r r r r r |
29 6| x x |
30 5| |
31 4| |
32 3| x x |
33 2| R R R R R R R R |
34 1| H C D M E D C H |
35 +-----------------+
36 a b c d e f g h"
37);
38
39let action = action!(d2n);
40let game_state = game_state.take_action(&action);
41```
42# Example: Generate valid steps
43
44A game state can be used to generate a valid list of actions. These actions are based on a list of valid steps.
45
46```
47use arimaa_engine_step::{action, board};
48
49let game_state = board!("
50 2g
51 +-----------------+
52 8| r |
53 7| |
54 6| R x e x |
55 5| |
56 4| |
57 3| x x |
58 2| |
59 1| |
60 +-----------------+
61 a b c d e f g h");
62
63let valid_actions = game_state.valid_actions();
64
65assert_eq!(&[action!(b6n), action!(b6e), action!(b6w)], &*valid_actions);
66```
67
68# Example: Detect terminals
69
70A game state can be used to determine if a terminal state has been reached.
71
72```
73use arimaa_engine_step::{Terminal, board, take_actions};
74
75let game_state = board!("
76 2g
77 +-----------------+
78 8| r |
79 7| |
80 6| R x e x |
81 5| |
82 4| |
83 3| x x |
84 2| |
85 1| |
86 +-----------------+
87 a b c d e f g h");
88
89assert_eq!(game_state.is_terminal(), None);
90
91let game_state = take_actions!(game_state => b6n, b7n, p);
92
93assert_eq!(game_state.is_terminal(), Some(Terminal::GoldWin));
94```
95
96# Example: Generate moves
97
98Generate a full set of unique moves.
99
100```
101use arimaa_engine_step::{take_actions, Action, GameState};
102use std::collections::HashSet;
103
104// Start a new game with a basic setup.
105let game_state = GameState::initial();
106let game_state = take_actions!(game_state => r, r, r, r, r, r, r, r);
107let game_state = take_actions!(game_state => c, d, h, c, e, h, d, c);
108
109let game_state = take_actions!(game_state => c, d, h, c, e, h, d, c);
110let game_state = take_actions!(game_state => r, r, r, r, r, r, r, r);
111
112fn recurse_actions_for_state(
113 game_state: &GameState,
114 actions: &[Action],
115 moves: &mut Vec<(Vec<Action>, GameState)>,
116 hashes: &mut HashSet<u64>,
117) {
118 let curr_player = game_state.is_p1_turn_to_move();
119
120 // Go through each valid action for the current step in the move.
121 for action in game_state.valid_actions() {
122 let new_game_state = game_state.take_action(&action);
123
124 // If the state was already explored in another transposition, then skip over that state.
125 let game_state_hash = new_game_state.transposition_hash();
126 if hashes.contains(&game_state_hash) {
127 continue;
128 } else {
129 hashes.insert(game_state_hash);
130 }
131
132 let mut actions = actions.to_vec();
133 actions.push(action);
134
135 // If the state is terminal or it is the end of the turn for the player, then it is a full move.
136 if matches!(new_game_state.is_terminal(), Some(_)) || curr_player != new_game_state.is_p1_turn_to_move() {
137 moves.push((actions, new_game_state));
138 continue;
139 }
140
141 // A full move has not been reached, it is not terminal and it is still the same players turn.
142 // Recurse to check the next set of valid steps in the move.
143 recurse_actions_for_state(&new_game_state, &actions, moves, hashes);
144 }
145}
146
147let mut moves = vec![];
148let mut hashes = HashSet::new();
149recurse_actions_for_state(&game_state, &[], &mut moves, &mut hashes);
150
151println!("Found {} unique moves", moves.len());
152assert_eq!(moves.len(), 2467, "The setup should have 2467 unique moves.");
153
154for (i, (actions, _game_state)) in moves.into_iter().enumerate() {
155 println!("{}: {:?}", i, actions);
156}
157```
158
159# Example: Create a plane out of piece board bits
160
161Creates a slice for a 8x8 board that would be used as an input plane for a neural network by taking
162the bits from the piece board plane and setting those matching bits into an array.
163
164```
165use arimaa_engine_step::{board, Piece};
166
167let game_state = board!("
168 2g
169 +-----------------+
170 8| r |
171 7| |
172 6| R x e x |
173 5| |
174 4| |
175 3| x R r x |
176 2| |
177 1| |
178 +-----------------+
179 a b c d e f g h");
180
181let mut gold_rabbit_bits = game_state.piece_board().bits_for_piece(Piece::Rabbit, true);
182let mut plane = [0; 64];
183
184while gold_rabbit_bits != 0 {
185 let bit_idx = gold_rabbit_bits.trailing_zeros() as usize;
186 let removed_bit_idx = bit_idx;
187
188 plane[removed_bit_idx] = 1;
189
190 gold_rabbit_bits ^= 1 << bit_idx;
191}
192
193assert_eq!(
194 plane,
195 [
196 0, 0, 0, 0, 0, 0, 0, 0,
197 0, 0, 0, 0, 0, 0, 0, 0,
198 0, 1, 0, 0, 0, 0, 0, 0,
199 0, 0, 0, 0, 0, 0, 0, 0,
200 0, 0, 0, 0, 0, 0, 0, 0,
201 0, 0, 0, 1, 0, 0, 0, 0,
202 0, 0, 0, 0, 0, 0, 0, 0,
203 0, 0, 0, 0, 0, 0, 0, 0,
204 ]
205);
206```
207*/
208
209#![allow(clippy::inconsistent_digit_grouping)]
210#![allow(clippy::unusual_byte_groupings)]
211
212#[macro_use]
213mod bit_manip;
214mod bit_mask;
215mod engine_tests;
216mod zobrist_values;
217
218pub mod action;
219pub mod constants;
220pub mod direction;
221pub mod display;
222pub mod engine;
223pub mod linked_list;
224pub mod macros;
225pub mod piece;
226pub mod square;
227pub mod terminal;
228pub mod zobrist;
229
230pub use action::*;
231pub use constants::*;
232pub use direction::*;
233pub use display::*;
234pub use engine::*;
235pub use linked_list::*;
236pub use macros::*;
237pub use piece::*;
238pub use square::*;
239pub use terminal::*;
240pub use zobrist::Zobrist;