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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//! # libmancala
//!
//! A frontend-agnostic library that implements the game of Mancala.
//!
//! The rules are implemented as specified on the Mancala page of the [Official Game Rules website](https://www.officialgamerules.org/mancala).
//!
//! ## Example
//! ```
//! use libmancala::{MancalaBoard, Move, MoveResult, Player};
//!
//! let mut board = MancalaBoard::new();
//!
//! // Your code to get input from the player to make a move
//! let input: Move = make_move();
//!
//! let (game_state, bonus_turn, captured) = board.update(&input);
//! ```

/// Used to identify a player.
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum Player {
    /// Player One
    P1,

    /// Player Two
    P2,
}

/// The state of the game as the result of a move.
#[derive(PartialEq, Debug)]
pub enum MoveResult {
    /// The game is over and was won by the specified `Player`.
    Won(Player),

    /// The game is continuing to the next move.
    Continuing,

    /// The game is over; nobody won.
    Draw,

    /// The most recent move was not valid, probably due to trying to move an
    /// empty pit.
    Invalid,
}

/// The pit the player has selected to move.
///
/// The pits on the player's row are identified left-to-right by letters A-F.
#[derive(Debug)]
pub enum Move {
    A,
    B,
    C,
    D,
    E,
    F,
}

/// Indicates where the last stone was dropped during a turn.
/// Used for calculating whether to capture pieces and/or grant a bonus turn.
#[derive(Debug)]
enum EndPos {
    /// Indicates the last stone was dropped in the current player's row, and the
    /// according array index.
    Home(usize),

    /// The last stone was dropped in the player's store.
    Store,

    /// The last stone was dropped in the opponent's row.
    Opponent,

    /// The last stone's position was not recorded for some reason.
    None,
}

/// Contains data relevant to the game.
pub struct MancalaBoard {
    /// The contents of Player One's row.
    pub p1_board: [u32; 6],

    /// The contents of Player Two's row.
    pub p2_board: [u32; 6],

    /// The number of stones is Player One's store.
    pub p1_store: u32,

    /// The number of stones is Player Two's store.
    pub p2_store: u32,

    /// Indicates whose turn it is.
    pub turn: Player,
}

impl MancalaBoard {
    /// Creates a new `MancalaBoard` with its values initialized to a new game
    /// layout according to the game rules of Mancala.
    pub fn new() -> Self {
        Self {
            p1_board: [4, 4, 4, 4, 4, 4],
            p2_board: [4, 4, 4, 4, 4, 4],
            p1_store: 0,
            p2_store: 0,
            turn: Player::P1,
        }
    }
    /// Make a move and update board data.
    /// # Arguments
    ///
    /// * `self` - the `MancalaBoard` to update
    /// * `move_pit` - The pit which the player has chosen to move
    ///
    /// # Return
    ///
    /// Returns a tuple containing three variables:
    ///
    /// * The result of the move
    /// * Whether a bonus turn was earned
    /// * How many (if any) stones were captured
    pub fn update(self: &mut Self, move_pit: &Move) -> (MoveResult, bool, Option<u32>) {
        // Map board data to relative variables depending on the current player.
        let row_player;
        let row_opponent;
        let store_player;
        let store_opponent;
        match self.turn {
            Player::P1 => {
                row_player = &mut self.p1_board;
                row_opponent = &mut self.p2_board;
                store_player = &mut self.p1_store;
                store_opponent = &mut self.p2_store;
            }
            Player::P2 => {
                row_player = &mut self.p2_board;
                row_opponent = &mut self.p1_board;
                store_player = &mut self.p2_store;
                store_opponent = &mut self.p1_store;
            }
        }
        // Index of rightmost pit
        let last_pit_index = row_player.len() - 1;

        let pit_pos = match move_pit {
            Move::A => 0,
            Move::B => 1,
            Move::C => 2,
            Move::D => 3,
            Move::E => 4,
            Move::F => 5,
        };
        if row_player[pit_pos] == 0 {
            // Move is invalid if selected pit is empty.
            return (MoveResult::Invalid, false, None);
        }
        // Empty the pit's contents into a "hand" to redistribute.
        let mut hand = row_player[pit_pos];
        row_player[pit_pos] = 0;

        let mut end_pos: EndPos = EndPos::None;

        // Distribute hand across player's row, starting at the pit to the
        // right of the chosen pit.
        for (index, pit) in row_player.iter_mut().enumerate() {
            if hand == 0 {
                break;
            }
            // Skip ahead to the selected pit.
            if index <= pit_pos {
                continue;
            }
            *pit += 1;
            hand -= 1;
            *&mut end_pos = EndPos::Home(index);
        }
        // Distribute remainder of hand around the board, depositing a piece in
        // the player's store at the appropriate point.
        if hand > 0 {
            *&mut end_pos = EndPos::Home(last_pit_index);
            'outer: loop {
                if hand > 0 {
                    *store_player += 1;
                    hand -= 1;
                    *&mut end_pos = EndPos::Store;
                } else {
                    break 'outer;
                }
                for pit in row_opponent.iter_mut() {
                    if hand == 0 {
                        break 'outer;
                    }
                    *pit += 1;
                    hand -= 1;
                    *&mut end_pos = EndPos::Opponent;
                }
                if hand == 0 {
                    break 'outer;
                }
                for (index, pit) in row_player.iter_mut().enumerate() {
                    if hand == 0 {
                        *&mut end_pos = EndPos::Home(index - 1);
                        break 'outer;
                    }
                    *pit += 1;
                    hand -= 1;
                }
            }
        }

        // Check for bonus turns and captures.
        let mut bonus_turn = false;
        let mut captured: Option<u32> = None;
        match end_pos {
            EndPos::Store => {
                bonus_turn = true;
            }
            EndPos::Home(x)
                if row_player[x] == 1 && row_opponent[MancalaBoard::opposite_pit_pos(&x)] > 0 =>
            {
                captured = Some(row_opponent[MancalaBoard::opposite_pit_pos(&x)] + 1);
                *store_player += captured.unwrap();
                row_player[x] = 0;
                row_opponent[MancalaBoard::opposite_pit_pos(&x)] = 0;
            }
            EndPos::None => println!("ERROR: No turn end position."),
            _ => (),
        }

        // Check for game over.
        let mut is_game_over = false;
        if MancalaBoard::is_row_empty(row_player) {
            // Move opponent row pieces to opponent's store.
            *store_opponent += MancalaBoard::row_sum(*row_opponent);
            for i in row_opponent.iter_mut() {
                *i = 0;
            }
            is_game_over = true;
        } else if MancalaBoard::is_row_empty(row_opponent) {
            // Move player row pieces to player's store.
            *store_player += MancalaBoard::row_sum(*row_player);
            for i in row_player.iter_mut() {
                *i = 0;
            }
            is_game_over = true;
        }

        // Determine winner by comparing store values.
        if is_game_over {
            if self.p1_store > self.p2_store {
                return (MoveResult::Won(Player::P1), bonus_turn, captured);
            } else if self.p1_store < self.p2_store {
                return (MoveResult::Won(Player::P2), bonus_turn, captured);
            } else {
                return (MoveResult::Draw, bonus_turn, captured);
            }
        }

        // Advance to next player's turn, or award bonus turn if appropriate.
        if bonus_turn {
            return (MoveResult::Continuing, bonus_turn, captured);
        } else {
            // Continue to next player's turn.
            self.turn = MancalaBoard::change_turn(&self.turn);
            return (MoveResult::Continuing, bonus_turn, captured);
        }
    }
    /// Checks if a given row from a `MancalaBoard` is empty.
    fn is_row_empty(row: &[u32]) -> bool {
        for i in row.iter() {
            if *i != 0 {
                return false;
            }
        }
        true
    }
    /// Given a pit index in one row of the board, calculates the index number
    /// of the pit in the opposite row.
    ///
    /// Assumes the rows are six pits long.
    fn opposite_pit_pos(pit: &usize) -> usize {
        5 - pit
    }
    /// Returns the opposite `Player`.
    fn change_turn(turn: &Player) -> Player {
        match turn {
            Player::P1 => return Player::P2,
            Player::P2 => return Player::P1,
        }
    }
    /// Calculates the sum of all pieces in a given row on the board.
    fn row_sum(row: [u32; 6]) -> u32 {
        let mut sum = 0;
        for i in row {
            sum += i;
        }
        sum
    }
}