Compare commits

...

1 Commits

Author SHA1 Message Date
Storm Dragon
1c442261d9 Migrate to libstormgames systems. 2025-09-16 01:51:36 -04:00
4 changed files with 247 additions and 83 deletions

View File

@@ -10,7 +10,6 @@ from src.grasping_hands import GraspingHands
from src.grave import GraveObject from src.grave import GraveObject
from src.object import Object from src.object import Object
from src.player import Player from src.player import Player
from src.projectile import Projectile
from src.powerup import PowerUp from src.powerup import PowerUp
from src.skull_storm import SkullStorm from src.skull_storm import SkullStorm
@@ -200,8 +199,8 @@ class Level:
self.objects.append(gameObject) self.objects.append(gameObject)
enemyCount = len(self.enemies) enemyCount = len(self.enemies)
coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken")) coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken"))
player.stats.update_stat("Enemies remaining", enemyCount) player.stats.set_stat("Enemies remaining", enemyCount, level_only=True)
player.stats.update_stat("Coffins remaining", coffinCount) player.stats.set_stat("Coffins remaining", coffinCount, level_only=True)
def update_audio(self): def update_audio(self):
"""Update all audio and entity state.""" """Update all audio and entity state."""
@@ -310,16 +309,22 @@ class Level:
def handle_combat(self, currentTime): def handle_combat(self, currentTime):
"""Handle combat interactions between player and enemies""" """Handle combat interactions between player and enemies"""
# Only get attack range if attack is active # Only get attack range if attack is active
if self.player.currentWeapon and self.player.currentWeapon.is_attack_active(currentTime): if self.player.currentWeapon and self.player.currentWeapon.is_attack_active():
attackRange = self.player.currentWeapon.get_attack_range(self.player.xPos, self.player.facingRight) # Calculate attack range manually
if self.player.facingRight:
attackRange = (self.player.xPos, self.player.xPos + self.player.currentWeapon.range_value)
else:
attackRange = (self.player.xPos - self.player.currentWeapon.range_value, self.player.xPos)
# Check for enemy hits # Check for enemy hits
for enemy in self.enemies: for enemy in self.enemies:
if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]: if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]:
# Only damage and play sound if this is a new hit for this attack # Use libstormgames weapon hit detection
if self.player.currentWeapon.register_hit(enemy, currentTime): if self.player.currentWeapon.can_hit_target(enemy.xPos, self.player.xPos, self.player.facingRight, id(enemy)):
play_sound(self.sounds[self.player.currentWeapon.hitSound]) damage = self.player.currentWeapon.hit_target(id(enemy))
enemy.take_damage(self.player.currentWeapon.damage) if damage > 0:
play_sound(self.sounds[self.player.currentWeapon.hit_sound])
enemy.take_damage(damage)
# Check for coffin hits # Check for coffin hits
for obj in self.objects: for obj in self.objects:
@@ -516,13 +521,16 @@ class Level:
# Check for enemy hits # Check for enemy hits
for enemy in self.enemies: for enemy in self.enemies:
if enemy.isActive and abs(proj.x - enemy.xPos) < 1: if enemy.isActive and proj.check_collision(enemy.xPos, 1.0):
proj.hit_enemy(enemy) damage = proj.hit()
enemy.take_damage(damage)
self.projectiles.remove(proj) self.projectiles.remove(proj)
# Calculate volume and pan for splat sound based on final position # Calculate volume and pan for splat sound based on final position
volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x) proj_pos = proj.get_position()
proj_x = proj_pos if isinstance(proj_pos, (int, float)) else proj_pos[0]
volume, left, right = calculate_volume_and_pan(self.player.xPos, proj_x)
if volume > 0: # Only play if within audible range if volume > 0: # Only play if within audible range
obj_play(self.sounds, "pumpkin_splat", self.player.xPos, proj.x, loop=False) obj_play(self.sounds, "pumpkin_splat", self.player.xPos, proj_x, loop=False)
break break
def throw_projectile(self): def throw_projectile(self):
@@ -532,6 +540,6 @@ class Level:
speak("No jack o'lanterns to throw!") speak("No jack o'lanterns to throw!")
return return
self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"])) self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"], speed=0.2, damage=5, max_range=12))
# Play throw sound # Play throw sound
play_sound(self.sounds["throw_jack_o_lantern"]) play_sound(self.sounds["throw_jack_o_lantern"])

View File

@@ -2,8 +2,6 @@
import pygame import pygame
from libstormgames import * from libstormgames import *
from src.stat_tracker import StatTracker
from src.weapon import Weapon
class Player: class Player:
@@ -27,7 +25,7 @@ class Player:
self._lives = 1 self._lives = 1
self.distanceSinceLastStep = 0 self.distanceSinceLastStep = 0
self.stepDistance = 0.5 self.stepDistance = 0.5
self.stats = StatTracker() self.stats = StatTracker({"Bone dust": 0, "Enemies killed": 0, "Coffins broken": 0, "Items collected": 0, "Total time": 0, "levelsCompleted": 0})
self.sounds = sounds self.sounds = sounds
# Footstep tracking # Footstep tracking
@@ -67,10 +65,10 @@ class Player:
Weapon( Weapon(
name="rusty_shovel", name="rusty_shovel",
damage=2, damage=2,
range=2, range_value=2,
attackSound="player_shovel_attack", attack_sound="player_shovel_attack",
hitSound="player_shovel_hit", hit_sound="player_shovel_hit",
attackDuration=200, # 200ms attack duration attack_duration=200, # 200ms attack duration
) )
) )
@@ -106,7 +104,7 @@ class Player:
if currentTime >= self.webPenaltyEndTime: if currentTime >= self.webPenaltyEndTime:
self.moveSpeed *= 2 # Restore speed self.moveSpeed *= 2 # Restore speed
if self.currentWeapon: if self.currentWeapon:
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed self.currentWeapon.attack_duration *= 0.5 # Restore attack speed
del self.webPenaltyEndTime del self.webPenaltyEndTime
# Check invincibility status # Check invincibility status
@@ -166,7 +164,7 @@ class Player:
def get_step_distance(self): def get_step_distance(self):
"""Get step distance based on current speed""" """Get step distance based on current speed"""
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
totalMultiplier = weaponBonus totalMultiplier = weaponBonus
if self.isRunning or self.isJumping: if self.isRunning or self.isJumping:
@@ -176,7 +174,7 @@ class Player:
def get_step_interval(self): def get_step_interval(self):
"""Get minimum time between steps based on current speed""" """Get minimum time between steps based on current speed"""
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
totalMultiplier = weaponBonus totalMultiplier = weaponBonus
if self.isRunning or self.isJumping: if self.isRunning or self.isJumping:
@@ -199,7 +197,7 @@ class Player:
def get_current_speed(self): def get_current_speed(self):
"""Calculate current speed based on state and weapon""" """Calculate current speed based on state and weapon"""
baseSpeed = self.moveSpeed baseSpeed = self.moveSpeed
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
if self.isJumping or self.isRunning: if self.isJumping or self.isRunning:
return baseSpeed * self.runMultiplier * weaponBonus return baseSpeed * self.runMultiplier * weaponBonus
@@ -207,7 +205,7 @@ class Player:
def get_current_jump_duration(self): def get_current_jump_duration(self):
"""Calculate current jump duration based on weapon bonus""" """Calculate current jump duration based on weapon bonus"""
weaponBonus = self.currentWeapon.jumpDurationBonus if self.currentWeapon else 1.0 weaponBonus = self.currentWeapon.stat_bonuses.get("jump_duration", 1.0) if self.currentWeapon else 1.0
return int(self.jumpDuration * weaponBonus) return int(self.jumpDuration * weaponBonus)
def set_footstep_sound(self, soundName): def set_footstep_sound(self, soundName):
@@ -304,7 +302,9 @@ class Player:
def start_attack(self, currentTime): def start_attack(self, currentTime):
"""Attempt to start an attack with the current weapon""" """Attempt to start an attack with the current weapon"""
if self.currentWeapon and self.currentWeapon.start_attack(currentTime): if self.currentWeapon and self.currentWeapon.can_attack():
damage = self.currentWeapon.attack()
if damage > 0:
self.isAttacking = True self.isAttacking = True
self.lastAttackTime = currentTime self.lastAttackTime = currentTime
return True return True
@@ -312,6 +312,11 @@ class Player:
def get_attack_range(self, currentTime): def get_attack_range(self, currentTime):
"""Get the current attack's range based on position and facing direction""" """Get the current attack's range based on position and facing direction"""
if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): if not self.currentWeapon or not self.currentWeapon.is_attack_active():
return None return None
return self.currentWeapon.get_attack_range(self.xPos, self.facingRight)
# Calculate attack range based on position and facing direction
if self.facingRight:
return (self.xPos, self.xPos + self.currentWeapon.range_value)
else:
return (self.xPos - self.currentWeapon.range_value, self.xPos)

View File

@@ -3,7 +3,6 @@
import pygame import pygame
from libstormgames import * from libstormgames import *
from src.object import Object from src.object import Object
from src.weapon import Weapon
class PowerUp(Object): class PowerUp(Object):
@@ -100,7 +99,16 @@ class PowerUp(Object):
self.check_for_nunchucks(player) self.check_for_nunchucks(player)
elif self.item_type == "witch_broom": elif self.item_type == "witch_broom":
broomWeapon = Weapon.create_witch_broom() broomWeapon = Weapon(
name="witch_broom",
damage=3,
range_value=3,
attack_sound="player_broom_attack",
hit_sound="player_broom_hit",
cooldown=500,
attack_duration=200,
stat_bonuses={"speed": 1.17, "jump_duration": 1.25}
)
player.add_weapon(broomWeapon) player.add_weapon(broomWeapon)
player.equip_weapon(broomWeapon) player.equip_weapon(broomWeapon)
elif self.item_type == "spiderweb": elif self.item_type == "spiderweb":
@@ -112,7 +120,7 @@ class PowerUp(Object):
# Half speed and double attack time for 15 seconds # Half speed and double attack time for 15 seconds
player.moveSpeed *= 0.5 player.moveSpeed *= 0.5
if player.currentWeapon: if player.currentWeapon:
player.currentWeapon.attackDuration *= 2 player.currentWeapon.attack_duration *= 2
# Set timer for penalty removal # Set timer for penalty removal
player.webPenaltyEndTime = pygame.time.get_ticks() + 15000 player.webPenaltyEndTime = pygame.time.get_ticks() + 15000
@@ -135,11 +143,19 @@ class PowerUp(Object):
and "guts" in player.collectedItems and "guts" in player.collectedItems
and not any(weapon.name == "nunchucks" for weapon in player.weapons) and not any(weapon.name == "nunchucks" for weapon in player.weapons)
): ):
nunchucksWeapon = Weapon.create_nunchucks() nunchucksWeapon = Weapon(
name="nunchucks",
damage=6,
range_value=4,
attack_sound="player_nunchuck_attack",
hit_sound="player_nunchuck_hit",
cooldown=250,
attack_duration=100
)
player.add_weapon(nunchucksWeapon) player.add_weapon(nunchucksWeapon)
player.equip_weapon(nunchucksWeapon) player.equip_weapon(nunchucksWeapon)
basePoints = nunchucksWeapon.damage * 1000 basePoints = nunchucksWeapon.damage * 1000
rangeModifier = nunchucksWeapon.range * 500 rangeModifier = nunchucksWeapon.range_value * 500
player.scoreboard.increase_score(basePoints + rangeModifier) player.scoreboard.increase_score(basePoints + rangeModifier)
play_sound(self.sounds["get_nunchucks"]) play_sound(self.sounds["get_nunchucks"])
player.stats.update_stat("Items collected", 1) player.stats.update_stat("Items collected", 1)

View File

@@ -10,7 +10,6 @@ from src.level import Level
from src.object import Object from src.object import Object
from src.player import Player from src.player import Player
from src.game_selection import select_game, get_level_path from src.game_selection import select_game, get_level_path
from src.save_manager import SaveManager
from src.survival_generator import SurvivalGenerator from src.survival_generator import SurvivalGenerator
@@ -28,7 +27,7 @@ class WickedQuest:
self.player = None self.player = None
self.currentGame = None self.currentGame = None
self.runLock = False # Toggle behavior of the run keys self.runLock = False # Toggle behavior of the run keys
self.saveManager = SaveManager() self.saveManager = SaveManager("wicked-quest")
self.survivalGenerator = None self.survivalGenerator = None
self.lastBroomLandingTime = 0 # Timestamp to prevent ducking after broom landing self.lastBroomLandingTime = 0 # Timestamp to prevent ducking after broom landing
self.survivalWave = 1 self.survivalWave = 1
@@ -75,7 +74,7 @@ class WickedQuest:
del self.player.webPenaltyEndTime # Remove the penalty timer del self.player.webPenaltyEndTime # Remove the penalty timer
self.player.moveSpeed *= 2 # Restore normal speed self.player.moveSpeed *= 2 # Restore normal speed
if self.player.currentWeapon: if self.player.currentWeapon:
self.player.currentWeapon.attackDuration *= 0.5 # Restore normal attack speed self.player.currentWeapon.attack_duration *= 0.5 # Restore normal attack speed
# Pass existing player to new level # Pass existing player to new level
pygame.event.clear() pygame.event.clear()
@@ -117,10 +116,14 @@ class WickedQuest:
messagebox("No save files found.") messagebox("No save files found.")
return None return None
# Create menu options # Create menu options with save info
options = [] options = []
save_infos = []
for save_file in save_files: for save_file in save_files:
options.append(save_file['display_name']) save_info = self.saveManager.get_save_info(save_file)
save_infos.append(save_info)
display_name = save_info.get("metadata", {}).get("display_name", "Unknown Save")
options.append(display_name)
options.append("Cancel") options.append("Cancel")
@@ -131,9 +134,9 @@ class WickedQuest:
return None return None
else: else:
# Find the corresponding save file # Find the corresponding save file
for save_file in save_files: for i, option in enumerate(options[:-1]): # Exclude "Cancel"
if save_file['display_name'] == choice: if option == choice:
return save_file return save_infos[i]
return None return None
def auto_save(self): def auto_save(self):
@@ -145,16 +148,31 @@ class WickedQuest:
if not self.player.can_save(): if not self.player.can_save():
return False return False
# Spend the bone dust
if not self.player.spend_save_bone_dust(200):
return False
# Automatically create save # Automatically create save
try: try:
success, message = self.saveManager.create_save( # Create save data structure
self.player, save_data = {
self.currentLevel.levelId, "player_state": self._serialize_player_state(),
self.gameStartTime, "game_state": {
self.currentGame "currentLevel": self.currentLevel.levelId,
) "currentGame": self.currentGame,
"gameStartTime": self.gameStartTime,
},
}
# Create metadata for display
metadata = {
"display_name": f"{self.currentGame} Level {self.currentLevel.levelId}",
"level": self.currentLevel.levelId,
"game": self.currentGame
}
save_path = self.saveManager.create_save(save_data, metadata)
if success:
try: try:
if 'save' in self.get_sounds(): if 'save' in self.get_sounds():
play_sound(self.get_sounds()['save']) play_sound(self.get_sounds()['save'])
@@ -163,14 +181,130 @@ class WickedQuest:
except Exception as e: except Exception as e:
print(f"Error playing save sound: {e}") print(f"Error playing save sound: {e}")
pass # Continue if save sound fails to play pass # Continue if save sound fails to play
else:
print(f"Save failed: {message}")
return success return True
except Exception as e: except Exception as e:
print(f"Error during save: {e}") print(f"Error during save: {e}")
return False return False
def _serialize_player_state(self):
"""Serialize player state for saving"""
return {
"xPos": self.player.xPos,
"yPos": self.player.yPos,
"health": self.player._health,
"maxHealth": self.player._maxHealth,
"lives": self.player._lives,
"coins": self.player._coins,
"saveBoneDust": self.player._saveBoneDust,
"jackOLanternCount": self.player._jack_o_lantern_count,
"shinBoneCount": self.player.shinBoneCount,
"inventory": self.player.inventory,
"collectedItems": self.player.collectedItems,
"weapons": self._serialize_weapons(self.player.weapons),
"currentWeaponName": self.player.currentWeapon.name if self.player.currentWeapon else None,
"stats": self.player.stats.to_dict(),
"scoreboard": self._serialize_scoreboard(self.player.scoreboard),
}
def _serialize_weapons(self, weapons):
"""Serialize weapons for saving"""
serialized = []
for weapon in weapons:
serialized.append({
"name": weapon.name,
"damage": weapon.damage,
"range": weapon.range,
"attackSound": weapon.attack_sound,
"hitSound": weapon.hit_sound,
"attackDuration": weapon.attack_duration,
"speedBonus": getattr(weapon, "speedBonus", 1.0),
"jumpDurationBonus": getattr(weapon, "jumpDurationBonus", 1.0),
})
return serialized
def _serialize_scoreboard(self, scoreboard):
"""Serialize scoreboard for saving"""
return {
"currentScore": getattr(scoreboard, "currentScore", 0),
"highScores": getattr(scoreboard, "highScores", []),
}
def _restore_player_state(self, player_state):
"""Restore player state from save data"""
# Restore basic attributes
self.player.xPos = player_state["xPos"]
self.player.yPos = player_state["yPos"]
self.player._health = player_state["health"]
self.player._maxHealth = player_state["maxHealth"]
self.player._lives = player_state["lives"]
self.player._coins = player_state["coins"]
self.player._saveBoneDust = player_state["saveBoneDust"]
self.player._jack_o_lantern_count = player_state["jackOLanternCount"]
self.player.shinBoneCount = player_state["shinBoneCount"]
self.player.inventory = player_state["inventory"]
self.player.collectedItems = player_state["collectedItems"]
# Restore weapons
self.player.weapons = self._deserialize_weapons(player_state["weapons"])
# Restore current weapon
current_weapon_name = player_state.get("currentWeaponName")
if current_weapon_name:
for weapon in self.player.weapons:
if weapon.name == current_weapon_name:
self.player.currentWeapon = weapon
break
# Restore stats
if "stats" in player_state:
self.player.stats = StatTracker.from_dict(player_state["stats"])
else:
self.player.stats = StatTracker()
# Restore scoreboard
if "scoreboard" in player_state:
self.player.scoreboard = self._deserialize_scoreboard(player_state["scoreboard"])
else:
self.player.scoreboard = Scoreboard()
def _deserialize_weapons(self, weapon_data):
"""Deserialize weapons from save data"""
from src.weapon import Weapon
weapons = []
for data in weapon_data:
# Handle backward compatibility for old saves
speedBonus = data.get("speedBonus", 1.0)
jumpDurationBonus = data.get("jumpDurationBonus", 1.0)
# For old saves, restore proper bonuses for specific weapons
if data["name"] == "witch_broom" and speedBonus == 1.0:
speedBonus = 1.17
jumpDurationBonus = 1.25
weapon = Weapon(
name=data["name"],
damage=data["damage"],
range=data["range"],
attackSound=data["attackSound"],
hitSound=data["hitSound"],
attackDuration=data["attackDuration"],
speedBonus=speedBonus,
jumpDurationBonus=jumpDurationBonus,
)
weapons.append(weapon)
return weapons
def _deserialize_scoreboard(self, scoreboard_data):
"""Deserialize scoreboard from save data"""
scoreboard = Scoreboard()
if "currentScore" in scoreboard_data:
scoreboard.currentScore = scoreboard_data["currentScore"]
if "highScores" in scoreboard_data:
scoreboard.highScores = scoreboard_data["highScores"]
return scoreboard
def handle_input(self): def handle_input(self):
"""Process keyboard input for player actions.""" """Process keyboard input for player actions."""
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
@@ -257,7 +391,7 @@ class WickedQuest:
# Handle attack with either CTRL key # Handle attack with either CTRL key
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime): if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
play_sound(self.get_sounds()[player.currentWeapon.attackSound]) play_sound(self.get_sounds()[player.currentWeapon.attack_sound])
# Handle jumping # Handle jumping
if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping: if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping:
@@ -291,14 +425,14 @@ class WickedQuest:
seconds = (timeTaken % 60000) // 1000 seconds = (timeTaken % 60000) // 1000
# Update time in stats # Update time in stats
self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True) self.currentLevel.player.stats.set_stat('Total time', timeTaken, level_only=True)
report = [f"Time taken: {minutes} minutes and {seconds} seconds"] report = [f"Time taken: {minutes} minutes and {seconds} seconds"]
# Add all level stats # Add all level stats
for key in self.currentLevel.player.stats.level: for key in self.currentLevel.player.stats.level:
if key != 'Total time': # Skip time since we already displayed it if key != 'Total time': # Skip time since we already displayed it
report.append(f"{key}: {self.currentLevel.player.stats.get_level_stat(key)}") report.append(f"{key}: {self.currentLevel.player.stats.get_stat(key)}")
report.append(f"Score: {int(self.currentLevel.levelScore)}") report.append(f"Score: {int(self.currentLevel.levelScore)}")
@@ -324,7 +458,7 @@ class WickedQuest:
# Add all total stats # Add all total stats
for key in self.currentLevel.player.stats.total: for key in self.currentLevel.player.stats.total:
if key not in ['Total time', 'levelsCompleted']: # Skip these if key not in ['Total time', 'levelsCompleted']: # Skip these
report.append(f"Total {key}: {self.currentLevel.player.stats.get_total_stat(key)}") report.append(f"Total {key}: {self.currentLevel.player.stats.get_stat(key, from_total=True)}")
report.append(f"Final Score: {self.player.scoreboard.get_score()}") report.append(f"Final Score: {self.player.scoreboard.get_score()}")
@@ -350,7 +484,7 @@ class WickedQuest:
# Add all total stats # Add all total stats
for key in self.currentLevel.player.stats.total: for key in self.currentLevel.player.stats.total:
if key not in ['Total time', 'levelsCompleted']: # Skip these if key not in ['Total time', 'levelsCompleted']: # Skip these
report.append(f"Total {key}: {self.currentLevel.player.stats.get_total_stat(key)}") report.append(f"Total {key}: {self.currentLevel.player.stats.get_stat(key, from_total=True)}")
if self.currentLevel.player.scoreboard.check_high_score(): if self.currentLevel.player.scoreboard.check_high_score():
pygame.event.clear() pygame.event.clear()
@@ -479,8 +613,9 @@ class WickedQuest:
elif choice == "load_game": elif choice == "load_game":
selected_save = self.load_game_menu() selected_save = self.load_game_menu()
if selected_save: if selected_save:
success, save_data = self.saveManager.load_save(selected_save['filepath']) try:
if success: save_data, metadata = self.saveManager.load_save(selected_save['filepath'])
# Load the saved game # Load the saved game
self.currentGame = save_data['game_state']['currentGame'] self.currentGame = save_data['game_state']['currentGame']
self.gameStartTime = save_data['game_state']['gameStartTime'] self.gameStartTime = save_data['game_state']['gameStartTime']
@@ -491,12 +626,12 @@ class WickedQuest:
# Load the level # Load the level
if self.load_level(current_level): if self.load_level(current_level):
# Restore player state # Restore player state
self.saveManager.restore_player_state(self.player, save_data) self._restore_player_state(save_data['player_state'])
self.game_loop(current_level) self.game_loop(current_level)
else: else:
messagebox("Failed to load saved level.") messagebox("Failed to load saved level.")
else: except Exception as e:
messagebox(f"Failed to load save: {save_data}") messagebox(f"Failed to load save: {e}")
elif choice == "play": elif choice == "play":
self.currentGame = select_game(self.get_sounds()) self.currentGame = select_game(self.get_sounds())
if self.currentGame is None: if self.currentGame is None: