Created
January 27, 2020 07:05
-
-
Save Big-Shark/ced3c20f57abab8793ea3cd14672e88a to your computer and use it in GitHub Desktop.
tic-tac-toe.php
This file contains hidden or 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\Command; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\Helper\ProgressBar; | |
use Symfony\Component\Console\Input\InputArgument; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Input\InputOption; | |
use Symfony\Component\Console\Output\OutputInterface; | |
use Symfony\Component\Console\Style\SymfonyStyle; | |
use TgBotApi\BotApiBase\BotApi; | |
use TgBotApi\BotApiBase\BotApiInterface; | |
use TgBotApi\BotApiBase\Method\EditMessageReplyMarkupMethod; | |
use TgBotApi\BotApiBase\Method\GetUpdatesMethod; | |
use TgBotApi\BotApiBase\Method\SendMessageMethod; | |
use TgBotApi\BotApiBase\Type\InlineKeyboardButtonType; | |
use TgBotApi\BotApiBase\Type\InlineKeyboardMarkupType; | |
use TgBotApi\BotApiBase\Type\UpdateType; | |
class BotUpdateCommand extends Command | |
{ | |
protected static $defaultName = 'bot:update'; | |
private BotApiInterface $botApi; | |
private $fields; | |
public function __construct(BotApiInterface $botApi) | |
{ | |
parent::__construct(); | |
$this->botApi = $botApi; | |
} | |
/** | |
* @param InputInterface $input | |
* @param OutputInterface $output | |
* @return int | |
* @throws \TgBotApi\BotApiBase\Exception\BadArgumentException | |
* @throws \TgBotApi\BotApiBase\Exception\ResponseException | |
*/ | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$progressBar = new ProgressBar($output); | |
$progressBar->setFormat('%elapsed:6s% / %memory:6s% '); | |
$progressBar->start(); | |
$offset = null; | |
while(true) { | |
$output->writeln((new \DateTimeImmutable('now'))->format(DATE_ATOM)); | |
$getUpdatesMethod = GetUpdatesMethod::create(['offset' => $offset, 'timeout' => 120]); | |
$updates = $this->botApi->getUpdates($getUpdatesMethod); | |
$progressBar->advance(); | |
foreach ($updates as $update) { | |
try { | |
$this->handleUpdate($update); | |
} catch (\Exception $e) { | |
$output->writeln($e->getMessage()); | |
//throw $e; | |
} | |
$offset = $update->updateId + 1; | |
} | |
} | |
$progressBar->finish(); | |
return 0; | |
} | |
private function handleUpdate(UpdateType $update) | |
{ | |
if($update->message) | |
{ | |
$message = $update->message; | |
$field = $this->getField($message->chat->id); | |
$keyboard = $this->renderField($field); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'Game:', ['replyMarkup' => $keyboard])); | |
return; | |
} | |
if($update->callbackQuery) | |
{ | |
$message = $update->callbackQuery->message; | |
$data = $update->callbackQuery->data; | |
if($data === 'play') { | |
$this->cleanField($message->chat->id); | |
$field = $this->getField($message->chat->id); | |
$keyboard = $this->renderField($field); | |
$this->botApi->edit(EditMessageReplyMarkupMethod::create($message->chat->id, $message->messageId, ['replyMarkup' => $keyboard])); | |
return; | |
} | |
$field = $this->getField($message->chat->id); | |
[$rowKey, $colKey] = str_split($data); | |
$field->update($rowKey, $colKey, 'X'); | |
if($this::isWin($field, 'X')) | |
{ | |
$keyboard = $this->renderField($field); | |
$this->botApi->edit(EditMessageReplyMarkupMethod::create($message->chat->id, $message->messageId, ['replyMarkup' => $keyboard])); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'You win')); | |
$this->cleanField($message->chat->id); | |
$keyboard = InlineKeyboardMarkupType::create([[InlineKeyboardButtonType::create('New Game', ['callbackData' => 'play'])]]); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'More:', ['replyMarkup' => $keyboard])); | |
return; | |
} | |
$position = self::AI($field); | |
if(null === $position) { | |
$keyboard = $this->renderField($field); | |
$this->botApi->edit(EditMessageReplyMarkupMethod::create($message->chat->id, $message->messageId, ['replyMarkup' => $keyboard])); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'Draw')); | |
$this->cleanField($message->chat->id); | |
$keyboard = InlineKeyboardMarkupType::create([[InlineKeyboardButtonType::create('New Game', ['callbackData' => 'play'])]]); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'More:', ['replyMarkup' => $keyboard])); | |
return; | |
} | |
$field->update($position[0], $position[1], 'O'); | |
if($this::isWin($field, 'O')) | |
{ | |
$keyboard = $this->renderField($field); | |
$this->botApi->edit(EditMessageReplyMarkupMethod::create($message->chat->id, $message->messageId, ['replyMarkup' => $keyboard])); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'You lose')); | |
$this->cleanField($message->chat->id); | |
$keyboard = InlineKeyboardMarkupType::create([[InlineKeyboardButtonType::create('New Game', ['callbackData' => 'play'])]]); | |
$this->botApi->send(SendMessageMethod::create($message->chat->id, 'More:', ['replyMarkup' => $keyboard])); | |
return; | |
} | |
$keyboard = $this->renderField($field); | |
$this->botApi->edit(EditMessageReplyMarkupMethod::create($message->chat->id, $message->messageId, ['replyMarkup' => $keyboard])); | |
} | |
} | |
private function renderField(Field $field): InlineKeyboardMarkupType | |
{ | |
$keyboard = []; | |
foreach($field->getFieldArray() as $rowKey => $row) { | |
$rowButtons = []; | |
foreach($row as $colKey => $column) { | |
if(null === $column) | |
{ | |
$column = ' '; | |
} | |
$rowButtons[] = InlineKeyboardButtonType::create($column, ['callbackData' => $rowKey.$colKey]); | |
} | |
$keyboard[] = $rowButtons; | |
} | |
return InlineKeyboardMarkupType::create($keyboard); | |
} | |
public function getField($id): Field | |
{ | |
if(!isset($this->fields[$id])) { | |
$this->fields[$id] = new Field(); | |
} | |
return $this->fields[$id]; | |
} | |
public function cleanField($id): void | |
{ | |
unset($this->fields[$id]); | |
} | |
static function isWin(Field $field, $mark): bool | |
{ | |
$is = function($row, $col) use ($field, $mark) | |
{ | |
return $field->get($row, $col) === $mark; | |
}; | |
return $is(0,0) && $is(0,1) && $is(0,2) || #Верхняя линия | |
$is(1,0) && $is(1,1) && $is(1,2) || #Средняя линия | |
$is(2,0) && $is(2,1) && $is(2,2) || #Нижняя линия | |
$is(0,0) && $is(1,0) && $is(2,0) || #Левая вертикальная линия | |
$is(0,1) && $is(1,1) && $is(2,1) || #Центральная вертикаль | |
$is(0,2) && $is(1,2) && $is(2,2) || #Верхняя линия | |
$is(0,0) && $is(1,1) && $is(2,2) || #Диагональ | |
$is(0,2) && $is(1,1) && $is(2,0);#Диагональ | |
} | |
public static function AI(Field $field) | |
{ | |
$isEmpty = function($row, $col) use ($field) | |
{ | |
return $field->get($row, $col) === null; | |
}; | |
#Здесь начинается алгоритм ИИ "Крестики-Нолики" | |
#Первым шагом будет определение возможности победы на следующем ходу | |
foreach ($field->getFieldArray() as $rowKey => $row) | |
{ | |
foreach ($row as $colKey => $mark) | |
{ | |
if(null !== $mark) { | |
continue; | |
} | |
$clone = clone $field; | |
$clone->update($rowKey, $colKey, 'O'); | |
if(self::isWin($clone, 'O')) | |
{ | |
return [$rowKey, $colKey]; | |
} | |
} | |
} | |
#Проверяем, может ли игрок выиграть на следющем ходу, чтобы заблокировать его | |
foreach ($field->getFieldArray() as $rowKey => $row) { | |
foreach ($row as $colKey => $mark) { | |
if (null !== $mark) { | |
continue; | |
} | |
$clone = clone $field; | |
$clone->update($rowKey, $colKey, 'X'); | |
if (self::isWin($clone, 'X')) { | |
return [$rowKey, $colKey]; | |
} | |
} | |
} | |
#Попытаемся занять один из углов, если они свободны | |
$places = [[0,0], [0,2], [2,0], [2,2]]; | |
shuffle($places); | |
foreach ($places as $position) { | |
if($isEmpty($position[0], $position[1])) { | |
return $position; | |
} | |
} | |
#Занимаем центр, если он свободен | |
if($isEmpty(1,1)) { | |
return [1,1]; | |
} | |
#Занимаем одну из боковых клеток | |
foreach ($field->getFieldArray() as $rowKey => $row) | |
{ | |
foreach ($row as $colKey => $mark) | |
{ | |
if($isEmpty($rowKey, $colKey)) { | |
return [$rowKey, $colKey]; | |
} | |
} | |
} | |
return null; | |
} | |
} | |
class Field | |
{ | |
private array $field = [ | |
[null, null, null], | |
[null, null, null], | |
[null, null, null], | |
]; | |
public function getFieldArray():array | |
{ | |
return $this->field; | |
} | |
public function get($row, $col): ?string | |
{ | |
return $this->field[$row][$col]; | |
} | |
public function update($row, $col, $mark) | |
{ | |
$this->field[$row][$col] = $mark; | |
} | |
} | |
//$this->botApi->send(SendMessageMethod::create(162424472, 'text', ['replyMarkup' => $keyboard])); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment