diff --git a/README.md b/README.md index 6c38a87..c44bcc8 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # python-text-adventure + +A story program. \ No newline at end of file diff --git a/story.json b/story.json index 35c2ad9..3ef4959 100644 --- a/story.json +++ b/story.json @@ -1 +1 @@ -{"progress": [{"id": 2, "player": "umbrella", "lastPlayed": "2021-01-02 22:09:06.027832", "position": 2, "state": {}}, {"id": 3, "player": "jntowell", "lastPlayed": "2021-01-02 22:42:36.478003", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 4, "player": "rex", "lastPlayed": "2021-01-02 22:22:25.713141", "position": 4, "state": {"inventory": ["potion"]}}, {"id": 5, "player": "frog", "lastPlayed": "2021-01-02 22:52:08.063381", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 6, "player": "rudolph", "lastPlayed": "2021-01-02 22:54:23.407919", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 7, "player": "jj", "lastPlayed": "2021-01-02 22:55:13.186008", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 8, "player": "rr", "lastPlayed": "2021-01-02 22:55:51.925230", "position": 0, "state": {"inventory": ["potion"]}}, {"id": 9, "player": "gerdrude", "lastPlayed": "2021-01-02 22:56:23.939869", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 10, "player": "fr", "lastPlayed": "2021-01-02 22:59:39.238532", "position": 0, "state": {"health": -20}}, {"id": 11, "player": "efer", "lastPlayed": "2021-01-02 23:01:10.638514", "position": 4, "state": {"inventory": ["potion"]}}, {"id": 12, "player": "fg", "lastPlayed": "2021-01-02 23:02:18.107944", "position": 5, "state": {"inventory": ["potion"], "health": -20}}, {"id": 13, "player": "ert", "lastPlayed": "2021-01-02 23:03:31.083821", "position": 5, "state": {"inventory": ["potion"], "health": -20}}, {"id": 14, "player": "dfs", "lastPlayed": "2021-01-02 23:04:34.797636", "position": 5, "state": {"inventory": ["potion"], "health": -20}}], "content": [{"id": 1, "text": "You are in the woods.", "options": [{"id": 1, "text": "Go east", "nextText": 2}, {"id": 2, "text": "Go west", "nextText": 3}]}, {"id": 2, "text": "You went east. There is a potion!", "options": [{"id": 1, "text": "Collect potion", "setState": {"newInventory": "potion"}, "nextText": 4}, {"id": 2, "text": "Leave potion", "nextText": 4}]}, {"id": 3, "text": "You went west. There is a goblin!", "options": [{"id": 1, "text": "Fight the goblin", "nextText": 4}, {"id": 2, "text": "Run away", "nextText": 4}]}, {"id": 4, "text": "There's a wizard. He wants something from you!", "options": [{"id": 1, "text": "Give potion", "setState": {"removeInventory": "potion"}, "requiredState": {"inventory": "potion"}, "nextText": 5}, {"id": 2, "text": "Fight him", "setState": {"health": -20}, "nextText": 5}, {"id": 3, "text": "Run away", "nextText": 5}]}, {"id": 5, "text": "You find a haunted house.", "options": [{"id": 1, "text": "Go straight in"}, {"id": 2, "text": "Ask if anyone is home"}, {"id": 3, "text": "Continue on path", "setState": {"endGame": "true"}}]}]} \ No newline at end of file +{"progress": [{"id": 2, "player": "umbrella", "lastPlayed": "2021-01-02 22:09:06.027832", "position": 2, "state": {}}, {"id": 3, "player": "jntowell", "lastPlayed": "2021-01-02 22:42:36.478003", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 4, "player": "rex", "lastPlayed": "2021-01-02 22:22:25.713141", "position": 4, "state": {"inventory": ["potion"]}}, {"id": 5, "player": "frog", "lastPlayed": "2021-01-02 22:52:08.063381", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 6, "player": "rudolph", "lastPlayed": "2021-01-02 22:54:23.407919", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 7, "player": "jj", "lastPlayed": "2021-01-02 22:55:13.186008", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 8, "player": "rr", "lastPlayed": "2021-01-02 22:55:51.925230", "position": 0, "state": {"inventory": ["potion"]}}, {"id": 9, "player": "gerdrude", "lastPlayed": "2021-01-02 22:56:23.939869", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 10, "player": "fr", "lastPlayed": "2021-01-02 22:59:39.238532", "position": 0, "state": {"health": -20}}, {"id": 11, "player": "efer", "lastPlayed": "2021-01-02 23:01:10.638514", "position": 4, "state": {"inventory": ["potion"]}}, {"id": 12, "player": "fg", "lastPlayed": "2021-01-02 23:02:18.107944", "position": 5, "state": {"inventory": ["potion"], "health": -20}}, {"id": 13, "player": "ert", "lastPlayed": "2021-01-02 23:03:31.083821", "position": 5, "state": {"inventory": ["potion"], "health": -20}}, {"id": 14, "player": "dfs", "lastPlayed": "2021-01-02 23:04:34.797636", "position": 5, "state": {"inventory": ["potion"], "health": -20}}, {"id": 15, "player": "freddy", "lastPlayed": "2021-01-14 23:10:56.713022", "position": 0, "state": {"inventory": ["potion"], "health": -20}}, {"id": 16, "player": "ready", "lastPlayed": "2021-03-08 00:02:23.069947", "position": 0, "state": {}}, {"id": 17, "player": "georgr", "lastPlayed": "2021-03-08 00:03:15.170369", "position": 0, "state": {"inventory": []}}, {"id": 18, "player": "george", "lastPlayed": "2022-01-04 18:44:45.397536", "position": 0, "state": {"inventory": ["potion"], "health": -20}}], "content": [{"id": 1, "text": "You are in the woods.", "options": [{"id": 1, "text": "Go east", "nextText": 2}, {"id": 2, "text": "Go west", "nextText": 3}]}, {"id": 2, "text": "You went east. There is a potion!", "options": [{"id": 1, "text": "Collect potion", "setState": {"newInventory": "potion"}, "nextText": 4}, {"id": 2, "text": "Leave potion", "nextText": 4}]}, {"id": 3, "text": "You went west. There is a goblin!", "options": [{"id": 1, "text": "Fight the goblin", "nextText": 4}, {"id": 2, "text": "Run away", "nextText": 4}]}, {"id": 4, "text": "There's a wizard. He wants something from you!", "options": [{"id": 1, "text": "Give potion", "setState": {"removeInventory": "potion"}, "requiredState": {"inventory": "potion"}, "nextText": 5}, {"id": 2, "text": "Fight him", "setState": {"health": -20}, "nextText": 5}, {"id": 3, "text": "Run away", "nextText": 5}]}, {"id": 5, "text": "You find a haunted house.", "options": [{"id": 1, "text": "Go straight in"}, {"id": 2, "text": "Ask if anyone is home"}, {"id": 3, "text": "Continue on path", "setState": {"endGame": "true"}}]}]} \ No newline at end of file diff --git a/textAdventure.py b/textAdventure.py index 1e85d45..ac52cd8 100644 --- a/textAdventure.py +++ b/textAdventure.py @@ -4,6 +4,10 @@ import datetime as dt import traceback ## Allows visual of errors -> traceback.print_exc() +##=========## +## SETUP ## +##=========## + ## Name of file containing progress and content FILENAME = "" @@ -26,12 +30,16 @@ ## Menu option lists MAIN_LIST = ["Play a story", "Write a story", "Manage players", "Quit"] -WRITE_LIST = ["Create a new story", "Load an existing story", "Back"] +WRITE_LIST = ["Create a new story", "Create a story from an existing text", "Load an existing story", "Back"] LOAD_LIST = ["Load default story", "Load from a known file", "Back"] PROGRESS_LIST = ["Load progress using player name", "Start a new game", "Back"] MULTISAVE_LIST = ["Overwrite existing progress", "Create a new progress save", "Re-enter player name"] MANAGE_LIST = ["View player saves", "Delete a player save", "Delete all player saves", "Back"] +##====================## +## GLOBAL FUNCTIONS ## +##====================## + def camelCaseSplit(string): ## Seperates camel case words into sentence case string return re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', string) @@ -58,6 +66,51 @@ def sPrint(text): time.sleep(duration) print("") +def createBlankLong(length, char = ' '): + blank = "" + for i in range(length): + blank += char + return blank + +def showMenu(menuName, menuList, subText = "", subList = []): + global PLAYER + nPrint(2) + sPrint("===| " + menuName + " |===") + if (len(PLAYER) > 0): + sPrint("Player: " + PLAYER) + if (len(subText) > 0): + sPrint(subText) + if (len(subList) > 0): + nPrint(1) + for i in subList: + if (i == "Player Progression"): + sPrint(i + ":") + longest = 0 + for p in subList.get(i): + if (len(p[0]) > longest): + longest = len(p[0]) + for p in subList.get(i): + sPrint(" " + p[0] + createBlankLong(longest + 3 - len(p[0])) + p[1]) + else: + sPrint(i + ": " + str(subList[i])) + nPrint(1) + if (len(menuList) > 0): + sPrint("Please select an option:") + ordinal = 1 + for o in menuList: + if (o == "Quit"): + prefix = "q" + elif (o == "Back"): + prefix = "b" + else: + prefix = str(ordinal) + sPrint("[{}] {}".format(prefix, o)) + ordinal += 1 + +##==================## +## GAME FUNCTIONS ## +##==================## + def showPlayerState(): global PLAYER global STATE @@ -158,7 +211,7 @@ def updateState(opt): STATE[req] = optState.get(req) return '' -def getOption(options, answer): +def getChosenOption(options, answer): ordinal = 0 for opt in options: optReq = opt.get('requiredState') @@ -168,7 +221,7 @@ def getOption(options, answer): if (answer == str(ordinal)): return opt -def loadPos(pos): +def getPos(pos): global CONTENT for obj in CONTENT: if (pos == obj.get('id')): @@ -188,7 +241,7 @@ def startGame(): showPlayerState() ## Load current object - obj = loadPos(HEAD_POS) + obj = getPos(HEAD_POS) objText = obj.get('text') objOpt = obj.get('options') @@ -199,7 +252,7 @@ def startGame(): while (nextText < 1 and answer != 'q'): showOptions(objOpt) answer = getInput() - chosenOption = getOption(objOpt, answer) + chosenOption = getChosenOption(objOpt, answer) if (chosenOption and answer != 'q'): nextText = getNextText(chosenOption) HEAD_POS = nextText @@ -216,14 +269,6 @@ def startGame(): sPrint("Error loading position. Restarting sequence.") nPrint(1) sPrint("|>>> END OF GAME <<<|") - -def createNewPlayer(players): - global PLAYER - newId = 1 - for player in players: - if (player.get('id') > newId): - newId = player.get('id') - return {"id": newId + 1, "player": PLAYER, "lastPlayed": str(dt.datetime.now()), "position": HEAD_POS, "state": STATE} def saveProgress(): global FILENAME @@ -313,6 +358,18 @@ def saveProgress(): return False return False +##=========================## +## MANAGE GAME FUNCTIONS ## +##=========================## + +def createNewPlayer(players): + global PLAYER + newId = 1 + for player in players: + if (player.get('id') > newId): + newId = player.get('id') + return {"id": newId + 1, "player": PLAYER, "lastPlayed": str(dt.datetime.now()), "position": HEAD_POS, "state": STATE} + def importAllData(existFilename = ""): global FILENAME global LOAD_LIST @@ -578,8 +635,14 @@ def managePlayers(): sPrint("Your input was not recognised.") sPrint("Error detail (MANAGE1): " + str(e)) +##==========================## +## WRITING GAME FUNCTIONS ## +##==========================## + def saveStory(new = False): global FILENAME + global PROGRESS + global CONTENT try: if (not new): with open(FILENAME + '.json', 'r') as readFile: @@ -595,9 +658,9 @@ def saveStory(new = False): return False with open(FILENAME + '.json', 'w') as writeFile: if (new): - json.dump({"progress": [], "content": []}, writeFile) - else: - json.dump({"progress": PROGRESS, "content": CONTENT}, writeFile) + PROGRESS = [] + CONTENT = [] + json.dump({"progress": PROGRESS, "content": CONTENT}, writeFile) return True except (json.decoder.JSONDecodeError, FileNotFoundError) as e: nPrint(1) @@ -605,6 +668,133 @@ def saveStory(new = False): sPrint("Error detail (STORY1): " + str(e)) return False +def highestId(listObj = CONTENT): + global CONTENT + highestId = 0 + for i in listObj: + if (i.get('id') and i.get('id') > highestId): + highestId = i.get('id') + return highestId + +def addToPosition(newId = 0, newText = "", newOption = {}): + global CONTENT + position = {} + for i in CONTENT: + if (i.get('id') and newId > 0 and i.get('id') == newId): + position = i + break + if (len(position) < 1 and newId > 0): + position = {'id': newId, 'text':"", 'options':{}} + if (len(newText) < 1): + saveObj(position) + return True + else: + sPrint("Unable to save position ID.") + return False + if (len(newText) > 0): + position['text'] = newText + saveObj(position) + return True + else: + sPrint("Unable to save position text.") + return False + if (len(options) > 0): + position['options'].append(newOption) + saveObj(position) + return True + else: + sPrint("Unable to save position text.") + return False + saveObj(position) + return True + +def addToOption(posId, newId = 0, newText = "", newList = []): + option = {} + if (newId > 0): + option = {'id': newId, 'text':""} + if (len(newText) < 1): + saveObj(option, listObj = getPosObj(posId).get('options')) + return True + else: + sPrint("Unable to save option ID.") + return False + if (len(newText) > 0): + option['text'] = newText + saveObj(option, listObj = getPosObj(posId).get('options')) + return True + else: + sPrint("Unable to save option text.") + return False + saveObj(option, listObj = getPosObj(posId).get('options')) + return True + +def getPosObj(posId): + global CONTENT + for i in CONTENT: + if (i.get('id') and i.get('id') == posID): + return i + sPrint("Position could not be loaded.") + +def saveObj(obj, listObj = CONTENT): + global CONTENT + for i in listObj: + if (i.get('id') and i.get('id') == obj['id']): + listObj[i] = obj + return True + sPrint("Object could not be saved.") + return False + +def writePosition(newId = 0, newText = "", newOption = {}): + answer = '' + allValid = False + while (not allValid): + showMenu("Edit Menu", []) + try: + sPrint("Please enter the position ID (current highest ID is '{}')...".format(highestId())) + newId = int(getInput()) + if (addToPosition(newId = newId)): + sPrint("Please enter the position text (story and question)...") + newText = str(getInput()) + if (addToPosition(newId = newId, newText = newText)): + sPrint("Please enter the number of options you wish to add for this position...".format(highestId())) + optionCount = int(getInput()) + while (optionCount > 0): + posObj = getPosObj(newId) + if (posObj): + optId = highestId(listObj = len(posObj.get('options')) + 1) + if (addToOption(newId, newId = optId)): + sPrint("Please enter the option text...") + optText = str(getInput()) + if (addToOption(newId, newId = optId)): + if (addToPosition(newId = newId, newOption = newOption)): + optionCount -= 1 + except (TypeError, ValueError, IndexError) as e: + sPrint("Your input was not recognised.") + sPrint("Error detail (EDIT1): " + str(e)) + +##def positionMenu(): +## answer = '' +## while (not answer == 'b'): +## showMenu("Navigation Menu", WRITE_LIST) +## answer = getInput() +## try: +## answer = int(answer) +## if (WRITE_LIST[answer - 1] == "Create a new story"): +## sPrint("Please enter the file name for your story (no spaces)...") +## filename = getInput() +## FILENAME = filename +## if (not saveStory(True)): +## nPrint(1) +## sPrint("File was unable to be created.") +## elif (WRITE_LIST[answer - 1] == "Create a story from an existing text"): +## sPrint("This feature is still in development.") +## elif (WRITE_LIST[answer - 1] == "Load an existing story"): +## sPrint("This feature is still in development.") +## except (TypeError, ValueError, IndexError) as e: +## if (answer != 'b'): +## sPrint("Your input was not recognised.") +## sPrint("Error detail (WRITE1): " + str(e)) + def writeStory(): global FILENAME answer = '' @@ -620,53 +810,20 @@ def writeStory(): if (not saveStory(True)): nPrint(1) sPrint("File was unable to be created.") +## while (): ## HELP + writePosition() + elif (WRITE_LIST[answer - 1] == "Create a story from an existing text"): + sPrint("This feature is still in development.") elif (WRITE_LIST[answer - 1] == "Load an existing story"): sPrint("This feature is still in development.") except (TypeError, ValueError, IndexError) as e: if (answer != 'b'): sPrint("Your input was not recognised.") sPrint("Error detail (WRITE1): " + str(e)) - -def createBlankLong(length, char = ' '): - blank = "" - for i in range(length): - blank += char - return blank - -def showMenu(menuName, menuList, subText = "", subList = []): - global PLAYER - nPrint(2) - sPrint("===| " + menuName + " |===") - if (len(PLAYER) > 0): - sPrint("Player: " + PLAYER) - if (len(subText) > 0): - sPrint(subText) - if (len(subList) > 0): - nPrint(1) - for i in subList: - if (i == "Player Progression"): - sPrint(i + ":") - longest = 0 - for p in subList.get(i): - if (len(p[0]) > longest): - longest = len(p[0]) - for p in subList.get(i): - sPrint(" " + p[0] + createBlankLong(longest + 3 - len(p[0])) + p[1]) - else: - sPrint(i + ": " + str(subList[i])) - nPrint(1) - if (len(menuList) > 0): - sPrint("Please select an option:") - ordinal = 1 - for o in menuList: - if (o == "Quit"): - prefix = "q" - elif (o == "Back"): - prefix = "b" - else: - prefix = str(ordinal) - sPrint("[{}] {}".format(prefix, o)) - ordinal += 1 + +##==================## +## MAIN FUNCTIONS ## +##==================## def mainMenu(): global MAIN_LIST