Telegram-бот на Python и aiogram: играем в шахматы
В предыдущей части мы написали базового бота, который отвечал нам текстом нашего же сообщения, однако это не очень полезно.
Давайте теперь сделаем так, чтобы бот мог играть с нами в шахматы.
Куда добавлять код?
Мы будем писать обработчики сообщений между обработчиком команды /start и обработчиком всех остальных сообщений в коде из статьи предыдущей части.
Также мы добавим новый модуль (назовем его chess_utils), где мы будем писать функции, не относящиеся напрямую к телеграм-боту, а к логике, связанной с шахматами.
Команда /newgame
Напишем команду /newgame, которая будет начинать новую партию с ботом в шахматы.
@dp.message(Command("newgame")) async def start_game(message: Message) -> None: initial_board = chess.Board() boards[message.from_user.id] = initial_board board_image = generate_board_image(initial_board) await message.answer_photo( photo=BufferedInputFile(board_image, "game.png"), caption="Игра началась! Делай первый ход. Например: e2e4" )
После ввода этой команды мы отправляем фото текущего состояния доски (которое есть начальное состояние), и приглашаем пользователя сделать ход.
Мы используем экземпляр chess.Board из пакета python-chess для управления шахматной доской, и вспомогательную функцию generate_board_image для того, чтобы из внутреннего представления доски сделать изображение, которое можно отправить пользователю в Telegram:
Файл chess_utils.py (поместить в ту же директорию, что и bot.py)
from io import BytesIO import random import chess import chess.svg from cairosvg import svg2png def generate_board_image(board: chess.Board) -> bytes: svg_image = chess.svg.board(board) png_image = BytesIO() svg2png(bytestring=svg_image, write_to=png_image) png_image.seek(0) return png_image.read()
А теперь начнем обрабатывать ходы пользователя. Для этого перепишем функцию echo из предыдущей части. Напомню, что ранее она просто отвечала текстом написанного сообщения.
Теперь же вместо функции echo будет функция process_move:
@dp.message() async def process_move(message: Message) -> None: if message.from_user.id not in boards: await message.answer("Нет начатой партии. Начните новую игру с помощью команды /newgame") return board = boards[message.from_user.id] try: make_move(board, message.text) except (chess.InvalidMoveError, chess.IllegalMoveError): await message.answer( "Некорректный ход. Пожалуйста, запишите ход в формате UCI " "https://ru.wikipedia.org/wiki/UCI_(%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB)" ) return outcome = board.outcome() if outcome is not None: board_image = generate_board_image(board) await message.answer_photo( photo=BufferedInputFile(board_image, "game.png"), caption=f"Игра закончена! Результат - {outcome.result()}. Новая игра - /newgame" ) del boards[message.from_user.id] return make_bot_move(board) outcome = board.outcome() if outcome is not None: board_image = generate_board_image(board) await message.answer_photo( photo=BufferedInputFile(board_image, "game.png"), caption=f"Игра закончена! Результат - {outcome.result()}. Новая игра - /newgame" ) del boards[message.from_user.id] return board_image = generate_board_image(board) await message.answer_photo( photo=BufferedInputFile(board_image, "game.png"), caption="Текущее состояние доски. Делайте свой следующий ход" )
А также несколько функций в chess_utils.py:
def make_move(board: chess.Board, move: str) -> None: uci_move = chess.Move.from_uci(move) if not board.is_legal(uci_move): raise chess.IllegalMoveError board.push(uci_move) def make_bot_move(board: chess.Board) -> None: bot_move = random.choice(list(board.legal_moves)) board.push(bot_move)
Компьютерный соперник у нас пока что не очень умный; он просто выбирает случайный из возможных ходов. Архив с кодом Telegram-бота (без файла с токеном) можно скачать здесь.