Updated version to 0.0.7.
[troncode.git] / troncode.py
blob571537cf9e705b02bd2f4bf5b6e3ba5924521831
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 BasicGameBoard import BasicGameBoard
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 = ( 0, 0, 0 )
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( "f" )
99 self.keys_fast.add( pygame.KEYDOWN, ord( 'f' ) )
101 self.keys_skip_to_end = mopelib.MyInputEvent( "k" )
102 self.keys_skip_to_end.add( pygame.KEYDOWN, ord( 'k' ) )
104 self.keys_voldown = mopelib.MyInputEvent( "-" )
105 self.keys_voldown.add( pygame.KEYDOWN, ord( '-' ) )
106 self.keys_voldown.add( pygame.JOYBUTTONDOWN, 17 ) # GP2X volume - button
108 self.keys_p1_up = mopelib.MyInputEvent( "p1_up" )
109 self.keys_p1_up.add( pygame.KEYDOWN, pygame.K_UP )
110 self.keys_p1_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
112 self.keys_p1_right = mopelib.MyInputEvent( "p1_right" )
113 self.keys_p1_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
114 self.keys_p1_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
116 self.keys_p1_down = mopelib.MyInputEvent( "p1_down" )
117 self.keys_p1_down.add( pygame.KEYDOWN, pygame.K_DOWN )
118 self.keys_p1_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
120 self.keys_p1_left = mopelib.MyInputEvent( "p1_left" )
121 self.keys_p1_left.add( pygame.KEYDOWN, pygame.K_LEFT )
122 self.keys_p1_left.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy left
124 self.keys_p2_up = mopelib.MyInputEvent( "p2_up" )
125 self.keys_p2_up.add( pygame.KEYDOWN, ord( '2' ) )
126 self.keys_p2_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
128 self.keys_p2_right = mopelib.MyInputEvent( "p2_right" )
129 self.keys_p2_right.add( pygame.KEYDOWN, ord( 'e' ) )
130 self.keys_p2_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
132 self.keys_p2_down = mopelib.MyInputEvent( "p2_down" )
133 self.keys_p2_down.add( pygame.KEYDOWN, ord( 'w' ) )
134 self.keys_p2_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
136 self.keys_p2_left = mopelib.MyInputEvent( "p2_left" )
137 self.keys_p2_left.add( pygame.KEYDOWN, ord( 'q' ) )
138 self.keys_p2_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
140 self.keys_p3_up = mopelib.MyInputEvent( "p3_up" )
141 self.keys_p3_up.add( pygame.KEYDOWN, ord( 'i' ) )
143 self.keys_p3_right = mopelib.MyInputEvent( "p3_right" )
144 self.keys_p3_right.add( pygame.KEYDOWN, ord( 'l' ) )
146 self.keys_p3_down = mopelib.MyInputEvent( "p3_down" )
147 self.keys_p3_down.add( pygame.KEYDOWN, ord( 'k' ) )
149 self.keys_p3_left = mopelib.MyInputEvent( "p3_left" )
150 self.keys_p3_left.add( pygame.KEYDOWN, ord( 'j' ) )
152 self.keys_p4_up = mopelib.MyInputEvent( "p4_up" )
153 self.keys_p4_up.add( pygame.KEYDOWN, pygame.K_KP8 )
155 self.keys_p4_right = mopelib.MyInputEvent( "p4_right" )
156 self.keys_p4_right.add( pygame.KEYDOWN, pygame.K_KP6 )
158 self.keys_p4_down = mopelib.MyInputEvent( "p4_down" )
159 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP5 )
160 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP2 )
162 self.keys_p4_left = mopelib.MyInputEvent( "p4_left" )
163 self.keys_p4_left.add( pygame.KEYDOWN, pygame.K_KP4 )
166 # ----------------------
168 class TronCodeSoundManager( mopelib.SoundManager ):
170 def __init__( self, volume ):
171 mopelib.SoundManager.__init__( self, config )
173 #self.add_sample_group( "waddles", ["waddle1"] )
175 # ----------------------
177 def intro_draw_keys():
178 keys_colour = (0, 0, 0)
179 write_text( "Keys", keys_colour, 0.2, 0.05 )
180 write_text( "Slow down: %s" % config.keys_slow.name, keys_colour, 0.1, 0.25 )
181 write_text( "Speed up: %s" % config.keys_fast.name, keys_colour, 0.1, 0.35 )
182 write_text( "Skip round: %s" % config.keys_skip_to_end.name, keys_colour, 0.1, 0.45 )
184 # ----------------------
186 def intro_draw_instructions():
187 write_text( "Press %s for menu, or %s to start" % (
188 config.keys_menu.name, config.keys_startgame.name ),
189 (0, 0, 0), 0.05, 0.99 )
191 # ----------------------
193 def general_menu_create_menu( menu, config, gamestate ):
194 menu.items = []
195 if gamestate == None: # We are on a title screen - Start Game option
196 menu.add_item( "Start game", MENU_START )
197 menu.add_item( "Number of players: %d" % (config.num_players),
198 MENU_NUM_PLAYERS )
199 for player_num in range( config.num_players ):
200 cls_name = "--unknown--"
201 if player_num < len( config.player_classes ):
202 cls_name = config.player_classes[player_num].GetName()
203 menu.add_item( "P%d: %s" % ( player_num, cls_name ),
204 MENU_CHANGE_PLAYER + player_num )
205 else:
206 menu.add_item( "Continue", MENU_START )
207 menu.add_item( "End game", MENU_END )
209 tmp_str = "Music: "
210 if config.music_on:
211 tmp_str += "on"
212 else:
213 tmp_str += "off"
214 menu.add_item( tmp_str, MENU_MUSIC )
216 tmp_str = "Effects: "
217 if config.sound_effects_on:
218 tmp_str += "on"
219 else:
220 tmp_str += "off"
221 menu.add_item( tmp_str, MENU_SOUND_EFFECTS )
223 menu.add_item( "Quit troncode", MENU_QUIT )
225 return menu
227 def general_menu_screen( config, gamestate ):
229 if gamestate == None:
230 menu_title = "troncode"
231 else:
232 menu_title = "troncode paused"
234 menu = mopelib.Menu()
235 general_menu_create_menu( menu, config, gamestate )
236 menurender.set_menu( menu, menu_title )
237 menurender.repaint_full()
239 game_start = False
241 waiting = True
242 while waiting:
243 event = pygame.event.wait()
244 if event.type == QUIT:
245 sys.exit(0)
246 elif config.keys_menu.matches( event ):
247 waiting = False
248 elif config.keys_down.matches( event ):
249 menurender.move_down()
250 elif config.keys_up.matches( event ):
251 menurender.move_up()
252 elif config.keys_return.matches( event ):
253 code = menu.get_selected_item().code
254 if code == MENU_START:
255 game_start = True
256 waiting = False
257 elif code == MENU_END:
258 gamestate.alive = INGAME_QUIT
259 waiting = False
260 elif code == MENU_MUSIC:
261 if config.music_on == 1:
262 config.music_on = 0
263 else:
264 config.music_on = 1
265 general_menu_create_menu( menu, config, gamestate )
266 menurender.repaint_full()
267 sound_mgr.setup( gamestate )
268 config.save()
269 elif code == MENU_SOUND_EFFECTS:
270 if config.sound_effects_on:
271 config.sound_effects_on = 0
272 else:
273 config.sound_effects_on = 1
274 general_menu_create_menu( menu, config, gamestate )
275 menurender.repaint_full()
276 sound_mgr.setup( gamestate )
277 config.save()
278 elif code == MENU_QUIT:
279 sys.exit(0)
281 return game_start
283 # ----------------------
285 def intro_draw_title():
286 screen.blit( intro_surface_title, (0,0) )
287 write_text( "Version " + troncode_version,
288 ( 0, 0, 0 ), 0.05, 0.88 )
289 write_text( "by Andy Balaam", ( 0, 0, 0 ), 0.06, 0.93 )
290 intro_draw_instructions()
292 def intro_draw_something( intro_mode ):
293 if intro_mode == INTRO_MODE_TITLE:
294 intro_draw_title()
295 elif intro_mode == INTRO_MODE_INSTR:
296 screen.blit( intro_surface_instr, (0,0) )
297 intro_draw_instructions()
298 elif intro_mode == INTRO_MODE_MUSIC:
299 screen.blit( intro_surface_music, (0,0) )
300 intro_draw_keys()
301 intro_draw_instructions()
302 pygame.display.update()
304 def intro_input( event, config, intro_mode ):
305 if event.type == QUIT:
306 sys.exit(0)
307 elif config.keys_volup.matches( event ):
308 config.volume = sound_mgr.increase_volume()
309 config.save()
310 elif config.keys_voldown.matches( event ):
311 config.volume = sound_mgr.decrease_volume()
312 config.save()
313 else:
314 if event.type == EVENTTYPE_TITLE_TICK:
315 intro_mode += 1
316 if intro_mode == INTRO_MODE_ENDED:
317 intro_mode = INTRO_MODE_TITLE
318 intro_draw_something( intro_mode )
319 elif config.keys_menu.matches( event ):
320 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
321 start_game = general_menu_screen( config, None )
322 if start_game:
323 intro_mode = INTRO_MODE_ENDED
324 else:
325 intro_draw_something( intro_mode )
326 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
327 elif config.keys_startgame.matches( event ):
328 intro_mode = INTRO_MODE_ENDED
329 return intro_mode
331 # ----------------------
333 def intro_mainloop( config ):
334 intro_draw_title()
336 pygame.display.update()
337 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
339 intro_mode = INTRO_MODE_TITLE
340 while intro_mode < INTRO_MODE_ENDED:
341 intro_mode = intro_input( pygame.event.wait(), config, intro_mode )
343 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
345 def draw_pixel( gamestate, surface, colour, x, y ):
346 if colour in gamestate.alive_colours:
347 col = colour
348 else:
349 col = mopelib.dim_colour( colour, gamestate.dim )
350 adj_x = screen_border[0] + scale * x
351 adj_y = screen_border[1] + scale * y
352 if scale < 1:
353 surface.set_at( ( int(adj_x), int(adj_y) ), col )
354 else:
355 pygame.draw.rect( surface, col,
356 (adj_x, adj_y, ceil_scale, ceil_scale ) )
358 def inlevel_screen_blit( gamestate ):
359 screen.blit( gamestate.offscreen_buff, (0,0) )
360 pygame.display.update()
362 def inlevel_redraw_screen( gamestate, arrows, scores ):
363 gamestate.offscreen_buff = pygame.Surface( gamestate.config.screen_size )
364 gamestate.offscreen_buff.blit( ingame_surface_background, (0,0) )
365 for x, y, colour in gamestate.pixels_list:
366 draw_pixel( gamestate, gamestate.offscreen_buff, colour, x, y )
367 inlevel_draw_players( gamestate )
368 write_text_ingame( gamestate, scores )
369 # TODO: draw arrows
370 inlevel_screen_blit( gamestate )
372 def inlevel_draw_players( gamestate ):
373 for player in gamestate.players:
374 x, y = gamestate.GetPosition( player )
375 draw_pixel( gamestate, gamestate.offscreen_buff, player.GetColour(),
376 x, y )
378 def inlevel_update_screen( gamestate, scores ):
379 inlevel_draw_players( gamestate )
380 inlevel_screen_blit( gamestate )
384 # ----------------------
386 def inlevel_input( event, gamestate, scores ):
387 if event.type == QUIT:
388 sys.exit(0)
389 elif config.keys_menu.matches( event ):
390 general_menu_screen( config, gamestate )
391 inlevel_redraw_screen( gamestate, False, scores )
392 elif config.keys_volup.matches( event ):
393 config.volume = sound_mgr.increase_volume()
394 config.save()
395 elif config.keys_voldown.matches( event ):
396 config.volume = sound_mgr.decrease_volume()
397 config.save()
398 elif config.keys_slow.matches( event ):
399 gamestate.framerate = 0
400 inlevel_redraw_screen( gamestate, False, scores )
401 elif config.keys_fast.matches( event ):
402 if gamestate.framerate == 0:
403 gamestate.framerate = 1
404 elif gamestate.framerate == 1:
405 gamestate.framerate = 250
406 else:
407 gamestate.framerate *= 2
408 elif config.keys_skip_to_end.matches( event ):
409 gamestate.framerate = 1000000
410 else:
411 gamestate.key_events.append( event )
414 # ----------------------
416 def finishedgame_input( event, waiting ):
417 if event.type == QUIT:
418 sys.exit(0)
419 elif config.keys_volup.matches( event ):
420 config.volume = sound_mgr.increase_volume()
421 config.save()
422 elif config.keys_voldown.matches( event ):
423 config.volume = sound_mgr.decrease_volume()
424 config.save()
425 elif config.keys_startgame.matches( event ):
426 waiting = False
427 return waiting
429 # ----------------------
431 class PlayerStatus:
432 def __init__( self, x, y, direction, player ):
433 self._x = x
434 self._y = y
435 self._dir = direction
436 self._colour = player.GetColour()
437 self._dead = False
439 def GetPosition( self ):
440 return ( self._x, self._y )
442 def GetDirection( self ):
443 return ( self._dir )
445 def SetDirection( self, direction ):
446 self._dir = direction
448 def SetDead( self, dead ):
449 self._dead = dead
451 def IsDead( self ):
452 return self._dead
454 def Move( self, gamestate ):
455 """Moves the player one position depending on its direction, and
456 returns True if it hit anything, False otherwise."""
458 if self._dead:
459 return self._dead
461 if self._dir == DIR_UP:
462 self._y -= 1
463 elif self._dir == DIR_RIGHT:
464 self._x += 1
465 elif self._dir == DIR_DOWN:
466 self._y += 1
467 elif self._dir == DIR_LEFT:
468 self._x -= 1
470 self._dead = gamestate.AddPixel( self._x, self._y, self._colour )
471 if self._dead:
472 gamestate.alive_colours.remove( self._colour )
474 return self._dead
476 class GameBoard( BasicGameBoard ):
478 def __init__( self, gamestate ):
479 self._gamestate = gamestate
481 def GetArenaSize( self ):
482 return self._gamestate.config.arena_size
484 def GetAbsolutePixel( self, pos ):
485 if pos in self._gamestate.pixels_set:
486 retval = 1
487 else:
488 retval = 0
489 return retval
491 def GetPlayerPositions( self, pos_to_exclude = None ):
492 ret = []
494 for player in self._gamestate.statuses.keys():
495 status = self._gamestate.statuses[player]
496 if not status.IsDead():
497 pos = status.GetPosition()
498 if pos_to_exclude != pos:
499 ret.append( ( pos, status.GetDirection() ) )
500 return ret
502 def class_is_human( cls ):
503 return "IsHuman" in cls.__dict__ and cls.IsHuman()
505 class GameState:
506 def __init__( self, config, classes ):
507 self.config = config
509 self.players = []
510 self.key_events = []
511 self.any_humans = False
513 for cls in classes:
514 if class_is_human( cls ):
515 self.players.append( cls( config.keys_p1_up,
516 config.keys_p1_right, config.keys_p1_down,
517 config.keys_p1_left ) )
518 self.any_humans = True
519 else:
520 self.players.append( cls() )
522 self.alive = INGAME_TWO_ALIVE
523 self.dim = 0.5
525 self.pixels_list = []
526 self.pixels_set = set()
527 self.create_initial_pixels()
529 self.alive_colours = []
530 self.statuses = {}
531 for player in self.players:
532 x = random.randint( config.starting_border, config.arena_size[0]
533 - config.starting_border )
534 y = random.randint( config.starting_border, config.arena_size[1]
535 - config.starting_border )
536 dr = random.randint( DIR_UP, DIR_LEFT )
537 self.statuses[player] = PlayerStatus( x, y, dr, player )
538 self.AddPixel( x, y, player.GetColour() )
539 self.alive_colours.append( player.GetColour() )
541 self._gameboard = GameBoard( self )
543 def timer_tick( self ):
544 num_alive = 0
545 for player in self.players:
546 status = self.statuses[player]
547 if not status.IsDead():
548 if class_is_human( player.__class__ ):
549 new_dir = player.GetDirWithInput( status.GetDirection(),
550 self.key_events )
551 else:
552 try:
553 new_dir = player.GetDir( status.GetPosition(),
554 status.GetDirection(), self._gameboard )
555 except Exception, e:
556 sys.stderr.write( ( "Player '%s' threw "
557 + "an exception from GetDir(). "
558 + "Killing it.\nException: '%s'\n" ) %
559 ( player.GetName(), e ) )
560 new_dir = DIR_UP
561 status.SetDead( True )
563 if new_dir not in (DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT):
564 sys.stderr.write( "Player '" + player.GetName()
565 + "' returned an invalid value from GetDir(). Killing it.\n" )
566 new_dir = DIR_UP
567 status.SetDead( True )
569 status.SetDirection( new_dir )
570 # TODO: copy pixels list for "security"?
572 dead = self.statuses[player].Move( self )
573 if not dead:
574 num_alive += 1
576 if num_alive < 2:
577 self.alive = INGAME_MOST_DEAD
579 def create_initial_pixels( self ):
580 colour = (100, 100, 100)
581 size_x, size_y = config.arena_size
582 for x in range( size_x ):
583 self.AddPixel( x, 0, colour )
584 self.AddPixel( x, size_y - 1, colour )
585 for y in range( 1, size_y - 1 ):
586 self.AddPixel( 0, y, colour )
587 self.AddPixel( size_x - 1, y, colour )
589 def GetPosition( self, player ):
590 return self.statuses[player].GetPosition()
592 def AddPixel( self, x, y, colour ):
593 if ( x, y ) in self.pixels_set:
594 dead = True
595 else:
596 dead = False
597 self.pixels_set.add( ( x, y ) )
599 self.pixels_list.append( ( x, y, colour ) )
601 return dead
604 # ----------------------
606 def ingame_mainloop( config ):
608 scores = {}
609 for cls in config.player_classes:
610 scores[cls] = 0
612 while True:
613 gamestate = GameState( config, config.player_classes )
614 inlevel_mainloop( config, gamestate, scores )
615 if gamestate.alive == INGAME_QUIT:
616 break
618 return gamestate
620 def increment_scores( gamestate, scores ):
621 for player in gamestate.players:
622 if not gamestate.statuses[player]._dead:
623 scores[player.__class__] += 1
625 # ----------------------
627 def inlevel_mainloop( config, gamestate, scores ):
629 gamestate.alive = INGAME_TWO_ALIVE
631 inlevel_redraw_screen( gamestate, True, scores )
632 if gamestate.any_humans:
633 time.sleep( 1 )
634 inlevel_redraw_screen( gamestate, False, scores )
636 gc.disable()
637 tick_counter = 0
639 gamestate.framerate = 0
640 num_alive = len(gamestate.alive_colours)
642 while gamestate.alive == INGAME_TWO_ALIVE:
643 for evt in pygame.event.get():
644 inlevel_input( evt, gamestate, scores )
645 gamestate.timer_tick()
646 gamestate.key_events = []
647 tick_counter += 1
648 if gamestate.alive != INGAME_MOST_DEAD:
649 if gamestate.framerate <= 1:
650 inlevel_update_screen( gamestate, scores )
651 if gamestate.framerate == 0:
652 pygame.time.wait( config.tick_time )
653 elif tick_counter >= gamestate.framerate:
654 tick_counter = 0
655 inlevel_redraw_screen( gamestate, False, scores )
656 if num_alive != len(gamestate.alive_colours):
657 num_alive = len(gamestate.alive_colours)
658 # redraw with dead players dimmed
659 inlevel_redraw_screen( gamestate, False, scores )
662 gc.enable()
663 mopelib.clear_events( EVENTTYPE_INGAME_TICK )
664 increment_scores( gamestate, scores )
666 finishedlevel_mainloop( gamestate, scores )
668 ingame_surface_background.fill( config.colour_background )
671 # ----------------------
673 def write_text( txt, colour, size, y_pos ):
674 ft = pygame.font.Font( None, int( config.screen_size[1] * size ) )
675 sf = ft.render( txt, True, colour )
676 screen.blit( sf, ( (config.screen_size[0] - sf.get_width() )/2,
677 (config.screen_size[1] - sf.get_height() ) * y_pos ) )
679 # ----------------------
681 def write_text_ingame( gamestate, scores ):
682 global ingame_font
684 txt = ""
685 for cls in scores.keys():
686 txt += "%s: %d " % ( cls.GetName(), scores[cls] )
688 colour = (128, 128, 128)
689 bgcolour = config.colour_background
690 y_pos = 0.996
691 sf = ingame_font.render( txt, True, colour )
692 sf_bg = pygame.Surface( ( int( sf.get_width() ), sf.get_height() ) )
693 sf_bg.fill( bgcolour )
695 tlx = ( config.screen_size[0] - sf_bg.get_width() ) / 2
696 tly = ( config.screen_size[1] - sf_bg.get_height() ) * y_pos
698 dirty_rect = Rect( tlx, tly, sf_bg.get_width(), sf_bg.get_height() )
699 gamestate.offscreen_buff.blit( sf_bg, dirty_rect )
700 gamestate.offscreen_buff.blit( sf, ( tlx * 1.01, tly ) )
702 # ----------------------
704 def finishedlevel_input( event, waiting, gamestate, scores ):
705 if event.type == QUIT:
706 sys.exit(0)
707 elif( ( event.type == pygame.ACTIVEEVENT and event.state == 2 )
708 or config.keys_menu.matches( event ) ):
709 general_menu_screen( config, gamestate )
710 inlevel_redraw_screen( gamestate, False, scores )
711 if gamestate.alive == INGAME_QUIT:
712 waiting = False
713 elif config.keys_volup.matches( event ):
714 config.volume = sound_mgr.increase_volume()
715 config.save()
716 elif config.keys_voldown.matches( event ):
717 config.volume = sound_mgr.decrease_volume()
718 config.save()
719 elif event.type == EVENTTYPE_TITLE_TICK:
720 waiting = False
721 elif config.keys_startgame.matches( event ):
722 waiting = False
723 return waiting
725 def finishedlevel_mainloop( gamestate, scores ):
727 if not gamestate.any_humans:
728 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
730 inlevel_redraw_screen( gamestate, False, scores )
731 waiting = True
732 while waiting:
733 waiting = finishedlevel_input( pygame.event.wait(), waiting, gamestate, scores )
735 if not gamestate.any_humans:
736 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
738 def finishedgame_mainloop( config, gamestate ):
739 pass
740 #config.start_level = 0
741 # config.save()
743 # inlevel_redraw_screen( gamestate )
744 # write_text( "Congratulations!", (255,255,255), 0.125, 0.38 )
745 # write_text( "You won!", (255,255,255), 0.125, 0.52 )
746 # waiting = True
747 # write_text( "Press %s" % config.keys_startgame.name, (255,255,255),
748 # 0.05, 0.8 )
749 # pygame.display.update()
750 # while waiting:
751 # waiting = finishedgame_input( pygame.event.wait(), waiting )
753 # ingame_surface_background.fill( config.colour_background )
755 def sort_by_score( class2score ):
756 scorename_sorted = []
758 for cls in class2score.keys():
759 scorename_sorted.append( ( class2score[cls], cls.GetName() ) )
761 scorename_sorted.sort( reverse = True )
763 return scorename_sorted
765 def execute_tests( config ):
766 module_name = config.test_player_name
767 module = __import__( "players." + module_name,
768 globals(), locals() ).__dict__[module_name]
770 module.run_tests()
772 def execute_tournament( config ):
773 total_scores = {}
775 classes = []
776 for cls in config.player_classes:
777 if not class_is_human( cls ):
778 classes.append( cls )
779 total_scores[cls] = 0
781 print
782 print " " * 30 + "=== Pairings ==="
783 print
785 pairs = []
786 num_classes = len( classes )
787 for i in range( num_classes ):
788 for j in range( i+1, num_classes ):
789 execute_match(
790 ( classes[i], classes[j] ),
791 total_scores, False, config.num_games )
793 print
794 print " " * 30 + u"=== Melee ==="
795 print
797 execute_match( classes, total_scores, True,
798 config.num_games * (len( classes )-1) )
800 print
801 print " " * 30 + "=== Total Scores ==="
802 print
804 scorename_sorted = sort_by_score( total_scores )
806 for score, name in scorename_sorted:
807 print "%30s % 4d" % ( name, score )
808 print
811 def execute_match( classes, total_scores, newlines, num_games ):
812 match_scores = {}
813 for cls in classes:
814 match_scores[cls] = 0
816 print_dots = False
818 for i in range( num_games ):
819 if print_dots:
820 sys.stdout.write( "." )
821 gamestate = GameState( config, classes )
823 while gamestate.alive == INGAME_TWO_ALIVE:
824 gamestate.timer_tick()
826 for player in gamestate.statuses.keys():
827 status = gamestate.statuses[player]
828 if not status.IsDead():
829 match_scores[player.__class__] += 1
831 if print_dots:
832 print
834 scorename_sorted = sort_by_score( match_scores )
836 oldscore = -1
837 for score, name in scorename_sorted:
838 if oldscore != -1 and not newlines:
839 if oldscore == score:
840 print " drew",
841 else:
842 print " beat",
843 print "%- 4d %-30s" % ( score, name ),
844 else:
845 print "%30s% 4d" % ( name, score ),
846 oldscore = score
849 if newlines:
850 print
852 if not newlines:
853 print
855 for cls in classes:
856 total_scores[cls] += match_scores[cls]
858 def get_players( players_dir ):
859 ret_classes = []
861 player_re = re.compile( """^(\w*Player).py$""" )
862 for filename in os.listdir( players_dir ):
863 m = player_re.match( filename )
864 if m:
865 class_name = m.group( 1 )
866 cls = __import__( "players." + class_name,
867 globals(), locals() ).__dict__[class_name].__dict__[class_name]
868 ret_classes.append( cls )
870 #return ret_classes[:2]
871 return ret_classes
873 # ----------------------
874 # Execution starts here
875 # ----------------------
877 # Fixed constants
879 INTRO_MODE_TITLE = 0
880 INTRO_MODE_INSTR = 1
881 INTRO_MODE_MUSIC = 2
882 INTRO_MODE_ENDED = 3
884 MENU_START = 0
885 MENU_NUM_PLAYERS = 1
886 MENU_END = 2
887 MENU_MUSIC = 3
888 MENU_SOUND_EFFECTS = 4
889 MENU_QUIT = 6
890 MENU_CHANGE_PLAYER = 100 # Must go at end of this list
892 INGAME_TWO_ALIVE = 0
893 INGAME_MOST_DEAD = 1
894 INGAME_QUIT = 2
896 TITLE_TICK_TIME = 4000
898 EVENTTYPE_INGAME_TICK = pygame.USEREVENT
899 EVENTTYPE_TITLE_TICK = pygame.USEREVENT + 1
901 num_args = len( sys.argv )
903 config_filename = os.path.expanduser( "~/.troncode/config" )
904 install_dir = "."
906 run_tests = False
907 run_tournament = False
909 config = TronCodeConfig( config_filename )
911 if num_args > 1:
912 if sys.argv[1] == "--tournament":
913 run_tournament = True
914 config.num_games = 100
915 elif sys.argv[1] == "--test":
916 if num_args > 2:
917 run_tests = True
918 config.test_player_name = sys.argv[2]
919 config.unsaved.append( "test_player_name" )
920 else:
921 sys.stderr.write( "Please supply the name of the player to test.\n" )
922 sys.exit( 1 )
924 config.install_dir = install_dir
925 config.unsaved.append( "install_dir" )
927 config.images_dir = os.path.join( install_dir, "images" )
928 config.unsaved.append( "images_dir" )
930 config.music_dir = os.path.join( install_dir, "music" )
931 config.unsaved.append( "music_dir" )
933 if num_args > 3:
934 config.screen_size = config.parse_value( sys.argv[3] )
936 config.arena_size = ( 200, 200 )
937 config.unsaved.append( "arena_size" )
939 config.starting_border = 35
940 config.unsaved.append( "starting_border" )
942 config.players_dir = "players"
943 config.unsaved.append( "players_dir" )
945 config.player_classes = get_players( config.players_dir )
946 config.unsaved.append( "player_classes" )
948 config.players = []
949 config.unsaved.append( "players" )
951 if run_tests:
952 execute_tests( config )
953 elif run_tournament:
954 execute_tournament( config )
955 else:
956 pygame.init()
957 pygame.font.init()
959 window = pygame.display.set_mode( config.screen_size )
960 pygame.display.set_caption( 'troncode' )
961 screen = pygame.display.get_surface()
963 fixed_border = 5
964 bottom_border = 25
965 scale = min(
966 ( float( config.screen_size[0] - fixed_border*2 )
967 / float( config.arena_size[0] ) ),
968 ( float( config.screen_size[1] - ( fixed_border*2 + bottom_border ) )
969 / float( config.arena_size[1] ) ) )
971 screen_border = ( float( config.screen_size[0] - config.arena_size[0]*scale )
972 / 2.0,
973 float( ( config.screen_size[1] - config.arena_size[1]*scale ) - ( bottom_border - fixed_border ) ) / 2.0 )
975 ceil_scale = math.ceil( scale )
977 # General initialisation
979 num_joysticks = pygame.joystick.get_count()
980 for j in range( num_joysticks ):
981 pygame.joystick.Joystick( j ).init()
983 intro_surface_title = mopelib.load_and_scale_image( "title.png", config )
984 intro_surface_instr = mopelib.load_and_scale_image( "instructions.png", config )
985 intro_surface_music = mopelib.load_and_scale_image( "music.png", config )
987 ingame_surface_background = pygame.Surface( screen.get_size() ).convert()
988 ingame_surface_background.fill( config.colour_background )
990 intro_mode = INTRO_MODE_TITLE
992 sound_mgr = TronCodeSoundManager( config.volume )
994 troncode_version = mopelib.read_version( config )
996 ingame_font = pygame.font.Font( None, int( config.screen_size[1] * 0.019 ) )
998 menurender = mopelib.MenuRenderer( screen, config, ingame_surface_background,
999 (128, 128, 128), (128, 255, 128), (128, 128, 128) )
1001 while True:
1002 sound_mgr.music_loud()
1003 intro_mainloop( config )
1004 sound_mgr.music_quiet()
1005 gamestate = ingame_mainloop( config )
1006 intro_mode = finishedgame_mainloop( config, gamestate )