diff --git a/README.MD b/README.MD index 1db39e4..4df7998 100644 --- a/README.MD +++ b/README.MD @@ -5,38 +5,37 @@ In diesem Dokument sammeln wir Ideen zu unserem Stream Mini-Projekt. ### Ideen - Game Design - Inventar - - Items aufheben - - Items benutzen - - (Items wegwerfen) - - Keine Begrezung beim Inventarplatz - - (Item Kategorien / getrennte Beutel) - - (Suchfunktion) - - Kombinieren (von zwei Items) - - Inspizieren von Items (z.B. `inspect `) + - [x] Items aufheben + - [x] Items benutzen + - [ ] (Items wegwerfen) + - [x] Keine Begrezung beim Inventarplatz + - [ ] (Item Kategorien / getrennte Beutel) + - [ ] (Suchfunktion) + - [ ] Kombinieren (von zwei Items) + - [x] Inspizieren von Items (z.B. `inspect `) - Navigation - - Objektbezogene Navigation (`enter `) - - Informationen ueber den jeweiligen Raum (`look around`) - - Sagt einem nur, welche Dinge offensichtlich in einem Raum erkennbar sind - - Beim Betreten eines Raumes, bekommt der Spieler direkt Informationen darueber, welche Dinge im Raum offensichtlich vorhanden sind (z.B. eine Tuer, oder ein absurd grosser Schlumpf) - - Um verstecktere bzw. weniger offensichtliche Dinge finden zu koennen, muss man die Umgebung genauer betrachten (z.B. `look at table`, um ein Feuerzeug zu finden) + - [x] Objektbezogene Navigation (`enter `) + - [x] Informationen ueber den jeweiligen Raum (`look around`) + - [x] Sagt einem nur, welche Dinge offensichtlich in einem Raum erkennbar sind + - [ ] Beim Betreten eines Raumes, bekommt der Spieler direkt Informationen darueber, welche Dinge im Raum offensichtlich vorhanden sind (z.B. eine Tuer, oder ein absurd grosser Schlumpf) + - [ ] Um verstecktere bzw. weniger offensichtliche Dinge finden zu koennen, muss man die Umgebung genauer betrachten (z.B. `look at table`, um ein Feuerzeug zu finden) - Interaktion - - Objekte anschauen mit `look at ` - - Gibt dem Spieler auch mehr Informationen, als `look around` - - Objekte nehmen mit `take ` - - Objekte benutzen mit `use ` + - [x] Objekte anschauen mit `look at ` + - [x] Gibt dem Spieler auch mehr Informationen, als `look around` + - [x] Objekte nehmen mit `take ` + - [x] Objekte benutzen mit `use ` ### Ideen - Level Design -- Raeume befinden sich in JSON Spieldaten -- Jeder Raum hat mindestens eine Tuer zu einem weiteren Raum +- [x] Raeume befinden sich in JSON Spieldaten +- [x] Jeder Raum hat mindestens eine Tuer zu einem weiteren Raum - Ausnahme 1: Ein abgezweigter Raum, aus dem man aber zurueck kommt - Ausnahme 2: Der erste und letzte Raum im Spiel - - Jede Tuer hat die Information darueber, in welcher Himmelsrichtung sie liegt -- Raeume koennen beleuchtet sein, oder Beleuchtung erfordern -- Manche Tueren koennen einen passenden Schluessel erfordern -- Manche Kisten koennen einen passenden Schluessel erfordern +- [x] Raeume koennen beleuchtet sein, oder Beleuchtung erfordern +- [ ] Manche Tueren koennen einen passenden Schluessel erfordern +- [ ] Manche Kisten koennen einen passenden Schluessel erfordern ### Ideen - Story / Lore diff --git a/data.json b/data.json index 494d213..4e83e88 100644 --- a/data.json +++ b/data.json @@ -1,10 +1,118 @@ { + "items": { + "matches": { + "name": "Streichholzpackung", + "description": "Eine Packung mit Streichhoelzern, leider ist aber nur noch ein einziges uebrig." + }, + "key": { + "name": "Ein rostiger, alter Schluessel", + "description": "Wo der wohl passen koennte?" + } + }, "rooms": { "entrance": { "name": "Haupthalle", "is_start": true, "enter_text": "Du betrittst die Haupthalle. Nebst Spinnenweben und Staub findet sich jede Menge kaputter Moebel.", - "interactables": [], + "interactables": [ + { + "name": "eine Streichholzpackung", + "identifier": [ "matches", "streichhoelzer", "streichholzpackung" ], + "visible": true, + "requirements": [], + "interactions": + { + "use": + { + "failed": + { + "action": null, + "text": "Vielleicht sollte ich sie erstmal mitnehmen, bevor ich versuche damit irgendwas zu tun?" + } + }, + "take": + { + "success": + { + "action": "add_item matches", + "text": "Die nehme ich mal mit ... vielleicht will ich ja spontan was abfackeln" + } + }, + "look": + { + "success": + { + "action": null, + "text": "Streichhoelzer. Man streicht sie und sie sind aus Holz. Macht Sinn, oder?" + } + } + } + }, + { + "name": "eine Kerze", + "identifier": [ "kerze", "candle" ], + "visible": true, + "requirements": [ + { + "type": "item", + "id": "matches", + "actions": [ "use" ] + } + ], + "interactions": { + "use": { + "success": { + "action": "set_room_flag entrance lit_up", + "text": "Du hast die Kerze mit einem Streichholz entzuendet, es ist nun ein wenig heller im Raum" + }, + "failed": { + "action": null, + "text": "Du kannst die Kerze nicht einfach mit deinen Haenden anzuenden." + } + }, + "take": { + "failed": { + "action": null, + "text": "DU NICHT NEHMEN KERZE!" + } + }, + "look": + { + "success": { + "action": null, + "text": "Eine dicke Kerze, wie man sie in einer Kirche findet." + } + } + } + }, + { + "name": "ein Schluessel", + "identifier": [ "schluessel", "key" ], + "visible": "lit_up", + "requirements": [], + "interactions": { + "use": { + "failed": { + "action": null, + "text": "Vielleicht sollte ich diesen eventuell wichtigen Gegenstand ja erstmal mitnehmen ..." + } + }, + "take": { + "success": { + "action": "add_item key", + "text": "Den nehme ich mal lieber mit ..." + } + }, + "look": + { + "success": { + "action": null, + "text": "Ein rostiger, alter Schluessel - wo der wohl passt?" + } + } + } + } + ], "doors": [ "laundry" ] diff --git a/src/game.py b/src/game.py index f025e51..7142e81 100644 --- a/src/game.py +++ b/src/game.py @@ -6,45 +6,86 @@ from room import Room from interactable import Interactable class Game: - def __init__(self): - self.inventory = Inventory() - self.rooms = [] + def __init__(self) -> None: + self.is_running : bool = True + self.inventory : Inventory = None + self.game_data : dict = None + self.rooms : list[Room] = [] + self.current_room : Room = None - def load_game(self, file): + def load_game(self, file: str) -> None: try: with open(file, 'r') as f: raw_game_data = f.read() - game_data = json.loads(raw_game_data) + self.game_data = json.loads(raw_game_data) - if 'rooms' in game_data: - for room_data in game_data['rooms']: - g_room = Room(room_data['name']) - g_room.doors = room_data['doors'] + self.inventory = Inventory(self.game_data["items"]) + + if 'rooms' in self.game_data: + + for room_id in self.game_data['rooms']: + room = self.game_data['rooms'][room_id] + + g_room = Room(room_id, room['name']) + g_room.enter_text = room['enter_text'] + g_room.doors = room['doors'] g_room.interactables = [] - g_room.is_start = False + g_room.is_start = room['is_start'] if 'is_start' in room else False - if 'is_start' in room_data: + # Check if we had a starting room already + if g_room.is_start: for r in self.rooms: if r.is_start: print(f"Error: {g_room.name} is marked as the starting room, but {r.name} is already marked as the starting room.") os._exit(1) - - g_room.is_start = room_data['is_start'] - else: - g_room.is_start = False - if 'interactables' in room_data: - for interactable in room_data['interactables']: - i = Interactable(interactable['name'], interactable['text']) + if 'interactables' in room: + for interactable in room['interactables']: + i = Interactable(self, g_room, interactable) g_room.interactables.append(i) self.rooms.append(g_room) - + self.current_room = self.get_start_room() except FileNotFoundError: print("File not found.") - def get_rooms(self): - if not self.game_data: - return None + def get_rooms(self) -> list[Room]: + if not self.rooms or len(self.rooms) == 0: + return [] + else: + return self.rooms + def get_room(self, id : str) -> Room | None: + for room in self.rooms: + if room.id == id: + return room + + return None + + def get_start_room(self) -> Room | None: + for room in self.rooms: + if room.is_start: + return room + + return None + + def run_action(self, action : str) -> bool: + print(f"[DEBUG] run_action('{action}')") + + action_parts = action.split(' ') + action_cmd = action_parts[0] + action_args = action_parts[1:] if len(action_parts) >= 2 else [] + + if action_cmd == "set_room_flag": + room = self.get_room(action_args[0]) + if room is not None: + room.set_flag(action_args[1]) + else: + print(f"[DEBUG] Error: {action} (Room not found)") + + elif action_cmd == "add_item": + self.inventory.add_item(action_args[0]) + + else: + return False \ No newline at end of file diff --git a/src/interactable.py b/src/interactable.py index dc1b2a3..b3444dc 100644 --- a/src/interactable.py +++ b/src/interactable.py @@ -1,5 +1,102 @@ +#from game import Game +#from room import Room +from interactable_requirement import InteractableRequirement + class Interactable: - def __init__(self, name, text): - self.name = name - self.text = text - \ No newline at end of file + def __init__(self, game, room, data : dict): + self.game = game + self.room = room + self.data : dict = data + self.name : str = data["name"] + self.identifier : list[str] = data["identifier"] + self.visible : bool|str = data["visible"] + self.requirements : list[InteractableRequirement] = [] + + self.load_requirements() + + def load_requirements(self): + if "requirements" not in self.data: + return + + for raw_requirement in self.data["requirements"]: + requirement = InteractableRequirement() + requirement.interactable = self + requirement.type = raw_requirement["type"] + requirement.id = raw_requirement["id"] + requirement.actions = raw_requirement["actions"] + + self.requirements.append(requirement) + + def get_action_requirements(self, action : str) -> list[InteractableRequirement]: + matching_requirements : list[InteractableRequirement] = [] + + for requirement in self.requirements: + if action in requirement.actions: + matching_requirements.append(requirement) + + return matching_requirements + + def check_action_requirements(self, action : str, requirements : list[InteractableRequirement]) -> bool: + all_met = True + + for requirement in requirements: + if requirement.type != "item": + print(f"[WARN] Unsupported requirement type {requirement.type} in interactable {requirement.interactable}:{action}") + continue + + if requirement.type == "item": + if requirement.id not in self.game.inventory.get_items(): + all_met = False + + return all_met + + def can_see(self): + return self.visible == True or self.room.has_flag(self.visible) + + def run(self, action : str): + if not self.can_see(): + print(f"[{self.room.name}] Du kannst nicht genug sehen, um damit etwas zu tun") + return + + requirements = self.get_action_requirements(action) + + if len(requirements) == 0: + if "success" in self.data["interactions"][action]: + print(f"[{self.room.name}] {self.data['interactions'][action]['success']['text']}") + + if "action" in self.data["interactions"][action]["success"] and self.data["interactions"][action]["success"]["action"] != None: + self.game.run_action(self.data["interactions"][action]["success"]["action"]) + pass + + elif "failed" in self.data["interactions"][action]: + print(f"[{self.room.name}] {self.data['interactions'][action]['failed']['text']}") + + if "action" in self.data["interactions"][action]["failed"] and self.data["interactions"][action]["failed"]["action"] != None: + self.game.run_action(self.data["interactions"][action]["failed"]["action"]) + pass + else: + all_met = self.check_action_requirements(action, requirements) + + if not all_met: + print(f"[{self.room.name}] {self.data['interactions'][action]['failed']['text']}") + + if self.data["interactions"][action]["failed"]["action"] is not None: + self.game.run_action(self.data["interactions"][action]["failed"]["action"]) + else: + print(f"[{self.room.name}] {self.data['interactions'][action]['success']['text']}") + + if self.data["interactions"][action]["success"]["action"] is not None: + self.game.run_action(self.data["interactions"][action]["success"]["action"]) + + + def use(self): + self.run("use") + + def take(self): + self.run("take") + + def look(self): + self.run("look") + + def __str__(self) -> str: + return self.name \ No newline at end of file diff --git a/src/interactable_requirement.py b/src/interactable_requirement.py new file mode 100644 index 0000000..9c9fcf5 --- /dev/null +++ b/src/interactable_requirement.py @@ -0,0 +1,6 @@ +class InteractableRequirement: + def __init__(self): + self.interactable : str|None = None + self.type : str|None = None + self.id : str|None = None + self.actions : list[str] = [] \ No newline at end of file diff --git a/src/inventory.py b/src/inventory.py index f663e49..4381921 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -1,9 +1,17 @@ class Inventory: - def __init__(self): + def __init__(self, item_db : dict): self.items = [] + self.item_db = item_db def add_item(self, item): self.items.append(item) def get_items(self): - return self.items \ No newline at end of file + return self.items + + def get_item(self, name : str) -> None|dict: + lname = name.lower().strip() + if lname in self.item_db and lname in self.items: + return self.item_db[lname] + else: + return None \ No newline at end of file diff --git a/src/main.py b/src/main.py index eced2bb..537e0fa 100644 --- a/src/main.py +++ b/src/main.py @@ -3,6 +3,116 @@ from game import Game def main(): game = Game() game.load_game('data.json') + print() + + while game.is_running: + print(f"[{game.current_room.name}] {game.current_room.enter_text}") + + cmd = input("[Player] ") + cmd_parts = cmd.split(' ') + cmd_name = cmd_parts[0] + cmd_args = cmd_parts[1:] if len(cmd_parts) >= 2 else [] + + if cmd_name.lower().strip() == "quit": + game.is_running = False + + if cmd_name.lower().strip() == "look": + if len(cmd_args) >= 1 and cmd_args[0].lower().strip() == "around": + door_count = len(game.current_room.doors) + interactables_count = len(game.current_room.interactables) + + if door_count == 0: + print("[Player] In diesem Raum befinden sich keine Tueren.") + elif door_count == 1: + room = game.get_room(game.current_room.doors[0]) + print(f"[Player] In diesem Raum befindet sich eine Tuer zu {room.name}.") + else: + rooms = [] + for room_id in game.current_room.doors: + rooms.append(game.get_room(room_id).name) + + print(f"[Player] In diesem Raum befinden sich {door_count} Tueren zu {', '.join(rooms)}.") + + if interactables_count == 0: + print("[Player] Ansonsten kann ich hier nichts interessantes erkennen.") + else: + visible_interactables = [] + for i in game.current_room.interactables: + if i.can_see(): + visible_interactables.append(i.name) + + if len(visible_interactables) == 1: + print(f"[Player] Da ist doch {visible_interactables[0]}?") + elif len(visible_interactables) == 2: + print(f"[Player] Hier finden sich so allerlei Dinge, zum Beispiel {visible_interactables[0]} und {visible_interactables[1]}.") + elif len(visible_interactables) > 1: + interactables_text = ", ".join(visible_interactables) + print(f"[Player] Hier finden sich so allerlei Dinge, zum Beispiel {interactables_text}.") + + elif len(cmd_args) >= 2 and cmd_args[0].lower().strip() == "at": + at = cmd_args[1] + interactable = game.current_room.get_interactable(at) + + if interactable is None: + print(f"[Player] Ich kann hier kein {at} erkennen") + else: + interactable.look() + + else: + print(f"[Player] Wie soll ich denn dort hinschauen?") + + if cmd_name.lower().strip() == "take": + if len(cmd_args) == 1: + what = cmd_args[0] + interactable = game.current_room.get_interactable(what) + + if what is None: + print(f"[Player] Ich kann {what} nicht nehmen, da es nicht da ist!") + else: + interactable.take() + else: + print(f"[Player] Klar, ich nehme die Luft hier mal mit - sie wird mir sicher noch von Nutzen sein.") + + if cmd_name.lower().strip() == "use": + if len(cmd_args) == 1: + what = cmd_args[0] + interactable = game.current_room.get_interactable(what) + + if what is None: + print(f"[Player] Ich kann {what} nicht benutzen, da es nicht da ist!") + else: + interactable.use() + else: + print(f"[Player] Ich will mich nicht selbst benutzen ... das ist irgendwie falsch.") + + if cmd_name.lower().strip() == "inspect": + if len(cmd_args) == 1: + item_name = cmd_args[0].lower().strip() + item = game.inventory.get_item(item_name) + + if item is not None: + print(f"[Player] {item['name']}: {item['description']}") + else: + print(f"[Player] {item_name} habe ich nicht bei mir") + else: + print(f"[Player] Was soll ich mir denn anschauen?") + + if cmd_name.lower().strip() == "enter": + if len(cmd_args) == 1: + room_id = cmd_args[0].lower().strip() + room = game.get_room(room_id) + + if room is not None and room_id in game.current_room.doors: + print(f"[{game.current_room.name}] Du gehst durch die Tuer zum {room.name}") + game.current_room = room + else: + print(f"[Player] Hier gibt es keine Tuer dort hin ... vielleicht sollte ich ja einfach mit dem Kopf durch die Wand?") + else: + print(f"[Player] Wenn ich nicht weiss, wo ich rein soll, dann bleibe ich lieber einfach, wo ich bin.") + + + print() + print("Game has ended.") if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/room.py b/src/room.py index 71977a8..5b1d702 100644 --- a/src/room.py +++ b/src/room.py @@ -1,6 +1,37 @@ +from interactable import Interactable + class Room: - def __init__(self, name): - self.name = name - self.is_start = False - self.doors = [] - self.interactables = [] \ No newline at end of file + def __init__(self, id : str, name : str): + self.id : str = id + self.name : str = name + self.enter_text : str = "" + self.is_start : bool = False + self.doors : list[str] = [] + self.interactables : list[Interactable] = [] + self.flags : list = [] + + def set_flag(self, flag : str) -> None: + if flag not in self.flags: + self.flags.append(flag) + print(f"[DEBUG] set_flag({flag}) in {self.name}") + + def has_flag(self, flag : str) -> bool: + return flag in self.flags + + def get_interactable(self, name : str) -> Interactable|None: + for i in self.interactables: + if name.lower() in i.identifier: + return i + + return None + + def __str__(self): + r = f"[Room] ID: {self.id} | Name: {self.name} | IsStart: {self.is_start}" + + for door in self.doors: + r += f"\n\t- [Door] To: {door}" + + for interactable in self.interactables: + r += f"\n\t- [Interactable] Name: {interactable.name} | Text: {interactable.text}" + + return r \ No newline at end of file