Last active
March 2, 2023 19:22
-
-
Save Nex-Otaku/81b338f8a5b21f13dec42f5aba7a76ff to your computer and use it in GitHub Desktop.
Крестики-нолики
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Module\Demo\Xo; | |
use App\Module\Runtime\FilesystemRuntimeState; | |
use App\Module\Runtime\MemoryCachedRuntimeState; | |
use App\Module\Runtime\Types\StoredString; | |
class Xo | |
{ | |
private const FIGURE_X = 'X'; | |
private const FIGURE_O = 'O'; | |
private const FIGURES = [ | |
self::FIGURE_X, | |
self::FIGURE_O, | |
]; | |
private const WINNER_X_ROW = self::FIGURE_X . self::FIGURE_X . self::FIGURE_X; | |
private const WINNER_Y_ROW = self::FIGURE_O . self::FIGURE_O . self::FIGURE_O; | |
private const CELL_X = 'X'; | |
private const CELL_O = 'O'; | |
private const CELL_EMPTY = '-'; | |
private const NEW_GAMEFIELD = '---------'; | |
private const STATE_PLAYING = 'playing'; | |
private const STATE_WINNER = 'winner'; | |
private const STATE_DRAW = 'draw'; | |
private StoredString $storedGameField; | |
private StoredString $storedNextFigure; | |
private StoredString $storedState; | |
private StoredString $storedWinner; | |
public function __construct() | |
{ | |
$runtimeState = new MemoryCachedRuntimeState(new FilesystemRuntimeState()); | |
$this->storedGameField = new StoredString('xo.gameField', $runtimeState); | |
$this->storedNextFigure = new StoredString('xo.nextFigure', $runtimeState); | |
$this->storedState = new StoredString('xo.state', $runtimeState); | |
$this->storedWinner = new StoredString('xo.winner', $runtimeState); | |
} | |
public function commandMove(string $figure, int $x, int $y): void | |
{ | |
$figureNormalized = strtoupper($figure); | |
if (!in_array(strtoupper($figureNormalized), self::FIGURES)) { | |
throw new \InvalidArgumentException('Неизвестный тип фигуры'); | |
} | |
if ($x < 1 || $x > 3) { | |
throw new \InvalidArgumentException('X должен быть в диапазоне от 1 до 3'); | |
} | |
if ($y < 1 || $y > 3) { | |
throw new \InvalidArgumentException('Y должен быть в диапазоне от 1 до 3'); | |
} | |
if (!$this->isPlaying()) { | |
throw new \InvalidArgumentException('Ход нельзя сделать, перезапустите игру'); | |
} | |
if ($this->isCellTaken($x, $y)) { | |
throw new \InvalidArgumentException('Клетка уже занята'); | |
} | |
if (!$this->isNextFigure($figureNormalized)) { | |
throw new \InvalidArgumentException('Сейчас не ваш ход'); | |
} | |
$this->updateCell($x, $y, $figureNormalized); | |
if ($this->hasWinnerX()) { | |
$this->setStateWinner(self::FIGURE_X); | |
return; | |
} | |
if ($this->hasWinnerO()) { | |
$this->setStateWinner(self::FIGURE_O); | |
return; | |
} | |
if (!$this->hasEmptyCells()) { | |
$this->setStateDraw(); | |
} | |
$this->switchNextFigure(); | |
} | |
public function commandNewGame(): void | |
{ | |
$this->setState(self::STATE_PLAYING); | |
$this->clearWinner(); | |
$this->setGameField(self::NEW_GAMEFIELD); | |
$this->setNextFigure(self::FIGURE_X); | |
} | |
public function commandGiveUp(): void | |
{ | |
$this->setStateWinner($this->getOtherFigure()); | |
} | |
public function queryState(): string | |
{ | |
$out = $this->isPlaying() | |
? "Ждём ход игрока \"{$this->getNextFigure()}\"\n" | |
: ( | |
$this->isDraw() | |
? "Ничья\n" | |
: "Победил игрок \"{$this->getWinner()}\"\n" | |
); | |
$out .= "\n"; | |
$x1y1 = $this->getCellDraw(1, 1); | |
$x2y1 = $this->getCellDraw(2, 1); | |
$x3y1 = $this->getCellDraw(3, 1); | |
$x1y2 = $this->getCellDraw(1, 2); | |
$x2y2 = $this->getCellDraw(2, 2); | |
$x3y2 = $this->getCellDraw(3, 2); | |
$x1y3 = $this->getCellDraw(1, 3); | |
$x2y3 = $this->getCellDraw(2, 3); | |
$x3y3 = $this->getCellDraw(3, 3); | |
$gameField = ''; | |
$gameField .= "+---+---+---+\n"; | |
$gameField .= "| {$x1y1} | {$x2y1} | {$x3y1} |\n"; | |
$gameField .= "+---+---+---+\n"; | |
$gameField .= "| {$x1y2} | {$x2y2} | {$x3y2} |\n"; | |
$gameField .= "+---+---+---+\n"; | |
$gameField .= "| {$x1y3} | {$x2y3} | {$x3y3} |\n"; | |
$gameField .= "+---+---+---+\n"; | |
$out .= $gameField; | |
return $out; | |
} | |
private function isCellTaken(int $x, int $y): bool | |
{ | |
return !$this->isCellEmpty($x, $y); | |
} | |
private function isCellEmpty(int $x, int $y): bool | |
{ | |
return !$this->isCellX($x, $y) && !$this->isCellO($x, $y); | |
} | |
private function isCellX(int $x, int $y): bool | |
{ | |
return $this->getCell($x, $y) === self::CELL_X; | |
} | |
private function isCellO(int $x, int $y): bool | |
{ | |
return $this->getCell($x, $y) === self::CELL_O; | |
} | |
private function getCell(int $x, int $y): string | |
{ | |
return $this->getGameField()[$this->getPosition($x, $y)]; | |
} | |
private function getCellDraw(int $x, int $y): string | |
{ | |
$cell = $this->getCell($x, $y); | |
return $cell !== self::CELL_EMPTY ? $cell : ' '; | |
} | |
private function isPlaying(): bool | |
{ | |
return $this->getState() === self::STATE_PLAYING; | |
} | |
private function isDraw(): bool | |
{ | |
return $this->getState() === self::STATE_DRAW; | |
} | |
private function isNextFigure(string $figure): bool | |
{ | |
return $this->getNextFigure() === $figure; | |
} | |
private function updateCell(int $x, int $y, string $figure): void | |
{ | |
$position = $this->getPosition($x, $y); | |
$this->setGameField( | |
substr($this->getGameField(), 0, $position) | |
. $figure | |
. substr($this->getGameField(), $position + 1) | |
); | |
} | |
private function getPosition(int $x, int $y): int | |
{ | |
return 3 * ($y - 1) + $x - 1; | |
} | |
private function hasWinnerX(): bool | |
{ | |
return in_array(self::WINNER_X_ROW, $this->getCombinations()); | |
} | |
private function hasWinnerO(): bool | |
{ | |
return in_array(self::WINNER_Y_ROW, $this->getCombinations()); | |
} | |
private function getCombinations(): array | |
{ | |
$coordinates = [ | |
[ | |
[1, 1], | |
[2, 1], | |
[3, 1], | |
], | |
[ | |
[1, 2], | |
[2, 2], | |
[3, 2], | |
], | |
[ | |
[1, 3], | |
[2, 3], | |
[3, 3], | |
], | |
[ | |
[1, 1], | |
[1, 2], | |
[1, 3], | |
], | |
[ | |
[2, 1], | |
[2, 2], | |
[2, 3], | |
], | |
[ | |
[3, 1], | |
[3, 2], | |
[3, 3], | |
], | |
[ | |
[1, 1], | |
[2, 2], | |
[3, 3], | |
], | |
[ | |
[1, 3], | |
[2, 2], | |
[3, 1], | |
], | |
]; | |
$combinations = []; | |
foreach ($coordinates as $row) { | |
$combination = ''; | |
foreach ($row as $cell) { | |
$combination .= $this->getCell($cell[0], $cell[1]); | |
} | |
$combinations[] = $combination; | |
} | |
return $combinations; | |
} | |
private function setStateWinner(string $figure): void | |
{ | |
$this->setState(self::STATE_WINNER); | |
$this->setWinner($figure); | |
} | |
private function getGameField(): string | |
{ | |
return $this->storedGameField->read() ?? self::NEW_GAMEFIELD; | |
} | |
private function setGameField(string $value) | |
{ | |
$this->storedGameField->write($value); | |
} | |
private function hasEmptyCells(): bool | |
{ | |
return str_contains($this->getGameField(), self::CELL_EMPTY); | |
} | |
private function setStateDraw(): void | |
{ | |
$this->setState(self::STATE_DRAW); | |
$this->clearWinner(); | |
} | |
private function switchNextFigure(): void | |
{ | |
$this->setNextFigure($this->getOtherFigure()); | |
} | |
private function setNextFigure(string $value): void | |
{ | |
$this->storedNextFigure->write($value); | |
} | |
private function getNextFigure(): string | |
{ | |
return $this->storedNextFigure->read() ?? self::FIGURE_X; | |
} | |
private function setState(string $value): void | |
{ | |
$this->storedState->write($value); | |
} | |
private function getState(): string | |
{ | |
return $this->storedState->read() ?? self::STATE_PLAYING; | |
} | |
private function setWinner(string $figure): void | |
{ | |
$this->storedWinner->write($figure); | |
} | |
private function clearWinner(): void | |
{ | |
$this->storedWinner->clear(); | |
} | |
private function getWinner(): string | |
{ | |
return $this->storedWinner->read() ?? '-'; | |
} | |
private function getOtherFigure(): string | |
{ | |
return $this->getNextFigure() === self::FIGURE_X ? self::FIGURE_O : self::FIGURE_X; | |
} | |
public function queryStateJson(): array | |
{ | |
$label = $this->isPlaying() | |
? "Ждём ход игрока \"{$this->getNextFigure()}\"\n" | |
: ( | |
$this->isDraw() | |
? "Ничья\n" | |
: "Победил игрок \"{$this->getWinner()}\"\n" | |
); | |
$x1y1 = $this->getCellDraw(1, 1); | |
$x2y1 = $this->getCellDraw(2, 1); | |
$x3y1 = $this->getCellDraw(3, 1); | |
$x1y2 = $this->getCellDraw(1, 2); | |
$x2y2 = $this->getCellDraw(2, 2); | |
$x3y2 = $this->getCellDraw(3, 2); | |
$x1y3 = $this->getCellDraw(1, 3); | |
$x2y3 = $this->getCellDraw(2, 3); | |
$x3y3 = $this->getCellDraw(3, 3); | |
$gamefield = [ | |
[$x1y1, $x2y1, $x3y1], | |
[$x1y2, $x2y2, $x3y2], | |
[$x1y3, $x2y3, $x3y3], | |
]; | |
return [ | |
'label' => $label, | |
'gamefield' => $gamefield, | |
'nextFigure' => $this->getNextFigure(), | |
]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment