Sfoglia il codice sorgente

Add hangguy, could use some refactoring but it's fine

Kirk Trombley 4 anni fa
parent
commit
f44d7e3a70
1 ha cambiato i file con 301 aggiunte e 0 eliminazioni
  1. 301 0
      commands/commands/hangguy.py

+ 301 - 0
commands/commands/hangguy.py

@@ -0,0 +1,301 @@
+from typing import Optional
+from datetime import datetime
+from base64 import b64decode
+from uuid import uuid4
+import re
+
+from rollbot import as_command, initialize_data, RollbotFailure, Response, Command
+from rollbot.injection import Data, Config, Subcommand, Lazy, Args, Origin, SenderName
+
+
+KEY = "HANG_GUY_SINGLETON"
+GUY_STAGES = [
+""
+,
+"""\
+๐Ÿ˜Ž
+"""
+,
+"""\
+๐Ÿ˜Ž
+๐Ÿ‘”
+"""
+,
+"""\
+๐Ÿ˜Ž
+๐Ÿ‘”
+โ›ฝ
+"""
+,
+"""\
+๐Ÿ˜Ž
+๐Ÿ‘”
+โ›ฝ
+โšก
+"""
+,
+"""\
+๐Ÿ‘     ๐Ÿ˜Ž
+          ๐Ÿ‘”
+          โ›ฝ
+          โšก
+"""
+,
+"""\
+๐Ÿ‘     ๐Ÿ˜Ž
+๐Ÿ›     ๐Ÿ‘”
+          โ›ฝ
+          โšก
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜Ž
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”
+          โ›ฝ
+          โšก
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜Ž
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ
+          โšก
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜Ÿ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ ๐Ÿ‘ข
+          โšก
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜Ÿ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ ๐Ÿ‘ข
+          โšก๐Ÿ‘Š
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜จ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜จ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š
+      ๐ŸŽธ
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜ฐ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š
+      ๐ŸŽธ
+  ๐Ÿ‘ข
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜ฐ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š
+      ๐ŸŽธ    ๐ŸŒฝ
+  ๐Ÿ‘ข
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜ฐ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š
+      ๐ŸŽธ    ๐ŸŒฝ
+  ๐Ÿ‘ข             ๐Ÿ‘ข
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜ซ
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š=D
+      ๐ŸŽธ    ๐ŸŒฝ
+  ๐Ÿ‘ข             ๐Ÿ‘ข
+"""
+,
+"""\
+๐Ÿ–•     ๐Ÿ˜ต
+๐Ÿ›๐Ÿ’ค๐Ÿ‘”๐Ÿ›
+          โ›ฝ  ๐Ÿ‘ข
+          โšก8=๐Ÿ‘Š=D๐Ÿ’ฆ
+      ๐ŸŽธ    ๐ŸŒฝ
+  ๐Ÿ‘ข             ๐Ÿ‘ข
+"""
+,
+]
+
+
+@initialize_data
+class HangGuy:
+    puzzle: Optional[str] = None
+    game_state: Optional[str] = None
+    bad_guesses: str = ""
+    guy_state: int = 0
+    guy_lifetime: int = 0
+
+    def new_game(self, phrase: str):
+        self.puzzle = b64decode(phrase).decode("utf-8").strip().upper()
+        self.game_state = re.sub("[A-Z]", "_", self.puzzle)
+        self.bad_guesses = ""
+
+    def is_active(self):
+        return self.puzzle is not None
+
+    def is_finished(self):
+        return self.puzzle == self.game_state
+
+    def end_game(self):
+        self.puzzle = None
+
+    def reset_guy(self):
+        self.guy_state = 0
+        self.guy_lifetime = 0
+
+    def is_dead(self):
+        return self.guy_state >= (len(GUY_STAGES) - 1)
+
+    def survival_msg(self):
+        return f"The current guy has survived {self.guy_lifetime} game{'' if self.guy_lifetime == 1 else 's'}"
+
+    def render(self):
+        if 0 <= self.guy_state < len(GUY_STAGES):
+            guy = GUY_STAGES[self.guy_state]
+        elif self.guy_state >= len(GUY_STAGES):
+            guy = GUY_STAGES[-1]
+        else:
+            guy = f"INVALID GUY STATE {self.guy_state}"
+
+        if not self.is_active():
+            return guy
+        
+        return f"{guy}\n{' '.join(self.game_state)}\nBad Guesses: {', '.join(self.bad_guesses)}"
+
+
+@initialize_data
+class DeadGuy:
+    state: int
+    lifetime: int
+    timestamp: float
+
+    @staticmethod
+    def from_guy(hg: HangGuy):
+        return DeadGuy(
+            state=hg.guy_state,
+            lifetime=hg.guy_lifetime,
+            timestamp=datetime.now().timestamp(),
+        )
+
+
+@as_command
+async def hangguy(
+    cmd: Command,
+    name: SenderName,
+    origin: Origin,
+    store: Data(HangGuy),
+    dead_store: Data(DeadGuy),
+    alert_channel: Config("hangguy.alert_channel"),
+):
+    if len(cmd.args) == 0:
+        RollbotFailure.INVALID_ARGUMENTS.raise_exc(detail="Must provide subcommand or guess")
+    
+    game = await store.load_or(KEY)
+    is_active = game.is_active()
+
+    subc = cmd.get_subcommand(inherit_bang=False) # get subcommand, requiring bang
+
+    if subc is None or cmd.bang != subc.bang:
+        if game.is_active():
+            guess = cmd.args.strip().upper()
+            prefix = ""
+            if len(guess) == 1:
+                if not guess.isalpha():
+                    prefix = "You should try sticking to letters!"
+                elif guess in game.bad_guesses or guess in game.game_state:
+                    prefix = f"You've already guessed '{guess}'!"
+                else:
+                    find = [i for i, x in enumerate(game.puzzle) if x == guess]
+                    if len(find) == 0:
+                        game.bad_guesses += guess
+                        game.guy_state += 1
+                        prefix = "Bad guess!"
+                    else:
+                        game.game_state = "".join(guess if i in find else s for i, s in enumerate(game.game_state))
+                        prefix = f"Great! '{guess}' appears {len(find)} time{'' if len(find) == 1 else 's'}!"
+            elif game.puzzle.split() == guess.split():
+                prefix = "You've guessed the full phrase!"
+                game.game_state = game.puzzle
+            else:
+                prefix = f"{guess} is not the phrase!"
+                game.guy_state += 2
+
+            txt = f"{prefix}\n{game.render()}"
+
+            if game.is_finished():
+                game.end_game()
+                game.guy_lifetime += 1
+                txt += f"\nThe game is over, and the guy lives on!\n{game.survival_msg()}"
+            
+            if game.is_dead():
+                game.end_game()
+                dead = DeadGuy.from_guy(game)
+                game.reset_guy()
+                await dead_store.save(f"{uuid4().hex}-{datetime.now().isoformat()}", dead_guy)
+                txt += f"\noh god oh fuck, the guy is dead!\nYou failed to guess {game.puzzle}\nThe guy has been buried."
+            
+            await store.save(KEY, game)
+            return txt
+        RollbotFailure.MISSING_SUBCOMMAND.raise_exc(
+            detail="You are not currently in a game, and so you must use one of the subcommands: !view, !retire, !start, !alert"
+        )
+
+    if subc.name == "alert":
+        return Response(
+            origin_id=origin,
+            channel_id=alert_channel,
+            text=f"{name} wants you to check out the hangguy chat!",
+        )
+
+    if subc.name == "cancel" and is_active:
+        game.end_game()
+        await store.save(KEY, game)
+        return "The game has been cancelled. The guy has not been reset."
+
+    if subc.name == "retire" and not is_active:
+        txt = f"{game.survival_msg()} and is retiring\n{game.render()}"
+        dead_guy = DeadGuy.from_guy(game)
+        game.reset_guy()
+        await dead_store.save(f"{uuid4().hex}-{datetime.now().isoformat()}", dead_guy)
+        await store.save(KEY, game)
+        return txt
+
+    if subc.name == "start" and not is_active:
+        game.new_game(subc.args)
+        await store.save(KEY, game)
+        return game.render()
+
+    if subc.name == "view" and not is_active:
+        return f"{game.survival_msg()}\n" + game.render()
+
+
+    if is_active:
+        RollbotFailure.INVALID_SUBCOMMAND.raise_exc(
+            detail="The only in-game subcommands are !cancel to end the game and !alert to alert the main chat."
+        )
+
+    RollbotFailure.INVALID_SUBCOMMAND.raise_exc(
+        detail="You must use one of the subcommands: !view, !retire, !start, !alert"
+    )