txlyre 6 цаг өмнө
parent
commit
28bc01ce49
5 өөрчлөгдсөн 289 нэмэгдсэн , 2 устгасан
  1. 127 0
      chess0.py
  2. 141 0
      commands.py
  3. 2 0
      config.yml.sample
  4. 17 1
      openkriemy.py
  5. 2 1
      requirements.txt

+ 127 - 0
chess0.py

@@ -0,0 +1,127 @@
+import time
+
+import chess
+import chess.svg
+import chess.engine
+
+
+class GameOver(Exception):
+    pass
+
+
+class IllegalMove(Exception):
+    pass
+
+
+def outcome_to_str(outcome):
+    return f"{outcome.result()} {outcome.termination.name}"
+
+
+class ChessSession:
+    def __init__(self, engine):
+        self.engine = engine
+        self.board = chess.Board()
+
+        self.ts = time.time()
+        self.move_ts = time.time()
+
+    def check_game_over(self):
+        if self.board.is_game_over():
+            raise GameOver(outcome_to_str(self.board.outcome()))
+
+    async def move(self, move=None):
+        if move is not None:
+            try:
+                move = chess.Move.from_uci(move)
+            except chess.InvalidMoveError:
+                raise IllegalMove(move)
+
+            if move not in self.board.legal_moves:
+                raise IllegalMove(move)
+        else:
+            move = await self.engine.play(self.board, chess.engine.Limit(time=10))
+            move = move.move
+
+        self.board.push(move)
+        self.move_ts = time.time()
+
+        self.check_game_over()
+
+    def skip(self):
+        self.board.push(chess.Move.null())
+        self.move_ts = time.time()
+
+        self.check_game_over()
+
+    async def end(self):
+        await self.engine.quit()
+
+
+class ChessManager:
+    def __init__(self, engine_path):
+        self.engine_path = engine_path
+
+        self.sessions = {}
+
+    async def cleanup(self):
+        for id in list(self.sessions.keys()):
+            session = self.sessions[id]
+
+            if (
+                time.time() - session.move_ts >= 60 * 60 * 12
+                or time.time() - session.ts >= 60 * 60 * 24 * 7
+            ):
+                await self.end(id)
+
+    def get(self, id):
+        return self.sessions.get(id)
+
+    async def begin(self, id):
+        if id in self.sessions:
+            await self.stop(id)
+
+        _, engine = await chess.engine.popen_uci(self.engine_path)
+
+        self.sessions[id] = ChessSession(engine)
+
+    async def end(self, id):
+        session = self.sessions.get(id)
+        if not session:
+            return False
+
+        del self.sessions[id]
+
+        await session.end()
+
+        return True
+
+    async def move(self, id, move=None):
+        session = self.get(id)
+        if not session:
+            raise KeyError(id)
+
+        await session.move(move)
+
+    def undo(self, id):
+        session = self.get(id)
+        if not session:
+            raise KeyError(id)
+
+        session.board.pop()
+
+    async def skip(self, id):
+        await self.move(id)
+
+    async def svg(self, id):
+        session = self.get(id)
+        if not session:
+            raise KeyError(id)
+
+        return chess.svg.board(session.board)
+
+    async def ascii(self, id):
+        session = self.get(id)
+        if not session:
+            raise KeyError(id)
+
+        return str(session.board)

+ 141 - 0
commands.py

@@ -47,6 +47,7 @@ from utils import (
     unparse,
 )
 from rand import roll_dices
+from chess0 import IllegalMove, GameOver
 
 
 class Handler:
@@ -651,6 +652,146 @@ async def roll_handler(bot, event, command):
     )
 
 
+async def chess_start_handler(chess, id):
+    await chess.begin(id)
+
+    return [svg2png(chess.svg(id))]
+
+
+async def chess_stop_handler(chess, id):
+    if await chess.end(id):
+        return ["Игра завершена."]
+
+    return ["Нет активной игры."]
+
+
+async def chess_move_handler(chess, id, move):
+    try:
+        await chess.move(id, move)
+    except KeyError:
+        return ["Нет активной игры."]
+    except GameOver as e:
+        board = svg2png(chess.svg(id))
+
+        await chess.end(id)
+
+        return [
+            board,
+            f"Конец игры: {str(e)}",
+        ]
+    except IllegalMove:
+        return ["Некорректный ход."]
+
+    return [svg2png(chess.svg(id))]
+
+
+async def chess_undo_handler(chess, id):
+    try:
+        await chess.undo(id)
+    except KeyError:
+        return ["Нет активной игры."]
+    except IndexError:
+        return ["Нечего отменять."]
+
+    return ["Последний ход отменён.", svg2png(chess.svg(id))]
+
+
+async def chess_skip_handler(chess, id):
+    try:
+        await chess.skip(id)
+    except KeyError:
+        return ["Нет активной игры."]
+    except GameOver as e:
+        board = svg2png(chess.svg(id))
+
+        await chess.end(id)
+
+        return [
+            board,
+            f"Конец игры: {str(e)}",
+        ]
+
+    return [svg2png(chess.svg(id))]
+
+
+async def chess_pass_handler(chess, id):
+    try:
+        await chess.move(id)
+    except KeyError:
+        return ["Нет активной игры."]
+    except GameOver as e:
+        board = svg2png(chess.svg(id))
+
+        await chess.end(id)
+
+        return [
+            board,
+            f"Конец игры: {str(e)}",
+        ]
+
+    return [svg2png(chess.svg(id))]
+
+
+async def chess_board_handler(chess, id, move):
+    try:
+        board = svg2png(chess.svg(id))
+    except KeyError:
+        return ["Нет активной игры."]
+
+    return [board]
+
+
+CHESS_COMMANDS = {
+    "start": (chess_start_handler, "Начать новую игру", 0),
+    "end": (chess_stop_handler, "Завершить игру", 0),
+    "move": (chess_move_handler, "Сделать ход", 1),
+    "undo": (chess_undo_handler, "Отменить ход", 0),
+    "skip": (chess_skip_handler, "Пропустить ход", 0),
+    "pass": (chess_pass_handler, "Сделать ход вместо вас.", 0),
+    "board": (chess_board_handler, "Показать состояние доски"),
+}
+
+
+async def chess_handler(bot, event, command):
+    if not bot.chess:
+        await event.reply("Извините, данная функция недоступна....")
+
+        return
+
+    if command.argc < 1:
+        buffer = "Доступные субкоманды:\n"
+
+        for command in CHESS_COMMANDS:
+            buffer += f"- {command} - {CHESS_COMMANDS[command][1]}.\n"
+
+        await event.reply(buffer.strip())
+
+        return
+
+    if command.args[0] not in CHESS_COMMANDS:
+        await event.reply(f"Неизвестная субкоманда: '{command.args[0]}'.")
+
+        return
+
+    subcommand = CHESS_COMMANDS[command.args[0]]
+    if command.argc - 1 != subcommand[2]:
+        await event.reply(
+            f"Некорректное количество аргументов для субкоманды '{command.args[0]}'."
+        )
+
+        return
+
+    result = await subcommand[0](bot.chess, event.sender.id, *command.args[1:])
+
+    for reply in result:
+        if isinstance(reply, bytes):
+            await bot.send_file(
+                event.peer_id, file=reply, reply_to=event, force_document=False
+            )
+        else:
+            await event.reply(reply)
+
+
 COMMANDS = {
     "newadmin": Handler(newadmin_handler, is_restricted=True),
     "deladmin": Handler(deladmin_handler, is_restricted=True),

+ 2 - 0
config.yml.sample

@@ -16,3 +16,5 @@ MARKOV_CORPUS_PATH: './markov_corpus.json'
 MARKOV_TRIGGER_WORDS:
     - 'bot'
     - 'amogus'
+
+CHESS_UCI_ENGINE: ''

+ 17 - 1
openkriemy.py

@@ -1,3 +1,4 @@
+import time
 from random import random, randint
 from asyncio import sleep
 from datetime import datetime, timedelta, date, time
@@ -23,15 +24,22 @@ from actions import (
 )
 from commands import COMMANDS
 from markov import Markov
+from chess0 import ChessManager
 
 bot = TelegramClient("openkriemy", config.API_ID, config.API_HASH).start(
     bot_token=config.API_TOKEN
 )
 markov = Markov()
 
+if config.CHESS_UCI_ENGINE:
+    chess = ChessManager(config.CHESS_UCI_ENGINE)
+else:
+    chess = None
+
 
 # Wait isn't that illegal??
 bot.markov = markov
+bot.chess = chess
 
 
 @bot.on(InlineQuery)
@@ -146,7 +154,7 @@ async def on_message(event):
 
     try:
         await event.delete()
-    except:
+    except Exception:
         pass
 
     if event.sender is None:
@@ -208,9 +216,17 @@ async def markov_say_loop():
                 await markov_say(bot, chat.peer_id)
 
 
+async def chess_cleanup():
+    while True:
+        await sleep(60 * 60)
+        await chess.cleanup()
+
+
 with bot:
     bot.loop.run_until_complete(init_db())
     bot.loop.create_task(notify_birthdays_loop())
     bot.loop.create_task(markov_say_loop())
+    if chess:
+        bot.loop.create_task(chess_cleanup())
     bot.start()
     bot.run_until_disconnected()

+ 2 - 1
requirements.txt

@@ -7,4 +7,5 @@ telethon
 emoji
 markovify
 ujson
-spacy
+spacy
+chess