Source code for storyforge.story

# storyforge/story.py

import json
import os
from typing import Optional

import pygame

IMAGE_DIR = "images/character"

[docs] class Story: """ Class responsible for manipulating the game's story. Attributes: - scenes (Dict[str, Dict[str, Union[str, List[Tuple[str, str]]]]]): A dictionary that contains the game's scenes. - choices (Dict[Tuple[str, str], str]): A dictionary that maps choices made by the player for upcoming scenes. - current_scene (str): The name of the current scene. - character (Dict[str, Optional[str]]): A dictionary that contains information about the character, such as name. - default_player_name (Dict[str, str]): A dictionary containing the player's default name. Methods: - save_state(): Saves the current state of the game in a JSON file. - load_state() -> bool: Loads the previously saved state, if it exists. - create_empty_state(): Creates an empty state to start the game. - set_character_name(name: str): Sets the character's name. - add_scene(name: str, text: str, image: Optional[str] = None, character_image: Optional[str] = None, character_speech: Optional[str] = None): Adds a new scene to the story. - add_choice(from_scene: str, text: str, to_scene: str): Adds a choice that the player can make in a given scene. - has_choices() -> bool: Checks if there are choices available in the current scene. - validate_story(): Validates the consistency of the story, looking for inaccessible scenes. - run(): Runs the main game loop, showing the scenes, available options and processing the player's choices. - show_scene(): Displays the current game scene to standard output. - show_choices(): Displays the options available to the player on standard output. - make_choice(player_choice: str): Processes the player's choice and advances to the next corresponding scene. """ def __init__(self, start_scene): self.scenes = {} self.choices = {} self.current_scene = start_scene self.character = {"name": None} self.character_name = None self.default_player_name = {"name": "BezerraC"}
[docs] def save_state(self) -> None: """ Saves the current game state to a JSON file. Return: None """ print("Saving state...") data = { "current_scene": self.current_scene, "character_name": self.character["name"] } with open("story_state.json", "w") as file: json.dump(data, file) print("Saved state.")
[docs] def load_state(self) -> bool: """ Loads the previously saved state, if it exists. Return: bool: True if the state was loaded successfully, False otherwise. """ try: with open("story_state.json", "r") as file: data = json.load(file) self.current_scene = data["current_scene"] if "character_name" in data: self.character["name"] = data["character_name"] return True except FileNotFoundError: return False
[docs] def create_empty_state(self) -> None: """ Creates an empty state to start the game. Return: None """ print("Creating empty state...") # Creates an empty initial state self.current_scene = "start" self.character["name"] = None # Call save_state to create story_state.json file self.save_state() print("Empty state created.")
[docs] def set_character_name(self, name: str) -> None: """ Sets the character's name. Parameters: - name (str): The character's name. Return: None """ self.character["name"] = name
[docs] def add_scene(self, name: str, text: str, image: Optional[str] = None, character_image: Optional[str] = None, character_speech: Optional[str] = None) -> None: """ Adds a new scene to the story. Parameters: - name (str): The name of the scene. - text (str): The text of the scene. - image (Optional[str]): The name of the scene's image file. - character_image (Optional[str]): The name of the character image file in the scene. - character_speech (Optional[str]): The character's speech in the scene. Return: None """ self.scenes[name] = {"text": text, "image": image, "character_image": character_image, "character_speech": character_speech, "choices": []}
[docs] def add_choice(self, from_scene: str, text: str, to_scene: str) -> None: """ Adds a choice the player can make in a given scene. Parameters: - from_scene (str): The name of the source scene of the choice. - text (str): The text of the choice. - to_scene (str): The name of the scene the choice leads to. Return: None """ self.choices[(from_scene, text)] = to_scene self.scenes[from_scene]["choices"].append((text, to_scene))
[docs] def has_choices(self) -> bool: """ Checks whether there are choices available in the current scene. Return: bool: True if choices are available, False otherwise. """ current_scene = self.scenes[self.current_scene] return bool(current_scene.get("choices"))
[docs] def validate_story(self) -> None: """ Validates the consistency of the story by looking for inaccessible scenes. Return: None """ inaccessible_scenes = set(self.scenes.keys()) - set(self.choices.values()) if self.current_scene in inaccessible_scenes: inaccessible_scenes.remove(self.current_scene) for scene_name in inaccessible_scenes: print(f"Warning: Inaccessible scene '{scene_name}'.")
[docs] def run(self) -> None: """ Runs the main game loop, showing the scenes, available options and processing the player's choices. Return: None """ # Loads the saved state, if it exists if not self.load_state(): # If it doesn't exist, it creates an empty state self.create_empty_state() while self.current_scene is not None: self.show_scene() self.show_choices() player_choice = input("Choose an option: ") self.make_choice(player_choice) # Call save_state before exiting the game self.save_state()
[docs] def show_scene(self) -> None: """ Displays the current game scene to standard output. Return: None """ scene = self.scenes[self.current_scene] print(scene["text"]) if scene["image"]: print("Showing background image:", scene["image"]) if scene["character_image"]: character_image = pygame.image.load(os.path.join(IMAGE_DIR, scene["character_image"])) character_rect = character_image.get_rect(center=(self.screen.get_width() // 2, self.screen.get_height() // 0.8)) self.screen.blit(character_image, character_rect) if scene["character_speech"]: print(f"{self.character['name']}: {scene['character_speech']}")
[docs] def show_choices(self) -> None: """ Displays the options available to the player on standard output. Return: None """ for choice_text, _ in self.scenes[self.current_scene]["choices"]: print(choice_text)
[docs] def make_choice(self, player_choice: str) -> None: """ Processes the player's choice and advances to the next corresponding scene. Parameters - player_choice (str): The choice made by the player. Return: None """ choices = self.scenes[self.current_scene]["choices"] if 1 <= int(player_choice) <= len(choices): selected_choice = choices[int(player_choice) - 1] self.current_scene = selected_choice[1] else: print("Invalid choice.")
[docs] def save_state_to_json(self, filename: str) -> None: """ Saves the current game state to a JSON file. Parameters: - filename (str): The name of the JSON file to save the state. Return: None """ print("Saving state to JSON...") # Convertendo as chaves do dicionĂ¡rio choices para strings choices_str = {str(k): v for k, v in self.choices.items()} data = { "current_scene": self.current_scene, "character_name": self.character["name"], "scenes": self.scenes, "choices": choices_str # Usando as chaves convertidas para strings } with open(filename, "w") as file: json.dump(data, file, indent=4) print("Saved state to JSON.")