diff --git a/README.md b/README.md index 10922a4..785ecf6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A Python library to make creating audio games easier. + ## Overview `libstormgames` provides a comprehensive set of tools for developing accessible games with audio-first design. It handles common game development tasks including: @@ -14,6 +15,7 @@ A Python library to make creating audio games easier. - Menu systems and text display - GUI initialization + ## Installation ### Requirements @@ -31,18 +33,23 @@ A Python library to make creating audio games easier. - Linux/Unix: `python-speechd` or `accessible-output2>=0.14` - Windows/macOS: `accessible-output2>=0.14` + ### Install from source +If you are on Linux, check your package manager first to see if the packages in requirements.txt are available. + ```bash git clone https://git.stormux.org/storm/libstormgames cd libstormgames pip install -e . ``` + ## Getting Started You can use libstormgames in two ways: the traditional function-based approach or the new class-based approach. + ### Traditional Function-Based Approach ```python @@ -69,8 +76,8 @@ def main(): # Define menu options while True: - choice = sg.game_menu(sounds, "play_game", "instructions", "credits", "donate", "exit_game") - if choice == "play_game": + choice = sg.game_menu(sounds) + if choice == "play": play_game() if __name__ == "__main__": @@ -87,7 +94,7 @@ def main(): # Create and initialize a game game = sg.Game("My First Audio Game").initialize() - # Welcome message (using fluent API) + # Welcome message game.speak("Welcome to My First Audio Game!") # Main game loop @@ -100,14 +107,15 @@ def main(): # Define menu options while True: - choice = sg.game_menu(game.sound.get_sounds(), "play_game", "instructions", "credits", "donate", "exit_game") - if choice == "play_game": + choice = sg.game_menu(game.sound.get_sounds()) + if choice == "play": play_game() if __name__ == "__main__": main() ``` + ## Library Structure The library is organized into modules, each with a specific focus: @@ -122,6 +130,7 @@ The library is organized into modules, each with a specific focus: - **menu**: Menu systems - **utils**: Utility functions and Game class + ## Core Classes ### Game @@ -133,19 +142,27 @@ The Game class provides a central way to manage all game systems: game = sg.Game("My Game").initialize() # Use fluent API for chaining commands -game.speak("Hello").play_bgm("music/theme.ogg") +game.speak("Hello").play_bgm("music/theme") # Access components directly game.scoreboard.increase_score(10) game.sound.play_random("explosion") # Display text + game.display_text(["Line 1", "Line 2"]) # Clean exit + game.exit() ``` +The initialization process sets up proper configuration paths: +- Creates game-specific directories if they don't exist +- Ensures all services have access to correct paths +- Connects all components for seamless operation + + ### Services The library includes several service classes that replace global variables: @@ -181,6 +198,14 @@ config.read_local_config() difficulty = config.local_config.get("settings", "difficulty") ``` +Configuration files are automatically stored in standard system locations: +- Linux/Unix: `~/.config/storm-games/` +- Windows: `%APPDATA%\storm-games\` +- macOS: `~/Library/Application Support/storm-games/` + +Each game has its own subdirectory based on the game name (lowercase with hyphens), for example, Wicked Quest becomes wicked-quest. + + ### Sound Manages sound loading and playback with positional audio support. @@ -213,6 +238,7 @@ sg.adjust_bgm_volume(-0.1) # Decrease background music volume sg.adjust_sfx_volume(0.1) # Increase sound effects volume ``` + ### Speech Provides text-to-speech functionality using available speech providers. @@ -231,9 +257,10 @@ sg.speak("Hello, world!") sg.speak("This won't interrupt", interrupt=False) # Clean up when done -speech.close() +exit_game() or as the class, game.exit_game() ``` + ### Scoreboard Tracks scores and manages high score tables. @@ -261,6 +288,15 @@ for entry in high_scores: print(f"{entry['name']}: {entry['score']}") ``` +The Scoreboard system automatically manages high score data in the game's configuration directory: +- Linux/Unix: `~/.config/storm-games/game-name/config.ini` +- Windows: `%APPDATA%\storm-games\game-name\config.ini` +- macOS: `~/Library/Application Support/storm-games/game-name/config.ini` + +Where `game-name` is the lowercase, hyphenated version of your game title. For example, +"My Awesome Game" would use the directory `my-awesome-game`. + + ## Key Functions ### Game Initialization and Control @@ -286,22 +322,167 @@ sg.exit_game() ### Menu System ```python -# Display a menu with options (functions should exist with these names) -choice = sg.game_menu(sounds, "play_game", "high_scores", "instructions", "credits", "exit_game") +# Basic menu example (returns option string) +choice = sg.game_menu(sounds) +if choice == "play": + # Start the game + play_game() +elif choice == "instructions": + sg.instructions() +# etc... -# Display built-in instructions -sg.instructions() +# Menu with play callback (callback is executed directly) +def play_callback(): + sg.speak("Game started with callback!") + # Game code goes here + +sg.game_menu(sounds, play_callback) -# Display built-in credits -sg.credits() - -# Open donation page -sg.donate() - -# Interactive menu to learn available sounds -sg.learn_sounds(sounds) +# Menu with custom options +choice = sg.game_menu(sounds, None, "practice_mode") +if choice == "practice_mode": + # Handle practice mode + practice_game() +elif choice == "difficulty": + # Handle difficulty settings + set_difficulty() +elif choice == "options": + # Handle options screen + show_options() +# etc... ``` + +### Complete Menu Example + +```python +import libstormgames as sg +import pygame + +def main(): + # Initialize the game + sounds = sg.initialize_gui("Menu Example Game") + + # Play menu music + sg.play_bgm("sounds/music_menu.ogg") + + # Define play function with callback + def play_game(): + sg.speak("Starting game!") + # Game code here + sg.speak("Game over!") + return "menu" # Return to menu after game ends + + # Define custom menu option handlers + def practice_mode(): + sg.speak("Starting practice mode!") + # Practice mode code here + return "menu" + + def difficulty_settings(): + # Show difficulty options + options = ["easy", "normal", "hard", "back"] + current = 0 + + while True: + sg.speak(options[current]) + + event = pygame.event.wait() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP and current > 0: + current -= 1 + elif event.key == pygame.K_DOWN and current < len(options) - 1: + current += 1 + elif event.key == pygame.K_RETURN: + if options[current] == "back": + return "menu" + sg.speak(f"Difficulty set to {options[current]}") + return "menu" + elif event.key == pygame.K_ESCAPE: + return "menu" + + # Main menu loop + while True: + # Display menu with play callback and custom options + choice = sg.game_menu(sounds, play_game, "practice_mode", "difficulty") + + # Handle menu choices + if choice == "practice_mode": + practice_mode() + elif choice == "difficulty": + difficulty_settings() + # Other options are handled automatically by game_menu + +if __name__ == "__main__": + main() +``` + + +### Class-Based Menu Example + +```python +import libstormgames as sg +import pygame + +class MyGame: + def __init__(self): + # Create and initialize game + self.game = sg.Game("Class-Based Menu Example").initialize() + self.sounds = self.game.sound.get_sounds() + + def play_game(self): + self.game.speak("Starting game!") + # Game code here + self.game.speak("Game over!") + return "menu" + + def practice_mode(self): + self.game.speak("Starting practice mode!") + # Practice mode code here + return "menu" + + def settings(self): + # A nested menu example + submenu_options = ["graphics", "audio", "controls", "back"] + current = 0 + + while True: + self.game.speak(submenu_options[current]) + + event = pygame.event.wait() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP and current > 0: + current -= 1 + elif event.key == pygame.K_DOWN and current < len(submenu_options) - 1: + current += 1 + elif event.key == pygame.K_RETURN: + if submenu_options[current] == "back": + return "menu" + self.game.speak(f"Selected {submenu_options[current]} settings") + return "menu" + elif event.key == pygame.K_ESCAPE: + return "menu" + + def run(self): + # Main menu loop + while True: + # Use playCallback parameter to directly call play_game when "play" is selected + choice = sg.game_menu(self.sounds, None, "practice_mode", + "settings") + + # Handle other menu options + if choice == "practice_mode": + self.practice_mode() + elif choice == "settings": + self.settings() + +# Run the game +if __name__ == "__main__": + game = MyGame() + game.run() +``` + + ### Text Display ```python @@ -319,6 +500,7 @@ sg.messagebox("Game Over! You scored 100 points.") name = sg.get_input("Enter your name:", "Player") ``` + ### Sound Effects ```python @@ -358,6 +540,7 @@ mid_value = sg.lerp(start=0, end=10, factor=0.5) # Returns 5.0 smooth_value = sg.smooth_step(edge0=0, edge1=10, x=5) # Smooth transition ``` + ## Advanced Examples ### Using the Game Class (Modern Approach) @@ -409,6 +592,7 @@ if __name__ == "__main__": main() ``` + ### Complex Sound Environment ```python @@ -459,6 +643,7 @@ def create_sound_environment(player_x, player_y): sg.exit_game() ``` + ### Complete Game Structure with Class-Based Architecture ```python @@ -549,12 +734,10 @@ class MyGame: # Main menu loop while True: sounds = self.game.sound.get_sounds() - choice = sg.game_menu(sounds, "play_game", "settings", + choice = sg.game_menu(sounds, self.play_game, "settings", "instructions", "credits", "donate", "exit_game") - if choice == "play_game": - self.play_game() - elif choice == "settings": + if choice == "settings": self.settings() elif choice == "instructions": sg.instructions() @@ -571,6 +754,7 @@ if __name__ == "__main__": game.run() ``` + ## Best Practices 1. **Modern vs Traditional Approach**: @@ -586,18 +770,16 @@ if __name__ == "__main__": - Implement the Alt+key volume controls in your game - Use volume services for better control -4. **Speech feedback**: - - Provide clear speech feedback for all actions - - Use the `interrupt` parameter to control speech priority - -5. **Sound positioning**: - - Use positional audio to create an immersive environment - - Update object positions as the game state changes - -6. **Configuration**: +4. **Configuration**: - Save user preferences using the Config class - Load settings at startup +5. **Path initialization**: + - Always initialize the framework with a proper game title + - Game title is used to determine configuration directory paths + - Services are interconnected, so proper initialization ensures correct operation + + ## Troubleshooting ### No Sound @@ -618,6 +800,26 @@ if __name__ == "__main__": - Ensure pygame is properly handling events - Check event loop for proper event handling +### Scoreboard/Configuration Issues + +- Check if path services are properly initialized with a game title +- Verify write permissions in the configuration directory +- For debugging, use the following code to view path configuration: + + ```python + from libstormgames.services import PathService, ConfigService + + # Print path information + path_service = PathService.get_instance() + print(f"Game Name: {path_service.gameName}") + print(f"Game Path: {path_service.gamePath}") + + # Check config service connection + config_service = ConfigService.get_instance() + print(f"Config connected to path: {hasattr(config_service, 'pathService')}") + ``` + + ## Contributing Contributions are welcome! Please feel free to submit a Pull Request.