在Rust中实现国际象棋引擎:Trait与Enum的选型对比
用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...}
棋盘通过存储所有棋子的数组处理逻辑。
Rust的初步尝试与困惑
你尝试用Rust的增强型Enum实现,但设计如下:
struct Piece { position: Coordinate, isWhite: bool, charCode: char } enum PieceType { King(Piece), Queen(Piece), Bishop(Piece), Knight(Piece), Rook(Piece), Pawn(Piece) }
但遇到了困惑:
- 每个
Piece实例不知道自身类型,把PieceType加入Piece结构体又违背设计初衷 - 若在
Board中用包含大量match分支的GetMovesFor方法处理所有棋子走法,会导致方法职责过重 - 用Trait模仿C#多态又感觉不符合Rust风格
符合Rust风格的解决方案
Rust的Enum是代数数据类型,核心优势就是处理这种有限、明确的类型集合(国际象棋刚好是固定6种棋子),正确的设计应该让Enum直接代表棋子类型,每个变体携带该棋子的必要属性:
方案1:结构体式Enum变体
#[derive(Debug, Clone, Copy)] // 假设Coordinate和Move已定义 struct Coordinate(i8, i8); struct Move { from: Coordinate, to: Coordinate, } #[derive(Debug, Clone, Copy)] enum Piece { King { position: Coordinate, is_white: bool }, Queen { position: Coordinate, is_white: bool }, Bishop { position: Coordinate, is_white: bool }, Knight { position: Coordinate, is_white: bool }, Rook { position: Coordinate, is_white: bool }, Pawn { position: Coordinate, is_white: bool }, } impl Piece { // 获取当前棋子的可行走法 fn get_possible_moves(&self, board: &Board) -> Vec<Move> { match self { Piece::King { position, is_white } => { // 王的走法逻辑:可向8个方向走1格,需考虑边界和己方棋子 let mut moves = Vec::new(); for dx in [-1, 0, 1] { for dy in [-1, 0, 1] { if dx == 0 && dy == 0 { continue; } let new_pos = Coordinate(position.0 + dx, position.1 + dy); if board.is_valid_position(new_pos) && !board.has_own_piece(new_pos, *is_white) { moves.push(Move { from: *position, to: new_pos }); } } } moves } Piece::Pawn { position, is_white } => { // 兵的走法逻辑:白兵向上(y-1),黑兵向下(y+1),首次可走2格,吃子斜向等 let mut moves = Vec::new(); let direction = if *is_white { -1 } else { 1 }; // 前进1格 let forward = Coordinate(position.0, position.1 + direction); if board.is_valid_position(forward) && !board.has_piece(forward) { moves.push(Move { from: *position, to: forward }); // 首次走可前进2格 if (*is_white && position.1 == 6) || (!*is_white && position.1 == 1) { let forward_two = Coordinate(position.0, position.1 + 2 * direction); if board.is_valid_position(forward_two) && !board.has_piece(forward_two) { moves.push(Move { from: *position, to: forward_two }); } } } // 斜向吃子 for dx in [-1, 1] { let capture = Coordinate(position.0 + dx, position.1 + direction); if board.is_valid_position(capture) && board.has_enemy_piece(capture, *is_white) { moves.push(Move { from: *position, to: capture }); } } moves } // 其他棋子(后、象、马、车)的走法逻辑同理实现 Piece::Queen { position, is_white } => vec![], Piece::Bishop { position, is_white } => vec![], Piece::Knight { position, is_white } => vec![], Piece::Rook { position, is_white } => vec![], } } // 获取棋子的字符编码(如白王'K',黑王'k') fn piece_code(&self) -> char { match self { Piece::King { is_white, .. } => if *is_white { 'K' } else { 'k' }, Piece::Queen { is_white, .. } => if *is_white { 'Q' } else { 'q' }, Piece::Bishop { is_white, .. } => if *is_white { 'B' } else { 'b' }, Piece::Knight { is_white, .. } => if *is_white { 'N' } else { 'n' }, Piece::Rook { is_white, .. } => if *is_white { 'R' } else { 'r' }, Piece::Pawn { is_white, .. } => if *is_white { 'P' } else { 'p' }, } } fn is_white(&self) -> bool { match self { Piece::King { is_white, .. } => *is_white, Piece::Queen { is_white, .. } => *is_white, Piece::Bishop { is_white, .. } => *is_white, Piece::Knight { is_white, .. } => *is_white, Piece::Rook { is_white, .. } => *is_white, Piece::Pawn { is_white, .. } => *is_white, } } } // 假设Board结构体的简化定义 struct Board { pieces: Vec<Piece>, } impl Board { fn is_valid_position(&self, pos: Coordinate) -> bool { pos.0 >= 0 && pos.0 < 8 && pos.1 >= 0 && pos.1 < 8 } fn has_piece(&self, pos: Coordinate) -> bool { self.pieces.iter().any(|p| match p { Piece::King { position, .. } => *position == pos, Piece::Queen { position, .. } => *position == pos, // 其他变体同理,或者可以给Piece实现position()方法简化 _ => false, }) } fn has_own_piece(&self, pos: Coordinate, is_white: bool) -> bool { self.pieces.iter().any(|p| match p { Piece::King { position, is_white: p_white, .. } => *position == pos && *p_white == is_white, // 其他变体同理 _ => false, }) } fn has_enemy_piece(&self, pos: Coordinate, is_white: bool) -> bool { self.pieces.iter().any(|p| match p { Piece::King { position, is_white: p_white, .. } => *position == pos && *p_white != is_white, // 其他变体同理 _ => false, }) } }
方案2:提取公共属性到结构体
如果觉得每个变体重复写position和is_white太冗余,可以把这些公共属性抽成一个结构体,让Enum变体持有该结构体:
#[derive(Debug, Clone, Copy)] struct PieceData { position: Coordinate, is_white: bool, } #[derive(Debug, Clone, Copy)] enum Piece { King(PieceData), Queen(PieceData), Bishop(PieceData), Knight(PieceData), Rook(PieceData), Pawn(PieceData), } impl Piece { fn get_possible_moves(&self, board: &Board) -> Vec<Move> { match self { Piece::King(data) => { // 使用data.position和data.is_white实现走法逻辑 vec![] } // 其他变体同理 _ => vec![], } } }
设计优势
- 类型明确:Enum直接代表棋子类型,每个变体天然知道自己的类型,无需额外标记
- 职责清晰:走法逻辑封装在
Piece的get_possible_moves方法中,每个match分支只负责对应棋子的逻辑,不会让Board方法臃肿 - 符合Rust风格:利用代数数据类型处理有限类型集合,比模仿C#多态更高效(无需虚函数调用,编译时就能确定分支)
内容的提问来源于stack exchange,提问作者ScottishTapWater
相关产品推荐
相关产品推荐

