Ensure the screen is properly drawn when returning to slow mode.
[troncode.git] / troncode.py
blob0afc504cfef1fbdd3a379ddc498639ce97218ea2
1 #!/usr/bin/python -u
3 # troncode - write programs to play the classic lines game
5 # Copyright (C) 2008 Andy Balaam
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 # USA.
23 import gc
24 import math
25 import os
26 import pygame
27 from pygame.locals import *
28 import random
29 import re
30 import sys
31 import time
33 from AbstractGameBoard import AbstractGameBoard
34 from mopelib import mopelib
35 from troncode_values import *
37 # ----------------------
38 class TronCodeConfig( mopelib.Config ):
40 def default_config( self ):
41 self.num_players = 2
42 self.tick_time = 20
44 self.screen_size = ( 610, 635 )
45 self.colour_background = ( 15, 45, 15 )
47 self.volume = 50
49 self.music_on = 1
50 self.sound_effects_on = 1
52 self.keys_menu = mopelib.MyInputEvent( "Escape" )
53 self.keys_menu.add( pygame.KEYDOWN, pygame.K_ESCAPE )
54 self.keys_menu.add( pygame.JOYBUTTONDOWN, 8 ) # GP2X Start
55 self.keys_menu.add( pygame.JOYBUTTONDOWN, 9 ) # GP2X Select
57 self.keys_return = mopelib.MyInputEvent( "Return" )
58 self.keys_return.add( pygame.KEYDOWN, pygame.K_RETURN )
59 self.keys_return.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
61 self.keys_startgame = mopelib.MyInputEvent( "any key" )
62 self.keys_startgame.add_all( pygame.KEYDOWN )
63 self.keys_startgame.add_all( pygame.JOYBUTTONDOWN )
64 self.keys_startgame.add_all( pygame.MOUSEBUTTONDOWN )
66 self.keys_up = mopelib.MyInputEvent( "up" )
67 self.keys_up.add( pygame.KEYDOWN, ord( 'q' ) )
68 self.keys_up.add( pygame.KEYDOWN, pygame.K_UP )
69 self.keys_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
70 self.keys_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
72 self.keys_right = mopelib.MyInputEvent( "right" )
73 self.keys_right.add( pygame.KEYDOWN, ord( 'p' ) )
74 self.keys_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
75 self.keys_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
76 self.keys_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
78 self.keys_down = mopelib.MyInputEvent( "down" )
79 self.keys_down.add( pygame.KEYDOWN, ord( 'a' ) )
80 self.keys_down.add( pygame.KEYDOWN, pygame.K_DOWN )
81 self.keys_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
82 self.keys_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
84 self.keys_left = mopelib.MyInputEvent( "left" )
85 self.keys_left.add( pygame.KEYDOWN, ord( 'o' ) )
86 self.keys_left.add( pygame.KEYDOWN, pygame.K_LEFT )
87 self.keys_left.add( pygame.JOYBUTTONDOWN, 2 ) # GP2X Joy left
88 self.keys_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
90 self.keys_volup = mopelib.MyInputEvent( "+" )
91 self.keys_volup.add( pygame.KEYDOWN, ord( '+' ) )
92 self.keys_volup.add( pygame.KEYDOWN, ord( '=' ) )
93 self.keys_volup.add( pygame.JOYBUTTONDOWN, 16 ) # GP2X volume + button
95 self.keys_slow = mopelib.MyInputEvent( "s" )
96 self.keys_slow.add( pygame.KEYDOWN, ord( 's' ) )
98 self.keys_fast = mopelib.MyInputEvent( "s" )
99 self.keys_fast.add( pygame.KEYDOWN, ord( 'f' ) )
101 self.keys_voldown = mopelib.MyInputEvent( "-" )
102 self.keys_voldown.add( pygame.KEYDOWN, ord( '-' ) )
103 self.keys_voldown.add( pygame.JOYBUTTONDOWN, 17 ) # GP2X volume - button
105 self.keys_p1_up = mopelib.MyInputEvent( "p1_up" )
106 self.keys_p1_up.add( pygame.KEYDOWN, pygame.K_UP )
107 self.keys_p1_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
109 self.keys_p1_right = mopelib.MyInputEvent( "p1_right" )
110 self.keys_p1_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
111 self.keys_p1_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
113 self.keys_p1_down = mopelib.MyInputEvent( "p1_down" )
114 self.keys_p1_down.add( pygame.KEYDOWN, pygame.K_DOWN )
115 self.keys_p1_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
117 self.keys_p1_left = mopelib.MyInputEvent( "p1_left" )
118 self.keys_p1_left.add( pygame.KEYDOWN, pygame.K_LEFT )
119 self.keys_p1_left.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy left
121 self.keys_p2_up = mopelib.MyInputEvent( "p2_up" )
122 self.keys_p2_up.add( pygame.KEYDOWN, ord( '2' ) )
123 self.keys_p2_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
125 self.keys_p2_right = mopelib.MyInputEvent( "p2_right" )
126 self.keys_p2_right.add( pygame.KEYDOWN, ord( 'e' ) )
127 self.keys_p2_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
129 self.keys_p2_down = mopelib.MyInputEvent( "p2_down" )
130 self.keys_p2_down.add( pygame.KEYDOWN, ord( 'w' ) )
131 self.keys_p2_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
133 self.keys_p2_left = mopelib.MyInputEvent( "p2_left" )
134 self.keys_p2_left.add( pygame.KEYDOWN, ord( 'q' ) )
135 self.keys_p2_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
137 self.keys_p3_up = mopelib.MyInputEvent( "p3_up" )
138 self.keys_p3_up.add( pygame.KEYDOWN, ord( 'i' ) )
140 self.keys_p3_right = mopelib.MyInputEvent( "p3_right" )
141 self.keys_p3_right.add( pygame.KEYDOWN, ord( 'l' ) )
143 self.keys_p3_down = mopelib.MyInputEvent( "p3_down" )
144 self.keys_p3_down.add( pygame.KEYDOWN, ord( 'k' ) )
146 self.keys_p3_left = mopelib.MyInputEvent( "p3_left" )
147 self.keys_p3_left.add( pygame.KEYDOWN, ord( 'j' ) )
149 self.keys_p4_up = mopelib.MyInputEvent( "p4_up" )
150 self.keys_p4_up.add( pygame.KEYDOWN, pygame.K_KP8 )
152 self.keys_p4_right = mopelib.MyInputEvent( "p4_right" )
153 self.keys_p4_right.add( pygame.KEYDOWN, pygame.K_KP6 )
155 self.keys_p4_down = mopelib.MyInputEvent( "p4_down" )
156 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP5 )
157 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP2 )
159 self.keys_p4_left = mopelib.MyInputEvent( "p4_left" )
160 self.keys_p4_left.add( pygame.KEYDOWN, pygame.K_KP4 )
163 # ----------------------
165 class TronCodeSoundManager( mopelib.SoundManager ):
167 def __init__( self, volume ):
168 mopelib.SoundManager.__init__( self, config )
170 #self.add_sample_group( "waddles", ["waddle1"] )
172 # ----------------------
174 def intro_draw_instructions():
175 write_text( "Press %s for menu, or %s to start" % (
176 config.keys_menu.name, config.keys_startgame.name ),
177 (0, 0, 0), 0.05, 0.99 )
179 # ----------------------
181 def general_menu_create_menu( menu, config, gamestate ):
182 menu.items = []
183 if gamestate == None: # We are on a title screen - Start Game option
184 menu.add_item( "Start game", MENU_START )
185 menu.add_item( "Number of players: %d" % (config.num_players),
186 MENU_NUM_PLAYERS )
187 for player_num in range( config.num_players ):
188 cls_name = "--unknown--"
189 if player_num < len( config.player_classes ):
190 cls_name = config.player_classes[player_num].GetName()
191 menu.add_item( "P%d: %s" % ( player_num, cls_name ),
192 MENU_CHANGE_PLAYER + player_num )
193 else:
194 menu.add_item( "Continue", MENU_START )
195 menu.add_item( "End game", MENU_END )
197 tmp_str = "Music: "
198 if config.music_on:
199 tmp_str += "on"
200 else:
201 tmp_str += "off"
202 menu.add_item( tmp_str, MENU_MUSIC )
204 tmp_str = "Effects: "
205 if config.sound_effects_on:
206 tmp_str += "on"
207 else:
208 tmp_str += "off"
209 menu.add_item( tmp_str, MENU_SOUND_EFFECTS )
211 menu.add_item( "Quit troncode", MENU_QUIT )
213 return menu
215 def general_menu_screen( config, gamestate ):
217 if gamestate == None:
218 menu_title = "troncode"
219 else:
220 menu_title = "troncode paused"
222 menu = mopelib.Menu()
223 general_menu_create_menu( menu, config, gamestate )
224 menurender.set_menu( menu, menu_title )
225 menurender.repaint_full()
227 game_start = False
229 waiting = True
230 while waiting:
231 event = pygame.event.wait()
232 if event.type == QUIT:
233 sys.exit(0)
234 elif config.keys_menu.matches( event ):
235 waiting = False
236 elif config.keys_down.matches( event ):
237 menurender.move_down()
238 elif config.keys_up.matches( event ):
239 menurender.move_up()
240 elif config.keys_return.matches( event ):
241 code = menu.get_selected_item().code
242 if code == MENU_START:
243 game_start = True
244 waiting = False
245 elif code == MENU_END:
246 gamestate.alive = INGAME_QUIT
247 waiting = False
248 elif code == MENU_MUSIC:
249 if config.music_on == 1:
250 config.music_on = 0
251 else:
252 config.music_on = 1
253 general_menu_create_menu( menu, config, gamestate )
254 menurender.repaint_full()
255 sound_mgr.setup( gamestate )
256 config.save()
257 elif code == MENU_SOUND_EFFECTS:
258 if config.sound_effects_on:
259 config.sound_effects_on = 0
260 else:
261 config.sound_effects_on = 1
262 general_menu_create_menu( menu, config, gamestate )
263 menurender.repaint_full()
264 sound_mgr.setup( gamestate )
265 config.save()
266 elif code == MENU_QUIT:
267 sys.exit(0)
269 return game_start
271 # ----------------------
273 def intro_draw_title():
274 screen.blit( intro_surface_title, (0,0) )
275 write_text( "Version " + troncode_version,
276 ( 0, 0, 0 ), 0.05, 0.88 )
277 write_text( "by Andy Balaam", ( 0, 0, 0 ), 0.06, 0.93 )
278 intro_draw_instructions()
280 def intro_draw_something( intro_mode ):
281 if intro_mode == INTRO_MODE_TITLE:
282 intro_draw_title()
283 elif intro_mode == INTRO_MODE_INSTR:
284 screen.blit( intro_surface_instr, (0,0) )
285 intro_draw_instructions()
286 elif intro_mode == INTRO_MODE_MUSIC:
287 screen.blit( intro_surface_music, (0,0) )
288 intro_draw_instructions()
289 pygame.display.update()
291 def intro_input( event, config, intro_mode ):
292 if event.type == QUIT:
293 sys.exit(0)
294 elif config.keys_volup.matches( event ):
295 config.volume = sound_mgr.increase_volume()
296 config.save()
297 elif config.keys_voldown.matches( event ):
298 config.volume = sound_mgr.decrease_volume()
299 config.save()
300 else:
301 if event.type == EVENTTYPE_TITLE_TICK:
302 intro_mode += 1
303 if intro_mode == INTRO_MODE_ENDED:
304 intro_mode = INTRO_MODE_TITLE
305 intro_draw_something( intro_mode )
306 elif config.keys_menu.matches( event ):
307 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
308 start_game = general_menu_screen( config, None )
309 if start_game:
310 intro_mode = INTRO_MODE_ENDED
311 else:
312 intro_draw_something( intro_mode )
313 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
314 elif config.keys_startgame.matches( event ):
315 intro_mode = INTRO_MODE_ENDED
316 return intro_mode
318 # ----------------------
320 def intro_mainloop( config ):
321 intro_draw_title()
323 pygame.display.update()
324 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
326 intro_mode = INTRO_MODE_TITLE
327 while intro_mode < INTRO_MODE_ENDED:
328 intro_mode = intro_input( pygame.event.wait(), config, intro_mode )
330 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
332 def draw_pixel( gamestate, surface, colour, x, y, alive_colours ):
333 if colour in alive_colours:
334 col = colour
335 else:
336 col = mopelib.dim_colour( colour, gamestate.dim )
337 adj_x = screen_border[0] + scale * x
338 adj_y = screen_border[1] + scale * y
339 if scale < 1:
340 surface.set_at( ( int(adj_x), int(adj_y) ), col )
341 else:
342 pygame.draw.rect( surface, col,
343 (adj_x, adj_y, ceil_scale, ceil_scale ) )
345 def inlevel_screen_blit( gamestate ):
346 screen.blit( gamestate.offscreen_buff, (0,0) )
347 pygame.display.update()
349 def inlevel_redraw_screen( gamestate, arrows, scores ):
350 alive_colours = []
351 if gamestate.alive == INGAME_MOST_DEAD:
352 for player in gamestate.players:
353 if not gamestate.statuses[player]._dead:
354 alive_colours.append( player.GetColour() )
356 gamestate.offscreen_buff = pygame.Surface( gamestate.config.screen_size )
357 gamestate.offscreen_buff.blit( ingame_surface_background, (0,0) )
358 for x, y, colour in gamestate.pixels_list:
359 draw_pixel( gamestate, gamestate.offscreen_buff, colour, x, y, alive_colours )
360 inlevel_draw_players( gamestate, alive_colours )
361 write_text_ingame( gamestate, scores )
362 # TODO: draw arrows
363 inlevel_screen_blit( gamestate )
365 def inlevel_draw_players( gamestate, alive_colours ):
366 for player in gamestate.players:
367 x, y = gamestate.GetPosition( player )
368 draw_pixel( gamestate, gamestate.offscreen_buff, player.GetColour(),
369 x, y, alive_colours )
371 def inlevel_update_screen( gamestate, scores ):
372 inlevel_draw_players( gamestate, [] )
373 inlevel_screen_blit( gamestate )
377 # ----------------------
379 def inlevel_input( event, gamestate, scores ):
380 if event.type == QUIT:
381 sys.exit(0)
382 elif config.keys_menu.matches( event ):
383 general_menu_screen( config, gamestate )
384 inlevel_redraw_screen( gamestate, False, scores )
385 elif config.keys_volup.matches( event ):
386 config.volume = sound_mgr.increase_volume()
387 config.save()
388 elif config.keys_voldown.matches( event ):
389 config.volume = sound_mgr.decrease_volume()
390 config.save()
391 elif config.keys_slow.matches( event ):
392 gamestate.framerate = 1
393 inlevel_redraw_screen( gamestate, False, scores )
394 elif config.keys_fast.matches( event ):
395 if gamestate.framerate == 1:
396 gamestate.framerate = 500
397 else:
398 gamestate.framerate *= 2
399 else:
400 gamestate.key_events.append( event )
403 # ----------------------
405 def finishedgame_input( event, waiting ):
406 if event.type == QUIT:
407 sys.exit(0)
408 elif config.keys_volup.matches( event ):
409 config.volume = sound_mgr.increase_volume()
410 config.save()
411 elif config.keys_voldown.matches( event ):
412 config.volume = sound_mgr.decrease_volume()
413 config.save()
414 elif config.keys_startgame.matches( event ):
415 waiting = False
416 return waiting
418 # ----------------------
420 class PlayerStatus:
421 def __init__( self, x, y, direction, player ):
422 self._x = x
423 self._y = y
424 self._dir = direction
425 self._colour = player.GetColour()
426 self._dead = False
428 def GetPosition( self ):
429 return ( self._x, self._y )
431 def GetDirection( self ):
432 return ( self._dir )
434 def SetDirection( self, direction ):
435 self._dir = direction
437 def SetDead( self, dead ):
438 self._dead = dead
440 def IsDead( self ):
441 return self._dead
443 def Move( self, gamestate ):
444 """Moves the player one position depending on its direction, and
445 returns True if it hit anything, False otherwise."""
447 if self._dead:
448 return self._dead
450 if self._dir == DIR_UP:
451 self._y -= 1
452 elif self._dir == DIR_RIGHT:
453 self._x += 1
454 elif self._dir == DIR_DOWN:
455 self._y += 1
456 elif self._dir == DIR_LEFT:
457 self._x -= 1
459 self._dead = gamestate.AddPixel( self._x, self._y, self._colour )
461 return self._dead
463 class GameBoard( AbstractGameBoard ):
464 def __init__( self, gamestate ):
465 self._gamestate = gamestate
467 def GetRelativePixel( self, start_pos, facing_dir,
468 offset_fwd, offset_right ):
469 """Given a position to stand in the arena, and a direction to face,
470 return the status (0 for empty, >0 for non-empty) of a pixel that
471 is offset_fwd pixels in front, and offset_right pixels to the right
472 (negative values may be used to go backwards or left respectively).
473 Pixels outside the arena also return >0."""
475 if facing_dir == DIR_UP:
476 found_pos = ( start_pos[0] + offset_right,
477 start_pos[1] - offset_fwd )
478 elif facing_dir == DIR_RIGHT:
479 found_pos = ( start_pos[0] + offset_fwd,
480 start_pos[1] + offset_right )
481 elif facing_dir == DIR_DOWN:
482 found_pos = ( start_pos[0] - offset_right,
483 start_pos[1] + offset_fwd )
484 elif facing_dir == DIR_LEFT:
485 found_pos = ( start_pos[0] - offset_fwd,
486 start_pos[1] - offset_right )
488 if ( found_pos[0] < 0 or
489 found_pos[0] >= self._gamestate.config.arena_size[0] or
490 found_pos[1] < 0 or
491 found_pos[1] >= self._gamestate.config.arena_size[1] ):
492 retval = 100
493 elif found_pos in self._gamestate.pixels_set:
494 retval = 1
495 else:
496 retval = 0
498 return retval
500 def GetPlayerPositions( self, pos_to_exclude = None ):
501 """Returns a list of pairs (pos, dir) for each player on screen.
502 Excludes the player at the position specified if pos_to_exclude
503 is not None."""
505 ret = []
507 for player in self._gamestate.statuses.keys():
508 status = self._gamestate.statuses[player]
509 if not status.IsDead():
510 pos = status.GetPosition()
511 if pos_to_exclude != pos:
512 ret.append( ( pos, status.GetDirection() ) )
514 return ret
516 def TurnRight( self, direction ):
517 """Return the direction found by turning 90 degrees right from
518 the supplied direction."""
519 direction += 1
520 if direction > DIR_LEFT:
521 direction = DIR_UP
522 return direction
524 def TurnLeft( self, direction ):
525 """Return the direction found by turning 90 degrees left from
526 the supplied direction."""
527 direction -= 1
528 if direction < DIR_UP:
529 direction = DIR_LEFT
530 return direction
532 def class_is_human( cls ):
533 return "IsHuman" in cls.__dict__ and cls.IsHuman()
535 class GameState:
536 def __init__( self, config, classes ):
537 self.config = config
539 self.players = []
540 self.key_events = []
541 self.any_humans = False
543 for cls in classes:
544 if class_is_human( cls ):
545 self.players.append( cls( config.keys_p1_up,
546 config.keys_p1_right, config.keys_p1_down,
547 config.keys_p1_left ) )
548 self.any_humans = True
549 else:
550 self.players.append( cls() )
552 self.alive = INGAME_TWO_ALIVE
553 self.dim = 1
555 self.pixels_list = []
556 self.pixels_set = set()
557 self.create_initial_pixels()
559 self.statuses = {}
560 for player in self.players:
561 x = random.randint( config.starting_border, config.arena_size[0]
562 - config.starting_border )
563 y = random.randint( config.starting_border, config.arena_size[1]
564 - config.starting_border )
565 dr = random.randint( DIR_UP, DIR_LEFT )
566 self.statuses[player] = PlayerStatus( x, y, dr, player )
567 self.AddPixel( x, y, player.GetColour() )
569 self._gameboard = GameBoard( self )
571 def timer_tick( self ):
572 num_alive = 0
573 for player in self.players:
574 status = self.statuses[player]
575 if not status.IsDead():
576 if class_is_human( player.__class__ ):
577 new_dir = player.GetDirWithInput( status.GetDirection(),
578 self.key_events )
579 else:
580 new_dir = player.GetDir( status.GetPosition(),
581 status.GetDirection(), self._gameboard )
582 if new_dir not in (DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT):
583 sys.stderr.write( "Player '" + player.GetName()
584 + "' returned an invalid value from GetDir(). Killing it.\n" )
585 new_dir = DIR_UP
586 status.SetDead( True )
588 status.SetDirection( new_dir )
589 # TODO: copy pixels list for "security"?
591 dead = self.statuses[player].Move( self )
592 if not dead:
593 num_alive += 1
595 if num_alive < 2:
596 self.alive = INGAME_MOST_DEAD
598 def create_initial_pixels( self ):
599 colour = (100, 100, 100)
600 size_x, size_y = config.arena_size
601 for x in range( size_x ):
602 self.AddPixel( x, 0, colour )
603 self.AddPixel( x, size_y - 1, colour )
604 for y in range( 1, size_y - 1 ):
605 self.AddPixel( 0, y, colour )
606 self.AddPixel( size_x - 1, y, colour )
608 def GetPosition( self, player ):
609 return self.statuses[player].GetPosition()
611 def AddPixel( self, x, y, colour ):
612 if ( x, y ) in self.pixels_set:
613 dead = True
614 else:
615 dead = False
616 self.pixels_set.add( ( x, y ) )
618 self.pixels_list.append( ( x, y, colour ) )
620 return dead
623 # ----------------------
625 def ingame_mainloop( config ):
627 scores = {}
628 for cls in config.player_classes:
629 scores[cls] = 0
631 while True:
632 gamestate = GameState( config, config.player_classes )
633 inlevel_mainloop( config, gamestate, scores )
634 if gamestate.alive == INGAME_QUIT:
635 break
637 return gamestate
639 def increment_scores( gamestate, scores ):
640 for player in gamestate.players:
641 if not gamestate.statuses[player]._dead:
642 scores[player.__class__] += 1
644 # ----------------------
646 def inlevel_mainloop( config, gamestate, scores ):
648 gamestate.alive = INGAME_TWO_ALIVE
650 inlevel_redraw_screen( gamestate, True, scores )
651 if gamestate.any_humans:
652 time.sleep( 1 )
653 inlevel_redraw_screen( gamestate, False, scores )
655 gc.disable()
656 tick_counter = 0
658 gamestate.framerate = 1
660 while gamestate.alive == INGAME_TWO_ALIVE:
661 for evt in pygame.event.get():
662 inlevel_input( evt, gamestate, scores )
663 gamestate.timer_tick()
664 gamestate.key_events = []
665 tick_counter += 1
666 if gamestate.alive != INGAME_MOST_DEAD:
667 if gamestate.framerate == 1:
668 inlevel_update_screen( gamestate, scores )
669 pygame.time.wait( config.tick_time )
670 elif tick_counter >= gamestate.framerate:
671 tick_counter = 0
672 inlevel_redraw_screen( gamestate, False, scores )
674 gc.enable()
675 mopelib.clear_events( EVENTTYPE_INGAME_TICK )
676 increment_scores( gamestate, scores )
678 finishedlevel_mainloop( gamestate, scores )
680 ingame_surface_background.fill( config.colour_background )
683 # ----------------------
685 def write_text( txt, colour, size, y_pos ):
686 ft = pygame.font.Font( None, int( config.screen_size[1] * size ) )
687 sf = ft.render( txt, True, colour )
688 screen.blit( sf, ( (config.screen_size[0] - sf.get_width() )/2,
689 (config.screen_size[1] - sf.get_height() ) * y_pos ) )
691 # ----------------------
693 def write_text_ingame( gamestate, scores ):
694 global ingame_font
696 txt = ""
697 for cls in scores.keys():
698 txt += "%s: %d " % ( cls.GetName(), scores[cls] )
700 colour = (128, 128, 128)
701 bgcolour = config.colour_background
702 y_pos = 0.996
703 sf = ingame_font.render( txt, True, colour )
704 sf_bg = pygame.Surface( ( int( sf.get_width() * 1.29 ), sf.get_height() ) )
705 sf_bg.fill( bgcolour )
707 tlx = ( config.screen_size[0] - sf_bg.get_width() ) / 2
708 tly = ( config.screen_size[1] - sf_bg.get_height() ) * y_pos
710 dirty_rect = Rect( tlx, tly, sf_bg.get_width(), sf_bg.get_height() )
711 gamestate.offscreen_buff.blit( sf_bg, dirty_rect )
712 gamestate.offscreen_buff.blit( sf, ( tlx * 1.01, tly ) )
714 # ----------------------
716 def finishedlevel_input( event, waiting, gamestate, scores ):
717 if event.type == QUIT:
718 sys.exit(0)
719 elif( ( event.type == pygame.ACTIVEEVENT and event.state == 2 )
720 or config.keys_menu.matches( event ) ):
721 general_menu_screen( config, gamestate )
722 inlevel_redraw_screen( gamestate, False, scores )
723 if gamestate.alive == INGAME_QUIT:
724 waiting = False
725 elif config.keys_volup.matches( event ):
726 config.volume = sound_mgr.increase_volume()
727 config.save()
728 elif config.keys_voldown.matches( event ):
729 config.volume = sound_mgr.decrease_volume()
730 config.save()
731 elif event.type == EVENTTYPE_TITLE_TICK:
732 waiting = False
733 elif config.keys_startgame.matches( event ):
734 waiting = False
735 return waiting
737 def finishedlevel_mainloop( gamestate, scores ):
738 gamestate.dim = 0.3
740 if not gamestate.any_humans:
741 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
743 inlevel_redraw_screen( gamestate, False, scores )
744 waiting = True
745 while waiting:
746 waiting = finishedlevel_input( pygame.event.wait(), waiting, gamestate, scores )
748 if not gamestate.any_humans:
749 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
751 def finishedgame_mainloop( config, gamestate ):
752 pass
753 #config.start_level = 0
754 # config.save()
756 # inlevel_redraw_screen( gamestate )
757 # write_text( "Congratulations!", (255,255,255), 0.125, 0.38 )
758 # write_text( "You won!", (255,255,255), 0.125, 0.52 )
759 # waiting = True
760 # write_text( "Press %s" % config.keys_startgame.name, (255,255,255),
761 # 0.05, 0.8 )
762 # pygame.display.update()
763 # while waiting:
764 # waiting = finishedgame_input( pygame.event.wait(), waiting )
766 # ingame_surface_background.fill( config.colour_background )
768 def sort_by_score( class2score ):
769 scorename_sorted = []
771 for cls in class2score.keys():
772 scorename_sorted.append( ( class2score[cls], cls.GetName() ) )
774 scorename_sorted.sort( reverse = True )
776 return scorename_sorted
778 def execute_tournament( config ):
779 total_scores = {}
781 classes = []
782 for cls in config.player_classes:
783 if not class_is_human( cls ):
784 classes.append( cls )
785 total_scores[cls] = 0
787 print
788 print " " * 30 + "=== Pairings ==="
789 print
791 pairs = []
792 num_classes = len( classes )
793 for i in range( num_classes ):
794 for j in range( i+1, num_classes ):
795 execute_match(
796 ( classes[i], classes[j] ),
797 total_scores, False )
799 print
800 print " " * 30 + "=== Medley ==="
801 print
803 execute_match( classes, total_scores, True )
805 print
806 print " " * 30 + "=== Total Scores ==="
807 print
809 scorename_sorted = sort_by_score( total_scores )
811 for score, name in scorename_sorted:
812 print "%30s % 4d" % ( name, score )
813 print
816 def execute_match( classes, total_scores, newlines ):
817 match_scores = {}
818 for cls in classes:
819 match_scores[cls] = 0
821 for i in range( config.num_games ):
822 gamestate = GameState( config, classes )
824 while gamestate.alive == INGAME_TWO_ALIVE:
825 gamestate.timer_tick()
827 for player in gamestate.statuses.keys():
828 status = gamestate.statuses[player]
829 if not status.IsDead():
830 match_scores[player.__class__] += 1
832 scorename_sorted = sort_by_score( match_scores )
834 oldscore = -1
835 for score, name in scorename_sorted:
836 if oldscore != -1 and not newlines:
837 if oldscore == score:
838 print " drew",
839 else:
840 print " beat",
841 print "%- 4d %-30s" % ( score, name ),
842 else:
843 print "%30s% 4d" % ( name, score ),
844 oldscore = score
847 if newlines:
848 print
850 if not newlines:
851 print
853 for cls in classes:
854 total_scores[cls] += match_scores[cls]
856 def get_players( players_dir ):
857 ret_classes = []
859 player_re = re.compile( """^(\w*Player).py$""" )
860 for filename in os.listdir( players_dir ):
861 m = player_re.match( filename )
862 if m:
863 class_name = m.group( 1 )
864 cls = __import__( "players." + class_name,
865 globals(), locals() ).__dict__[class_name].__dict__[class_name]
866 ret_classes.append( cls )
868 #return ret_classes[:2]
869 return ret_classes
871 # ----------------------
872 # Execution starts here
873 # ----------------------
875 # Fixed constants
877 INTRO_MODE_TITLE = 0
878 INTRO_MODE_INSTR = 1
879 INTRO_MODE_MUSIC = 2
880 INTRO_MODE_ENDED = 3
882 MENU_START = 0
883 MENU_NUM_PLAYERS = 1
884 MENU_END = 2
885 MENU_MUSIC = 3
886 MENU_SOUND_EFFECTS = 4
887 MENU_QUIT = 6
888 MENU_CHANGE_PLAYER = 100 # Must go at end of this list
890 INGAME_TWO_ALIVE = 0
891 INGAME_MOST_DEAD = 1
892 INGAME_QUIT = 2
894 TITLE_TICK_TIME = 4000
896 EVENTTYPE_INGAME_TICK = pygame.USEREVENT
897 EVENTTYPE_TITLE_TICK = pygame.USEREVENT + 1
899 num_args = len( sys.argv )
900 #if num_args > 1:
901 # if sys.argv[1] == "--help":
902 # print "Usage:"
903 # print "troncode.py [install_dir] [config_file] [resolution] "
904 # print " [num_players] [--tournament]"
905 # print
906 # print "e.g. ./troncode.py . troncoderc.txt '(640,480)' 2 --tournament"
907 # print
908 # sys.exit( 0 )
910 # install_dir = sys.argv[1]
911 #else:
912 # install_dir = "."
914 #if num_args > 2:
915 # config_filename = sys.argv[2]
916 #else:
917 # config_filename = os.path.expanduser( "~/.troncode/config" )
919 config_filename = os.path.expanduser( "~/.troncode/config" )
920 install_dir = "."
922 config = TronCodeConfig( config_filename )
924 if num_args > 1 and sys.argv[1] == "--tournament":
925 run_tournament = True
926 config.num_games = 100
927 else:
928 run_tournament = False
930 config.install_dir = install_dir
931 config.unsaved.append( "install_dir" )
933 config.images_dir = os.path.join( install_dir, "images" )
934 config.unsaved.append( "images_dir" )
936 config.music_dir = os.path.join( install_dir, "music" )
937 config.unsaved.append( "music_dir" )
939 if num_args > 3:
940 config.screen_size = config.parse_value( sys.argv[3] )
942 config.arena_size = ( 200, 200 )
943 config.unsaved.append( "arena_size" )
945 config.starting_border = 35
946 config.unsaved.append( "starting_border" )
948 config.players_dir = "players"
949 config.unsaved.append( "players_dir" )
951 config.player_classes = get_players( config.players_dir )
952 config.unsaved.append( "player_classes" )
954 config.players = []
955 config.unsaved.append( "players" )
957 if run_tournament:
958 execute_tournament( config )
959 else:
960 pygame.init()
961 pygame.font.init()
963 window = pygame.display.set_mode( config.screen_size )
964 pygame.display.set_caption( 'troncode' )
965 screen = pygame.display.get_surface()
967 fixed_border = 5
968 bottom_border = 25
969 scale = min(
970 ( float( config.screen_size[0] - fixed_border*2 )
971 / float( config.arena_size[0] ) ),
972 ( float( config.screen_size[1] - ( fixed_border*2 + bottom_border ) )
973 / float( config.arena_size[1] ) ) )
975 screen_border = ( float( config.screen_size[0] - config.arena_size[0]*scale )
976 / 2.0,
977 float( ( config.screen_size[1] - config.arena_size[1]*scale ) - ( bottom_border - fixed_border ) ) / 2.0 )
979 ceil_scale = math.ceil( scale )
981 # General initialisation
983 num_joysticks = pygame.joystick.get_count()
984 for j in range( num_joysticks ):
985 pygame.joystick.Joystick( j ).init()
987 intro_surface_title = mopelib.load_and_scale_image( "title.png", config )
988 intro_surface_instr = mopelib.load_and_scale_image( "instructions.png", config )
989 intro_surface_music = mopelib.load_and_scale_image( "music.png", config )
991 ingame_surface_background = pygame.Surface( screen.get_size() ).convert()
992 ingame_surface_background.fill( config.colour_background )
994 intro_mode = INTRO_MODE_TITLE
996 sound_mgr = TronCodeSoundManager( config.volume )
998 troncode_version = mopelib.read_version( config )
1000 ingame_font = pygame.font.Font( None, int( config.screen_size[1] * 0.03 ) )
1002 menurender = mopelib.MenuRenderer( screen, config, ingame_surface_background,
1003 (128, 128, 128), (128, 255, 128), (128, 128, 128) )
1005 while True:
1006 sound_mgr.music_loud()
1007 intro_mainloop( config )
1008 sound_mgr.music_quiet()
1009 gamestate = ingame_mainloop( config )
1010 intro_mode = finishedgame_mainloop( config, gamestate )