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 (поместить в ту же директорию, что и chessbot.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-бота (без файла с токеном) можно скачать здесь.