Parcourir la source

First pass at hangguy logic

Kirk Trombley il y a 6 ans
Parent
commit
c1b9bcfe20
1 fichiers modifiés avec 221 ajouts et 1 suppressions
  1. 221 1
      src/plugins/hangguy.py

+ 221 - 1
src/plugins/hangguy.py

@@ -1,6 +1,226 @@
-from command_system import as_plugin, RollbotResponse
+import datettime
+import re
+
+from sqlalchemy import Column, Integer, String, DateTime
+
+from command_system import as_plugin, RollbotResponse, RollbotFailure, ModelBase
+
+
+class GameState(ModelBase):
+    __tablename__ = "hangguy_game_state"
+    group_id = Column(String, primary_key=True)
+    target = Column(String)
+    state = Column(String)
+    bad_guesses = Column(String)
+
+
+class GuyState(ModelBase):
+    __tablename__ = "hangguy_guy_state"
+    group_id = Column(String, primary_key=True)
+    state = Column(Integer)
+    games = Column(Integer)
+
+
+class DeadGuy(ModelBase):
+    """
+    Tracks "dead" and "retired" guys for record-keeping purposes.
+    If state >= 17, the guy was dead, otherwise it was retired.
+    """
+    __tablename__ = "hangguy_dead_guy"
+    guy_id = Column(Integer, primary_key=True, autoincrement=True)
+    group_id = Column(String)
+    state = Column(Integer)
+    games = Column(Integer)
+    timestamp = Column(DateTime)
+
+    @staticmethod
+    def from_guy(guy):
+        return DeadGuy(
+            group_id=guy.group_id,
+            state=guy.state,
+            games=guy.games,
+            timestamp=datetime.datetime.now():
+        )
+
+
+def get_game(db, group_id):
+    game_state = db.query(GameState).get(group_id)
+    if game_state is None:
+        game_state = GameState(
+            group_id=group_id,
+            target=None,
+            state=None,
+            bad_guesses=""
+        )
+        db.add(game_state)
+    return game_state
+
+
+def get_guy(db, group_id):
+    guy_state = db.query(GuyState).get(group_id)
+    if guy_state is None:
+        guy_state = GuyState(
+            group_id=group_id,
+            state=0,
+            games=0
+        )
+        db.add(guy_state)
+    return guy_state
+
+
+def init_game(game_state):
+    new_phrase = "TEST PHRASE" # TODO this needs to actually pull from something (and sanitize it) eventually
+
+    game_state.state = re.sub("[A-Z]", "_", new_phrase)
+    game_state.target = new_phrase
+    game_state.bad_guesses = ""
+
+
+def end_game(game_state):
+    game_state.target = None
+
+
+def game_is_active(game_state):
+    return game_state.target is not None
+
+
+def game_is_finished(game_state):
+    return game_state.target == game_state.state
+
+
+def guess_letter(game_state, letter):
+    """
+    Guess a letter in a given game state. Assumes letter is already
+    a single, uppercased, alpha character.
+
+    Returns number of appearances of the letter, or -1 if it was
+    already guessed.
+    """
+
+    if letter in game_state.bad_guesses or letter in game_state.state:
+        return -1
+
+    find = [i for i, x in game_state.target if x == letter]
+    if len(find) == 0:
+        game_state.bad_guesses = game_state.bad_guesses + letter
+        return 0
+
+    game_state.state = "".join(letter if i in find else s for i, s in enumerate(game_state.state))
+    return len(find)
+
+
+def end_guy(db, guy_state):
+    db.add(DeadGuy.from_guy(guy_state))
+    guy_state.state = 0
+    guy_state.games = 0
+
+
+def guy_is_dead(guy_state):
+    return guy_state.state >= 17
+
+
+def survival_msg(guy_state):
+    return f"survived {guy_state.games} game{'' if guy_state.games == 1 else 's'}"
+
+
+def render_guy(guy_state):
+    return str(guy_state.state) # TODO actually render guy
+
+
+def render_game(game_state, guy_state):
+    return render_guy(guy_state) + "\n" + \
+        " ".join(game_state.state) + "\n" + \
+        "Bad Guesses: " + ", ".join(game_state.bad_guesses)
+
+
+def handle_in_game(db, msg, game_state, guy_state):
+    if msg.raw_args.startswith("!"):
+        subc = msg.raw_args[1:].strip().lower()
+        if subc == "cancel":
+            end_game(game_state)
+            return RollbotResponse(msg, txt="The game has been cancelled. The guy has not been reset.")
+        else:
+            return RollbotResponse(
+                msg,
+                failure=RollbotFailure.INVALID_SUBCOMMAND,
+                debugging=dict(
+                    explain="The only in-game subcommand is !cancel to end the game"
+                )
+            )
+
+    guess = msg.raw_args.upper()
+    prefix = ""
+    if len(guess) == 1:
+        res = guess_letter(game_state, guess)
+        if res == -1:
+            prefix += f"You've already guessed '{guess}'!"
+        elif res == 0:
+            prefix += "Bad guess!"
+        else:
+            prefix += f"Great! '{guess}' appears {res} time{'' if res == 1 else 's'}!"
+    else:
+        prefix = "Full phrase guessing not yet supported!"
+        # TODO guess and handle the result
+
+    txt = prefix + "\n" + render_game(game_state, guy_state)
+
+    if game_is_finished(game_state):
+        txt += "\nThe game is over, and the guy lives on!"
+        end_game(game_state)
+        guy_state.games = guy_state.games + 1
+        txt += "\nThis guy has now {survival_msg(guy_state)}"
+
+    if guy_is_dead(guy_state):
+        txt += "\noh god oh fuck, the guy is dead!"
+        end_game(game_state)
+        end_guy(db, guy_state)
+        txt += "\nThe guy has been buried."
+
+    return RollbotResponse(msg, txt=txt)
+
+
+def subc_retire(db, guy_state):
+    txt = f"Retiring the previous guy, who {survival_msg(guy_state)}\n"
+    txt += render_guy(guy_state)
+    end_guy(db, guy_state)
+    return txt
 
 
 @as_plugin("hangguy")
 def hangguy(db, msg):
+    game_state = get_game(db, msg.group_id)
+    guy_state = get_guy(db, msg.group_id)
+
+    if game_is_active(game_state):
+        return handle_in_game(db, msg, game_state, guy_state)
+
+    if not msg.raw_args.startswith("!"):
+        return RollbotResponse(
+            msg,
+            failure=RollbotFailure.MISSING_SUBCOMMAND,
+            debugging=dict(
+                explain="You are not currently in a game, and so you must use one of the subcommands: !view, !retire, !start"
+            )
+        )
+
+    subc = msg.raw_args[1:].strip().lower()
+
+    if msg.raw_args == "view":
+        txt = f"The current guy has {survival_msg(guy_state)}\n"
+        txt += render_guy(guy_state)
+        return RollbotResponse(msg, txt=txt)
+    elif msg.raw_args == "retire":
+        return RollbotResponse(msg, txt=subc_retire(db, guy_state))
+    elif msg.raw_args == "start"
+        init_game(game_state)
+        return RollbotResponse(msg, txt=render_game(game_state, guy_state))
+    else:
+        return RollbotResponse(
+            msg,
+            failure=RollbotFailure.INVALID_SUBCOMMAND,
+            debugging=dict(
+                explain="You must use one of the subcommands: !view, !retire, !start"
+            )
+        )
+
     return RollbotResponse(msg, txt="My first plugin!")