1#!/usr/local/bin/python3.8 2""" 3Copyright (C) 2003 John-Paul Gignac 4 5This program is free software; you can redistribute it and/or modify 6it under the terms of the GNU General Public License as published by 7the Free Software Foundation; either version 2 of the License, or 8(at your option) any later version. 9 10This program is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program; if not, write to the Free Software 17Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18""" 19 20# Import Modules 21import os, pygame, random, time, math, re, sys, md5 22from pygame.locals import * 23 24# Parse the command line 25highscores_file = "pathological_scores" 26screenshot = 0 27fullscreen = 0 28colorblind = 0 29sound_on = 1 30music_on = 1 31for arg in sys.argv[1:]: 32 if arg == '-s': 33 screenshot = 1 34 elif arg == '-f': 35 fullscreen = 1 36 elif arg == '-cb': 37 colorblind = 1 38 elif arg == '-q': 39 sound_on = 0 40 music_on = 0 41 elif arg[0] == '-': 42 print "Usage: "+sys.argv[0]+" [-cb] [-f] [-s] [highscores-file]\n" 43 sys.exit(1) 44 else: 45 highscores_file = arg 46 47if colorblind: 48 cbext = '-cb.png' 49else: 50 cbext = '.png' 51 52# The location of the setgid script for writing highscores 53# This script is only used if the highscores file is not writable directly 54write_highscores = "/usr/lib/pathological/bin/write-highscores" 55 56# Game constants 57wheel_steps = 9 58frames_per_sec = 100 59timer_width = 36 60timer_margin = 4 61info_height = 20 62initial_lives = 3 63extra_life_frequency = 5000 # Extra life awarded every this many points 64max_spare_lives = 10 65 66# Volume levels 67intro_music_volume = 0.6 68ingame_music_volume = 0.9 69sound_effects_volume = 0.6 70 71# Changing these may affect the playability of levels 72default_colors = (2,3,4,6) # Blue, Green, Yellow, Red 73default_stoplight = (6,4,3) # Red, Yellow, Green 74default_launch_timer = 6 # 6 passes 75default_board_timer = 30 # 30 seconds per wheel 76marble_speed = 2 # Marble speed in pixels/frame (must be 1, 2 or 4) 77trigger_time = 30 # 30 seconds 78replicator_delay = 35 # 35 frames 79 80# Don't change these constants unless you 81# redo all of the levels 82horiz_tiles = 8 83vert_tiles = 6 84 85# Don't change these constants unless you 86# update the graphics files correspondingly. 87screen_width = 800 88screen_height = 600 89marble_size = 28 90tile_size = 92 91wheel_margin = 4 92stoplight_marble_size = 28 93life_marble_size = 16 94 95# The positions of the holes in the wheels in 96# each of the three rotational positions 97holecenter_radius = (tile_size - marble_size) / 2 - wheel_margin 98holecenters = [] 99for i in range(wheel_steps): 100 theta = math.pi * i / (2 * wheel_steps) 101 c = math.floor( 0.5 + math.cos(theta)*holecenter_radius) 102 s = math.floor( 0.5 + math.sin(theta)*holecenter_radius) 103 holecenters.append(( 104 (tile_size/2 + s, tile_size/2 - c), 105 (tile_size/2 + c, tile_size/2 + s), 106 (tile_size/2 - s, tile_size/2 + c), 107 (tile_size/2 - c, tile_size/2 - s))) 108 109# Direction references 110dirs = ((0,-1),(1,0),(0,1),(-1,0)) 111 112# More global variables 113board_width = horiz_tiles * tile_size 114board_height = vert_tiles * tile_size 115launch_timer_pos = (0,info_height) 116board_pos = (timer_width, info_height + marble_size) 117timer_height = board_height + marble_size 118music_loaded = 0 119 120# Functions to create our resources 121def load_image(name, colorkey=-1, size=None): 122 fullname = os.path.join('graphics', name) 123 try: 124 image = pygame.image.load(fullname) 125 except pygame.error, message: 126 print 'Cannot load image:', fullname 127 raise SystemExit, message 128 129 if size is not None: 130 image = pygame.transform.scale( image, size) 131 image = image.convert() 132 133 if colorkey is not None: 134 if colorkey is -1: 135 colorkey = image.get_at((0,0)) 136 image.set_colorkey(colorkey, RLEACCEL) 137 return image 138 139def load_sound(name, volume=1.0): 140 class NoneSound: 141 def play(self): pass 142 if not pygame.mixer or not pygame.mixer.get_init(): 143 return NoneSound() 144 fullname = os.path.join('sounds', name) 145 try: 146 sound = pygame.mixer.Sound(fullname) 147 except pygame.error, message: 148 print 'Cannot load sound:', fullname 149 return NoneSound() 150 151 sound.set_volume( volume * sound_effects_volume) 152 153 return sound 154 155def play_sound(sound): 156 if sound_on: sound.play() 157 158def start_music(name, volume=-1): 159 global music_pending_song, music_loaded, music_volume 160 161 music_volume = volume 162 163 if not music_on: 164 music_pending_song = name 165 return 166 167 if not pygame.mixer or not pygame.mixer.music: 168 print "Background music not available." 169 return 170 fullname = os.path.join('music', name) 171 try: 172 pygame.mixer.music.load(fullname) 173 except pygame.error, message: 174 print 'Cannot load music:', fullname 175 return 176 music_loaded = 1 177 pygame.mixer.music.play(-1) 178 179 if music_volume >= 0: 180 pygame.mixer.music.set_volume( music_volume) 181 182 music_pending_song = 0 183 184def toggle_fullscreen(): 185 global fullscreen 186 if pygame.display.toggle_fullscreen(): 187 fullscreen = fullscreen ^ 1 188 return 1 189 else: 190 return 0 191 192def toggle_sound(): 193 global sound_on 194 sound_on = sound_on ^ 1 195 196def toggle_music(): 197 global music_on 198 music_on = music_on ^ 1 199 if music_on: 200 if music_pending_song: 201 start_music( music_pending_song) 202 elif music_loaded: 203 pygame.mixer.music.unpause() 204 elif music_loaded: 205 if not music_pending_song: 206 pygame.mixer.music.pause() 207 208# A better tick function 209next_frame = pygame.time.get_ticks() 210def my_tick( frames_per_sec): 211 global next_frame 212 # Wait for the next frame 213 next_frame += 1000.0 / frames_per_sec 214 now = pygame.time.get_ticks() 215 if next_frame < now: 216 # No time to wait - just hide our mistake 217 # and keep going as fast as we can. 218 next_frame = now 219 else: 220 pygame.time.wait( int(next_frame) - now) 221 222# Load the sounds 223def load_sounds(): 224 global filter_admit,wheel_turn,wheel_completed,change_color 225 global direct_marble,ping,trigger_setup,teleport,marble_release 226 global levelfinish,die,incorrect,switch,shredder,replicator 227 global extra_life,menu_scroll,menu_select 228 229 filter_admit = load_sound('filter_admit.wav', 0.8) 230 wheel_turn = load_sound('wheel_turn.wav', 0.8) 231 wheel_completed = load_sound('wheel_completed.wav', 0.7) 232 change_color = load_sound('change_color.wav', 0.8) 233 direct_marble = load_sound('direct_marble.wav', 0.6) 234 ping = load_sound('ping.wav', 0.8) 235 trigger_setup = load_sound('trigger_setup.wav') 236 teleport = load_sound('teleport.wav', 0.6) 237 marble_release = load_sound('marble_release.wav', 0.5) 238 levelfinish = load_sound('levelfinish.wav', 0.6) 239 die = load_sound('die.wav') 240 incorrect = load_sound('incorrect.wav', 0.15) 241 switch = load_sound('switch.wav') 242 shredder = load_sound('shredder.wav') 243 replicator = load_sound('replicator.wav') 244 extra_life = load_sound('extra_life.wav') 245 menu_scroll = load_sound('menu_scroll.wav', 0.8) 246 menu_select = load_sound('switch.wav') 247 248# Load the fonts for various parts of the game 249def load_fonts(): 250 global launch_timer_font,active_marbles_font,popup_font,info_font 251 252 launch_timer_font = pygame.font.Font(None, timer_width - 2*timer_margin) 253 active_marbles_font = pygame.font.Font(None, marble_size) 254 popup_font = pygame.font.Font(None, 24) 255 info_font = pygame.font.Font(None, info_height) 256 257# Load all of the images for the various game classes. 258# The images are stored as class variables in the corresponding classes. 259def load_images(): 260 Marble.images = [] 261 for i in range(9): 262 Marble.images.append( load_image('marble-'+`i`+cbext, -1, 263 (marble_size, marble_size))) 264 265 Tile.plain_tiles = [] 266 Tile.tunnels = [] 267 for i in range(16): 268 tile = load_image('tile.png', (206,53,53), (tile_size,tile_size)) 269 path = load_image('path-'+`i`+'.png', -1, (tile_size,tile_size)) 270 tile.blit( path, (0,0)) 271 Tile.plain_tiles.append( tile) 272 Tile.tunnels.append(load_image('tunnel-'+`i`+'.png', 273 -1,(tile_size,tile_size))) 274 Tile.paths = 0 275 276 Wheel.images = ( 277 load_image('wheel.png',-1,(tile_size,tile_size)), 278 load_image('wheel-dark.png',-1,(tile_size, tile_size)), 279 ) 280 Wheel.blank_images = ( 281 load_image('blank-wheel.png',-1,(tile_size,tile_size)), 282 load_image('blank-wheel-dark.png',-1,(tile_size, tile_size)), 283 ) 284 Wheel.moving_holes = ( 285 load_image('moving-hole.png',-1,(marble_size,marble_size)), 286 load_image('moving-hole-dark.png',-1,(marble_size, marble_size)), 287 ) 288 289 Buffer.bottom = load_image('buffer.png',-1,(tile_size,tile_size)) 290 Buffer.top = load_image('buffer-top.png',-1,(tile_size,tile_size)) 291 292 Painter.images = [] 293 for i in range(8): 294 Painter.images.append( load_image('painter-'+`i`+cbext, -1, 295 (tile_size,tile_size))) 296 297 Filter.images = [] 298 for i in range(8): 299 Filter.images.append( load_image('filter-'+`i`+cbext, -1, 300 (tile_size,tile_size))) 301 302 Director.images = ( 303 load_image('director-0.png',-1,(tile_size,tile_size)), 304 load_image('director-1.png',-1,(tile_size,tile_size)), 305 load_image('director-2.png',-1,(tile_size,tile_size)), 306 load_image('director-3.png',-1,(tile_size,tile_size)), 307 ) 308 309 Shredder.image = load_image('shredder.png',-1,(tile_size,tile_size)) 310 311 Switch.images = [] 312 for i in range(4): 313 Switch.images.append( []) 314 for j in range(4): 315 if i == j: Switch.images[i].append( None) 316 else: Switch.images[i].append( load_image( 317 'switch-'+`i`+`j`+'.png',-1,(tile_size,tile_size))) 318 319 Replicator.image = load_image('replicator.png',-1,(tile_size,tile_size)) 320 321 Teleporter.image_h = load_image('teleporter-h.png',-1,(tile_size,tile_size)) 322 Teleporter.image_v = load_image('teleporter-v.png',-1,(tile_size,tile_size)) 323 324 Trigger.image = load_image('trigger.png',-1,(tile_size,tile_size)) 325 326 Stoplight.image = load_image('stoplight.png',-1,(tile_size,tile_size)) 327 Stoplight.smallmarbles = [] 328 for im in Marble.images: 329 Stoplight.smallmarbles.append( pygame.transform.scale(im, 330 (stoplight_marble_size,stoplight_marble_size))) 331 332 Board.life_marble = load_image('life-marble.png', -1, 333 (life_marble_size, life_marble_size)) 334 Board.launcher_background = load_image('launcher.png', None, 335 (horiz_tiles * tile_size,marble_size)) 336 Board.launcher_v = load_image('launcher-v.png', None, 337 (marble_size, vert_tiles * tile_size + marble_size)) 338 Board.launcher_corner = load_image('launcher-corner.png', (255,0,0), 339 ((tile_size-marble_size)/2+marble_size,marble_size)) 340 Board.launcher_entrance = load_image('entrance.png', -1, 341 (tile_size,marble_size)) 342 343 IntroScreen.background = load_image('intro.png', None, 344 (screen_width, screen_height)) 345 IntroScreen.menu_font = pygame.font.Font( 346 None, IntroScreen.menu_font_height) 347 IntroScreen.scroller_font = pygame.font.Font( 348 None, IntroScreen.scroller_font_height) 349 IntroScreen.hs_font = pygame.font.Font( 350 None, IntroScreen.hs_font_height) 351 352# Function to set the video mode 353def set_video_mode(): 354 global screen 355 356 icon = pygame.image.load(os.path.join('graphics','icon.png')) 357 icon.set_colorkey(icon.get_at((0,0)), RLEACCEL) 358 pygame.display.set_icon(icon) # Needed both before and after set_mode 359 screen = pygame.display.set_mode( (screen_width, screen_height), 360 fullscreen * FULLSCREEN) 361 pygame.display.set_icon(icon) # Needed both before and after set_mode 362 pygame.display.set_caption('Pathological') 363 364# Classes for our game objects 365 366class Marble: 367 def __init__(self, color, center, direction): 368 self.color = color 369 self.rect = pygame.Rect((0,0,marble_size,marble_size)) 370 self.rect.center = center 371 self.direction = direction 372 373 def update(self, board): 374 self.rect.move_ip( 375 marble_speed * dirs[self.direction][0], 376 marble_speed * dirs[self.direction][1]) 377 378 board.affect_marble( self) 379 380 def undraw(self, screen, background): 381 screen.set_clip( self.rect) 382 screen.blit( background, (0,0)) 383 screen.set_clip() 384 385 def draw(self, screen): 386 screen.blit( self.images[self.color], self.rect.topleft) 387 388class Tile: 389 def __init__(self, paths=0, center=None): 390 self.paths = paths 391 392 if center is None: 393 center = (0,0) 394 395 self.center = center 396 self.rect = pygame.Rect((0,0,tile_size,tile_size)) 397 self.rect.center = center 398 self.drawn = 0 399 400 def draw_back(self, surface): 401 if self.drawn: return 0 402 surface.blit( self.plain_tiles[self.paths], self.rect.topleft) 403 self.drawn = 1 404 return 1 405 406 def update(self, board): pass 407 408 def draw_fore(self, surface): return 0 409 410 def click(self, board, posx, posy, tile_x, tile_y): pass 411 412 def affect_marble(self, board, marble, rpos): 413 if rpos == (tile_size/2,tile_size/2): 414 if self.paths & (1 << marble.direction): return 415 416 # Figure out the new direction 417 t = self.paths - (1 << (marble.direction^2)) 418 if t == 1: marble.direction = 0 419 elif t == 2: marble.direction = 1 420 elif t == 4: marble.direction = 2 421 elif t == 8: marble.direction = 3 422 else: marble.direction = marble.direction ^ 2 423 424class Wheel(Tile): 425 def __init__(self, paths, center=None): 426 Tile.__init__(self, paths, center) # Call base class intializer 427 self.spinpos = 0 428 self.completed = 0 429 self.marbles = [ -3, -3, -3, -3 ] 430 431 def draw_back(self, surface): 432 if self.drawn: return 0 433 434 Tile.draw_back(self, surface) 435 436 if self.spinpos: 437 surface.blit( self.blank_images[self.completed], self.rect.topleft) 438 for i in range(4): 439 holecenter = holecenters[self.spinpos][i] 440 surface.blit( self.moving_holes[self.completed], 441 (holecenter[0]-marble_size/2+self.rect.left, 442 holecenter[1]-marble_size/2+self.rect.top)) 443 else: 444 surface.blit( self.images[self.completed], self.rect.topleft) 445 446 for i in range(4): 447 color = self.marbles[i] 448 if color >= 0: 449 holecenter = holecenters[self.spinpos][i] 450 surface.blit( Marble.images[color], 451 (holecenter[0]-marble_size/2+self.rect.left, 452 holecenter[1]-marble_size/2+self.rect.top)) 453 454 return 1 455 456 def update(self, board): 457 if self.spinpos > 0: 458 self.spinpos -= 1 459 self.drawn = 0 460 461 def click(self, board, posx, posy, tile_x, tile_y): 462 # Ignore all clicks while rotating 463 if self.spinpos: return 464 465 b1, b2, b3 = pygame.mouse.get_pressed() 466 if b3: 467 # First, make sure that no marbles are currently entering 468 for i in self.marbles: 469 if i == -1 or i == -2: return 470 471 # Start the wheel spinning 472 self.spinpos = wheel_steps - 1 473 play_sound( wheel_turn) 474 475 # Reposition the marbles 476 t = self.marbles[0] 477 self.marbles[0] = self.marbles[1] 478 self.marbles[1] = self.marbles[2] 479 self.marbles[2] = self.marbles[3] 480 self.marbles[3] = t 481 482 self.drawn = 0 483 484 elif b1: 485 # Determine which hole is being clicked 486 for i in range(4): 487 # If there is no marble here, skip it 488 if self.marbles[i] < 0: continue 489 490 holecenter = holecenters[0][i] 491 rect = pygame.Rect( 0, 0, marble_size, marble_size) 492 rect.center = holecenter 493 if rect.collidepoint( posx, posy): 494 495 # Determine the neighboring tile 496 neighbor = board.tiles[ (tile_y + dirs[i][1]) % 497 vert_tiles][ (tile_x + dirs[i][0]) % horiz_tiles] 498 499 if ( 500 # Disallow marbles to go off the top of the board 501 (tile_y == 0 and i==0) or 502 503 # If there is no way out here, skip it 504 ((self.paths & (1 << i)) == 0) or 505 506 # If the neighbor is a wheel that is either turning 507 # or has a marble already in the hole, disallow 508 # the ejection 509 (isinstance(neighbor, Wheel) and 510 (neighbor.spinpos or 511 neighbor.marbles[i^2] != -3)) 512 ): 513 play_sound( incorrect) 514 else: 515 # If the neighbor is a wheel, apply a special lock 516 if isinstance(neighbor, Wheel): 517 neighbor.marbles[i^2] = -2 518 elif len(board.marbles) >= board.live_marbles_limit: 519 # Impose the live marbles limit 520 play_sound( incorrect) 521 break 522 523 # Eject the marble 524 board.marbles.append( 525 Marble( self.marbles[i], 526 (holecenter[0]+self.rect.left, 527 holecenter[1]+self.rect.top), 528 i)) 529 self.marbles[i] = -3 530 play_sound( marble_release) 531 self.drawn = 0 532 533 break 534 535 def affect_marble(self, board, marble, rpos): 536 # Watch for marbles entering 537 if rpos[0]+marble_size/2 == wheel_margin or \ 538 rpos[0]-marble_size/2 == tile_size - wheel_margin or \ 539 rpos[1]+marble_size/2 == wheel_margin or \ 540 rpos[1]-marble_size/2 == tile_size - wheel_margin: 541 if self.spinpos or self.marbles[marble.direction^2] >= -1: 542 # Reject the marble 543 marble.direction = marble.direction ^ 2 544 play_sound( ping) 545 else: 546 self.marbles[marble.direction^2] = -1 547 548 for holecenter in holecenters[0]: 549 if rpos == holecenter: 550 # Accept the marble 551 board.marbles.remove( marble) 552 self.marbles[marble.direction^2] = marble.color 553 554 self.drawn = 0 555 556 break 557 558 def complete(self, board): 559 # Complete the wheel 560 for i in range(4): self.marbles[i] = -3 561 if self.completed: board.game.increase_score( 10) 562 else: board.game.increase_score( 50) 563 self.completed = 1 564 play_sound( wheel_completed) 565 self.drawn = 0 566 567 def maybe_complete(self, board): 568 if self.spinpos > 0: return 0 569 570 # Is there a trigger? 571 if (board.trigger is not None) and \ 572 (board.trigger.marbles is not None): 573 # Compare against the trigger 574 for i in range(4): 575 if self.marbles[i] != board.trigger.marbles[i] and \ 576 self.marbles[i] != 8: return 0 577 self.complete( board) 578 board.trigger.complete( board) 579 return 1 580 581 # Do we have four the same color? 582 color = 8 583 for c in self.marbles: 584 if c < 0: return 0 585 if color==8: color=c 586 elif c==8: c=color 587 elif c != color: return 0 588 589 # Is there a stoplight? 590 if (board.stoplight is not None) and \ 591 (board.stoplight.current < 3): 592 # Compare against the stoplight 593 if color != 8 and \ 594 color != board.stoplight.marbles[board.stoplight.current]: 595 return 0 596 else: 597 board.stoplight.complete( board) 598 599 self.complete( board) 600 return 1 601 602class Buffer(Tile): 603 def __init__(self, paths, color=-1): 604 Tile.__init__(self, paths) # Call base class intializer 605 self.marble = color 606 self.entering = None 607 608 def draw_back(self, surface): 609 if self.drawn: return 0 610 611 Tile.draw_back(self, surface) 612 613 color = self.marble 614 if color >= 0: 615 holecenter = self.rect.center 616 surface.blit( Marble.images[color], 617 (holecenter[0]-marble_size/2, 618 holecenter[1]-marble_size/2)) 619 else: 620 surface.blit( self.bottom, self.rect.topleft) 621 622 623 return 1 624 625 def draw_fore(self, surface): 626 surface.blit( self.tunnels[self.paths], self.rect.topleft) 627 surface.blit( self.top, self.rect.topleft) 628 return 0 629 630 def affect_marble(self, board, marble, rpos): 631 # Watch for marbles entering 632 if (rpos[0]+marble_size == tile_size/2 and marble.direction == 1) or \ 633 (rpos[0]-marble_size == tile_size/2 and marble.direction == 3) or \ 634 (rpos[1]+marble_size == tile_size/2 and marble.direction == 2) or \ 635 (rpos[1]-marble_size == tile_size/2 and marble.direction == 0): 636 637 if self.entering is not None: 638 # Bump the marble that is currently entering 639 newmarble = self.entering 640 newmarble.rect.center = self.rect.center 641 newmarble.direction = marble.direction 642 643 play_sound( ping) 644 645 # Let the base class affect the marble 646 Tile.affect_marble(self, board, newmarble, 647 (tile_size/2,tile_size/2)) 648 elif self.marble >= 0: 649 # Bump the marble that is currently caught 650 newmarble = Marble( self.marble, self.rect.center, marble.direction) 651 652 board.marbles.append( newmarble) 653 654 play_sound( ping) 655 656 # Let the base class affect the marble 657 Tile.affect_marble(self, board, newmarble, 658 (tile_size/2,tile_size/2)) 659 660 self.marble = -1 661 self.drawn = 0 662 663 # Remember which marble is on its way in 664 self.entering = marble 665 666 elif rpos == (tile_size/2, tile_size/2): 667 # Catch this marble 668 self.marble = marble.color 669 board.marbles.remove( marble) 670 self.entering = None 671 self.drawn = 0 672 673class Painter(Tile): 674 def __init__(self, paths, color, center=None): 675 Tile.__init__(self, paths, center) # Call base class intializer 676 self.color = color 677 678 def draw_fore(self, surface): 679 surface.blit( self.tunnels[self.paths], self.rect.topleft) 680 surface.blit( self.images[self.color], self.rect.topleft) 681 return 0 682 683 def affect_marble(self, board, marble, rpos): 684 Tile.affect_marble( self, board, marble, rpos) 685 if rpos == (tile_size/2, tile_size/2): 686 if marble.color != self.color: 687 # Change the color 688 marble.color = self.color 689 play_sound( change_color) 690 691class Filter(Tile): 692 def __init__(self, paths, color, center=None): 693 Tile.__init__(self, paths, center) # Call base class intializer 694 self.color = color 695 696 def draw_fore(self, surface): 697 surface.blit( self.tunnels[self.paths], self.rect.topleft) 698 surface.blit( self.images[self.color], self.rect.topleft) 699 return 0 700 701 def affect_marble(self, board, marble, rpos): 702 if rpos == (tile_size/2, tile_size/2): 703 # If the color is wrong, bounce the marble 704 if marble.color != self.color and marble.color != 8: 705 marble.direction = marble.direction ^ 2 706 play_sound( ping) 707 else: 708 Tile.affect_marble( self, board, marble, rpos) 709 play_sound( filter_admit) 710 711class Director(Tile): 712 def __init__(self, paths, direction, center=None): 713 Tile.__init__(self, paths, center) # Call base class intializer 714 self.direction = direction 715 716 def draw_fore(self, surface): 717 surface.blit( self.tunnels[self.paths], self.rect.topleft) 718 surface.blit( self.images[self.direction], self.rect.topleft) 719 return 0 720 721 def affect_marble(self, board, marble, rpos): 722 if rpos == (tile_size/2, tile_size/2): 723 marble.direction = self.direction 724 play_sound( direct_marble) 725 726class Shredder(Tile): 727 def __init__(self, paths, center=None): 728 Tile.__init__(self, paths, center) # Call base class intializer 729 730 def draw_fore(self, surface): 731 surface.blit( self.tunnels[self.paths], self.rect.topleft) 732 surface.blit( self.image, self.rect.topleft) 733 return 0 734 735 def affect_marble(self, board, marble, rpos): 736 if rpos == (tile_size/2, tile_size/2): 737 board.marbles.remove( marble) 738 play_sound( shredder) 739 740class Switch(Tile): 741 def __init__(self, paths, dir1, dir2, center=None): 742 Tile.__init__(self, paths, center) # Call base class intializer 743 self.curdir = dir1 744 self.otherdir = dir2 745 self.switched = 0 746 747 def switch(self): 748 t = self.curdir 749 self.curdir = self.otherdir 750 self.otherdir = t 751 self.switched = 1 752 play_sound( switch) 753 754 def draw_fore(self, surface): 755 surface.blit( self.tunnels[self.paths], self.rect.topleft) 756 surface.blit( self.images[self.curdir][self.otherdir], 757 self.rect.topleft) 758 rc = self.switched 759 self.switched = 0 760 return rc 761 762 def affect_marble(self, board, marble, rpos): 763 if rpos == (tile_size/2, tile_size/2): 764 marble.direction = self.curdir 765 self.switch() 766 767class Replicator(Tile): 768 def __init__(self, paths, count, center=None): 769 Tile.__init__(self, paths, center) # Call base class intializer 770 self.count = count 771 self.pending = [] 772 773 def draw_fore(self, surface): 774 surface.blit( self.tunnels[self.paths], self.rect.topleft) 775 surface.blit( self.image, self.rect.topleft) 776 return 0 777 778 def update(self, board): 779 for i in self.pending[:]: 780 i[3] -= 1 781 if i[3] == 0: 782 i[3] = replicator_delay 783 784 # Make sure that the active marble limit isn't exceeded 785 if len(board.marbles) >= board.live_marbles_limit: 786 # Clear the pending list 787 self.pending = [] 788 return 789 790 # Add the new marble 791 board.marbles.append(Marble(i[0],self.rect.center,i[1])) 792 play_sound( replicator) 793 794 i[2] -= 1 795 if i[2] <= 0: self.pending.remove( i) 796 797 def affect_marble(self, board, marble, rpos): 798 Tile.affect_marble( self, board, marble, rpos) 799 if rpos == (tile_size/2, tile_size/2): 800 # Add the marble to the pending list 801 self.pending.append( [marble.color,marble.direction, 802 self.count - 1, replicator_delay]); 803 play_sound( replicator) 804 805class Teleporter(Tile): 806 def __init__(self, paths, other=None, center=None): 807 Tile.__init__(self, paths, center) # Call base class intializer 808 if paths & 5: self.image = self.image_v 809 else: self.image = self.image_h 810 if other is not None: self.connect( other) 811 812 def draw_fore(self, surface): 813 surface.blit( self.tunnels[self.paths], self.rect.topleft) 814 surface.blit( self.image, self.rect.topleft) 815 return 0 816 817 def connect(self, other): 818 self.other = other 819 other.other = self 820 821 def affect_marble(self, board, marble, rpos): 822 if rpos == (tile_size/2, tile_size/2): 823 marble.rect.center = self.other.rect.center 824 play_sound( teleport) 825 826class Trigger(Tile): 827 def __init__(self, colors, center=None): 828 Tile.__init__(self, 0, center) # Call base class intializer 829 self.marbles = None 830 self._setup( colors) 831 832 def _setup(self, colors): 833 self.countdown = 0 834 self.marbles = [ 835 random.choice(colors), 836 random.choice(colors), 837 random.choice(colors), 838 random.choice(colors), 839 ] 840 self.drawn = 0 841 842 def update(self, board): 843 if self.countdown > 0: 844 self.countdown -= 1 845 if self.countdown == 0: 846 self._setup( board.colors) 847 play_sound( trigger_setup) 848 849 def draw_back(self, surface): 850 if self.drawn: return 0 851 Tile.draw_back(self, surface) 852 surface.blit( self.image, self.rect.topleft) 853 if self.marbles is not None: 854 for i in range(4): 855 surface.blit( Marble.images[self.marbles[i]], 856 (holecenters[0][i][0]+self.rect.left-marble_size/2, 857 holecenters[0][i][1]+self.rect.top-marble_size/2)) 858 return 1 859 860 def complete(self, board): 861 self.marbles = None 862 self.countdown = trigger_time * frames_per_sec 863 self.drawn = 0 864 board.game.increase_score( 50) 865 866class Stoplight(Tile): 867 def __init__(self, colors, center=None): 868 Tile.__init__(self, 0, center) # Call base class intializer 869 self.marbles = list(colors) 870 self.current = 0 871 872 def draw_back(self, surface): 873 if self.drawn: return 0 874 Tile.draw_back(self, surface) 875 surface.blit( self.image, self.rect.topleft) 876 for i in range(self.current,3): 877 surface.blit( self.smallmarbles[self.marbles[i]], 878 (self.rect.centerx-14, 879 self.rect.top+3+(29*i))) 880 return 1 881 882 def complete(self, board): 883 for i in range(3): 884 if self.marbles[i] >= 0: 885 self.marbles[i] = -1 886 break 887 self.current += 1 888 self.drawn = 0 889 board.game.increase_score( 20) 890 891class Board: 892 def __init__(self, game, pos): 893 self.game = game 894 self.pos = pos 895 self.marbles = [] 896 self.screen = game.screen 897 self.trigger = None 898 self.stoplight = None 899 self.launch_queue = [] 900 self.board_complete = 0 901 self.paused = 0 902 self.name = "Unnamed" 903 self.live_marbles_limit = 10 904 self.launch_timeout = -1 905 self.board_timeout = -1 906 self.colors = default_colors 907 self.launched = 1 908 909 self.set_launch_timer( default_launch_timer) 910 self.set_board_timer( default_board_timer) 911 912 # Create the board array 913 self.tiles = [] 914 for j in range( vert_tiles): 915 row = range( horiz_tiles) 916 self.tiles.append( row) 917 918 # Load the level 919 # For levels above game.level, use a pseudo-random 920 # level selection method. 921 if( game.level < game.numlevels): 922 self._load( game.circuit, game.level) 923 else: 924 # Compute a hash of the current level, involving 925 # a static timestamp. This provides a consistent, 926 # backtrackable pseudo-random function. 927 hash = md5.new(`game.gamestart`+"/"+`game.level`).digest() 928 hashval = (ord(hash[0]) + (ord(hash[1]) << 8) + \ 929 (ord(hash[2]) << 16) + (ord(hash[3]) << 24)) & 32767; 930 self._load( game.circuit, hashval % game.numlevels); 931 932 # Create the launch timer text object 933 self.launch_timer_text = launch_timer_font.render( 934 `self.launch_timer`, 1, (255,255,255)) 935 self.launch_timer_text_rect = self.launch_timer_text.get_rect() 936 self.launch_timer_text_rect.centerx = launch_timer_pos[0]+timer_width/2+1 937 self.launch_timer_text_rect.bottom = \ 938 launch_timer_pos[1] + timer_height - timer_margin 939 940 # Fill up the launch queue 941 for i in range( vert_tiles * tile_size / marble_size + 2): 942 self.launch_queue.append(random.choice(self.colors)) 943 944 # Create The Background 945 self.background = pygame.Surface(screen.get_size()).convert() 946 self.background.fill((200, 200, 200)) # Color of Info Bar 947 948 # Draw the Backdrop 949 backdrop = load_image('backdrop.jpg', None, 950 (horiz_tiles * tile_size, vert_tiles * tile_size)) 951 self.background.blit( backdrop, board_pos); 952 953 # Draw the launcher 954 self.background.blit( self.launcher_background, 955 (board_pos[0], board_pos[1] - marble_size)) 956 self.background.blit( self.launcher_v, 957 (board_pos[0]+horiz_tiles*tile_size, board_pos[1])) 958 for i in range( horiz_tiles): 959 if self.tiles[0][i].paths & 1: 960 self.background.blit( self.launcher_entrance, 961 (board_pos[0]+tile_size*i, board_pos[1]-marble_size)) 962 self.background.blit( self.launcher_corner, 963 (board_pos[0]+horiz_tiles*tile_size-(tile_size-marble_size)/2, 964 board_pos[1] - marble_size)) 965 966 # Draw the board name 967 board_name = `self.game.level+1` + " - " + self.name 968 if self.game.level >= self.game.numlevels: 969 board_name += " (Random)" 970 text = info_font.render( board_name, 1, (0,0,0)) 971 rect = text.get_rect() 972 rect.left = 8 973 self.background.blit( text, rect) 974 975 # Figure out the score location 976 text = "Score: 00000000" 977 self.score_pos = screen_width - 8 - \ 978 info_font.render( text, 1, (0,0,0)).get_rect().width 979 980 # Figure out the board timer location 981 text = "00:00" 982 self.board_timer_pos = self.score_pos - 16 - \ 983 info_font.render( text, 1, (0,0,0)).get_rect().width 984 985 # Initialize the screen 986 screen.blit(self.background, (0, 0)) 987 988 def draw_back(self, dirty_rects): 989 # Draw the launch timer 990 if self.launch_timer_height is None: 991 height = timer_height 992 rect = (launch_timer_pos[0],launch_timer_pos[1], 993 timer_width,timer_height) 994 self.screen.fill((0,0,0), rect) 995 self.screen.fill((0,40,255), 996 (launch_timer_pos[0]+timer_margin, 997 launch_timer_pos[1]+timer_height-height, 998 timer_width-timer_margin*2,height)) 999 dirty_rects.append( rect) 1000 else: 1001 height = timer_height*self.launch_timeout/self.launch_timeout_start 1002 if height < self.launch_timer_height: 1003 rect = (launch_timer_pos[0] + timer_margin, 1004 launch_timer_pos[1] + timer_height - self.launch_timer_height, 1005 timer_width-2*timer_margin, self.launch_timer_height - height) 1006 self.screen.fill((0,0,0), rect) 1007 dirty_rects.append( rect) 1008 self.launch_timer_height = height 1009 self.screen.blit( self.launch_timer_text, self.launch_timer_text_rect) 1010 dirty_rects.append( self.launch_timer_text_rect) 1011 1012 # Clear the info bar 1013 rect = (0,0,screen_width,info_height) 1014 self.screen.set_clip( rect) 1015 self.screen.blit( self.background, (0,0)) 1016 self.screen.set_clip() 1017 dirty_rects.append( rect) 1018 1019 # Draw the score 1020 text = "Score: "+("00000000"+`self.game.score`)[-8:] 1021 text = info_font.render( text, 1, (0,0,0)) 1022 rect = text.get_rect() 1023 rect.left = self.score_pos 1024 self.screen.blit( text, rect) 1025 1026 # Draw the board timer 1027 time_remaining = (self.board_timeout+frames_per_sec-1)/frames_per_sec 1028 text = `time_remaining/60`+":"+("00"+`time_remaining%60`)[-2:] 1029 text = info_font.render( text, 1, (0,0,0)) 1030 rect = text.get_rect() 1031 rect.left = self.board_timer_pos 1032 self.screen.blit( text, rect) 1033 1034 # Draw the lives counter 1035 right_edge = self.board_timer_pos - 32 1036 for i in range(self.game.lives - 1): 1037 rect = self.life_marble.get_rect() 1038 rect.centery = info_height / 2 1039 rect.right = right_edge 1040 self.screen.blit( self.life_marble, rect) 1041 right_edge -= rect.width + 4 1042 1043 # Draw the live marbles 1044 num_marbles = len(self.marbles) 1045 if num_marbles > self.live_marbles_limit: 1046 num_marbles = self.live_marbles_limit 1047 text = `num_marbles`+"/"+`self.live_marbles_limit` 1048 text = active_marbles_font.render( text, 1, (40,40,40)) 1049 rect = text.get_rect() 1050 rect.left = self.pos[0] + 8 1051 rect.centery = self.pos[1] - marble_size / 2 1052 rect.width += 100 1053 self.screen.set_clip( rect) 1054 self.screen.blit( self.background, (0,0)) 1055 self.screen.set_clip() 1056 self.screen.blit( text, rect) 1057 1058 dirty_rects.append( rect) 1059 1060 for row in self.tiles: 1061 for tile in row: 1062 if tile.draw_back( self.background): 1063 self.screen.set_clip( tile.rect) 1064 self.screen.blit( self.background, (0,0)) 1065 self.screen.set_clip() 1066 dirty_rects.append( tile.rect) 1067 1068 if self.launched: 1069 for i in range(len(self.launch_queue)): 1070 self.background.blit( Marble.images[self.launch_queue[i]], 1071 (self.pos[0] + horiz_tiles * tile_size, 1072 self.pos[1] + i * marble_size - marble_size)) 1073 rect = (self.pos[0] + horiz_tiles * tile_size, 1074 self.pos[1] - marble_size, marble_size, 1075 marble_size + tile_size * vert_tiles) 1076 self.screen.set_clip( rect) 1077 self.screen.blit( self.background, (0,0)) 1078 self.screen.set_clip() 1079 dirty_rects.append( rect) 1080 self.launched = 0 1081 1082 def draw_fore(self, dirty_rects): 1083 for row in self.tiles: 1084 for tile in row: 1085 if tile.draw_fore(self.screen): 1086 dirty_rects.append( tile.rect) 1087 1088 def update(self): 1089 # Create the list of dirty rectangles 1090 dirty_rects = [] 1091 1092 # Erase the marbles 1093 for marble in self.marbles: 1094 marble.undraw( self.screen, self.background) 1095 dirty_rects.append( list(marble.rect)) 1096 1097 # Animate the marbles 1098 for marble in self.marbles[:]: 1099 marble.update( self) 1100 1101 # Animate the tiles 1102 for row in self.tiles: 1103 for tile in row: 1104 tile.update( self) 1105 if tile.drawn == 0: dirty_rects.append( tile.rect) 1106 1107 # Complete any wheels, if appropriate 1108 try_again = 1 1109 while try_again: 1110 try_again = 0 1111 for row in self.tiles: 1112 for tile in row: 1113 if isinstance( tile, Wheel): 1114 try_again |= tile.maybe_complete( self) 1115 1116 # Check if the board is complete 1117 self.board_complete = 1 1118 for row in self.tiles: 1119 for tile in row: 1120 if isinstance( tile, Wheel): 1121 if tile.completed == 0: self.board_complete = 0 1122 1123 # Decrement the launch timer 1124 if self.launch_timeout > 0: 1125 self.launch_timeout -= 1 1126 if self.launch_timeout == 0: self.board_complete = -1 1127 1128 # Decrement the board timer 1129 if self.board_timeout > 0: 1130 self.board_timeout -= 1 1131 if self.board_timeout == 0: self.board_complete = -2 1132 1133 # Draw the background 1134 self.draw_back( dirty_rects) 1135 1136 # Draw all of the marbles 1137 for marble in self.marbles: 1138 marble.draw( self.screen) 1139 dirty_rects.append( marble.rect) 1140 1141 # Draw the foreground 1142 self.draw_fore( dirty_rects) 1143 1144 # Flip the display 1145 pygame.display.update( dirty_rects) 1146 1147 def set_tile(self, x, y, tile): 1148 self.tiles[y][x] = tile 1149 tile.rect.left = self.pos[0] + tile_size * x 1150 tile.rect.top = self.pos[1] + tile_size * y 1151 1152 tile.x = x 1153 tile.y = y 1154 1155 # If it's a trigger, keep track of it 1156 if isinstance( tile, Trigger): 1157 self.trigger = tile 1158 1159 # If it's a stoplight, keep track of it 1160 if isinstance( tile, Stoplight): 1161 self.stoplight = tile 1162 1163 def set_launch_timer(self, passes): 1164 self.launch_timer = passes 1165 self.launch_timeout_start = (marble_size + 1166 (horiz_tiles * tile_size - marble_size) * passes) / marble_speed 1167 self.launch_timer_height = None 1168 1169 def set_board_timer(self, seconds): 1170 self.board_timer = seconds 1171 self.board_timeout_start = seconds * frames_per_sec 1172 self.board_timeout = self.board_timeout_start 1173 1174 def launch_marble(self): 1175 self.launch_queue.append(random.choice(self.colors)) 1176 self.marbles.insert( 0, Marble( self.launch_queue[0], 1177 (self.pos[0]+tile_size*horiz_tiles+marble_size/2, 1178 self.pos[1]-marble_size/2), 3)) 1179 del self.launch_queue[0] 1180 self.launched = 1 1181 1182 self.launch_timeout = self.launch_timeout_start 1183 self.launch_timer_height = None 1184 1185 def affect_marble(self, marble): 1186 c = marble.rect.center 1187 cx = c[0] - self.pos[0] 1188 cy = c[1] - self.pos[1] 1189 1190 # Bounce marbles off of the top 1191 if cy == marble_size/2: 1192 marble.direction = 2 1193 return 1194 1195 if cy < 0: 1196 if cx == marble_size/2: 1197 marble.direction = 1 1198 return 1199 if cx == tile_size * horiz_tiles - marble_size/2 \ 1200 and marble.direction == 1: 1201 marble.direction = 3 1202 return 1203 1204 # The special case of new marbles at the top 1205 effective_cx = cx 1206 effective_cy = cy + marble_size 1207 else: 1208 effective_cx = cx + marble_size/2 * dirs[marble.direction][0] 1209 effective_cy = cy + marble_size/2 * dirs[marble.direction][1] 1210 1211 tile_x = effective_cx / tile_size 1212 tile_y = effective_cy / tile_size 1213 tile_xr = cx - tile_x * tile_size 1214 tile_yr = cy - tile_y * tile_size 1215 1216 if tile_x >= horiz_tiles: return 1217 1218 tile = self.tiles[tile_y][tile_x] 1219 1220 if cy < 0 and marble.direction != 2: 1221 # The special case of new marbles at the top 1222 if tile_xr == tile_size / 2 and (tile.paths & 1): 1223 if isinstance( tile, Wheel): 1224 if tile.spinpos > 0 or tile.marbles[0] != -3: return 1225 tile.marbles[0] = -2 1226 marble.direction = 2 1227 self.launch_marble() 1228 elif len(self.marbles) < self.live_marbles_limit: 1229 marble.direction = 2 1230 self.launch_marble() 1231 else: 1232 tile.affect_marble( self, marble, (tile_xr, tile_yr)) 1233 1234 def click(self, pos): 1235 # Determine which tile the pointer is in 1236 tile_x = (pos[0] - self.pos[0]) / tile_size 1237 tile_y = (pos[1] - self.pos[1]) / tile_size 1238 tile_xr = pos[0] - self.pos[0] - tile_x * tile_size 1239 tile_yr = pos[1] - self.pos[1] - tile_y * tile_size 1240 if tile_x >= 0 and tile_x < horiz_tiles and \ 1241 tile_y >= 0 and tile_y < vert_tiles: 1242 tile = self.tiles[tile_y][tile_x] 1243 tile.click( self, tile_xr, tile_yr, tile_x, tile_y) 1244 1245 def _load(self, circuit, level): 1246 fullname = os.path.join('circuits', circuit) 1247 f = open( fullname) 1248 1249 # Skip the previous levels 1250 j = 0 1251 while j < vert_tiles * level: 1252 line = f.readline() 1253 if line == '': 1254 f.close() 1255 return 0 1256 if line[0] == '|': j += 1 1257 1258 teleporters = [] 1259 teleporter_names = [] 1260 stoplight = default_stoplight 1261 1262 numwheels = 0 1263 boardtimer = -1 1264 1265 j = 0 1266 while j < vert_tiles: 1267 line = f.readline() 1268 1269 if line[0] != '|': 1270 if line[0:5] == 'name=': 1271 self.name = line[5:-1] 1272 elif line[0:11] == 'maxmarbles=': 1273 self.live_marbles_limit = int(line[11:-1]) 1274 elif line[0:12] == 'launchtimer=': 1275 self.set_launch_timer( int(line[12:-1])) 1276 elif line[0:11] == 'boardtimer=': 1277 boardtimer = int(line[11:-1]) 1278 elif line[0:7] == 'colors=': 1279 self.colors = [] 1280 for c in line[7:-1]: 1281 if c >= '0' and c <= '7': 1282 self.colors.append(int(c)) 1283 self.colors.append(int(c)) 1284 self.colors.append(int(c)) 1285 elif c == '8': 1286 # Crazy marbles are one-third as common 1287 self.colors.append(8) 1288 elif line[0:10] == 'stoplight=': 1289 stoplight = [] 1290 for c in line[10:-1]: 1291 if c >= '0' and c <= '7': 1292 stoplight.append(int(c)) 1293 1294 continue 1295 1296 for i in range(horiz_tiles): 1297 type = line[i*4+1] 1298 paths = line[i*4+2] 1299 if paths == ' ': pathsint = 0 1300 elif paths >= 'a': pathsint = ord(paths)-ord('a')+10 1301 elif paths >= '0' and paths <= '9': pathsint = int(paths) 1302 else: pathsint = int(paths) 1303 color = line[i*4+3] 1304 if color == ' ': colorint = 0 1305 elif color >= 'a': colorint = ord(color)-ord('a')+10 1306 elif color >= '0' and color <= '9': colorint = int(color) 1307 else: colorint = 0 1308 1309 if type == 'O': 1310 tile = Wheel( pathsint) 1311 numwheels += 1 1312 elif type == '%': tile = Trigger(self.colors) 1313 elif type == '!': tile = Stoplight(stoplight) 1314 elif type == '&': tile = Painter(pathsint, colorint) 1315 elif type == '#': tile = Filter(pathsint, colorint) 1316 elif type == '@': 1317 if color == ' ': tile = Buffer(pathsint) 1318 else: tile = Buffer(pathsint, colorint) 1319 elif type == ' ' or \ 1320 (type >= '0' and type <= '8'): tile = Tile(pathsint) 1321 elif type == 'X': tile = Shredder(pathsint) 1322 elif type == '*': tile = Replicator(pathsint, colorint) 1323 elif type == '^': 1324 if color == ' ': tile = Director(pathsint, 0) 1325 elif color == '>': tile = Switch(pathsint, 0, 1) 1326 elif color == 'v': tile = Switch(pathsint, 0, 2) 1327 elif color == '<': tile = Switch(pathsint, 0, 3) 1328 elif type == '>': 1329 if color == ' ': tile = Director(pathsint, 1) 1330 elif color == '^': tile = Switch(pathsint, 1, 0) 1331 elif color == 'v': tile = Switch(pathsint, 1, 2) 1332 elif color == '<': tile = Switch(pathsint, 1, 3) 1333 elif type == 'v': 1334 if color == ' ': tile = Director(pathsint, 2) 1335 elif color == '^': tile = Switch(pathsint, 2, 0) 1336 elif color == '>': tile = Switch(pathsint, 2, 1) 1337 elif color == '<': tile = Switch(pathsint, 2, 3) 1338 elif type == '<': 1339 if color == ' ': tile = Director(pathsint, 3) 1340 elif color == '^': tile = Switch(pathsint, 3, 0) 1341 elif color == '>': tile = Switch(pathsint, 3, 1) 1342 elif color == 'v': tile = Switch(pathsint, 3, 2) 1343 elif type == '=': 1344 if color in teleporter_names: 1345 other = teleporters[teleporter_names.index(color)] 1346 tile = Teleporter( pathsint, other) 1347 else: 1348 tile = Teleporter( pathsint) 1349 teleporters.append( tile) 1350 teleporter_names.append( color) 1351 1352 self.set_tile( i, j, tile) 1353 1354 if type >= '0' and type <= '8': 1355 if color == '^': direction = 0 1356 elif color == '>': direction = 1 1357 elif color == 'v': direction = 2 1358 else: direction = 3 1359 self.marbles.append( 1360 Marble(int(type),tile.rect.center,direction)) 1361 1362 j += 1 1363 if boardtimer < 0: boardtimer = default_board_timer * numwheels 1364 self.set_board_timer( boardtimer) 1365 f.close() 1366 return 1 1367 1368 # Return values for this function: 1369 # -4: User closed the application window 1370 # -3: User aborted the level 1371 # -2: Board timer expired 1372 # -1: Launch timer expired 1373 # 1: Level completed successfully 1374 # 2: User requested a skip to the next level 1375 # 3: User requested a skip to the previous level 1376 def play_level( self): 1377 # Perform the first render 1378 self.update() 1379 1380 # Play the start sound 1381 #play_sound( levelbegin) 1382 1383 # Launch the first marble 1384 self.launch_marble() 1385 1386 # Do the first update 1387 pygame.display.update() 1388 1389 # Game Loop 1390 while not self.board_complete: 1391 # Wait for the next frame 1392 my_tick( frames_per_sec) 1393 1394 # Handle Input Events 1395 for event in pygame.event.get(): 1396 if event.type is QUIT: 1397 return -4 1398 elif event.type is KEYDOWN: 1399 if event.key is K_ESCAPE: return -3 1400 elif event.key == ord('n'): return 2 1401 elif event.key == ord('b'): return 3 1402 elif event.key == ord(' ') or \ 1403 event.key == ord('p') or \ 1404 event.key == K_PAUSE: 1405 self.paused = self.paused ^ 1 1406 if self.paused: 1407 if screenshot: 1408 pause_popup = None 1409 else: 1410 pause_popup = popup('Game Paused') 1411 else: 1412 popdown( pause_popup) 1413 elif event.key == K_F2: 1414 toggle_fullscreen() 1415 elif event.key == K_F3: 1416 toggle_music() 1417 elif event.key == K_F4: 1418 toggle_sound() 1419 1420 elif event.type is MOUSEBUTTONDOWN: 1421 if self.paused: 1422 self.paused = 0 1423 popdown( pause_popup) 1424 else: self.click( pygame.mouse.get_pos()) 1425 1426 if not self.paused: self.update() 1427 1428 # Play the end sound 1429 if self.board_complete > 0: 1430 play_sound( levelfinish) 1431 else: 1432 play_sound( die) 1433 1434 return self.board_complete 1435 1436class HighScores: 1437 num_highscores = 10 1438 1439 def __init__(self, filename): 1440 self.filename = filename 1441 self.current_score = -1 1442 self.load() 1443 1444 def qualifies(self, score): 1445 self.load() 1446 return score >= self.scores[-1][0] 1447 1448 def add_score(self, score, circuit, level, name): 1449 self.load() 1450 for i in range(len(self.scores)): 1451 if score >= self.scores[i][0]: 1452 self.scores.insert( i, (score, circuit, level, name)) 1453 del self.scores[self.num_highscores:] 1454 self.save() 1455 self.current_score = i 1456 return i 1457 return -1 1458 1459 def load( self): 1460 self.scores = [] 1461 1462 parser = re.compile("([0-9]+) ([^ ]+) ([0-9]+) (.*)\n") 1463 1464 try: 1465 f = open( self.filename) 1466 while len(self.scores) < self.num_highscores: 1467 line = f.readline() 1468 if line == '': break 1469 match = parser.match(line) 1470 if match is not None: 1471 (score,circuit,level,name) = match.groups() 1472 self.scores.append( 1473 (int(score), circuit, int(level), name)) 1474 f.close() 1475 except: pass 1476 1477 # Extend the list if it is shorter than needed 1478 while len(self.scores) < self.num_highscores: 1479 self.scores.append( (0, 'all-boards', 1, '')) 1480 1481 # Shrink the list if it is longer than allowed 1482 del self.scores[self.num_highscores:] 1483 1484 def save(self): 1485 try: 1486 f = open( self.filename, "w") 1487 except: 1488 try: 1489 f = os.popen(write_highscores, "w") 1490 except OSError, message: 1491 print "Warning: Can't save highscores:", message 1492 return 1493 1494 try: 1495 for i in self.scores: 1496 f.write( `i[0]`+' '+i[1]+' '+`i[2]`+' '+i[3]+'\n') 1497 f.close() 1498 except: 1499 print "Warning: Problem saving highscores." 1500 1501def wait_one_sec(): 1502 time.sleep(1) 1503 pygame.event.get() # Clear the event queue 1504 1505def popup( text, minsize=None): 1506 maxwidth = 0 1507 objs = [] 1508 while text != "": 1509 if '\n' in text: 1510 newline = text.index('\n') 1511 line = text[:newline] 1512 text = text[newline+1:] 1513 else: 1514 line = text 1515 text = "" 1516 1517 obj = popup_font.render( line, 1, (0, 0, 0)) 1518 maxwidth = max( maxwidth, obj.get_rect().width) 1519 objs.append( obj) 1520 1521 linespacing = popup_font.get_ascent() - \ 1522 popup_font.get_descent() + popup_font.get_linesize() 1523 # Work around an apparent pygame bug on Windows 1524 linespacing = min( linespacing, int(1.2 * popup_font.get_height())) 1525 1526 # Leave a bit more room 1527 linespacing = int(linespacing * 1.3) 1528 1529 window_width = maxwidth + 40 1530 window_height = popup_font.get_height()+linespacing*(len(objs)-1)+40 1531 if minsize is not None: 1532 window_width = max( window_width, minsize[0]) 1533 window_height = max( window_height, minsize[1]) 1534 1535 window = pygame.Surface((window_width, window_height)) 1536 winrect = window.get_rect() 1537 window.fill((0, 0, 0)) 1538 window.fill((250, 250, 250), winrect.inflate(-2,-2)) 1539 1540 y = 20 1541 for obj in objs: 1542 textpos = obj.get_rect() 1543 textpos.top = y 1544 textpos.centerx = winrect.centerx 1545 window.blit( obj, textpos) 1546 y += linespacing 1547 1548 winrect.center = screen.get_rect().center 1549 winrect.top -= 40 1550 1551 backbuf = pygame.Surface(winrect.size).convert() 1552 backbuf.blit( screen, (0,0), winrect) 1553 1554 screen.blit( window, winrect) 1555 pygame.display.update() 1556 1557 return (backbuf, winrect) 1558 1559def popdown( popup_rc): 1560 if popup_rc is not None: 1561 screen.blit( popup_rc[0], popup_rc[1]) 1562 pygame.display.update( popup_rc[1]) 1563 1564class Game: 1565 def __init__(self, screen, circuit, highscores): 1566 self.screen = screen 1567 self.circuit = circuit 1568 self.highscores = highscores 1569 1570 # Count the number of levels 1571 fullname = os.path.join('circuits', circuit) 1572 f = open( fullname) 1573 j = 0 1574 while 1: 1575 line = f.readline() 1576 if line == '': break 1577 if line[0] == '|': j += 1 1578 f.close() 1579 self.numlevels = j / vert_tiles 1580 1581 self.level = 0 1582 self.score = 0 1583 self.lives = initial_lives 1584 1585 self.gamestart = time.time() 1586 1587 def increase_score(self, amount): 1588 # Add the amount to the score 1589 self.score += amount 1590 1591 # Award any extra lives that are due 1592 extra_lives = amount / extra_life_frequency + \ 1593 (self.score % extra_life_frequency < amount % extra_life_frequency) 1594 extra_lives = min( extra_lives, max_spare_lives+1 - self.lives) 1595 if extra_lives > 0: 1596 self.lives += extra_lives 1597 play_sound( extra_life) 1598 1599 # Return values for this function: 1600 # -1: User closed the application window 1601 # 0: The game was aborted 1602 # 1: Game completed normally 1603 # 2: User achieved a highscore 1604 def play(self): 1605 # Draw the loading screen 1606 backdrop = load_image('backdrop.jpg', None, 1607 (screen_width, screen_height)) 1608 screen.blit( backdrop, (0,0)) 1609 pygame.display.update() 1610 1611 popup("Please wait...\n", (150, 50)) 1612 1613 start_music("background.xm", ingame_music_volume) 1614 1615 self.highscores.current_score = -1 1616 1617 while 1: 1618 # Play a level 1619 board = Board( self, board_pos) 1620 1621 rc = board.play_level() 1622 1623 # Check for the user closing the window 1624 if rc == -4: return -1 1625 1626 if rc == 2: 1627 self.level += 1 1628 continue 1629 1630 if rc == 3: 1631 if self.level > 0: self.level -= 1 1632 self.score = 0 1633 self.lives = initial_lives 1634 continue 1635 1636 if rc < 0: 1637 # The board was not completed 1638 1639 if rc == -3: message = 'Level Aborted.' 1640 elif rc == -2: message = 'The board timer has expired.' 1641 else: message = 'The launch timer has expired.' 1642 1643 self.lives -= 1 1644 if self.lives > 0: 1645 rc = self.board_dialog( message+'\nClick to try again.', 1646 rc != -3) 1647 elif self.highscores.qualifies( self.score): 1648 popup("Congratulations!\n"+ 1649 "You have a highscore!\n"+ 1650 "Please enter your name:", (300, 180)) 1651 name = get_name( self.screen, popup_font, 1652 ((screen_width-250)/2,310,250,popup_font.get_height()), 1653 (255,255,255), (0,0,0)) 1654 if name is None: return -1 1655 1656 self.highscores.add_score( self.score, self.circuit, 1657 self.level+1, name) 1658 1659 return 2 1660 else: 1661 rc = self.board_dialog( message + 1662 '\nGame Over.\nClick to continue\n', rc != -3) 1663 if rc == 1: return 1 1664 1665 self.score = 0 1666 self.lives = initial_lives 1667 else: 1668 # The board was completed 1669 1670 # Compute time remaining bonus 1671 time_remaining = 100 * board.board_timeout / \ 1672 board.board_timeout_start 1673 time_bonus = 5 * time_remaining 1674 1675 # Compute empty holes bonus 1676 total_holes = 0 1677 empty_holes = 0 1678 for row in board.tiles: 1679 for tile in row: 1680 if isinstance( tile, Wheel): 1681 total_holes += 4 1682 for i in tile.marbles: 1683 if i < 0: empty_holes += 1 1684 empty_holes = (100 * empty_holes + total_holes/2) / total_holes 1685 holes_bonus = 2 * empty_holes 1686 1687 self.increase_score( time_bonus + holes_bonus) 1688 1689 message = 'Level Complete!\n'+ \ 1690 "Bonus for " + `time_remaining` + "% time remaining: " + \ 1691 `time_bonus` + "\n" + \ 1692 "Bonus for " + `empty_holes` + "% holes empty: " + \ 1693 `holes_bonus` + '\nClick to continue.' 1694 1695 rc = self.board_dialog( message, 1, 1) 1696 self.level += 1 1697 1698 if rc == -2: return -1 1699 if rc < 0: return 0 1700 1701 # Return values for this function: 1702 # -2: User closed the application window 1703 # -1: User pressed escape 1704 # 0: User pressed 'b' or 'n' 1705 # 1: Some other user event 1706 def board_dialog( self, message, pause=1, complete=0): 1707 popup(message) 1708 if pause: wait_one_sec() 1709 1710 # Wait for a mouse click to continue 1711 while 1: 1712 pygame.time.wait(20) 1713 for event in pygame.event.get(): 1714 if event.type is QUIT: 1715 return -2 1716 elif event.type is KEYDOWN: 1717 if event.key == K_ESCAPE: return -1 1718 if event.key == ord('b'): 1719 if self.level > 0: self.level -= 1 1720 self.score = 0 1721 self.lives = initial_lives 1722 return 0 1723 elif event.key == ord('n'): 1724 if complete == 0: 1725 # Skip to the next level 1726 self.level += 1 1727 return 0 1728 elif event.key == K_F2: 1729 toggle_fullscreen() 1730 continue 1731 elif event.key == K_F3: 1732 toggle_music() 1733 continue 1734 elif event.key == K_F4: 1735 toggle_sound() 1736 continue 1737 elif event.key == K_LSHIFT or \ 1738 event.key == K_RSHIFT or \ 1739 event.key == K_LALT or \ 1740 event.key == K_RALT or \ 1741 event.key == K_LCTRL or \ 1742 event.key == K_RCTRL: 1743 continue 1744 return 1 1745 elif event.type is MOUSEBUTTONDOWN: 1746 return 1 1747 1748def translate_key( key, shift_state): 1749 if shift_state: 1750 if key >= ord('a') and key <= ord('z'): key += ord('A') - ord('a') 1751 elif key == ord('1'): key = ord('!') 1752 elif key == ord('2'): key = ord('@') 1753 elif key == ord('3'): key = ord('#') 1754 elif key == ord('4'): key = ord('$') 1755 elif key == ord('5'): key = ord('%') 1756 elif key == ord('6'): key = ord('^') 1757 elif key == ord('7'): key = ord('&') 1758 elif key == ord('8'): key = ord('*') 1759 elif key == ord('9'): key = ord('(') 1760 elif key == ord('0'): key = ord(')') 1761 elif key == ord('`'): key = ord('~') 1762 elif key == ord("'"): key = ord('"') 1763 elif key == ord(";"): key = ord(':') 1764 elif key == ord("\\"): key = ord('|') 1765 elif key == ord("["): key = ord('{') 1766 elif key == ord("]"): key = ord('}') 1767 elif key == ord(","): key = ord('<') 1768 elif key == ord("."): key = ord('>') 1769 elif key == ord("/"): key = ord('?') 1770 elif key == ord("-"): key = ord('_') 1771 elif key == ord("="): key = ord('+') 1772 return key 1773 1774def get_name( screen, font, cursor_box, backcol, forecol): 1775 cursor_width = cursor_box[3] / 3 1776 cursor_pos = [cursor_box[0], cursor_box[1], cursor_width, cursor_box[3]] 1777 name = "" 1778 1779 inner_box = pygame.Rect(cursor_box) 1780 cursor_box = inner_box.inflate( 2, 2) 1781 outer_box = cursor_box.inflate( 2, 2) 1782 1783 enter_pressed = 0 1784 while not enter_pressed: 1785 screen.fill( forecol, outer_box) 1786 screen.fill( backcol, cursor_box) 1787 cursor_pos[0] = inner_box.left 1788 if name != "": 1789 obj = font.render( name, 1, forecol) 1790 screen.blit( obj, inner_box) 1791 cursor_pos[0] += obj.get_width() 1792 screen.fill( forecol, cursor_pos) 1793 pygame.display.update( (outer_box,)) 1794 1795 # Keep track of the shift keys 1796 shift_state = pygame.key.get_mods() & KMOD_SHIFT 1797 1798 pygame.time.wait(20) 1799 for event in pygame.event.get(): 1800 if event.type is QUIT: 1801 return None 1802 elif event.type is KEYUP: 1803 if event.key == K_LSHIFT: 1804 shift_state &= ~KMOD_LSHIFT 1805 elif event.key == K_RSHIFT: 1806 shift_state &= ~KMOD_RSHIFT 1807 elif event.type is KEYDOWN: 1808 if event.key == K_LSHIFT: 1809 shift_state |= KMOD_LSHIFT 1810 elif event.key == K_RSHIFT: 1811 shift_state |= KMOD_RSHIFT 1812 elif event.key == K_ESCAPE or event.key == K_RETURN: 1813 enter_pressed = 1 1814 break 1815 elif event.key == K_F2: 1816 toggle_fullscreen() 1817 elif event.key == K_F3: 1818 toggle_music() 1819 elif event.key == K_F4: 1820 toggle_sound() 1821 elif event.key == K_BACKSPACE: 1822 name = name[:-1] 1823 elif event.key >= 32 and event.key <= 127: 1824 key = translate_key( event.key, shift_state) 1825 name = name + chr(key) 1826 1827 return name 1828 1829class IntroScreen: 1830 menu = ("Start Game", "High Scores", "Fullscreen:", "Music:", 1831 "Sound Effects:", "Quit Game") 1832 menu_width = 240 1833 menu_pos = ((800 - menu_width)/2, 145) 1834 menu_font_height = 32 1835 menu_color = (255,255,255) 1836 menu_cursor_color = (60,60,60) 1837 menu_cursor_leftright_margin = 2 1838 menu_cursor_bottom_margin = -2 1839 menu_cursor_top_margin = 0 1840 menu_option_left = 200 1841 menu_rect = (menu_pos[0]-menu_cursor_leftright_margin, 1842 menu_pos[1]-menu_cursor_top_margin, 1843 menu_width + 2 * menu_cursor_leftright_margin, 1844 menu_font_height * len(menu) + 1845 menu_cursor_top_margin + menu_cursor_bottom_margin) 1846 1847 scroller_font_height = 28 1848 scroller_rect = (10,550,780,scroller_font_height) 1849 scroller_text = \ 1850 " Copyright (C) 2003 John-Paul Gignac. "+ \ 1851 " Soundtrack by Matthias Le Bidan. "+ \ 1852 " Board designs contributed by Mike Brenneman and Kim Gignac. "+ \ 1853 " To contribute your own board designs, see the website: "+ \ 1854 "http://pathological.sourceforge.net/ "+ \ 1855 " Logo by Carrie Bloomfield. "+ \ 1856 " Other graphics based on artwork by Mike Brenneman. "+ \ 1857 " Project motivated by Paul Prescod. "+ \ 1858 " Thanks to all my friends who helped make this project "+ \ 1859 "a success! "+ \ 1860 " This program is free software; you can redistribute it and/or "+ \ 1861 "modify it under the terms of the GNU General Public License. "+ \ 1862 "See the LICENSE file for details. " 1863 1864 scroller_color = (60,60,60) 1865 scroller_speed = 2 1866 1867 def __init__(self, screen, highscores): 1868 self.screen = screen 1869 self.highscores = highscores 1870 self.curpage = 0 1871 1872 self.scroller_image = self.scroller_font.render( 1873 self.scroller_text, 1, self.scroller_color) 1874 1875 self.menu_cursor = 0 1876 1877 def draw_background(self): 1878 self.screen.blit( self.background, (0,0)) 1879 1880 def undraw_menu(self): 1881 if self.curpage == 1: 1882 self.undraw_highscores() 1883 return 1884 1885 self.screen.set_clip( self.menu_rect) 1886 self.draw_background() 1887 self.screen.set_clip() 1888 self.dirty_rects.append( self.menu_rect) 1889 1890 def undraw_highscores(self): 1891 self.screen.set_clip( self.hs_rect) 1892 self.draw_background() 1893 self.screen.set_clip() 1894 self.dirty_rects.append( self.hs_rect) 1895 1896 def draw_menu(self): 1897 if self.curpage == 1: 1898 self.draw_highscores() 1899 return 1900 1901 self.undraw_menu() 1902 1903 self.screen.fill( self.menu_cursor_color, 1904 (self.menu_pos[0]-self.menu_cursor_leftright_margin, 1905 self.menu_pos[1]-self.menu_cursor_top_margin + 1906 self.menu_cursor * self.menu_font_height, 1907 self.menu_width + 2 * self.menu_cursor_leftright_margin, 1908 self.menu_font_height + self.menu_cursor_top_margin + 1909 self.menu_cursor_bottom_margin)) 1910 1911 y = self.menu_pos[1] 1912 for i in self.menu: 1913 menu_option = self.menu_font.render(i, 1, self.menu_color) 1914 self.screen.blit( menu_option, (self.menu_pos[0], y)) 1915 y += self.menu_font_height 1916 1917 if fullscreen: offon = 'On' 1918 else: offon = 'Off' 1919 offon = self.menu_font.render( offon, 1, self.menu_color) 1920 self.screen.blit( offon, 1921 (self.menu_pos[0]+self.menu_option_left, 1922 self.menu_pos[1]+self.menu_font_height * 2)) 1923 1924 if music_on: offon = 'On' 1925 else: offon = 'Off' 1926 offon = self.menu_font.render( offon, 1, self.menu_color) 1927 self.screen.blit( offon, 1928 (self.menu_pos[0]+self.menu_option_left, 1929 self.menu_pos[1]+self.menu_font_height * 3)) 1930 1931 if sound_on: offon = 'On' 1932 else: offon = 'Off' 1933 offon = self.menu_font.render( offon, 1, self.menu_color) 1934 self.screen.blit( offon, 1935 (self.menu_pos[0]+self.menu_option_left, 1936 self.menu_pos[1]+self.menu_font_height * 4)) 1937 1938 self.dirty_rects.append( self.menu_rect) 1939 1940 def draw_scroller(self): 1941 self.screen.set_clip( self.scroller_rect) 1942 self.draw_background() 1943 self.screen.blit( self.scroller_image, 1944 (self.scroller_rect[0] - self.scroller_pos, 1945 self.scroller_rect[1])) 1946 self.screen.set_clip() 1947 self.dirty_rects.append( self.scroller_rect) 1948 1949 def draw(self): 1950 self.dirty_rects = [] 1951 self.draw_background() 1952 self.draw_menu() 1953 self.draw_scroller() 1954 pygame.display.update() 1955 1956 def go_to_main_menu(self): 1957 # Return to the main menu 1958 play_sound( menu_select) 1959 self.undraw_menu() 1960 self.curpage = 0 1961 self.draw_menu() 1962 1963 def go_to_highscores(self): 1964 # Go to the highscores page 1965 self.undraw_menu() 1966 self.curpage = 1 1967 self.draw_menu() 1968 1969 def do(self, show_highscores=0): 1970 self.scroller_pos = -self.scroller_rect[2] 1971 1972 if( show_highscores): 1973 self.dirty_rects = [] 1974 self.go_to_highscores() 1975 pygame.display.update( self.dirty_rects) 1976 1977 self.draw() 1978 1979 start_music("intro.xm", intro_music_volume) 1980 1981 while 1: 1982 # Wait for the next frame 1983 my_tick( frames_per_sec) 1984 1985 self.dirty_rects = [] 1986 self.draw_scroller() 1987 1988 # Advance the scroller 1989 self.scroller_pos += self.scroller_speed 1990 if self.scroller_pos >= self.scroller_image.get_rect().width: 1991 self.scroller_pos = -self.scroller_rect[2] 1992 1993 pygame.time.wait(20) 1994 for event in pygame.event.get(): 1995 if event.type is QUIT: 1996 if self.curpage == 1: 1997 self.go_to_main_menu() 1998 continue 1999 return -2 2000 elif event.type is KEYDOWN: 2001 if event.key == K_F2: 2002 play_sound( menu_select) 2003 if not toggle_fullscreen(): return -3 2004 self.draw_menu() 2005 elif event.key == K_F3: 2006 play_sound( menu_select) 2007 toggle_music() 2008 self.draw_menu() 2009 elif event.key == K_F4: 2010 toggle_sound(1, self.dirty_rects) 2011 play_sound( menu_select) 2012 self.draw_menu() 2013 elif self.curpage == 1: 2014 self.go_to_main_menu() 2015 elif event.key == K_ESCAPE: 2016 return -1 2017 elif event.key == K_DOWN: 2018 self.menu_cursor += 1 2019 play_sound( menu_scroll) 2020 if self.menu_cursor == len(self.menu): 2021 self.menu_cursor = 0 2022 self.draw_menu() 2023 elif event.key == K_UP: 2024 self.menu_cursor -= 1 2025 play_sound( menu_scroll) 2026 if self.menu_cursor < 0: 2027 self.menu_cursor = len(self.menu) - 1 2028 self.draw_menu() 2029 elif event.key == K_SPACE or event.key == K_RETURN: 2030 rc = self.menu_select( self.menu_cursor) 2031 if rc < 1: return rc 2032 continue 2033 elif event.type is MOUSEBUTTONDOWN: 2034 if self.curpage == 1: 2035 self.go_to_main_menu() 2036 continue 2037 2038 pos = pygame.mouse.get_pos() 2039 2040 # Figure out which menu option is being clicked, if any 2041 2042 if pos[0] < self.menu_pos[0]: continue 2043 if pos[0] >= self.menu_pos[0] + self.menu_width: continue 2044 if pos[1] < self.menu_pos[1]: continue 2045 i = (pos[1] - self.menu_pos[1]) / self.menu_font_height 2046 if i >= len(self.menu): continue 2047 2048 rc = self.menu_select( i) 2049 if rc < 1: return rc 2050 2051 pygame.display.update( self.dirty_rects) 2052 2053 # Return values: 2054 # -3 - Toggle fullscreen requires warm restart 2055 # -1 - User selected the Quit option 2056 # 0 - User selected Begin Game 2057 # 1 - Unknown option 2058 def menu_select( self, i): 2059 if i == 0: 2060 return 0 2061 elif i == 1: 2062 play_sound( menu_select) 2063 self.go_to_highscores() 2064 elif i == 2: 2065 play_sound( menu_select) 2066 if not toggle_fullscreen(): return -3 2067 self.draw_menu() 2068 elif i == 3: 2069 play_sound( menu_select) 2070 toggle_music() 2071 self.draw_menu() 2072 elif i == 4: 2073 toggle_sound() 2074 play_sound( menu_select) 2075 self.draw_menu() 2076 elif i == 5: 2077 return -1 2078 return 1 2079 2080 hs_font_height = 24 2081 hs_width = 320 2082 hs_pos = ((800-hs_width)/2, 114) 2083 hs_margin = 8 2084 hs_column_margin = 70 2085 hs_score_width = 70 2086 hs_level_width = 24 2087 hs_heading_color = (0,0,0) 2088 hs_heading_background = (0,80,0) 2089 hs_number_color = (210,210,210) 2090 hs_body_color = (240,240,240) 2091 hs_current_color = (240,50,50) 2092 hs_rows = HighScores.num_highscores 2093 hs_rect = (hs_pos[0],hs_pos[1],hs_width,hs_font_height * 11) 2094 2095 def draw_highscores(self): 2096 self.undraw_menu() 2097 2098 tname = self.hs_font.render("Name", 1, self.hs_heading_color) 2099 tscore = self.hs_font.render("Score", 1, self.hs_heading_color) 2100 tlevel = self.hs_font.render("Level", 1, self.hs_heading_color) 2101 2102 # Compute the column positions 2103 score_right = self.hs_width 2104 score_left = score_right - tscore.get_size()[0] 2105 level_right = score_right - self.hs_score_width - \ 2106 self.hs_margin 2107 level_left = level_right - tlevel.get_size()[0] 2108 name_right = level_right - self.hs_level_width - \ 2109 self.hs_margin 2110 number = self.hs_font.render('10.',1,(0,0,0)) 2111 number_width = number.get_size()[0] 2112 name_left = number_width + 5 2113 name_width = name_right - name_left 2114 2115 x = self.hs_pos[0] 2116 for j in range(len(self.highscores.scores)): 2117 if (j % self.hs_rows) == 0: 2118 if j > 0: x += self.hs_column_margin + self.hs_width 2119 y = self.hs_pos[1] 2120 2121 # Draw the headings 2122# self.screen.fill( self.hs_heading_background, 2123# (x - 30, y, self.hs_width + 30, self.hs_font_height)) 2124 self.screen.blit( tname, (x + name_left, y)) 2125 self.screen.blit( tlevel, (x + level_left, y)) 2126 self.screen.blit( tscore, (x + score_left, y)) 2127 2128 i = self.highscores.scores[j] 2129 2130 numcolor = self.hs_number_color 2131 color = self.hs_body_color 2132 if j == self.highscores.current_score: 2133 numcolor = color = self.hs_current_color 2134 2135 y += self.hs_font_height 2136 number = self.hs_font.render(`j+1`+'.',1,numcolor) 2137 self.screen.blit( number, 2138 (x + number_width - number.get_size()[0], y)) 2139 if i[3] != '': 2140 name = self.hs_font.render( i[3], 1, color) 2141 if name.get_width() > name_width: 2142 name = name.subsurface( (0,0,name_width,name.get_height())) 2143 self.screen.blit( name, (x + name_left, y)) 2144 level = self.hs_font.render( `i[2]`, 1, color) 2145 self.screen.blit( level, (x + level_right - level.get_width(), y)) 2146 score = self.hs_font.render( `i[0]`, 1, color) 2147 self.screen.blit( score, (x + score_right - score.get_width(), y)) 2148 2149 self.dirty_rects.append( self.hs_rect) 2150 2151def setup_everything(): 2152 global introscreen 2153 2154 # Configure the audio settings 2155 if sys.platform[0:3] == 'win': 2156 # On Windows platforms, increase the sample rate and the buffer size 2157 pygame.mixer.pre_init(44100,-16,1,4096) 2158 2159 # Initialize the game module 2160 pygame.init() 2161 2162 if not pygame.font: print 'Warning, fonts disabled' 2163 if not pygame.mixer: print 'Warning, sound disabled' 2164 2165 set_video_mode() 2166 load_sounds() 2167 load_fonts() 2168 load_images() 2169 2170 introscreen = IntroScreen( screen, highscores) 2171 2172# Load the highscores file 2173highscores = HighScores( highscores_file) 2174 2175setup_everything() 2176 2177# Main loop 2178show_highscores = 0 2179while 1: 2180 # Display the intro screen 2181 while 1: 2182 rc = introscreen.do( show_highscores) 2183 if rc == -3: 2184 # Warm restart to toggle fullscreen 2185 fullscreen = fullscreen ^ 1 2186 setup_everything() 2187 else: 2188 break 2189 2190 if rc < 0: break # Handle the QUIT message 2191 2192 game = Game(screen, 'all-boards', highscores) 2193 2194 show_highscores = 1 2195 2196 rc = game.play() 2197 if rc < 0: break # Handle the QUIT message 2198 if rc == 0: show_highscores = 0 2199 2200