# Copyright 2009 by Wade Brainerd. # This file is part of Bounce. # # Bounce is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Bounce is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Bounce. If not, see . #!/usr/bin/env python """Bounce - 3D action game by Wade Brainerd .""" import logging, os, time, math, threading, random, time from gettext import gettext as _ try: import json json.dumps except (ImportError, AttributeError): import simplejson as json from pongc import * # Import GTK. import gobject, pygtk, gtk, pango, cairo gobject.threads_init() # Import the PyGame mixer for sound output. import pygame.mixer pygame.mixer.init() # Import Sugar modules. from sugar.activity import activity from sugar.graphics import * from sugar.graphics import alert from sugar.graphics import toggletoolbutton from sugar.graphics import icon from sugar.presence import presenceservice # Initialize logging. log = logging.getLogger('bounce') log.setLevel(logging.DEBUG) logging.basicConfig() gtk.add_log_handlers() # This section can be modified to change the default stages that are built into the activity. DEFAULT_STAGE_DESCS = [ { 'Name': _('practice'), 'StageDepth': 160, 'StageXGravity': 0, 'StageYGravity': 0, 'BallSize': 1, 'BallSpeed': 2, 'PaddleWidth': 20, 'PaddleHeight': 20, 'AISpeed': 1, 'AIRecenter': 1, }, { 'Name': _('gravity'), 'StageDepth': 160, 'StageXGravity': 0, 'StageYGravity': 0.5, 'BallSize': 1, 'BallSpeed': 2, 'PaddleWidth': 20, 'PaddleHeight': 20, 'AISpeed': 2, 'AIRecenter': 1, }, { 'Name': _('wide'), 'StageDepth': 160, 'StageXGravity': 0, 'StageYGravity': 0.5, 'BallSize': 1, 'BallSpeed': 3, 'PaddleWidth': 50, 'PaddleHeight': 15, 'AISpeed': 3, 'AIRecenter': 1, }, { 'Name': _('deep'), 'StageDepth': 500, 'StageXGravity': 0, 'StageYGravity': 0, 'BallSize': 1, 'BallSpeed': 10, 'PaddleWidth': 25, 'PaddleHeight': 25, 'AISpeed': 2.5, 'AIRecenter': 0, }, { 'Name': _('rotate'), 'StageDepth': 160, 'StageXGravity': 0.5, 'StageYGravity': 0, 'BallSize': 1, 'BallSpeed': 5, 'PaddleWidth': 25, 'PaddleHeight': 20, 'AISpeed': 3, 'AIRecenter': 1, }, ] # These are the settings that will be applied when a new stage is created using the editor. NEW_STAGE = { 'Name': _('new stage'), 'StageDepth': 160, 'StageXGravity': 0, 'StageYGravity': 0, 'BallSize': 1, 'BallSpeed': 3, 'PaddleWidth': 20, 'PaddleHeight': 20, 'AISpeed': 1, 'AIRecenter': 1, } def clamp(a, b, c): if (ac): return c else: return a def to_fixed(a): return int(a * 256) def from_fixed(a): return a >> 8 def fixed_mul(a, b): return (a * b) >> 8 # Three dimensional vector class. class Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z zerovec = Vector(0, 0, 0) # RGB color class. class Color: def __init__(self, r=255, g=255, b=255): self.r = r self.g = g self.b = b # Two dimensional rectangle class. class Rect: def __init__(self): self.top = 0 self.left = 0 self.right = 0 self.bottom = 0 # Virtual screen dimensions screen_width = 1200 screen_height = 825 viewport_scale = to_fixed(100) # Game constants time_res = 32 def text_cairo (text, x, y, size, c): game.cairo.set_source_rgb(c, c, c) game.cairo.set_font_size(size) x_bearing, y_bearing, width, height = game.cairo.text_extents(text)[:4] if x == -1: x = screen_width/2 if y == -1: y = screen_height/2 game.cairo.move_to(x - width/2 - x_bearing, y - height/2 - y_bearing) game.cairo.show_text(text) class Ball: def __init__(self): self.lastpos = Vector() self.lastvel = Vector() self.pos = Vector() self.vel = Vector() self.size = 1 self.speed = 1 def setup (self, desc): self.size = to_fixed(desc['BallSize']) self.speed = to_fixed(desc['BallSpeed']) self.pos = Vector(to_fixed(50), to_fixed(25), to_fixed(desc['StageDepth'])/2) self.vel = Vector(to_fixed(2), to_fixed(2), self.speed) def draw_3d (self, stage): # Draw the ball. fill_circle_3d(game.drawimage, self.pos.x, self.pos.y, self.pos.z, self.size, game.brightness/100.0) # Draw the shadows. draw_ellipse_3d(game.drawimage, self.pos.x, stage.window.bottom, self.pos.z, self.size*2, self.size, game.brightness/2/100.0) draw_ellipse_3d(game.drawimage, self.pos.x, stage.window.top, self.pos.z, self.size*2, self.size, game.brightness/2/100.0) draw_ellipse_3d(game.drawimage, stage.window.left, self.pos.y, self.pos.z, self.size, self.size*2, game.brightness/2/100.0) draw_ellipse_3d(game.drawimage, stage.window.right, self.pos.y, self.pos.z, self.size, self.size*2, game.brightness/2/100.0) # 0 if nobody scored, 1 if Paddle1 scored, 2 if Paddle2 scored. def update (self, paddle1, paddle2, stage): # Ball collisions are handled very accurately, as this is the basis of the game. # All times are in 1sec/time_res units. # 1. Loop through all the surfaces and finds the first one the ball will collide with # in this animation frame (if any). # 2. Update the Ball velocity based on the collision, and advance the current time to # the exact time of the collision. # 3. Goto step 1, until no collisions remain in the current animation frame. time_left = time_res # Time remaining in this animation frame. first_collision_time = 0 # -1 means no collision found. first_collision_vel = Vector() # New ball velocity from first collision. first_collision_type = 0 # 0 for normal collision (wall), otherwise the scorezone number hit. self.lastpos.x = self.pos.x self.lastpos.y = self.pos.y self.lastpos.z = self.pos.z self.lastvel.x = self.vel.x self.lastvel.y = self.vel.y self.lastvel.z = self.vel.z next_ball_pos = Vector() # Hypothetical ball position assuming no collision. cur_time = 0 # cur_time of current collision. collision_type = 0 # Stored return value. iterations = 0 while True: iterations = iterations+1 if ( iterations > 5 ): break # Calculate new next ball position. next_ball_pos.x = self.pos.x + (self.vel.x * time_left) / time_res next_ball_pos.y = self.pos.y + (self.vel.y * time_left) / time_res next_ball_pos.z = self.pos.z + (self.vel.z * time_left) / time_res # Reset first_collision_cur_time. first_collision_cur_time = -1 # Check stage walls. First checks to see if the boundary was crossed, if so then calculates cur_time, etc. if ( next_ball_pos.x - self.size <= 0 ): # Left wall cur_time = ( self.pos.x - self.size ) * time_res / -self.vel.x # negative Vx is to account for left wall facing. if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = -self.vel.x first_collision_vel.y = self.vel.y first_collision_vel.z = self.vel.z first_collision_type = 5 if ( next_ball_pos.x + self.size >= stage.window.right ): # Right wall cur_time = ( stage.window.right - ( self.pos.x + self.size ) ) * time_res / self.vel.x if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = -self.vel.x first_collision_vel.y = self.vel.y first_collision_vel.z = self.vel.z first_collision_type = 5 if ( next_ball_pos.y - self.size <= 0 and self.vel.y != 0): # Top wall cur_time = ( self.pos.y - self.size ) * time_res / -self.vel.y if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x first_collision_vel.y = -self.vel.y first_collision_vel.z = self.vel.z first_collision_type = 5 if ( next_ball_pos.y + self.size >= stage.window.bottom and self.vel.y != 0): # Bottom wall cur_time = ( stage.window.bottom - ( self.pos.y + self.size ) ) * time_res / self.vel.y if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x first_collision_vel.y = -self.vel.y first_collision_vel.z = self.vel.z first_collision_type = 5 if ( next_ball_pos.z <= 0 ): # Front wall cur_time = self.pos.z * time_res / -self.vel.z if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x #(random.randint(0, 3))-1 * self.speed first_collision_vel.y = self.vel.y #(random.randint(0, 3))-1 * self.speed first_collision_vel.z = self.speed first_collision_type = 2 if ( next_ball_pos.z >= stage.depth ): # Back wall cur_time = ( stage.depth - self.pos.z ) * time_res / self.vel.z if ( first_collision_cur_time == -1 or cur_time < first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x #(random.randint(0, 3))-1 * self.speed first_collision_vel.y = self.vel.y #(random.randint(0, 3))-1 * self.speed first_collision_vel.z = -self.speed first_collision_type = 1 # Paddle collision. Paddle collisions are inaccurate, in that it doesn't take into account the velocity of # the ball in its 2D check, it uses the original 2D position. if ( self.vel.z < 0 and ( self.pos.z >= paddle1.pos.z or self.pos.z >= paddle1.pos.z - math.fabs(paddle1.delta.z) ) and ( next_ball_pos.z <= paddle1.pos.z or next_ball_pos.z <= paddle1.pos.z + math.fabs(paddle1.delta.z) ) and self.pos.x >= paddle1.pos.x - paddle1.halfwidth and self.pos.x <= paddle1.pos.x + paddle1.halfwidth and self.pos.y >= paddle1.pos.y - paddle1.halfheight and self.pos.y <= paddle1.pos.y + paddle1.halfheight ): cur_time = ( self.pos.z - paddle1.pos.z ) * time_res / -self.vel.z if ( first_collision_cur_time == -1 or cur_time <= first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x first_collision_vel.y = self.vel.y first_collision_vel.z = -self.vel.z # If paddle is moving forward, bounce the ball off. if ( paddle1.delta.z > 0 ): first_collision_vel.z += 4*246 # Apply some pong like angling based on where it hits the paddle. if ( next_ball_pos.x - paddle1.pos.x > 20 ): first_collision_vel.x += 2*256 if ( next_ball_pos.x - paddle1.pos.x < -20 ): first_collision_vel.x -= 2*256 if ( next_ball_pos.y - paddle1.pos.y > 15 ): first_collision_vel.y += 2*256 if ( next_ball_pos.y - paddle1.pos.y < -15 ): first_collision_vel.y -= 2*256 # Likewise if paddle is moving backwards, cushion it. #if ( paddle1.delta.z < 0 ): # first_collision_vel.z -= 2*256 first_collision_type = 3 # Computer paddle. if ( self.vel.z > 0 and ( self.pos.z <= paddle2.pos.z ) and ( next_ball_pos.z >= paddle2.pos.z ) and self.pos.x >= paddle2.pos.x - paddle2.halfwidth and self.pos.x <= paddle2.pos.x + paddle2.halfwidth and self.pos.y >= paddle2.pos.y - paddle2.halfheight and self.pos.y <= paddle2.pos.y + paddle2.halfheight ): # Paddle 2 cur_time = ( paddle2.pos.z - self.pos.z ) * time_res / self.vel.z if ( first_collision_cur_time == -1 or cur_time <= first_collision_cur_time ): # Set new first collision. first_collision_cur_time = cur_time first_collision_vel.x = self.vel.x first_collision_vel.y = self.vel.y first_collision_vel.z = -self.vel.z + ( paddle1.delta.z > 0 ) * 2*256 + ( paddle1.delta.z < 0 ) * 2*256 first_collision_type = 4 # Advance the ball to the point of the first collision. if ( first_collision_cur_time != -1 ): self.pos.x += self.vel.x * first_collision_cur_time / time_res self.pos.y += self.vel.y * first_collision_cur_time / time_res self.pos.z += self.vel.z * first_collision_cur_time / time_res self.vel.x = first_collision_vel.x self.vel.y = first_collision_vel.y self.vel.z = first_collision_vel.z time_left -= first_collision_cur_time collision_type = first_collision_type if ( not (first_collision_cur_time != -1 and time_left > 0) ): break # If there's time left in the frame w/o collision, finish it up. if time_left > 0: self.pos.x += self.vel.x * time_left / time_res self.pos.y += self.vel.y * time_left / time_res self.pos.z += self.vel.z * time_left / time_res # Apply gravity. self.vel.y += game.stage.gravity if ( self.pos.y + self.size + 20 > stage.window.bottom and math.fabs(self.vel.y) == 0 ): self.vel.y -= 6 self.vel.x += game.stage.crossgravity # Calculate scores if any collisions with the back wall happened. if collision_type == 1: paddle1.score += 1 game.scoresnd.play() elif collision_type == 2: paddle2.score += 1 game.scoresnd.play() elif collision_type == 3: game.paddle1snd.play() elif collision_type == 4: game.paddle2snd.play() elif collision_type == 5: game.wallsnd.play() return collision_type class Paddle: def __init__(self): # Center of the paddle self.pos = Vector() # Physics stuff self.delta = Vector() # Amount moved since last update for spin calc. self.halfwidth = 0 self.halfheight = 0 # Stuff for moving the paddle forward. self.targetz = 0 self.defaultz = 0 self.forwardz = 0 # AI stuff self.vel = Vector() self.speed = 0 # Game stuff self.score = 0 def draw_3d (self, stage): v = game.brightness/100.0 r = Rect() r.left = self.pos.x - self.halfwidth r.right = self.pos.x + self.halfwidth r.top = self.pos.y - self.halfheight r.bottom = self.pos.y + self.halfheight draw_rect_3d( game.drawimage, r.left, r.top, r.right, r.bottom, self.pos.z, v ) x = r.left + ( ( r.right - r.left ) / 2 ) draw_line_3d( game.drawimage, x, r.bottom, self.pos.z, x, stage.window.bottom, self.pos.z, v ) def clip_position (self): self.pos.x = max(self.pos.x, self.halfwidth) self.pos.y = max(self.pos.y, self.halfheight) self.pos.x = min(self.pos.x, game.stage.window.right - self.halfwidth) self.pos.y = min(self.pos.y, game.stage.window.bottom - self.halfheight) def setup_player(self, desc): w = to_fixed(desc['PaddleWidth']) h = to_fixed(desc['PaddleHeight']) self.halfwidth = w self.halfheight = h self.delta = zerovec self.pos = Vector(to_fixed(25), to_fixed(50), to_fixed(10)) self.clip_position() self.defaultz = self.pos.z self.targetz = self.pos.z self.forwardz = to_fixed(40) self.score = 0 def update_player (self, stage): """Check paddle inputs and apply to paddle.""" lastpos = Vector() lastpos.x = self.pos.x lastpos.y = self.pos.y lastpos.z = self.pos.z penx = game.mousex*100/screen_width peny = game.mousey*100/screen_height pendown = 1 if ( game.mousedown ): self.targetz = self.forwardz else: self.targetz = self.defaultz # Snaps forward, eases back. if ( self.pos.z < self.targetz ): if ( self.delta.z < to_fixed(4) ): self.delta.z = to_fixed(6) self.pos.z += self.delta.z + to_fixed(2) if ( self.pos.z > self.targetz ): self.pos.z = self.targetz if ( self.pos.z > self.targetz ): self.pos.z += ( self.targetz - self.pos.z ) / 4 # Get the 2d position from the pen. if ( pendown ): self.pos.x = to_fixed(penx) self.pos.y = to_fixed(peny) self.clip_position() self.delta.x = self.pos.x - lastpos.x self.delta.y = self.pos.y - lastpos.y self.delta.z = self.pos.z - lastpos.z def setup_ai(self, desc): w = to_fixed(desc['PaddleWidth']) h = to_fixed(desc['PaddleHeight']) self.score = 0 self.halfwidth = w self.halfheight = h self.pos = Vector(to_fixed(75), to_fixed(50), game.stage.depth - to_fixed(10)) self.defaultz = self.pos.z self.targetz = self.pos.z self.forwardz = game.stage.depth - 40*256 self.delta = zerovec self.vel = zerovec self.clip_position() def update_ai (self, ball, stage): """Compute AI and move paddle.""" # Only move when the ball is coming back, that way it appears to react to the players hit. # Actually, start moving just before the player hits it. if ( ball.vel.z > 0 or ball.vel.z < 0 and ball.pos.z < to_fixed(30)) : # Acceleration towards the ball. if ( math.fabs( ( self.pos.x - ball.pos.x ) ) > to_fixed(5) ): if ( self.pos.x < ball.pos.x ): self.vel.x += to_fixed(4) if ( self.pos.x > ball.pos.x ): self.vel.x -= to_fixed(4) if ( math.fabs( ( self.pos.y - ball.pos.y ) ) > to_fixed(5) ): if ( self.pos.y < ball.pos.y ): self.vel.y += to_fixed(4) if ( self.pos.y > ball.pos.y ): self.vel.y -= to_fixed(4) # Speed clamping self.vel.x = clamp( self.vel.x, -game.ai.speed, game.ai.speed ) self.vel.y = clamp( self.vel.y, -game.ai.speed, game.ai.speed ) elif ( ball.pos.z < game.stage.depth/2 ): self.vel.x = 0 self.vel.y = 0 # Drift towards the center. if ( game.ai.recenter ): self.pos.x += ( to_fixed(50) - self.pos.x ) / 4 self.pos.y += ( to_fixed(50) - self.pos.y ) / 4 # Friction if ( self.vel.x > 0 ): self.vel.x -= 1 if ( self.vel.x < 0 ): self.vel.x += 1 if ( self.vel.y > 0 ): self.vel.y -= 1 if ( self.vel.y < 0 ): self.vel.y += 1 self.pos.x += self.vel.x self.pos.y += self.vel.y self.clip_position() class Stage: def __init__(self): self.depth = 0 self.gravity = 0 self.crossgravity = 0 self.window = Rect() def setup(self, desc): self.name = desc['Name'] self.depth = to_fixed(desc['StageDepth']) self.gravity = to_fixed(desc['StageYGravity']) self.crossgravity = to_fixed(desc['StageXGravity']) self.window.left = 0 self.window.right = to_fixed(99) self.window.top = 0 self.window.bottom = to_fixed(99) def draw_3d (self): window = self.window # Wall grids. v = game.brightness/4/100.0 i = 1 while i < 5: x = i*(window.right-window.left)/5 i += 1 draw_line_3d(game.drawimage, x, window.top, 1, x, window.top, self.depth, v) draw_line_3d(game.drawimage, x, window.bottom, 1, x, window.bottom, self.depth, v) i = 1 while i < 5: x = i*(window.bottom-window.top)/5 i += 1 draw_line_3d(game.drawimage, window.left, x, 1, window.left, x, self.depth, v) draw_line_3d(game.drawimage, window.right, x, 1, window.right, x, self.depth, v) i = 1 while i < 5: x = i*(self.depth)/5 i += 1 draw_line_3d(game.drawimage, window.left, window.top, x, window.right, window.top, x, v) draw_line_3d(game.drawimage, window.left, window.bottom, x, window.right, window.bottom, x, v) draw_line_3d(game.drawimage, window.left, window.top, x, window.left, window.bottom, x, v) draw_line_3d(game.drawimage, window.right, window.top, x, window.right, window.bottom, x, v) # The actual stage. v = game.brightness/100.0 # Near and far rectangles draw_rect_3d( game.drawimage, window.left, window.top, window.right, window.bottom, 0, v ) draw_rect_3d( game.drawimage, window.left, window.top, window.right, window.bottom, self.depth, v ) # Diagonals draw_line_3d( game.drawimage, window.left, window.top, 1, window.left, window.top, self.depth, v ) draw_line_3d( game.drawimage, window.left, window.bottom, 1, window.left, window.bottom, self.depth, v ) draw_line_3d( game.drawimage, window.right, window.top, 1, window.right, window.top, self.depth, v ) draw_line_3d( game.drawimage, window.right, window.bottom, 1, window.right, window.bottom, self.depth, v ) class AI: def __init__(self): self.speed = 0 self.recenter = False def setup (self, desc): self.speed = to_fixed(desc['AISpeed']) self.recenter = desc['AIRecenter'] class IntroSequence: def enter (self): self.timer0 = 0 self.timer1 = 0 def leave (self): pass def draw_3d (self): if (self.timer1 == 1): game.draw_3d() def draw_cairo (self): if (self.timer1 == 1): game.draw_cairo() text_cairo(_("b o u n c e"), -1, -1, 100, self.timer0/100.0) def update (self): if (self.timer1 == 0): game.brightness = 0 self.timer0 += 1 if (self.timer0 >= 100): self.timer1 = 1 elif (self.timer1 == 1): if (game.brightness < 100): game.brightness += 1 self.timer0 -= 2 if (self.timer0 <= 0): game.introsnd.play() game.set_sequence(BallReleaseSequence()) class NewStageSequence: def __init__ (self, nextlevel): if nextlevel >= len(game.stage_descs): nextlevel = 0 self.nextlevel = nextlevel def enter (self): self.timer0 = 0 self.timer1 = 0 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() text_cairo(game.stage_descs[self.nextlevel]['Name'], -1, -1, 100, (100-game.brightness)/100.0) def update (self): if (self.timer1 == 0): if (game.brightness > 0): game.brightness -= 5 self.timer0 += 1 if (self.timer0 >= 20): self.timer0 = 0 self.timer1 = 1 game.set_level(self.nextlevel) elif (self.timer1 == 1): self.timer0 += 1 if (self.timer0 >= 20): self.timer0 = 0 self.timer1 = 2 elif (self.timer1 == 2): if (game.brightness < 100): game.brightness += 5 self.timer0 += 1 if (self.timer0 >= 20): game.introsnd.play() game.set_sequence(BallReleaseSequence()) class BallReleaseSequence: def enter (self): self.timer0 = 0 self.timer1 = 0 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() text_cairo(str(3-self.timer1), -1, -1, 20, math.sin(math.pi*self.timer0/35)) def update (self): if (game.brightness < 100): game.brightness += 1 self.timer0 += 1 if (self.timer0 > 35): self.timer1 += 1 self.timer0 = 0 if (self.timer1 >= 3): game.set_sequence(PlaySequence()) class PlaySequence: def enter (self): self.timer0 = 0 self.timer1 = 0 self.endtimeout = 0 game.brightness = 100 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() def update (self): # Process player input and AI. game.paddle1.update_player(game.stage) game.paddle2.update_ai(game.ball, game.stage) # Run the ball simulation. score = game.ball.update(game.paddle1, game.paddle2, game.stage) #if game.mousedown: # score = 1 # game.paddle1.score += 1 if score == 1 or score == 2: game.set_sequence(ScoreSequence()) class ScoreSequence: def enter (self): self.step = 0 self.num_steps = 20 #game.scoreWav.play() def leave (self): #game.ball.vel = Vector(to_fixed(2), to_fixed(2), game.ball.vel.z) pass def draw_3d (self): ring_spacing = to_fixed(1) ring_speed = to_fixed(1) num_rings = 10 v = (1.0-float(self.step)/self.num_steps) fill_circle_3d(game.drawimage, game.ball.lastpos.x+game.ball.lastvel.x*self.step/2, game.ball.lastpos.y+game.ball.lastvel.y*self.step/2, game.ball.lastpos.z+game.ball.lastvel.z*self.step/2, game.ball.size, v) random.seed(12345678) for ring in range(0, num_rings): b = (1.0-float(self.step)/self.num_steps)*(0.5+0.5*math.cos(math.pi*float(ring)/num_rings)) draw_circle_3d(game.drawimage, game.ball.lastpos.x+game.ball.lastvel.x*ring, game.ball.lastpos.y+game.ball.lastvel.y*ring, game.ball.lastpos.z+game.ball.lastvel.z*ring, (-ring+1)*ring_spacing + ring_speed*self.step, b) game.draw_3d() def draw_cairo (self): game.draw_cairo() def update (self): self.step += 1 if self.step >= self.num_steps: # Record the scores. game.stage_descs[game.curlevel]['Paddle1Score'] = game.paddle1.score game.stage_descs[game.curlevel]['Paddle2Score'] = game.paddle2.score # Win, Lose or Keep Playing. if game.paddle1.score == 5: if (game.curlevel == len(game.stage_descs)-1): game.set_sequence(WinSequence()) else: game.set_sequence(NewStageSequence(game.curlevel+1)) elif game.paddle2.score == 5: game.set_sequence(LoseSequence()) else: game.set_sequence(PlaySequence()) class LoseSequence: def enter (self): self.timer0 = 0 self.timer1 = 0 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() text_cairo("; - {", -1, -1, 24, self.timer0/100.0) def update (self): if (self.timer1 == 0): if (game.brightness > 0): game.brightness -= 5 self.timer0 += 2 if (self.timer0 >= 100): self.timer1 = 1 game.new_game() elif (self.timer1 == 1): self.timer0 -= 2 if (self.timer0 <= 0): game.set_sequence(IntroSequence()) self.timer0 = 0 self.timer1 = 0 class WinSequence: def enter (self): # Create a new score history entry. paddle1_score = 0 paddle2_score = 0 for i in range(0, len(game.stage_descs)): paddle1_score += game.stage_descs[i].get('Paddle1Score', 0) paddle2_score += game.stage_descs[i].get('Paddle2Score', 0) game.scores.append({'Player1Name':game.player1.props.nick, 'Player1Score':paddle1_score, 'Player2Name':game.player2.props.nick, 'Player2Score':paddle2_score}) self.timer0 = 0 self.timer1 = 0 def leave (self): pass def update (self): if (self.timer1 == 0): if (game.brightness > 0): game.brightness -= 5 if (game.brightness <= 0): self.timer0 = 0 self.timer1 = 1 elif (self.timer1 == 1): self.timer0 += 1 if self.timer0 >= 1000 or game.mousedown: self.timer1 = 2 self.timer0 = (len(game.stage_descs)-1)*30 elif (self.timer1 == 2): self.timer0 -= 1 if (self.timer0 <= 0): game.new_game() game.set_sequence(IntroSequence()) self.timer0 = 0 self.timer1 = 0 def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() starty = 250 total_score = 0 for i in range(0, len(game.stage_descs)): v = clamp(255*self.timer0/60.0 - i*60, 0, 255)/255.0 player_score = game.stage_descs[i].get('Paddle1Score', 0) ai_score = game.stage_descs[i].get('Paddle2Score', 0) diff_score = player_score - ai_score game.draw_score_cairo(250, starty + i*50, player_score, 1, v) text_cairo('-', 475, starty + i*50, 20, v) game.draw_score_cairo(550, starty + i*50, ai_score, 2, v) text_cairo('=', 775, starty + i*50, 20, v) game.draw_score_cairo(850, starty + i*50, diff_score, 1, v) text_cairo(game.stage_descs[i]['Name'], 125, starty + i*50, 24, v) total_score += diff_score v = self.timer0/60.0 game.cairo.set_source_rgb(v, v, v) game.cairo.move_to(250, starty + len(game.stage_descs)*50) game.cairo.line_to(950, starty + len(game.stage_descs)*50) game.cairo.stroke() x = 250 y = starty + (len(game.stage_descs)+1)*50 for j in range(0, 5*len(game.stage_descs)): game.cairo.move_to(game.xpoints[0][0]*20-10+x, game.xpoints[0][1]*20-10+y) for p in game.xpoints: game.cairo.line_to(p[0]*20-10+x, p[1]*20-10+y) game.cairo.line_to(game.xpoints[0][0]*20-10+x, game.xpoints[0][1]*20-10+y) if j >= total_score: game.cairo.stroke() else: game.cairo.fill() x += 30 if (x > 980): x = 250 y += 50 text = "; - |" if (total_score >= 5*len(game.stage_descs)): text = "; - D" elif (total_score >= 4*len(game.stage_descs)): text = "; - >" elif (total_score >= 3*len(game.stage_descs)): text = "; - )" elif (total_score >= 2*len(game.stage_descs)): text = "; - }" text_cairo(text, -1, 150, 24, v) class EditSequence: def enter (self): game.brightness = 100 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): game.draw_cairo() def update (self): pass class TestSequence: def enter (self): game.brightness = 100 def leave (self): pass def draw_3d (self): game.draw_3d() def draw_cairo (self): pass def update (self): # Process player input and AI. game.paddle1.update_player(game.stage) game.paddle2.update_ai(game.ball, game.stage) # Run the ball simulation. game.ball.update(game.paddle1, game.paddle2, game.stage) class Game: def __init__(self): self.endtimeout = 0 self.ai = AI() self.stage = Stage() self.ball = Ball() self.paddle1 = Paddle() self.paddle2 = Paddle() # Current stage. self.curlevel = 0 # Variables affecting the sequencer. self.sequence = IntroSequence() self.sequence.enter() self.brightness = 100 # Current mouse state. self.mousex = 0 self.mousey = 0 self.mousedown = 0 # Default stages. self.stage_descs = DEFAULT_STAGE_DESCS # Scores. self.scores = [] # The 'X' shape used as an icon. self.xpoints = [ (0,0), (0.3,0), (0.5,0.3), (0.7,0), (1,0), (0.7,0.5), (1,1), (0.7,1), (0.5,0.6), (0.3,1), (0,1), (0.3,0.5) ] # Create sounds. self.scoresnd = pygame.mixer.Sound(activity.get_bundle_path()+'/sound/score.wav') self.paddle1snd = pygame.mixer.Sound(activity.get_bundle_path()+'/sound/player1paddle.wav') self.paddle2snd = pygame.mixer.Sound(activity.get_bundle_path()+'/sound/player2paddle.wav') self.wallsnd = pygame.mixer.Sound(activity.get_bundle_path()+'/sound/wall.wav') self.introsnd = pygame.mixer.Sound(activity.get_bundle_path()+'/sound/intro.wav') def set_sequence (self, seq): self.sequence.leave() self.sequence = seq self.sequence.enter() def set_level(self, level): if level < 0 or level > len(self.stage_descs)-1: level = 0 self.curlevel = level desc = self.stage_descs[self.curlevel] self.stage.setup(desc) self.ball.setup(desc) self.ai.setup(desc) self.paddle1.setup_player(desc) self.paddle2.setup_ai(desc) def new_game(self): self.set_level(0) def draw_score_3d(self, x, y, score, player, v): for j in range(0, 5): px = x + j*30 py = y if j < score: fill_ellipse_2x(game.drawimage, px/2, py/2, 6, 6, int(v*255.0)) else: draw_ellipse_2x(game.drawimage, px/2, py/2, 6, 6, int(v*255.0)) def draw_score_cairo(self, x, y, score, player, c): game.cairo.set_source_rgb(c,c,c) for j in range(0, 5): px = x + j*30 py = y #if player == 1: # game.cairo.move_to(game.xpoints[0][0]*20-10+px, game.xpoints[0][1]*20-10+py) # for p in game.xpoints: # game.cairo.line_to(p[0]*20-10+px, p[1]*20-10+py) # game.cairo.line_to(game.xpoints[0][0]*20-10+px, game.xpoints[0][1]*20-10+py) #elif player == 2: game.cairo.move_to(px + 10, py) game.cairo.arc(px, py, 10, 0, 2*math.pi) if j < score: game.cairo.fill() else: game.cairo.stroke() def draw_3d(self): self.stage.draw_3d() self.paddle1.draw_3d(self.stage) self.paddle2.draw_3d(self.stage) self.ball.draw_3d(self.stage) v = self.brightness/100.0 self.draw_score_3d(screen_width*1/4-75, 30, self.paddle1.score, 1, v) self.draw_score_3d(screen_width*3/4-75, 30, self.paddle2.score, 2, v) def draw_cairo (self): #game.cairo.set_source_rgb(color[0]/255.0, color[1]/255.0, color[2]/255.0) #text_cairo(game.stage_descs[game.curlevel]['Name'], -1, 30, 24, v) text_cairo("%.2f fps" % self.fps, 50, 30, 12, 1.0) # Global game instance. game = Game() # Panel that appears above the game, allowing the currently active stage to be edited. class EditorPanel(gtk.EventBox): STEP_STAGENAME = 0 STEP_STAGEDEPTH = 1 STEP_STAGEGRAVITY = 2 STEP_BALL = 3 STEP_PADDLE = 4 STEP_AI = 5 STEP_MAX = 6 def __init__ (self, activity): gtk.EventBox.__init__(self) self.activity = activity self.hbox = gtk.HBox() self.add(self.hbox) self.hbox.set_border_width(10) self.hbox.set_spacing(10) self.prevbtn = gtk.Button() self.prevbtn.add(icon.Icon(icon_name='go-left')) self.prevbtn.connect('clicked', self.on_prev) self.nextbtn = gtk.Button() self.nextbtn.add(icon.Icon(icon_name='go-right')) self.nextbtn.connect('clicked', self.on_next) self.hbox.pack_end(self.nextbtn, False, False) self.hbox.pack_end(self.prevbtn, False, False) self.propbox = gtk.HBox() self.propbox.set_spacing(20) self.hbox.pack_start(self.propbox) self.separator = gtk.VSeparator() self.stage_label = gtk.Label() self.stage_label.set_markup(''+_('Stage')+'') self.stagename_label = gtk.Label(_('Name')) self.stagename_entry = gtk.Entry() self.stagename_entry.connect('changed', self.on_entry_changed) self.stagedepth_label = gtk.Label(_('Depth')) self.stagedepth_adjust = gtk.Adjustment(100, 10, 1000, 1) self.stagedepth_adjust.connect('value-changed', self.on_value_changed) self.stagedepth_scale = gtk.HScale(self.stagedepth_adjust) self.stagegravity_x_label = gtk.Label(_('X Gravity')) self.stagegravity_x_adjust = gtk.Adjustment(0, -3, 3, 1) self.stagegravity_x_adjust.connect('value-changed', self.on_value_changed) self.stagegravity_x_scale = gtk.HScale(self.stagegravity_x_adjust) self.stagegravity_y_label = gtk.Label(_('Y Gravity')) self.stagegravity_y_adjust = gtk.Adjustment(1, -3, 3, 1) self.stagegravity_y_adjust.connect('value-changed', self.on_value_changed) self.stagegravity_y_scale = gtk.HScale(self.stagegravity_y_adjust) self.ball_label = gtk.Label() self.ball_label.set_markup(''+_('Ball')+'') self.ballsize_label = gtk.Label(_('Size')) self.ballsize_adjust = gtk.Adjustment(1, 1, 5, 1) self.ballsize_adjust.connect('value-changed', self.on_value_changed) self.ballsize_scale = gtk.HScale(self.ballsize_adjust) self.ballspeed_label = gtk.Label(_('Speed')) self.ballspeed_adjust = gtk.Adjustment(10, 1, 20, 1) self.ballspeed_adjust.connect('value-changed', self.on_value_changed) self.ballspeed_scale = gtk.HScale(self.ballspeed_adjust) self.paddle_label = gtk.Label() self.paddle_label.set_markup(''+_('Paddle')+'') self.paddlesize_x_label = gtk.Label(_('X Size')) self.paddlesize_x_adjust = gtk.Adjustment(20, 1, 50, 1) self.paddlesize_x_adjust.connect('value-changed', self.on_value_changed) self.paddlesize_x_scale = gtk.HScale(self.paddlesize_x_adjust) self.paddlesize_y_label = gtk.Label(_('Y Size')) self.paddlesize_y_adjust = gtk.Adjustment(20, 1, 50, 1) self.paddlesize_y_adjust.connect('value-changed', self.on_value_changed) self.paddlesize_y_scale = gtk.HScale(self.paddlesize_y_adjust) self.ai_label = gtk.Label() self.ai_label.set_markup(''+_('AI')+'') self.aispeed_label = gtk.Label(_('Speed')) self.aispeed_adjust = gtk.Adjustment(1, 1, 10, 1) self.aispeed_adjust.connect('value-changed', self.on_value_changed) self.aispeed_scale = gtk.HScale(self.aispeed_adjust) self.show_all() self.ignore_changes = False self.set_step(EditorPanel.STEP_STAGENAME) def on_entry_changed (self, editable): if not self.ignore_changes: self.copy_to_desc(game.stage_descs[game.curlevel]) game.set_level(game.curlevel) self.activity.queue_draw() def on_value_changed (self, adjustment): if not self.ignore_changes: self.copy_to_desc(game.stage_descs[game.curlevel]) game.set_level(game.curlevel) self.activity.queue_draw() def copy_from_desc (self, desc): self.ignore_changes = True self.stagename_entry.set_text(desc['Name']) self.stagedepth_adjust.set_value(desc['StageDepth']) self.stagegravity_x_adjust.set_value(desc['StageXGravity']) self.stagegravity_y_adjust.set_value(desc['StageYGravity']) self.ballsize_adjust.set_value(desc['BallSize']) self.ballspeed_adjust.set_value(desc['BallSpeed']) self.paddlesize_x_adjust.set_value(desc['PaddleWidth']) self.paddlesize_y_adjust.set_value(desc['PaddleHeight']) self.aispeed_adjust.set_value(desc['AISpeed']) self.ignore_changes = False def copy_to_desc (self, desc): desc['Name'] = self.stagename_entry.get_text() desc['StageDepth'] = self.stagedepth_adjust.get_value() desc['StageXGravity'] = self.stagegravity_x_adjust.get_value() desc['StageYGravity'] = self.stagegravity_y_adjust.get_value() desc['BallSize'] = self.ballsize_adjust.get_value() desc['BallSpeed'] = self.ballspeed_adjust.get_value() desc['PaddleWidth'] = self.paddlesize_x_adjust.get_value() desc['PaddleHeight'] = self.paddlesize_y_adjust.get_value() desc['AISpeed'] = self.aispeed_adjust.get_value() def update_step_btns(self): self.prevbtn.set_sensitive(self.step > 0) self.nextbtn.set_sensitive(self.step < EditorPanel.STEP_MAX-1) def on_prev (self, btn): if self.step > 0: self.set_step(self.step-1) def on_next (self, btn): if self.step < EditorPanel.STEP_MAX-1: self.set_step(self.step+1) def set_step (self, step): for w in self.propbox.get_children(): self.propbox.remove(w) self.step = step self.update_step_btns() if self.step == EditorPanel.STEP_STAGENAME: self.propbox.pack_start(self.stage_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.stagename_label, False, False) self.propbox.pack_start(self.stagename_entry) if self.step == EditorPanel.STEP_STAGEDEPTH: self.propbox.pack_start(self.stage_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.stagedepth_label, False, False) self.propbox.pack_start(self.stagedepth_scale) if self.step == EditorPanel.STEP_STAGEGRAVITY: self.propbox.pack_start(self.stage_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.stagegravity_x_label, False, False) self.propbox.pack_start(self.stagegravity_x_scale) self.propbox.pack_start(self.stagegravity_y_label, False, False) self.propbox.pack_start(self.stagegravity_y_scale) if self.step == EditorPanel.STEP_BALL: self.propbox.pack_start(self.ball_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.ballsize_label, False, False) self.propbox.pack_start(self.ballsize_scale) self.propbox.pack_start(self.ballspeed_label, False, False) self.propbox.pack_start(self.ballspeed_scale) if self.step == EditorPanel.STEP_PADDLE: self.propbox.pack_start(self.paddle_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.paddlesize_x_label, False, False) self.propbox.pack_start(self.paddlesize_x_scale) self.propbox.pack_start(self.paddlesize_y_label, False, False) self.propbox.pack_start(self.paddlesize_y_scale) if self.step == EditorPanel.STEP_AI: self.propbox.pack_start(self.ai_label, False, False) self.propbox.pack_start(self.separator, False, False) self.propbox.pack_start(self.aispeed_label, False, False) self.propbox.pack_start(self.aispeed_scale) self.propbox.show_all() # Panel that appears over the game, showing a list of scores. Can be toggled on and off without pausing the game. class ScorePanel(gtk.VBox): def __init__ (self): gtk.VBox.__init__(self) # Header bar. headerbox = gtk.VBox() headerbox.set_spacing(20) lbl = gtk.Label() lbl.set_markup(""+_('History of Games')+"") headerbox.pack_start(lbl, False) headerbox.pack_start(gtk.HSeparator(), False) hbox = gtk.HBox() hbox.set_homogeneous(True) lbl = gtk.Label() lbl.set_markup(''+_('Player 1')+'') hbox.pack_start(lbl) lbl = gtk.Label() lbl.set_markup(''+_('Score')+'') hbox.pack_start(lbl) lbl = gtk.Label() lbl.set_markup(''+_('Player 2')+'') hbox.pack_end(lbl) lbl = gtk.Label() lbl.set_markup(''+_('Score')+'') hbox.pack_end(lbl) headerbox.pack_start(hbox, False) headerbox.pack_start(gtk.HSeparator(), False) # Score box. self.scorebox = gtk.VBox() self.scorebox.set_spacing(10) vbox = gtk.VBox() vbox.set_border_width(20) vbox.set_spacing(10) vbox.pack_start(headerbox, False) vbox.pack_start(self.scorebox, False) vbox.pack_start(gtk.HSeparator(), False) self.scroll = gtk.ScrolledWindow() self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) self.scroll.add_with_viewport(vbox) frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_OUT) frame.add(self.scroll) self.add(frame) self.rebuild() self.show_all() def rebuild (self): for w in self.scorebox.get_children(): w.destroy() for s in game.scores: hbox = gtk.HBox() hbox.set_homogeneous(True) hbox.pack_start(gtk.Label(s['Player1Name'])) hbox.pack_start(gtk.Label(s['Player1Score'])) hbox.pack_end(gtk.Label(s['Player2Name'])) hbox.pack_end(gtk.Label(s['Player2Score'])) self.scorebox.pack_start(hbox, False) # This is a fake Presence Service Buddy object that represents computer players. class ComputerBuddyProps: def __init__ (self): self.nick = 'Computer' class ComputerBuddy: def __init__ (self): self.props = ComputerBuddyProps() # Activity class for the game. Defines the game user interface (toolbar, etc), controls loading & saving, interacts # with the Sugar environment. class BounceActivity(activity.Activity): # Game mode definitions. MODE_GAME = 0 MODE_EDIT = 1 def __init__ (self, handle): activity.Activity.__init__(self, handle) self.set_title(_("Bounce")) # Build the toolbars. self.build_toolbox() # Set up the drawing window and context. self.build_drawarea() # Build the editor. self.build_editor() # Build the score panel. self.build_scorepanel() # Turn off double buffering except for the drawarea, which mixes cairo and custom drawing. self.set_double_buffered(False) self.drawarea.set_double_buffered(True) # Initialize the game. game.new_game() self.paused = False self.mode = BounceActivity.MODE_GAME # Initialize the FPS counter & limiter. self.lastclock = time.time() game.fps = 0.0 self.limitfps = 20.0 # 20fps is what the XO can currently handle. # Get current player info for the scores table. self.pservice = presenceservice.get_instance() self.owner = self.pservice.get_owner() # Fill in game players. game.player1 = self.owner game.player2 = ComputerBuddy() # Show everything except the panels. self.set_canvas(self.drawarea) self.show_interface() # Get the mainloop ready to run (this should come last). gobject.timeout_add(50, self.mainloop) #----------------------------------------------------------------------------------------------------------------- # User interface building. def build_drawarea (self): self.drawarea = gtk.Layout() #self.drawarea.set_visible_window(True) self.drawarea.set_size_request(gtk.gdk.screen_width(), gtk.gdk.screen_height()) self.vbox = gtk.VBox() self.drawarea.put(self.vbox, 0, 0) self.drawarea.connect('destroy', self.on_destroy) #self.drawarea.connect('configure-event', self.on_drawarea_resize) self.drawarea.connect('expose-event', self.on_drawarea_expose) self.drawarea.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.BUTTON_RELEASE_MASK) self.drawarea.connect('motion-notify-event', self.on_mouse) self.drawarea.connect('button-press-event', self.on_mouse) self.drawarea.connect('button-release-event', self.on_mouse) self.drawimage = None game.drawimage = None def build_gamebox (self): self.pausebtn = toolbutton.ToolButton('media-playback-pause') self.pausebtn.set_tooltip(_("Pause Game")) self.pausebtn.connect('clicked', self.on_game_pause) self.showscoresbtn = toggletoolbutton.ToggleToolButton('Show-history') self.showscoresbtn.set_tooltip(_("Show History")) self.showscoresbtn.connect('clicked', self.on_game_showscores) self.clearscoresbtn = toolbutton.ToolButton('Reset-history') self.clearscoresbtn.set_tooltip(_("Reset History")) self.clearscoresbtn.connect('clicked', self.on_game_clearscores) sep = gtk.SeparatorToolItem() sep.set_expand(True) sep.set_draw(False) self.editbtn = toolbutton.ToolButton('dialog-ok') self.editbtn.set_tooltip(_("Edit")) self.editbtn.connect('clicked', self.on_edit) self.gamebox = gtk.Toolbar() self.gamebox.insert(self.pausebtn, -1) self.gamebox.insert(self.showscoresbtn, -1) self.gamebox.insert(self.clearscoresbtn, -1) self.gamebox.insert(sep, -1) self.gamebox.insert(self.editbtn, -1) def build_editbox (self): self.testbtn = toggletoolbutton.ToggleToolButton('zoom-in') self.testbtn.set_tooltip(_("Test Stage")) self.testbtn.connect('clicked', self.on_edit_test) self.prevstagebtn = toolbutton.ToolButton('go-left') self.prevstagebtn.set_tooltip(_("Previous Stage")) self.prevstagebtn.connect('clicked', self.on_edit_prevstage) self.nextstagebtn = toolbutton.ToolButton('go-right') self.nextstagebtn.set_tooltip(_("Next Stage")) self.nextstagebtn.connect('clicked', self.on_edit_nextstage) self.deletestagebtn = toolbutton.ToolButton('list-remove') self.deletestagebtn.set_tooltip(_("Delete Stage")) self.deletestagebtn.connect('clicked', self.on_edit_deletestage) self.addstagebtn = toolbutton.ToolButton('list-add') self.addstagebtn.set_tooltip(_("Add New Stage")) self.addstagebtn.connect('clicked', self.on_edit_addstage) sep = gtk.SeparatorToolItem() sep.set_expand(True) sep.set_draw(False) self.gamebtn = toolbutton.ToolButton('dialog-ok') self.gamebtn.set_tooltip(_("Play")) self.gamebtn.connect('clicked', self.on_game) self.editbox = gtk.Toolbar() self.editbox.insert(self.testbtn, -1) self.editbox.insert(gtk.SeparatorToolItem(), -1) self.editbox.insert(self.prevstagebtn, -1) self.editbox.insert(self.nextstagebtn, -1) self.editbox.insert(gtk.SeparatorToolItem(), -1) self.editbox.insert(self.deletestagebtn, -1) self.editbox.insert(self.addstagebtn, -1) self.editbox.insert(sep, -1) self.editbox.insert(self.gamebtn, -1) def build_toolbox (self): self.build_gamebox() self.tbox = activity.ActivityToolbox(self) self.tbox.add_toolbar(_("Game"), self.gamebox) self.tbox.show_all() self.set_toolbox(self.tbox) def build_editor (self): self.editor = EditorPanel(self) self.vbox.pack_start(self.editor, False) def build_scorepanel (self): self.scorepanel = ScorePanel() align = gtk.Alignment(0.0, 0.0, 1.0, 1.0) align.set_padding(50, 50, 100, 100) align.add(self.scorepanel) self.vbox.pack_start(align, True, True) def show_interface (self): self.show_all() if self.showscoresbtn.get_active(): self.scorepanel.show_all() else: self.scorepanel.hide_all() if self.mode == BounceActivity.MODE_EDIT: self.editor.show_all() else: self.editor.hide_all() #----------------------------------------------------------------------------------------------------------------- # Game toolbar def on_game_pause (self, button): self.pause_game(not self.paused) def on_game_showscores (self, button): if button.get_active(): self.scorepanel.rebuild() self.scorepanel.show_all() else: self.scorepanel.hide_all() def on_game_clearscores (self, button): msg = alert.ConfirmationAlert() msg.props.title = _('Reset History?') msg.props.msg = _('This will erase the history of games played.') def response(alert, response_id, self): self.remove_alert(alert) if response_id is gtk.RESPONSE_OK: game.scores = [] self.scorepanel.rebuild() msg.connect('response', response, self) self.add_alert(msg) msg.show_all() #----------------------------------------------------------------------------------------------------------------- # Edit toolbar def on_edit_test (self, button): if button.get_active(): game.set_sequence(TestSequence()) self.pause_game(False) self.queue_draw() else: self.pause_game(True) game.set_level(game.curlevel) game.set_sequence(EditSequence()) self.queue_draw() def edit_stage(self, stage_num): game.set_level(stage_num) self.editor.copy_from_desc(game.stage_descs[stage_num]) self.queue_draw() self.prevstagebtn.set_sensitive(stage_num > 0) self.nextstagebtn.set_sensitive(stage_num < len(game.stage_descs)-1) def on_edit_prevstage (self, button): if game.curlevel > 0: self.edit_stage(game.curlevel-1) def on_edit_nextstage (self, button): if game.curlevel < len(game.stage_descs)-1: self.edit_stage(game.curlevel+1) def on_edit_addstage (self, button): game.stage_descs.append(NEW_STAGE.copy()) self.edit_stage(len(game.stage_descs)-1) def on_edit_deletestage (self, button): if len(game.stage_descs) <= 1: return del game.stage_descs[game.curlevel] if game.curlevel > len(game.stage_descs)-1: self.edit_stage(len(game.stage_descs)-1) else: self.edit_stage(game.curlevel) #----------------------------------------------------------------------------------------------------------------- # DrawArea event handlers - Drawing, resizing, etc. def on_drawarea_resize (self): rect = self.drawarea.get_allocation() #log.debug("drawarea resized to %dx%d", rect[2], rect[3]) self.vbox.set_size_request(rect[2], rect[3]) # Set 3D parameters. global screen_width global screen_height screen_width = rect[2] screen_height = rect[3] set_3d_params(screen_width, screen_height, viewport_scale) # Rebuild drawimage. self.drawimage = gtk.gdk.Image(gtk.gdk.IMAGE_FASTEST, gtk.gdk.visual_get_system(), rect[2], rect[3]) game.drawimage = self.drawimage return True def on_drawarea_expose (self, widget, event): if not self.drawarea.bin_window: return True rect = self.drawarea.get_allocation() if self.drawimage is None or (rect[2] != screen_width or rect[3] != screen_height): self.on_drawarea_resize() # Perform 3D rendering to the offscreen image and draw it to the screen. clear_image(self.drawimage) game.sequence.draw_3d() gc = self.drawarea.get_style().fg_gc[gtk.STATE_NORMAL] self.drawarea.bin_window.draw_image(gc, self.drawimage, 0, 0, 0, 0, -1, -1) # Perform Cairo rendering over the top. game.cairo = self.drawarea.bin_window.cairo_create() game.sequence.draw_cairo() game.cairo = None # Hack to fix toolbox refresh. #self.tbox.queue_draw() #----------------------------------------------------------------------------------------------------------------- # Game control - Editor / Game modes, pausing, etc. def set_mode (self, mode): self.mode = mode if self.mode == BounceActivity.MODE_GAME: self.tbox.remove_toolbar(1) self.build_gamebox() self.tbox.add_toolbar(_("Game"), self.gamebox) self.gamebox.show_all() self.tbox.set_current_toolbar(1) self.pause_game(False) self.editor.hide_all() game.set_sequence(IntroSequence()) self.queue_draw() if self.mode == BounceActivity.MODE_EDIT: self.showscoresbtn.set_active(False) game.scores = [] self.scorepanel.rebuild() self.tbox.remove_toolbar(1) self.build_editbox() self.tbox.add_toolbar(_("Edit"), self.editbox) self.editbox.show_all() self.tbox.set_current_toolbar(1) self.edit_stage(game.curlevel) self.pause_game(True) self.editor.show_all() game.set_sequence(EditSequence()) self.queue_draw() def on_edit (self, button): msg = alert.ConfirmationAlert() msg.props.title = _('Change to Edit Mode?') msg.props.msg = _('If you change to edit mode, the history of games will be cleared.') def response(alert, response_id, self): self.remove_alert(alert) if response_id is gtk.RESPONSE_OK: self.set_mode(BounceActivity.MODE_EDIT) msg.connect('response', response, self) self.add_alert(msg) msg.show_all() def on_game (self, button): self.set_mode(BounceActivity.MODE_GAME) def pause_game (self, p): self.paused = p if self.paused: self.pausebtn.set_icon('media-playback-start') else: self.pausebtn.set_icon('media-playback-pause') def on_mouse (self, widget, event): game.mousex = int(event.x) game.mousey = int(event.y) if event.type == gtk.gdk.BUTTON_PRESS: # Simple cheat for testing. #if (game.paddle1.score < 5): # game.paddle1.score += 1 game.mousedown = 1 if event.type == gtk.gdk.BUTTON_RELEASE: game.mousedown = 0 #----------------------------------------------------------------------------------------------------------------- # Main loop def on_destroy (self, widget): self.running = False def tick (self): if self.paused: return True # Update current game sequence and animate. game.sequence.update() self.drawarea.queue_draw() # Compute framerate. diff = float(time.time() - self.lastclock) if diff > 0: game.fps = 1.0 / diff self.lastclock = time.time() return True def mainloop (self): """Runs the game loop. Note that this doesn't actually return until the activity ends.""" self.running = True clock = pygame.time.Clock() while self.running: clock.tick(self.limitfps) self.tick() while gtk.events_pending(): gtk.main_iteration(False) return False #----------------------------------------------------------------------------------------------------------------- # Journal integration def read_file(self, file_path): # Load document. if self.metadata['mime_type'] == 'text/plain': fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() storage = json.loads(data) # Restore stages. game.stage_descs = storage['Stages'] game.scores = storage['Scores'] # Restore activity state. game.set_level(storage.get('curlevel', 0)) self.set_mode(storage.get('mode', BounceActivity.MODE_GAME)) self.showscoresbtn.set_active(storage.get('history_visible', False)) # Always start resumed games paused, with the scores up. self.pause_game(True) # (not working at the moment, the window comes up but the button stays inactive) if self.mode == BounceActivity.MODE_GAME: self.showscoresbtn.set_active(True) def write_file(self, file_path): # Save document. if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' storage = {} # Save stages. storage['Stages'] = game.stage_descs storage['Scores'] = game.scores # Save activity state. storage['curlevel'] = game.curlevel storage['mode'] = self.mode storage['history_visible'] = self.showscoresbtn.get_active() fd = open(file_path, 'w') try: fd.write(json.dumps(storage)) finally: fd.close() #def take_screenshot (self): # if self.easelarea and self.drawarea.bin_window: # self._preview.take_screenshot(self.drawarea.bin_window)