My family and I had fun playing Mancala together and I really liked the simplicity of its rules but have a few questions such as "What is the highest possible score". I thought it would be a fun little project to implement in Rust but I am stuck and need help.
There are many rules of how to play Mancala. I want to implement this version: https://www.youtube.com/watch?v=-A-djjimCcM. Knowing the rules of the game makes understanding my problem easier, but it probably isn't required to get it.
This is how a Mancala board looks like:
| |04|04|04|04|04|04| |
|00|-----------------|00|
| |04|04|04|04|04|04| |
Each of the numbers represent a hole. The numbers on the left and right in the bigger boxes represent a "mancala". A mancala is basically a hole where you count your points. The one on your right, is your own mancala, the one on the left is your opponent's mancala. The numbers represent the number of marbles in that specific hole.
In the game, you can select a hole, take all its marbles and then drop one marble in each of the next holes/mancala until you run out of marbles. You skip your opponent's mancala. This is what I am struggling with.
This is how I tried to solve it: The Mancala board is a struct that has four arrays storing Holes. One for each of the holes on the side of the player and one for their mancala. I want to chain together and cycle through three of these arrays of Holes so I can run an associated function on those Holes (the opponent's mancala gets skipped). This is my code:
pub const STARTING_MARBLES: i8 = 4;
pub const NO_OF_HOLES_OF_EACH_PLAYER: usize = 6;
// There can be two players
#[derive(Debug, Copy, Clone)]
pub enum Player {
A,
B,
}
// A dip in a mancala board that can contain a number of marbles
#[derive(Debug, Copy, Clone)]
struct Hole {
marbles: i8,
}
impl Hole {
// Adds x marbles to the hole
fn add_x(&mut self, x: i8) {
self.marbles = x;
}
// Takes all marbles from the hole and returns their number
fn take_all(&mut self) -> i8 {
let marbles = self.marbles;
self.marbles = 0;
marbles
}
// Returns the number of marbles in the hole
fn count(&self) -> i8 {
self.marbles
}
}
// A mancala board with all its holes and mancalas to count the players points
#[derive(Debug, Copy, Clone)]
pub struct Board {
holes_a: [Hole; NO_OF_HOLES_OF_EACH_PLAYER],
holes_b: [Hole; NO_OF_HOLES_OF_EACH_PLAYER],
mancala_a: [Hole; 1],
mancala_b: [Hole; 1],
}
impl Board {
// Create, initialize and return a new mancala board
pub fn new() -> Self {
let init_hole = Hole {
marbles: STARTING_MARBLES,
};
let holes_a = [init_hole; NO_OF_HOLES_OF_EACH_PLAYER];
let holes_b = [init_hole; NO_OF_HOLES_OF_EACH_PLAYER];
let mancala_a = [Hole { marbles: 0 }];
let mancala_b = [Hole { marbles: 0 }];
Board {
holes_a,
holes_b,
mancala_a,
mancala_b,
}
}
// Take all marbles from the chosen hole and add them to the following holes and the player's mancala
// player: Player whos turn it is
// no: number of the selected hole. The numbering starts with 0 on the very left hole of the player whos turn it is
pub fn choose_hole(mut self, player: Player, no: usize) {
let (mut players_own_holes, other_players_holes, players_mancala) = match player {
Player::A => (self.holes_a, self.holes_b, self.mancala_a),
Player::B => (self.holes_b, self.holes_a, self.mancala_b),
};
let marbles_to_spend = players_own_holes[no].take_all() as usize;
let holes_iter = self
.holes_a
.iter_mut()
.chain(self.mancala_a.iter_mut())
.chain(self.holes_b.iter_mut())
.cycle()
.skip(no 1)
.take(marbles_to_spend);
for mut hole in holes_iter {
hole.add_x(1);
}
}
}
However I get the following error:
error[E0277]: the trait bound `std::slice::IterMut<'_, Hole>: Clone` is not satisfied
--> src/lib.rs:75:14
|
75 | .cycle()
| ^^^^^ the trait `Clone` is not implemented for `std::slice::IterMut<'_, Hole>`
|
= note: required because of the requirements on the impl of `Clone` for `std::iter::Chain<std::iter::Chain<std::slice::IterMut<'_, Hole>, std::slice::IterMut<'_, Hole>>, std::slice::IterMut<'_, Hole>>`
note: required by a bound in `cycle`
--> /home/batman/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:3262:23
|
3262 | Self: Sized Clone,
| ^^^^^ required by this bound in `cycle`
I also tried using the into_iter()
method instead. I didn't get any errors then but the values of the holes did not change. I guess a copy was created and the method ran on the copy and then the copy went out of scope and it looked like nothing was changed.
CodePudding user response:
The cycle()
iterator method works internally by cloning the input iterator, iterating the clone until it returns None
, and then replacing the clone with another clone of the input iterator. This requires that the input iterator can be cloned, but an iterator of mutable references to slice elements can't be cloned, because then you'd be able to call next()
on the original and the clone and have two mutable references to the same value. This is supposed to be impossible in Rust, so std::slice::IterMut
can't be cloned, and therefore you can't use .cycle()
on it.
One way to solve this problem would be to alter your data structure. Arrays of one element generally indicate a design problem; you can accomplish the same thing with just a single value, anyway.
To make this problem simpler, just use a single array, with circular indices. Something like this:
| |12|11|10|09|08|07| |
|13|-----------------|06|
| |00|01|02|03|04|05| |
So now your data structure is simply struct Board { holes: [Hole; 14] }
.
Traversing this data structure now becomes incredibly simple -- you can just do (0..14).cycle()
to get a repeating iterator of array indices.
When using this data structure, we need to handle the game rule of skipping your opponent's mancala as marbles are distributed. We can handle this, along with handling which side of the board to start on, with a simple match
, skip
, and filter
:
let (opponent_mancala_index, start_idx) = match player {
Player::A => (13, 0),
Player::B => (6, 7),
};
let indexes = (0..14)
.cycle()
.skip(no start_idx)
.filter(|&v| v != opponent_mancala_index)
.take(marbles_to_spend);
for i in indexes {
self.holes[i].add_x(1);
}
You might consider making these special indexes named constants.
Also, note that your Board::choose_hole()
function should take &mut self
instead of mut self
, otherwise you are making changes to a copy and then discarding that copy.