<?php
class SimpleGo implements Game { const EMPTY = 0; const BLACK = 1; const WHITE = 2; const BOARD_SIZE = 5; private $board; private $currentPlayer; private $moveHistory; private $koPoint; public function __construct() { $this->board = array_fill(0, self::BOARD_SIZE, array_fill(0, self::BOARD_SIZE, self::EMPTY)); $this->currentPlayer = self::BLACK; $this->moveHistory = []; $this->koPoint = null; } public function getCurrentPlayer() { return $this->currentPlayer; } public function getLegalActions() { $actions = []; for ($i = 0; $i < self::BOARD_SIZE; $i++) { for ($j = 0; $j < self::BOARD_SIZE; $j++) { if ($this->isValidMove($i, $j)) { $actions[] = ['x' => $i, 'y' => $j]; } } } $actions[] = ['x' => -1, 'y' => -1]; return $actions; } public function makeMove($action) { if ($action['x'] == -1 && $action['y'] == -1) { $this->moveHistory[] = ['action' => $action, 'captures' => []]; $this->currentPlayer = $this->getOpponent($this->currentPlayer); $this->koPoint = null; return; } $x = $action['x']; $y = $action['y']; if (!$this->isValidMove($x, $y)) { throw new Exception("Invalid move: {$x}, {$y}"); } $this->board[$x][$y] = $this->currentPlayer; $captures = $this->captureStones($x, $y); if (empty($this->getGroupLiberties($x, $y))) { $this->board[$x][$y] = self::EMPTY; throw new Exception("Suicide move not allowed"); } $this->updateKoPoint($captures); $this->moveHistory[] = [ 'action' => $action, 'captures' => $captures, 'player' => $this->currentPlayer ]; $this->currentPlayer = $this->getOpponent($this->currentPlayer); } public function undoMove() { if (empty($this->moveHistory)) { return; } $lastMove = array_pop($this->moveHistory); $action = $lastMove['action']; if ($action['x'] == -1 && $action['y'] == -1) { $this->currentPlayer = $this->getOpponent($this->currentPlayer); return; } $this->board[$action['x']][$action['y']] = self::EMPTY; foreach ($lastMove['captures'] as $capture) { $this->board[$capture['x']][$capture['y']] = $lastMove['player'] == self::BLACK ? self::WHITE : self::BLACK; } $this->currentPlayer = $lastMove['player']; } public function isGameOver() { $passCount = 0; $emptyCount = 0; for ($i = count($this->moveHistory) - 1; $i >= max(0, count($this->moveHistory) - 2); $i--) { if (isset($this->moveHistory[$i]['action']) && $this->moveHistory[$i]['action']['x'] == -1) { $passCount++; } } for ($i = 0; $i < self::BOARD_SIZE; $i++) { for ($j = 0; $j < self::BOARD_SIZE; $j++) { if ($this->board[$i][$j] == self::EMPTY) { $emptyCount++; } } } return $passCount >= 2 || $emptyCount == 0; } public function getWinner() { if (!$this->isGameOver()) { return null; } $blackScore = 0; $whiteScore = 6.5; for ($i = 0; $i < self::BOARD_SIZE; $i++) { for ($j = 0; $j < self::BOARD_SIZE; $j++) { if ($this->board[$i][$j] == self::BLACK) { $blackScore++; } elseif ($this->board[$i][$j] == self::WHITE) { $whiteScore++; } } } if ($blackScore > $whiteScore) { return self::BLACK; } elseif ($whiteScore > $blackScore) { return self::WHITE; } else { return null; } } public function copy() { $copy = new SimpleGo(); $copy->board = []; foreach ($this->board as $row) { $copy->board[] = $row; } $copy->currentPlayer = $this->currentPlayer; $copy->moveHistory = $this->moveHistory; $copy->koPoint = $this->koPoint; return $copy; }
public function display() { $symbols = [ self::EMPTY => ' . ', self::BLACK => ' ● ', self::WHITE => ' ○ ' ]; echo "\n "; for ($j = 0; $j < self::BOARD_SIZE; $j++) { echo " {$j} "; } echo "\n"; for ($i = 0; $i < self::BOARD_SIZE; $i++) { echo "{$i} "; for ($j = 0; $j < self::BOARD_SIZE; $j++) { echo $symbols[$this->board[$i][$j]]; } echo "\n"; } echo "当前玩家: " . ($this->currentPlayer == self::BLACK ? "黑方" : "白方") . "\n"; } private function isValidMove($x, $y) { if ($x < 0 || $x >= self::BOARD_SIZE || $y < 0 || $y >= self::BOARD_SIZE) { return false; } if ($this->board[$x][$y] != self::EMPTY) { return false; } if ($this->koPoint && $x == $this->koPoint['x'] && $y == $this->koPoint['y']) { return false; } return true; } private function getOpponent($player) { return $player == self::BLACK ? self::WHITE : self::BLACK; } private function captureStones($x, $y) { $opponent = $this->getOpponent($this->currentPlayer); $captures = []; $directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; foreach ($directions as $dir) { $nx = $x + $dir[0]; $ny = $y + $dir[1]; if ($nx >= 0 && $nx < self::BOARD_SIZE && $ny >= 0 && $ny < self::BOARD_SIZE && $this->board[$nx][$ny] == $opponent) { $group = $this->getGroup($nx, $ny); if ($this->getGroupLiberties($nx, $ny) == 0) { foreach ($group as $stone) { $this->board[$stone['x']][$stone['y']] = self::EMPTY; $captures[] = $stone; } } } } return $captures; } private function getGroup($x, $y) { $color = $this->board[$x][$y]; $group = []; $visited = []; $this->dfsGroup($x, $y, $color, $group, $visited); return $group; } private function dfsGroup($x, $y, $color, &$group, &$visited) { if ($x < 0 || $x >= self::BOARD_SIZE || $y < 0 || $y >= self::BOARD_SIZE) { return; } $key = "{$x},{$y}"; if (isset($visited[$key]) || $this->board[$x][$y] != $color) { return; } $visited[$key] = true; $group[] = ['x' => $x, 'y' => $y]; $directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; foreach ($directions as $dir) { $this->dfsGroup($x + $dir[0], $y + $dir[1], $color, $group, $visited); } } private function getGroupLiberties($x, $y) { $group = $this->getGroup($x, $y); $liberties = 0; $visited = []; foreach ($group as $stone) { $directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; foreach ($directions as $dir) { $nx = $stone['x'] + $dir[0]; $ny = $stone['y'] + $dir[1]; if ($nx >= 0 && $nx < self::BOARD_SIZE && $ny >= 0 && $ny < self::BOARD_SIZE) { $key = "{$nx},{$ny}"; if (!isset($visited[$key]) && $this->board[$nx][$ny] == self::EMPTY) { $liberties++; $visited[$key] = true; } } } } return $liberties; } private function updateKoPoint($captures) { if (count($captures) == 1) { $capture = $captures[0]; $this->koPoint = $capture; } else { $this->koPoint = null; } } }
?>
|