如何在多个数组上创建一个可变的循环迭代器

3
我的家人和我一起玩Mancala很开心,我真的很喜欢它简单的规则,但有几个问题,比如“最高可能得分是多少”。我认为这将是一个有趣的小项目,在Rust中实现,但我陷入了困境,需要帮助。
有许多Mancala玩法的规则。我想要实现这个版本:https://www.youtube.com/watch?v=-A-djjimCcM。了解游戏规则可以更容易理解我的问题,但这可能不是必须的。
这是Mancala棋盘的样子:
|  |04|04|04|04|04|04|  |
|00|-----------------|00|
|  |04|04|04|04|04|04|  |

每个数字表示一个孔,较大方框左右两侧的数字表示“曼卡拉”。曼卡拉基本上是一个用于计算得分的洞。右侧的曼卡拉是您自己的曼卡拉,左侧的是对手的曼卡拉。数字表示该特定孔中弹珠的数量。

在游戏中,您可以选择一个孔,取出所有弹珠,然后在接下来的每个孔/曼卡拉中放置一个弹珠,直到您用尽弹珠为止。跳过对手的曼卡拉。这就是我正在努力解决的问题。

我的解决方法是:曼卡拉板是一个包含四个存储孔的数组的结构体。对于玩家一侧的每个孔和他们的曼卡拉分别有一个数组。我想将这三个Holes数组连接在一起并循环遍历,以便我可以在这些Holes上运行相关函数(跳过对手的曼卡拉)。以下是我的代码:

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);
        }
    }
}

然而,我遇到了以下错误:

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`

我还尝试使用into_iter()方法,但没有出现错误,然而孔的值没有发生改变。我想一个副本被创建并且该方法在副本上运行,然后副本超出了范围,看起来好像什么也没有改变。

1个回答

2
cycle() 迭代器方法的内部工作原理是通过克隆输入迭代器,迭代克隆体直到返回 None,然后用输入迭代器的另一个克隆体替换它。这要求输入迭代器可以被克隆,但是可变引用切片元素的迭代器无法被克隆,因为那样你就能在原始的和克隆的迭代器上调用 next() 并拥有两个对同一值的可变引用。这在 Rust 中是不可能的,所以 std::slice::IterMut 无法被克隆,因此你不能在其上使用 .cycle()
解决这个问题的一种方法是修改数据结构。通常,只包含一个元素的数组表示设计上的问题;反正你可以用单个值完成相同的操作。
为了简化问题,只需使用一个带有循环索引的数组,类似于以下内容:
|  |12|11|10|09|08|07|  |
|13|-----------------|06|
|  |00|01|02|03|04|05|  |

现在你的数据结构就是简单的struct Board { holes: [Hole; 14] }

遍历这个数据结构变得非常简单 - 你可以使用(0..14).cycle()来获取一个重复的数组索引迭代器。

在使用这个数据结构时,我们需要处理跳过对手的“Mancala”和分配弹珠的游戏规则。我们可以使用简单的matchskipfilter来处理这个问题,并确定从哪一侧开始游戏:

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);
}

你可能考虑将这些特殊的索引命名为常量。
另外,请注意你的 Board::choose_hole() 函数应该使用 &mut self 而不是 mut self,否则你会对一份副本进行更改然后丢弃该副本。

谢谢你的回答,非常有帮助。我的代码通过不将对手的Mancala与其他迭代器链接来“跳过”它。但正如你所解释的那样,我的想法并不像你的解决方案那样好。 - Tom Smith
@TomSmith 哦,确实是这样。我没注意到。 - cdhowie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接