feat(core): 禁手规则 — 长连/双三/双四检测

实现黑棋禁手检测(is_forbidden),包含:
- 长连禁手(超过5子连珠)
- 双三禁手(同时形成两个活三)
- 双四禁手(同时形成两个活四)
- 白棋不受禁手规则限制

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 23:59:19 +08:00
parent 4e336308ab
commit 963fc78007
2 changed files with 198 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
// Gobang core library — 纯游戏逻辑,零 GUI 依赖
pub mod board;
pub mod rules;
pub mod types;
+197
View File
@@ -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<CellState> {
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));
}
}