Skip to content

Instantly share code, notes, and snippets.

@Big-Shark
Created January 27, 2020 07:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Big-Shark/ced3c20f57abab8793ea3cd14672e88a to your computer and use it in GitHub Desktop.
Save Big-Shark/ced3c20f57abab8793ea3cd14672e88a to your computer and use it in GitHub Desktop.
tic-tac-toe.php
<?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