From 923376d863f34be7b436061eb50468f4d5378226 Mon Sep 17 00:00:00 2001 From: Nick Walton Date: Sun, 1 Dec 2019 19:00:01 -0700 Subject: [PATCH] Develop (#9) Adds saving and loading. Changes cmd line interface and adds new commands. --- play.py | 104 +++++++++++++++++++++++++++-------------- story/story_data.yaml | 2 +- story/story_manager.py | 52 +++++++++++++++++++-- story/utils.py | 16 +++---- 4 files changed, 126 insertions(+), 48 deletions(-) diff --git a/play.py b/play.py index 89be297..8cb98bf 100644 --- a/play.py +++ b/play.py @@ -18,7 +18,7 @@ def select_game(): else: print_str += " (experimental)" console_print(print_str) - console_print(str(len(settings)) + ") custom (for advanced players)") + console_print(str(len(settings)) + ") custom expiermental") choice = get_num_options(len(settings)+1) if choice == len(settings): @@ -52,19 +52,25 @@ def select_game(): def instructions(): text = "\nAI Dungeon 2 Instructions:" - text += '\n* Enter actions starting with a verb ex. "go to the tavern" or "attack the orc."' - text += '\n* To speak enter \'say "(thing you want to say)"\' or just "(thing you want to say)" ' - text += '\n* Enter "revert" for any action if you want to undo the last action and result.' - text += '\n* Finally if you want to end your game and start a new one just enter "restart" for any action. ' + text += '\n Enter actions starting with a verb ex. "go to the tavern" or "attack the orc."' + text += '\n To speak enter \'say "(thing you want to say)"\' or just "(thing you want to say)" ' + text += '\n\nThe following commands can be entered for any action: ' + text += '\n "revert" Reverts the last action allowing you to pick a different action.' + text += '\n "quit" Quits the game and saves' + text += '\n "restart" Starts a new game and saves your current one' + text += '\n "save" Makes a new save of your game and gives you the save ID' + text += '\n "load" Asks for a save ID and loads the game if the ID is valid' + text += '\n "print" Prints a transcript of your adventure (without extra newline formatting)' + text += '\n "help" Prints these instructions again' return text def play_aidungeon_2(): - save_story = input("Help AIDungeon by letting us store your adventure to improve the model? (Y/n) ") - if save_story.lower() in ["no", "No", "n"]: - upload_story = False - else: - upload_story = True + console_print("AI Dungeon 2 will save and use your actions and game to continually improve AI Dungeon." + + " If you would like to disable this enter 'nosaving' for any action. This will also turn off the " + + "ability to save games.") + + upload_story = True print("\nInitializing AI Dungeon! (This might take a few minutes)\n") generator = GPT2Generator() @@ -93,8 +99,36 @@ def play_aidungeon_2(): action = input("> ") if action == "restart": break + elif action == "quit": exit() + + elif action == "nosaving": + upload_story = False + story_manager.story.upload_story = False + console_print("Saving turned off.") + + elif action == "help": + console_print(instructions()) + + elif action == "save": + if upload_story: + id = story_manager.story.save_to_storage() + console_print("Game saved.") + console_print("To load the game, type 'load' and enter the following ID: " + id) + else: + console_print("Saving has been turned off. Cannot save.") + + elif action =="load": + load_ID = input("What is the ID of the saved game?") + result = story_manager.story.load_from_storage(load_ID) + console_print("\nLoading Game...\n") + console_print(result) + + elif action == "print": + print("\nPRINTING\n") + print(str(story_manager.story)) + elif action == "revert": if len(story_manager.story.actions) is 0: @@ -109,40 +143,42 @@ def play_aidungeon_2(): else: console_print(story_manager.story.story_start) continue - elif action == "": - action = "" - - elif action[0] == '"': - action = "You say " + action else: - action = action.strip() - action = action[0].lower() + action[1:] + if action == "": + action = "" + result = story_manager.act(action) + console_print(result) - action = first_to_second_person(action) + elif action[0] == '"': + action = "You say " + action - if "You" not in action: - action = "You " + action + else: + action = action.strip() + action = action[0].lower() + action[1:] - if action[-1] not in [".", "?", "!"]: - action = action + "." + if "You" not in action[:6] and "I" not in action[:6]: + action = "You " + action - action = "\n> " + action + "\n" + if action[-1] not in [".", "?", "!"]: + action = action + "." - result = "\n" + story_manager.act(action) + action = first_to_second_person(action) - if player_won(result): - console_print(result + "\n CONGRATS YOU WIN") - break - elif player_died(result): - console_print(result) - died = input("Did you die? (y/N)") - if died.lower() in ["yes", "y"]: + action = "\n> " + action + "\n" + + result = "\n" + story_manager.act(action) + + if player_won(result): + console_print(result + "\n CONGRATS YOU WIN") + break + elif player_died(result): + console_print(result) console_print("YOU DIED. GAME OVER") break - - else: - console_print(result) + + else: + console_print(result) if __name__ == '__main__': diff --git a/story/story_data.yaml b/story/story_data.yaml index 45f9b35..27129e1 100644 --- a/story/story_data.yaml +++ b/story/story_data.yaml @@ -11,7 +11,7 @@ settings: item2: "small dagger" knight: - prompts: ["You enter the forest where you believe the ogre that has been terrorizing your home has been hiding. You step inside and"] + prompts: ["You are on a quest to defeat the evil dragon of Larion. You've heard he lives up at the north of the kingdom. You set on the path to defeat him and walk into a dark forest. As you enter the forest you see"] item1: "steel longsword" item2: "wooden shield" diff --git a/story/story_manager.py b/story/story_manager.py index 11a1dc4..f1bb386 100644 --- a/story/story_manager.py +++ b/story/story_manager.py @@ -23,7 +23,7 @@ class Story(): self.seed = seed self.choices = [] self.possible_action_results = None - self.uuid = str(uuid.uuid1()) + self.uuid = None if game_state is None: game_state = dict() @@ -40,8 +40,7 @@ class Story(): except: pass - def initialize_from_json(self, json_string): - story_dict = json.loads(json_string) + def init_from_dict(self, story_dict): self.story_start = story_dict["story_start"] self.seed = story_dict["seed"] self.actions = story_dict["actions"] @@ -57,6 +56,11 @@ class Story(): else: self.rating = -1 + + def initialize_from_json(self, json_string): + story_dict = json.loads(json_string) + self.init_from_dict(story_dict) + def add_to_story(self, action, story_block): self.actions.append(action) self.results.append(story_block) @@ -81,8 +85,8 @@ class Story(): def __str__(self): story_list = [self.story_start] for i in range(len(self.results)): - story_list.append(self.actions[i]) - story_list.append(self.results[i]) + story_list.append("\n> " + self.actions[i] + "\n") + story_list.append("\n" + self.results[i]) return "".join(story_list) @@ -101,7 +105,26 @@ class Story(): return json.dumps(story_dict) + def save_to_local(self, save_name): + self.uuid = str(uuid.uuid1()) + story_json = self.to_json() + file_name = "AIDungeonSave_" + save_name + ".json" + f = open(file_name, "w") + f.write(story_json) + f.close() + + def load_from_local(self, save_name): + file_name = "AIDungeonSave_" + save_name + ".json" + print("Save ID that can be used to load game is: ", self.uuid) + + with open(file_name, 'r') as fp: + game = json.load(fp) + self.init_from_dict(game) + def save_to_storage(self): + self.uuid = str(uuid.uuid1()) + + story_json = self.to_json() file_name = "story" + str(self.uuid) + ".json" f = open(file_name, "w") @@ -110,6 +133,25 @@ class Story(): FNULL = open(os.devnull, 'w') p = Popen(['gsutil', 'cp', file_name, 'gs://aidungeonstories'], stdout=FNULL, stderr=subprocess.STDOUT) + return self.uuid + + def load_from_storage(self, story_id): + + file_name = "story" + story_id + ".json" + cmd = "gsutil cp gs://aidungeonstories/" + file_name + " . >/dev/null 2>&1" + os.system(cmd) + exists = os.path.isfile(file_name) + + with open(file_name, 'r') as fp: + game = json.load(fp) + self.init_from_dict(game) + + if exists: + return str(self) + else: + return "Error save not found." + + class StoryManager(): diff --git a/story/utils.py b/story/utils.py index e936512..c0b40c6 100644 --- a/story/utils.py +++ b/story/utils.py @@ -37,16 +37,16 @@ def get_num_options(num): def player_died(text): - reg_phrases = ["You[a-zA-Z ]* die.", "you[a-zA-Z ]* die.", "You[a-zA-Z ]* die ", "you[a-zA-Z ]* die ",] - - for phrase in reg_phrases: - reg_expr = re.compile(phrase) - matches = re.findall(reg_expr, text) - if len(matches) > 0: - return True + # reg_phrases = ["You[a-zA-Z ]* die.", "you[a-zA-Z ]* die.", "You[a-zA-Z ]* die ", "you[a-zA-Z ]* die ",] + # + # for phrase in reg_phrases: + # reg_expr = re.compile(phrase) + # matches = re.findall(reg_expr, text) + # if len(matches) > 0: + # return True dead_phrases = ["you die", "You die", "you died", "you are dead", "You died", "You are dead", "You're dead", - "you're dead", "you have died", "You have died", "finish you off", "Your death", "your death"] + "you're dead", "you have died", "You have died", "you bleed out"] for phrase in dead_phrases: if phrase in text: return True