Rust 练习册 :Queen Attack与国际象棋逻辑
在国际象棋中,皇后是最强大的棋子,可以沿着横线、竖线和对角线移动任意格数。在 Exercism 的 “queen-attack” 练习中,我们需要实现一个程序来判断两个皇后是否可以相互攻击。这不仅能帮助我们掌握二维坐标系统和几何计算,还能深入学习Rust中的结构体设计、错误处理和逻辑判断。
什么是皇后攻击?
在国际象棋中,皇后可以在同一行、同一列或同一对角线上攻击其他棋子。判断两个皇后是否可以相互攻击的规则如下:
- 同行攻击:两个皇后在同一行上
- 同列攻击:两个皇后在同一列上
- 对角线攻击:两个皇后在同一对角线上
棋盘使用标准的8x8网格,行号(rank)从0到7,列号(file)从0到7。
让我们先看看练习提供的结构体和函数:
#[derive(Debug)]
pub struct ChessPosition;#[derive(Debug)]
pub struct Queen;impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {unimplemented!("Construct a ChessPosition struct, given the following rank, file: ({}, {}). If the position is invalid return None.",rank,file);}
}impl Queen {pub fn new(position: ChessPosition) -> Self {unimplemented!("Given the chess position {:?}, construct a Queen struct.",position);}pub fn can_attack(&self, other: &Queen) -> bool {unimplemented!("Determine if this Queen can attack the other Queen {:?}",other);}
}
我们需要实现 ChessPosition 和 Queen 两个结构体,使它们能够表示棋盘位置和皇后,并判断两个皇后是否可以相互攻击。
设计分析
1. 核心要求
- 位置验证:验证棋盘位置是否有效(0-7范围内)
- 皇后创建:根据有效位置创建皇后实例
- 攻击判断:判断两个皇后是否可以相互攻击
- 错误处理:处理无效位置输入
2. 技术要点
- 结构体设计:合理设计 ChessPosition 和 Queen 结构体
- 坐标系统:理解和使用二维坐标系统
- 几何计算:实现行列和对角线判断逻辑
- 错误处理:使用 Option 类型处理无效输入
完整实现
1. 基础实现
#[derive(Debug)]
pub struct ChessPosition {rank: i32,file: i32,
}#[derive(Debug)]
pub struct Queen {position: ChessPosition,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {// 验证位置是否在有效范围内(0-7)if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(ChessPosition { rank, file })} else {None}}// 获取行号pub fn rank(&self) -> i32 {self.rank}// 获取列号pub fn file(&self) -> i32 {self.file}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen { position }}// 获取皇后位置pub fn position(&self) -> &ChessPosition {&self.position}pub fn can_attack(&self, other: &Queen) -> bool {let self_pos = &self.position;let other_pos = &other.position;// 检查是否在同一行if self_pos.rank() == other_pos.rank() {return true;}// 检查是否在同一列if self_pos.file() == other_pos.file() {return true;}// 检查是否在同一对角线let rank_diff = (self_pos.rank() - other_pos.rank()).abs();let file_diff = (self_pos.file() - other_pos.file()).abs();if rank_diff == file_diff {return true;}false}
}
2. 使用元组的实现
#[derive(Debug)]
pub struct ChessPosition {position: (i32, i32), // (rank, file)
}#[derive(Debug)]
pub struct Queen {position: ChessPosition,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(ChessPosition { position: (rank, file) })} else {None}}pub fn rank(&self) -> i32 {self.position.0}pub fn file(&self) -> i32 {self.position.1}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen { position }}pub fn can_attack(&self, other: &Queen) -> bool {let (self_rank, self_file) = (self.position.rank(), self.position.file());let (other_rank, other_file) = (other.position.rank(), other.position.file());// 同行、同列或同对角线self_rank == other_rank || self_file == other_file || (self_rank - other_rank).abs() == (self_file - other_file).abs()}
}
3. 使用字段直接存储的实现
#[derive(Debug)]
pub struct ChessPosition {rank: i32,file: i32,
}#[derive(Debug)]
pub struct Queen {rank: i32,file: i32,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {if (0..8).contains(&rank) && (0..8).contains(&file) {Some(ChessPosition { rank, file })} else {None}}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen {rank: position.rank,file: position.file,}}pub fn can_attack(&self, other: &Queen) -> bool {// 同行if self.rank == other.rank {return true;}// 同列if self.file == other.file {return true;}// 同对角线((self.rank - other.rank).abs() == (self.file - other.file).abs())}
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn chess_position_on_the_board_is_some() {assert!(ChessPosition::new(2, 4).is_some());
}
有效的棋盘位置(行2,列4)应该返回 Some。
#[test]
fn chess_position_off_the_board_is_none() {assert!(ChessPosition::new(-1, 2).is_none());assert!(ChessPosition::new(8, 2).is_none());assert!(ChessPosition::new(5, -1).is_none());assert!(ChessPosition::new(5, 8).is_none());
}
无效的棋盘位置应该返回 None。
#[test]
fn queen_is_created_with_a_valid_position() {Queen::new(ChessPosition::new(2, 4).unwrap());
}
应该能够使用有效位置创建皇后。
#[test]
fn queens_that_can_not_attack() {let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap());let black_queen = Queen::new(ChessPosition::new(6, 6).unwrap());assert!(!white_queen.can_attack(&black_queen));
}
不在同一行、列或对角线的皇后不能相互攻击。
#[test]
fn queens_on_the_same_rank_can_attack() {let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap());let black_queen = Queen::new(ChessPosition::new(2, 6).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在同一行的皇后可以相互攻击。
#[test]
fn queens_on_the_same_file_can_attack() {let white_queen = Queen::new(ChessPosition::new(4, 5).unwrap());let black_queen = Queen::new(ChessPosition::new(3, 5).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在同一列的皇后可以相互攻击。
#[test]
fn queens_on_the_same_diagonal_can_attack_one() {let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap());let black_queen = Queen::new(ChessPosition::new(0, 4).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在同一对角线的皇后可以相互攻击。
#[test]
fn queens_on_the_same_diagonal_can_attack_two() {let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap());let black_queen = Queen::new(ChessPosition::new(3, 1).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在另一条对角线的皇后也可以相互攻击。
#[test]
fn queens_on_the_same_diagonal_can_attack_three() {let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap());let black_queen = Queen::new(ChessPosition::new(1, 1).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在主对角线的皇后可以相互攻击。
#[test]
fn queens_on_the_same_diagonal_can_attack_four() {let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap());let black_queen = Queen::new(ChessPosition::new(5, 5).unwrap());assert!(white_queen.can_attack(&black_queen));
}
在主对角线的皇后可以相互攻击(另一个方向)。
性能优化版本
考虑性能的优化实现:
#[derive(Debug, Clone, Copy)]
pub struct ChessPosition {rank: u8, // 使用 u8 节省内存file: u8, // 使用 u8 节省内存
}#[derive(Debug, Clone, Copy)]
pub struct Queen {rank: u8,file: u8,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {// 使用位运算和范围检查进行优化if (rank as u8) < 8 && (file as u8) < 8 {Some(ChessPosition { rank: rank as u8, file: file as u8 })} else {None}}pub fn rank(&self) -> u8 {self.rank}pub fn file(&self) -> u8 {self.file}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen {rank: position.rank(),file: position.file(),}}pub fn can_attack(&self, other: &Queen) -> bool {// 使用位运算和绝对值计算优化let rank_diff = self.rank.abs_diff(other.rank);let file_diff = self.file.abs_diff(other.file);// 同行、同列或同对角线self.rank == other.rank || self.file == other.file || rank_diff == file_diff}
}// 使用预计算的版本
#[derive(Debug, Clone, Copy)]
pub struct QueenOptimized {rank: u8,file: u8,// 预计算对角线标识以提高性能diag1: u8, // rank + filediag2: u8, // 7 - rank + file
}impl QueenOptimized {pub fn new(position: ChessPosition) -> Self {QueenOptimized {rank: position.rank(),file: position.file(),diag1: position.rank() + position.file(),diag2: 7 - position.rank() + position.file(),}}pub fn can_attack(&self, other: &QueenOptimized) -> bool {self.rank == other.rank || self.file == other.file || self.diag1 == other.diag1 || self.diag2 == other.diag2}
}
错误处理和边界情况
考虑更多边界情况的实现:
#[derive(Debug, PartialEq)]
pub enum ChessError {InvalidPosition,SamePosition,
}impl std::fmt::Display for ChessError {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {match self {ChessError::InvalidPosition => write!(f, "无效的棋盘位置"),ChessError::SamePosition => write!(f, "两个皇后不能在同一个位置"),}}
}impl std::error::Error for ChessError {}#[derive(Debug)]
pub struct ChessPosition {rank: i32,file: i32,
}#[derive(Debug)]
pub struct Queen {position: ChessPosition,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(ChessPosition { rank, file })} else {None}}pub fn rank(&self) -> i32 {self.rank}pub fn file(&self) -> i32 {self.file}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen { position }}pub fn new_safe(position: ChessPosition, other: Option<&Queen>) -> Result<Self, ChessError> {if let Some(other_queen) = other {if position.rank == other_queen.position.rank && position.file == other_queen.position.file {return Err(ChessError::SamePosition);}}Ok(Queen { position })}pub fn can_attack(&self, other: &Queen) -> bool {// 检查是否为同一皇后(相同位置)if self.position.rank == other.position.rank && self.position.file == other.position.file {return false; // 同一个位置不视为攻击}let self_pos = &self.position;let other_pos = &other.position;// 同行、同列或同对角线self_pos.rank() == other_pos.rank() || self_pos.file() == other_pos.file() || (self_pos.rank() - other_pos.rank()).abs() == (self_pos.file() - other_pos.file()).abs()}// 返回Result的版本pub fn can_attack_safe(&self, other: &Queen) -> Result<bool, ChessError> {if self.position.rank == other.position.rank && self.position.file == other.position.file {Err(ChessError::SamePosition)} else {Ok(self.can_attack(other))}}
}// 支持更大棋盘的版本
#[derive(Debug)]
pub struct LargeChessPosition {rank: i32,file: i32,board_size: i32,
}#[derive(Debug)]
pub struct LargeQueen {position: LargeChessPosition,
}impl LargeChessPosition {pub fn new(rank: i32, file: i32, board_size: i32) -> Option<Self> {if board_size > 0 && rank >= 0 && rank < board_size && file >= 0 && file < board_size {Some(LargeChessPosition { rank, file, board_size })} else {None}}
}impl LargeQueen {pub fn new(position: LargeChessPosition) -> Self {LargeQueen { position }}pub fn can_attack(&self, other: &LargeQueen) -> bool {// 检查是否为同一棋盘if self.position.board_size != other.position.board_size {return false;}let self_pos = &self.position;let other_pos = &other.position;self_pos.rank == other_pos.rank || self_pos.file == other_pos.file || (self_pos.rank - other_pos.rank).abs() == (self_pos.file - other_pos.file).abs()}
}
扩展功能
基于基础实现,我们可以添加更多功能:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChessPosition {rank: i32,file: i32,
}#[derive(Debug, Clone)]
pub struct Queen {position: ChessPosition,
}impl ChessPosition {pub fn new(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(ChessPosition { rank, file })} else {None}}pub fn rank(&self) -> i32 {self.rank}pub fn file(&self) -> i32 {self.file}// 转换为代数记谱法(如 "a1", "h8")pub fn to_algebraic(&self) -> String {let file_char = (b'a' + self.file as u8) as char;let rank_char = (b'1' + self.rank as u8) as char;format!("{}{}", file_char, rank_char)}// 从代数记谱法创建位置pub fn from_algebraic(notation: &str) -> Option<Self> {if notation.len() != 2 {return None;}let chars: Vec<char> = notation.chars().collect();let file = chars[0] as i32 - 'a' as i32;let rank = chars[1] as i32 - '1' as i32;ChessPosition::new(rank, file)}
}impl Queen {pub fn new(position: ChessPosition) -> Self {Queen { position }}pub fn position(&self) -> &ChessPosition {&self.position}pub fn can_attack(&self, other: &Queen) -> bool {let self_pos = &self.position;let other_pos = &other.position;self_pos.rank() == other_pos.rank() || self_pos.file() == other_pos.file() || (self_pos.rank() - other_pos.rank()).abs() == (self_pos.file() - other_pos.file()).abs()}// 获取皇后可以攻击的所有位置pub fn attackable_positions(&self) -> Vec<ChessPosition> {let mut positions = Vec::new();let self_pos = &self.position;// 同一行的所有位置for file in 0..8 {if file != self_pos.file() {positions.push(ChessPosition::new(self_pos.rank(), file).unwrap());}}// 同一列的所有位置for rank in 0..8 {if rank != self_pos.rank() {positions.push(ChessPosition::new(rank, self_pos.file()).unwrap());}}// 对角线位置// 主对角线方向for i in 1..8 {let rank_up = self_pos.rank() + i;let rank_down = self_pos.rank() - i;let file_right = self_pos.file() + i;let file_left = self_pos.file() - i;// 右上对角线if rank_up < 8 && file_right < 8 {positions.push(ChessPosition::new(rank_up, file_right).unwrap());}// 右下对角线if rank_down >= 0 && file_right < 8 {positions.push(ChessPosition::new(rank_down, file_right).unwrap());}// 左上对角线if rank_up < 8 && file_left >= 0 {positions.push(ChessPosition::new(rank_up, file_left).unwrap());}// 左下对角线if rank_down >= 0 && file_left >= 0 {positions.push(ChessPosition::new(rank_down, file_left).unwrap());}}positions}
}// 国际象棋棋盘
pub struct ChessBoard {queens: Vec<Queen>,
}impl ChessBoard {pub fn new() -> Self {ChessBoard { queens: Vec::new() }}// 添加皇后pub fn add_queen(&mut self, position: ChessPosition) -> bool {let new_queen = Queen::new(position);// 检查是否与现有皇后冲突for queen in &self.queens {if queen.can_attack(&new_queen) {return false;}}self.queens.push(new_queen);true}// 获取所有皇后pub fn queens(&self) -> &[Queen] {&self.queens}// 检查棋盘是否有效(没有皇后相互攻击)pub fn is_valid(&self) -> bool {for i in 0..self.queens.len() {for j in (i + 1)..self.queens.len() {if self.queens[i].can_attack(&self.queens[j]) {return false;}}}true}// 显示棋盘pub fn display(&self) -> String {let mut board = vec![vec!['.'; 8]; 8];for queen in &self.queens {let pos = queen.position();board[pos.rank() as usize][pos.file() as usize] = 'Q';}board.iter().map(|row| row.iter().collect::<String>()).collect::<Vec<String>>().join("\n")}
}// N皇后问题求解器
pub struct NQueensSolver;impl NQueensSolver {pub fn new() -> Self {NQueensSolver}// 解决N皇后问题pub fn solve(&self, n: usize) -> Vec<Vec<ChessPosition>> {let mut solutions = Vec::new();let mut board = vec![0; n]; // board[i] 表示第i行皇后的列位置self.solve_recursive(n, 0, &mut board, &mut solutions);solutions}fn solve_recursive(&self,n: usize,row: usize,board: &mut [usize],solutions: &mut Vec<Vec<ChessPosition>>,) {if row == n {// 找到一个解let solution: Vec<ChessPosition> = board.iter().enumerate().map(|(r, &f)| ChessPosition::new(r as i32, f as i32).unwrap()).collect();solutions.push(solution);return;}for col in 0..n {if self.is_safe(board, row, col) {board[row] = col;self.solve_recursive(n, row + 1, board, solutions);}}}fn is_safe(&self, board: &[usize], row: usize, col: usize) -> bool {for i in 0..row {if board[i] == col || (board[i] as i32 - col as i32).abs() == (i as i32 - row as i32).abs() {return false;}}true}
}// 便利函数
pub fn can_queens_attack(pos1: (i32, i32), pos2: (i32, i32)) -> Option<bool> {let queen1_pos = ChessPosition::new(pos1.0, pos1.1)?;let queen2_pos = ChessPosition::new(pos2.0, pos2.1)?;let queen1 = Queen::new(queen1_pos);let queen2 = Queen::new(queen2_pos);Some(queen1.can_attack(&queen2))
}pub fn format_position(pos: &ChessPosition) -> String {format!("({}, {})", pos.rank(), pos.file())
}
实际应用场景
皇后攻击判断在实际开发中有以下应用:
- 游戏开发:国际象棋游戏和棋类游戏
- 算法教学:回溯算法和递归算法教学
- 人工智能:棋类AI和博弈算法
- 教育软件:国际象棋教学工具
- 益智游戏:N皇后问题求解器
- 数学研究:组合数学和离散数学研究
- 逻辑推理:约束满足问题求解
- 性能测试:算法复杂度分析和优化
算法复杂度分析
-
时间复杂度:O(1)
- 攻击判断只需要常数时间的比较操作
-
空间复杂度:O(1)
- 每个皇后对象只存储常数大小的位置信息
与其他实现方式的比较
// 使用枚举的实现
#[derive(Debug)]
pub enum ChessPiece {Queen { rank: i32, file: i32 },// 可以扩展其他棋子
}impl ChessPiece {pub fn new_queen(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(ChessPiece::Queen { rank, file })} else {None}}pub fn can_attack(&self, other: &ChessPiece) -> bool {match (self, other) {(ChessPiece::Queen { rank: r1, file: f1 }, ChessPiece::Queen { rank: r2, file: f2 }) => {r1 == r2 || f1 == f2 || (r1 - r2).abs() == (f1 - f2).abs()}}}
}// 使用第三方库的实现
// [dependencies]
// nalgebra = "0.32"use nalgebra::Vector2;#[derive(Debug)]
pub struct Position {coords: Vector2<i32>,
}impl Position {pub fn new(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {Some(Position {coords: Vector2::new(rank, file),})} else {None}}
}// 使用位运算的实现
#[derive(Debug)]
pub struct BitBoardQueen {position: u64, // 使用64位表示棋盘位置
}impl BitBoardQueen {pub fn new(rank: i32, file: i32) -> Option<Self> {if rank >= 0 && rank < 8 && file >= 0 && file < 8 {let bit_position = (rank * 8 + file) as u64;Some(BitBoardQueen {position: 1u64 << bit_position,})} else {None}}pub fn can_attack(&self, other: &BitBoardQueen) -> bool {// 位运算实现攻击判断unimplemented!("位运算实现较为复杂,此处省略")}
}// 使用函数式编程的实现
pub fn can_attack_functional(pos1: (i32, i32), pos2: (i32, i32)) -> bool {let (r1, f1) = pos1;let (r2, f2) = pos2;[r1 == r2, f1 == f2, (r1 - r2).abs() == (f1 - f2).abs()].iter().any(|&b| b)
}
总结
通过 queen-attack 练习,我们学到了:
- 二维坐标系统:掌握了棋盘坐标系统的表示和操作
- 几何计算:学会了判断行列和对角线关系的算法
- 结构体设计:理解了如何设计合适的结构体来表示现实世界对象
- 错误处理:深入理解了使用 Option 类型处理边界情况
- 性能优化:学会了使用适当的数据类型和算法优化性能
- 扩展设计:理解了如何设计可扩展的系统架构
这些技能在实际开发中非常有用,特别是在游戏开发、算法设计、几何计算等场景中。皇后攻击判断虽然是一个具体的国际象棋问题,但它涉及到了坐标系统、几何计算、错误处理、性能优化等许多核心概念,是学习Rust实用编程的良好起点。
通过这个练习,我们也看到了Rust在游戏逻辑实现和算法设计方面的强大能力,以及如何用安全且高效的方式实现复杂的逻辑判断。这种结合了安全性和性能的语言特性正是Rust的魅力所在。
