Skip to content

Instantly share code, notes, and snippets.

@Nex-Otaku
Last active March 2, 2023 19:22
Show Gist options
  • Save Nex-Otaku/81b338f8a5b21f13dec42f5aba7a76ff to your computer and use it in GitHub Desktop.
Save Nex-Otaku/81b338f8a5b21f13dec42f5aba7a76ff to your computer and use it in GitHub Desktop.
Крестики-нолики
<?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