在描述棋盘时如何最好地使用特征与枚举

Per*_*nce 2 enums rust

所以,我正在尝试制作一个国际象棋引擎来温习一下我的生锈,我通常是一名 C# 程序员。

在 C# 中,对于我的数据表示,我有类似的内容:

public abstract class Piece 
{
   public Coordinate Location;
   
   public abstract bool IsWhite {get;}
   public abstract string PieceCode {get;} // e.g. K for a king

   public abstract Move[] GetPossibleMoves(Board board);
}

public sealed class Pawn:Piece {//etc...}
Run Code Online (Sandbox Code Playgroud)

然后我的董事会将拥有一系列可以使用的所有组件。

我相信,在 Rust 中,这样的事情将是应用更高级枚举的好例子,所以像这样:

struct  Piece {
    position: Coordinate,
    isWhite: bool,
    charCode: char
}

enum PieceType {
    King(Piece),
    Queen(Piece),
    Bishop(Piece),
    Knight(Piece),
    Rook(Piece),
    Pawn(Piece)
}
Run Code Online (Sandbox Code Playgroud)

但随后我不太确定应该将实现放在哪里以获得可能的动作。每个Piece实例都不知道它是什么类型,将PieceType枚举添加到Piece结构中感觉就像是这种结构试图实现的目标的对立面。

我想我可以有一个Board带有 a 的结构Vec<PieceType>,并且有一个类似于GetMovesFor(PieceType)其中带有开关的方法?但在这一点上,感觉一种方法将完成的工作远远不止一项,它会包含每个部分的实现细节,而不是像我用 C# 构建它那样只包含一个部分的实现细节。

另一种选择是,我可以走与 C# 中非常相似的路线,只使用一个Piece特征,在每种类型上实现它,并为我的板提供一个Vec<Piece>. 但在这一点上,我觉得我只是在尝试模仿多态性,而不是以简单的方式做事。

所以我觉得我要么误解了这些扩展枚举的目标是什么,而这实际上并不是如何使用它们。或者,我遗漏了一些实现细节。

能够解决这两点的答案将是伟大的!

cdh*_*wie 6

我认为最简单的模型通常是最好的,这就是一个片段有一种颜色和一种类型,它们都可以表示为没有有效负载的枚举。我认为当你的枚举并不真正需要有效负载时,你会有点迷失方向。

pub struct Piece {
    pub kind: PieceKind,
    pub color: PieceColor,
}

pub enum PieceKind {
    Pawn,
    Knight,
    Bishop,
    Rook,
    Queen,
    King,
}

pub enum PieceColor {
    White,
    Black,
}
Run Code Online (Sandbox Code Playgroud)

棋盘只是一个 8x8 的Pieces 数组,加上一些确定易位和过路移动合法性所需的附加信息:

pub struct Board {
    pub pieces: [[Option<Piece>; 8]; 8],

    pub white_can_castle_a: bool,
    pub white_can_castle_h: bool,
    pub black_can_castle_a: bool,
    pub black_can_castle_h: bool,

    // Some if the last piece moved was a pawn that was moved two spaces;
    // contains the coordinates to check for a valid en passant capture.
    pub last_double_pawn_move: Option<(usize, usize)>,
}
Run Code Online (Sandbox Code Playgroud)

通过此模型,您可以获得生成任何棋子的有效走法列表所需的所有信息。

请注意,这里的棋子没有位置数据;棋子的位置由其在棋盘中的位置决定。在我看来,您希望“获得可能的动作”成为主要存在于棋子上的操作不正确的。您需要有关整个电路板的信息来确定这一点,通过让此方法接受来在您的模型中显示该信息Board。我将其建模为一种Board接受 x,y 坐标对的方法。

// This type could use more members to be able to annotate special
// moves like castling.
struct Move {
    from: (usize, usize),
    to: (usize, usize),
}

impl Board {
    pub fn get_legal_moves_for(&self, piece: (usize, usize)) -> Vec<Move> {
        todo!()
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为您感到困惑的部分原因是您试图使用特征来设计模型,以便在每种部件上具有多态性。虽然基于特征的多态性很好,但在这种模型中,它需要动态调度和堆分配(这在 Java 中才会发生),而 Rust 可以使用更有效的内存布局。基于 的多态性也没有任何问题match——事实上,这正是Either类型实现特征的方式!