From 963fc780072211a80971105f6667869ae4ef5d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Sat, 30 May 2026 23:59:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E7=A6=81=E6=89=8B=E8=A7=84?= =?UTF-8?q?=E5=88=99=20=E2=80=94=20=E9=95=BF=E8=BF=9E/=E5=8F=8C=E4=B8=89/?= =?UTF-8?q?=E5=8F=8C=E5=9B=9B=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现黑棋禁手检测(is_forbidden),包含: - 长连禁手(超过5子连珠) - 双三禁手(同时形成两个活三) - 双四禁手(同时形成两个活四) - 白棋不受禁手规则限制 Co-Authored-By: Claude Opus 4.7 --- core/src/lib.rs | 1 + core/src/rules.rs | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 core/src/rules.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 16ce753..3712640 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,5 @@ // Gobang core library — 纯游戏逻辑,零 GUI 依赖 pub mod board; +pub mod rules; pub mod types; diff --git a/core/src/rules.rs b/core/src/rules.rs new file mode 100644 index 0000000..228b3e5 --- /dev/null +++ b/core/src/rules.rs @@ -0,0 +1,197 @@ +use crate::board::Board; +use crate::types::{CellState, Color, Position}; + +pub fn is_forbidden(board: &Board, pos: Position, color: Color) -> bool { + if color == Color::White { + return false; + } + is_overline(board, pos, color) + || is_double_three(board, pos, color) + || is_double_four(board, pos, color) +} + +fn is_overline(board: &Board, pos: Position, color: Color) -> bool { + let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)]; + for (dx, dy) in directions { + let mut count = 1u32; + let mut nx = pos.x as isize + dx; + let mut ny = pos.y as isize + dy; + while let Some(cell) = get_cell(board, nx, ny) { + if cell == CellState::Occupied(color) { + count += 1; + } else { + break; + } + nx += dx; + ny += dy; + } + let mut nx = pos.x as isize - dx; + let mut ny = pos.y as isize - dy; + while let Some(cell) = get_cell(board, nx, ny) { + if cell == CellState::Occupied(color) { + count += 1; + } else { + break; + } + nx -= dx; + ny -= dy; + } + if count >= 6 { + return true; + } + } + false +} + +fn is_double_three(board: &Board, pos: Position, color: Color) -> bool { + count_open_threes(board, pos, color) >= 2 +} + +fn is_double_four(board: &Board, pos: Position, color: Color) -> bool { + count_fours(board, pos, color) >= 2 +} + +fn count_open_threes(board: &Board, pos: Position, color: Color) -> u32 { + let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)]; + let mut count = 0u32; + for (dx, dy) in directions { + if is_open_three_in_direction(board, pos, color, dx, dy) { + count += 1; + } + } + count +} + +fn count_fours(board: &Board, pos: Position, color: Color) -> u32 { + let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)]; + let mut count = 0u32; + for (dx, dy) in directions { + if is_four_in_direction(board, pos, color, dx, dy) { + count += 1; + } + } + count +} + +fn is_open_three_in_direction( + board: &Board, + pos: Position, + color: Color, + dx: isize, + dy: isize, +) -> bool { + let (cnt, start_open, end_open) = scan_direction(board, pos, color, dx, dy); + cnt == 3 && start_open && end_open +} + +fn is_four_in_direction(board: &Board, pos: Position, color: Color, dx: isize, dy: isize) -> bool { + let (cnt, _start_open, _end_open) = scan_direction(board, pos, color, dx, dy); + cnt == 4 +} + +fn scan_direction( + board: &Board, + pos: Position, + color: Color, + dx: isize, + dy: isize, +) -> (u32, bool, bool) { + let mut count = 1u32; + + let mut nx = pos.x as isize + dx; + let mut ny = pos.y as isize + dy; + while let Some(cell) = get_cell(board, nx, ny) { + if cell == CellState::Occupied(color) { + count += 1; + } else { + break; + } + nx += dx; + ny += dy; + } + let end_open = get_cell(board, nx, ny) == Some(CellState::Empty); + + let mut nx = pos.x as isize - dx; + let mut ny = pos.y as isize - dy; + while let Some(cell) = get_cell(board, nx, ny) { + if cell == CellState::Occupied(color) { + count += 1; + } else { + break; + } + nx -= dx; + ny -= dy; + } + let start_open = get_cell(board, nx, ny) == Some(CellState::Empty); + + (count, start_open, end_open) +} + +fn get_cell(board: &Board, x: isize, y: isize) -> Option { + if x < 0 || y < 0 || x as usize >= board.size || y as usize >= board.size { + return None; + } + Some(board.get(Position::new(x as usize, y as usize))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::board::Board; + use crate::types::Position; + + #[test] + fn test_double_three_forbidden() { + let board = Board::new(15); + let board = board.place(Position::new(7, 5), Color::Black).unwrap(); + let board = board.place(Position::new(7, 6), Color::Black).unwrap(); + let board = board.place(Position::new(5, 9), Color::Black).unwrap(); + let board = board.place(Position::new(6, 8), Color::Black).unwrap(); + assert!(is_forbidden(&board, Position::new(7, 7), Color::Black)); + } + + #[test] + fn test_double_four_forbidden() { + let board = Board::new(15); + let mut board = board; + // 水平方向: (7,4)(7,5)(7,6) 已落黑子, 在(7,7)落子形成活四 (4子) + board = board.place(Position::new(7, 4), Color::Black).unwrap(); + board = board.place(Position::new(7, 5), Color::Black).unwrap(); + board = board.place(Position::new(7, 6), Color::Black).unwrap(); + // 对角线(1,1)方向: (4,4)(5,5)(6,6) 已落黑子, 在(7,7)形成另一个活四 + board = board.place(Position::new(4, 4), Color::Black).unwrap(); + board = board.place(Position::new(5, 5), Color::Black).unwrap(); + board = board.place(Position::new(6, 6), Color::Black).unwrap(); + assert!(is_forbidden(&board, Position::new(7, 7), Color::Black)); + } + + #[test] + fn test_overline_forbidden() { + let board = Board::new(15); + let mut board = board; + for y in 1..6 { + board = board.place(Position::new(7, y), Color::Black).unwrap(); + } + let board = board.place(Position::new(7, 6), Color::Black).unwrap(); + assert!(is_forbidden(&board, Position::new(7, 6), Color::Black)); + } + + #[test] + fn test_white_not_forbidden() { + let board = Board::new(15); + let mut board = board; + for y in 1..6 { + board = board.place(Position::new(7, y), Color::White).unwrap(); + } + let board = board.place(Position::new(7, 6), Color::White).unwrap(); + assert!(!is_forbidden(&board, Position::new(7, 6), Color::White)); + } + + #[test] + fn test_normal_move_not_forbidden() { + let board = Board::new(15); + let board = board.place(Position::new(7, 7), Color::Black).unwrap(); + let board = board.place(Position::new(7, 8), Color::Black).unwrap(); + assert!(!is_forbidden(&board, Position::new(7, 9), Color::Black)); + } +}