Source code for unipress.games.demo_jump.game

"""
Demo Jump Game - Simple one-button jumping game.

Click to make the player jump over incoming obstacles.
Difficulty affects how much time you have to react to obstacles.
"""

import random

import arcade

from unipress.core.base_game import BaseGame
from unipress.core.logger import log_game_event


[docs] class Obstacle: """Simple obstacle that moves from right to left."""
[docs] def __init__(self, x: float, y: float, width: float = 30, height: float = 50): self.x = x self.y = y self.width = width self.height = height self.speed = 200 # pixels per second
[docs] def update(self, delta_time: float) -> None: """Move obstacle left.""" self.x -= self.speed * delta_time
[docs] def draw(self) -> None: """Draw obstacle as red rectangle.""" arcade.draw_lbwh_rectangle_filled( self.x, self.y, self.width, self.height, arcade.color.RED )
[docs] def collides_with( self, player_x: float, player_y: float, player_size: float ) -> bool: """Check collision with player.""" return ( player_x + player_size > self.x and player_x < self.x + self.width and player_y + player_size > self.y and player_y < self.y + self.height )
[docs] class DemoJumpGame(BaseGame): # type: ignore[misc] """ Simple jumping game demo. - Click to jump - Avoid red obstacles - Score increases over time - Difficulty affects obstacle spacing (reaction time) """
[docs] def __init__(self, difficulty: int = None): super().__init__( game_name="demo_jump", width=800, height=600, title="Demo Jump Game", difficulty=difficulty, ) # Update title to show actual difficulty from settings self.set_caption( f"{self.get_message('game.title')} (Difficulty: {self.difficulty})" ) # Player settings self.player_x = 100.0 self.player_y = 100.0 self.player_size = 40.0 self.player_jump_speed = 0.0 self.player_on_ground = True # Game settings based on difficulty settings = self.get_difficulty_settings() # Obstacle spawn timing based on reaction time # More difficult = less time between obstacles # Base interval should be longer to allow recovery time base_interval = settings["reaction_time"] * 2.5 # Increased from 1.5 self.obstacle_spawn_time = base_interval * random.uniform( 0.8, 1.5 ) # Add initial randomness self.last_obstacle_time = 0.0 # Game objects self.obstacles: list[Obstacle] = [] self.time_elapsed = 0.0
[docs] def get_difficulty_settings(self) -> dict[str, float | int]: """Override to add game-specific difficulty settings.""" base_settings = super().get_difficulty_settings() # Add game-specific settings # Jump height must be enough to clear 50px obstacles + safety margin # Base jump height ensures obstacle clearance, difficulty adds extra height obstacle_height = 50 base_jump_height = obstacle_height + 100 # 100px safety margin above obstacle difficulty_bonus = ( 11 - self.difficulty ) * 20 # More height on easier difficulties base_settings.update( { "obstacle_speed": 100 + (self.difficulty * 15), # Faster obstacles = harder "jump_height": base_jump_height + difficulty_bonus, # Guaranteed clearance } ) return base_settings
[docs] def reset_game(self) -> None: """Reset game state.""" self.player_y = 100.0 self.player_jump_speed = 0.0 self.player_on_ground = True self.obstacles.clear() self.time_elapsed = 0.0 self.score = 0 self.last_obstacle_time = 0.0
[docs] def on_action_press(self) -> None: """Handle main action (jump or start/restart game).""" # Check if we're in life lost pause first if self.handle_life_lost_continue(): return if not self.game_started: self.start_game() elif self.game_over: self.start_game() elif self.player_on_ground: # Jump! settings = self.get_difficulty_settings() desired_jump_height = float(settings["jump_height"]) gravity = 800 # Calculate initial velocity needed to reach desired height # Using physics: max_height = (initial_velocity^2) / (2 * gravity) self.player_jump_speed = (2 * gravity * desired_jump_height) ** 0.5 self.player_on_ground = False # Log jump action with physics details log_game_event( "player_jump", jump_height=desired_jump_height, jump_speed=self.player_jump_speed, gravity=gravity, difficulty=self.difficulty, )
[docs] def on_update(self, delta_time: float) -> None: """Update game logic.""" # Always update life lost effects for blinking self.update_life_lost_effects(delta_time) # Update end game screen for automatic cycling if self.show_end_screen and self.end_game_screen: self.end_game_screen.update(delta_time) # Pause game logic during life lost pause if self.is_game_paused(): return # Update periodic cursor positioning self.update_cursor_positioning(delta_time) self.time_elapsed += delta_time # Update player physics if not self.player_on_ground: self.player_jump_speed -= 800 * delta_time # Gravity self.player_y += self.player_jump_speed * delta_time # Land on ground if self.player_y <= 100: self.player_y = 100 self.player_on_ground = True self.player_jump_speed = 0 # Spawn obstacles if self.time_elapsed - self.last_obstacle_time > self.obstacle_spawn_time: self.obstacles.append(Obstacle(self.width + 50, 100)) self.last_obstacle_time = self.time_elapsed # Adjust spawn timing with more variety and longer intervals base_interval = self.reaction_time * 2.5 self.obstacle_spawn_time = base_interval * random.uniform( 0.7, 2.0 ) # Much more variation # Update obstacles for obstacle in self.obstacles[:]: obstacle.update(delta_time) # Remove obstacles that are off-screen if obstacle.x + obstacle.width < 0: self.obstacles.remove(obstacle) self.score += 10 log_game_event("obstacle_cleared", score=self.score) # Check collision if obstacle.collides_with(self.player_x, self.player_y, self.player_size): log_game_event( "obstacle_collision", player_x=self.player_x, player_y=self.player_y, obstacle_x=obstacle.x, obstacle_y=obstacle.y, score=self.score, ) self.lose_life() # Increase score over time self.score += int(delta_time * 5)
[docs] def on_draw(self) -> None: """Draw the game.""" self.clear() if self.game_started and not self.game_over: # Draw ground line arcade.draw_line(0, 100, self.width, 100, arcade.color.WHITE, 2) # Draw player (with blinking effect during life lost) if self.should_draw_player(): if self.life_lost_pause: color = arcade.color.RED else: color = ( arcade.color.BLUE if self.player_on_ground else arcade.color.LIGHT_BLUE ) arcade.draw_lbwh_rectangle_filled( self.player_x, self.player_y, self.player_size, self.player_size, color, ) # Draw obstacles for obstacle in self.obstacles: obstacle.draw() # Draw jump window indicator # Calculate the distance where jump is still possible # This is based on obstacle speed and jump duration settings = self.get_difficulty_settings() obstacle_speed = settings["obstacle_speed"] # Jump takes time to reach peak and come back down # Total jump time = 2 * sqrt(2 * jump_height / gravity) jump_height = settings["jump_height"] gravity = 800 jump_duration = 2 * (jump_height / gravity) ** 0.5 # Distance an obstacle travels during jump jump_window_distance = obstacle_speed * jump_duration # Draw the jump window as a green zone on screen jump_zone_start = self.player_x + self.player_size jump_zone_width = min(jump_window_distance, 200) # Cap at 200px for display arcade.draw_lbwh_rectangle_filled( jump_zone_start, 45, jump_zone_width, 10, arcade.color.GREEN ) arcade.draw_text( self.get_message( "game.jump_window_info", distance=f"{jump_window_distance:.2f}", duration=f"{jump_duration:.2f}", ), 10, 20, arcade.color.WHITE, 12, ) # Draw UI self.draw_ui()
[docs] def main() -> None: """Run the demo game.""" from unipress.core.logger import init_logger # Initialize logging for standalone game execution init_logger("demo_jump") try: # You can change difficulty here (1-10) DemoJumpGame(difficulty=5).run() except Exception as e: from unipress.core.logger import log_error log_error(e, "Demo game crashed during standalone execution") raise
if __name__ == "__main__": main()