Wrote proper tournament code so that each player is played off against each other...
[troncode.git] / troncode.py
blob6c713144a9c4ec9b154aae34ea23dc74f9652171
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 mopelib import mopelib
34 from troncode_values import *
36 # ----------------------
37 class TronCodeConfig( mopelib.Config ):
39 def default_config( self ):
40 self.num_players = 2
41 self.tick_time = 2
43 self.screen_size = ( 810, 810 )
44 self.colour_background = ( 15, 45, 15 )
46 self.volume = 50
48 self.music_on = 1
49 self.sound_effects_on = 1
51 self.keys_menu = mopelib.MyInputEvent( "Escape" )
52 self.keys_menu.add( pygame.KEYDOWN, pygame.K_ESCAPE )
53 self.keys_menu.add( pygame.JOYBUTTONDOWN, 8 ) # GP2X Start
54 self.keys_menu.add( pygame.JOYBUTTONDOWN, 9 ) # GP2X Select
56 self.keys_return = mopelib.MyInputEvent( "Return" )
57 self.keys_return.add( pygame.KEYDOWN, pygame.K_RETURN )
58 self.keys_return.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
60 self.keys_startgame = mopelib.MyInputEvent( "any key" )
61 self.keys_startgame.add_all( pygame.KEYDOWN )
62 self.keys_startgame.add_all( pygame.JOYBUTTONDOWN )
63 self.keys_startgame.add_all( pygame.MOUSEBUTTONDOWN )
65 self.keys_up = mopelib.MyInputEvent( "up" )
66 self.keys_up.add( pygame.KEYDOWN, ord( 'q' ) )
67 self.keys_up.add( pygame.KEYDOWN, pygame.K_UP )
68 self.keys_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
69 self.keys_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
71 self.keys_right = mopelib.MyInputEvent( "right" )
72 self.keys_right.add( pygame.KEYDOWN, ord( 'p' ) )
73 self.keys_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
74 self.keys_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
75 self.keys_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
77 self.keys_down = mopelib.MyInputEvent( "down" )
78 self.keys_down.add( pygame.KEYDOWN, ord( 'a' ) )
79 self.keys_down.add( pygame.KEYDOWN, pygame.K_DOWN )
80 self.keys_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
81 self.keys_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
83 self.keys_left = mopelib.MyInputEvent( "left" )
84 self.keys_left.add( pygame.KEYDOWN, ord( 'o' ) )
85 self.keys_left.add( pygame.KEYDOWN, pygame.K_LEFT )
86 self.keys_left.add( pygame.JOYBUTTONDOWN, 2 ) # GP2X Joy left
87 self.keys_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
89 self.keys_volup = mopelib.MyInputEvent( "+" )
90 self.keys_volup.add( pygame.KEYDOWN, ord( '+' ) )
91 self.keys_volup.add( pygame.KEYDOWN, ord( '=' ) )
92 self.keys_volup.add( pygame.JOYBUTTONDOWN, 16 ) # GP2X volume + button
94 self.keys_voldown = mopelib.MyInputEvent( "-" )
95 self.keys_voldown.add( pygame.KEYDOWN, ord( '-' ) )
96 self.keys_voldown.add( pygame.JOYBUTTONDOWN, 17 ) # GP2X volume - button
98 self.keys_p1_up = mopelib.MyInputEvent( "p1_up" )
99 self.keys_p1_up.add( pygame.KEYDOWN, pygame.K_UP )
100 self.keys_p1_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
102 self.keys_p1_right = mopelib.MyInputEvent( "p1_right" )
103 self.keys_p1_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
104 self.keys_p1_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
106 self.keys_p1_down = mopelib.MyInputEvent( "p1_down" )
107 self.keys_p1_down.add( pygame.KEYDOWN, pygame.K_DOWN )
108 self.keys_p1_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
110 self.keys_p1_left = mopelib.MyInputEvent( "p1_left" )
111 self.keys_p1_left.add( pygame.KEYDOWN, pygame.K_LEFT )
112 self.keys_p1_left.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy left
114 self.keys_p2_up = mopelib.MyInputEvent( "p2_up" )
115 self.keys_p2_up.add( pygame.KEYDOWN, ord( '2' ) )
116 self.keys_p2_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
118 self.keys_p2_right = mopelib.MyInputEvent( "p2_right" )
119 self.keys_p2_right.add( pygame.KEYDOWN, ord( 'e' ) )
120 self.keys_p2_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
122 self.keys_p2_down = mopelib.MyInputEvent( "p2_down" )
123 self.keys_p2_down.add( pygame.KEYDOWN, ord( 'w' ) )
124 self.keys_p2_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
126 self.keys_p2_left = mopelib.MyInputEvent( "p2_left" )
127 self.keys_p2_left.add( pygame.KEYDOWN, ord( 'q' ) )
128 self.keys_p2_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
130 self.keys_p3_up = mopelib.MyInputEvent( "p3_up" )
131 self.keys_p3_up.add( pygame.KEYDOWN, ord( 'i' ) )
133 self.keys_p3_right = mopelib.MyInputEvent( "p3_right" )
134 self.keys_p3_right.add( pygame.KEYDOWN, ord( 'l' ) )
136 self.keys_p3_down = mopelib.MyInputEvent( "p3_down" )
137 self.keys_p3_down.add( pygame.KEYDOWN, ord( 'k' ) )
139 self.keys_p3_left = mopelib.MyInputEvent( "p3_left" )
140 self.keys_p3_left.add( pygame.KEYDOWN, ord( 'j' ) )
142 self.keys_p4_up = mopelib.MyInputEvent( "p4_up" )
143 self.keys_p4_up.add( pygame.KEYDOWN, pygame.K_KP8 )
145 self.keys_p4_right = mopelib.MyInputEvent( "p4_right" )
146 self.keys_p4_right.add( pygame.KEYDOWN, pygame.K_KP6 )
148 self.keys_p4_down = mopelib.MyInputEvent( "p4_down" )
149 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP5 )
150 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP2 )
152 self.keys_p4_left = mopelib.MyInputEvent( "p4_left" )
153 self.keys_p4_left.add( pygame.KEYDOWN, pygame.K_KP4 )
156 # ----------------------
158 class TronCodeSoundManager( mopelib.SoundManager ):
160 def __init__( self, volume ):
161 mopelib.SoundManager.__init__( self, config )
163 #self.add_sample_group( "waddles", ["waddle1"] )
165 # ----------------------
167 def intro_draw_instructions():
168 write_text( "Press %s for menu, or %s to start" % (
169 config.keys_menu.name, config.keys_startgame.name ),
170 (0, 0, 0), 0.05, 0.99 )
172 # ----------------------
174 def general_menu_create_menu( menu, config, gamestate ):
175 menu.items = []
176 if gamestate == None: # We are on a title screen - Start Game option
177 menu.add_item( "Start game", MENU_START )
178 menu.add_item( "Number of players: %d" % (config.num_players),
179 MENU_NUM_PLAYERS )
180 for player_num in range( config.num_players ):
181 cls_name = "--unknown--"
182 if player_num < len( config.player_classes ):
183 cls_name = config.player_classes[player_num].GetName()
184 menu.add_item( "P%d: %s" % ( player_num, cls_name ),
185 MENU_CHANGE_PLAYER + player_num )
186 else:
187 menu.add_item( "Continue", MENU_START )
188 menu.add_item( "End game", MENU_END )
190 tmp_str = "Music: "
191 if config.music_on:
192 tmp_str += "on"
193 else:
194 tmp_str += "off"
195 menu.add_item( tmp_str, MENU_MUSIC )
197 tmp_str = "Effects: "
198 if config.sound_effects_on:
199 tmp_str += "on"
200 else:
201 tmp_str += "off"
202 menu.add_item( tmp_str, MENU_SOUND_EFFECTS )
204 menu.add_item( "Quit troncode", MENU_QUIT )
206 return menu
208 def general_menu_screen( config, gamestate ):
210 if gamestate == None:
211 menu_title = "troncode"
212 else:
213 menu_title = "troncode paused"
215 menu = mopelib.Menu()
216 general_menu_create_menu( menu, config, gamestate )
217 menurender.set_menu( menu, menu_title )
218 menurender.repaint_full()
220 game_start = False
222 waiting = True
223 while waiting:
224 event = pygame.event.wait()
225 if event.type == QUIT:
226 sys.exit(0)
227 elif config.keys_menu.matches( event ):
228 waiting = False
229 elif config.keys_down.matches( event ):
230 menurender.move_down()
231 elif config.keys_up.matches( event ):
232 menurender.move_up()
233 elif config.keys_return.matches( event ):
234 code = menu.get_selected_item().code
235 if code == MENU_START:
236 game_start = True
237 waiting = False
238 elif code == MENU_END:
239 gamestate.alive = INGAME_QUIT
240 waiting = False
241 elif code == MENU_MUSIC:
242 if config.music_on == 1:
243 config.music_on = 0
244 else:
245 config.music_on = 1
246 general_menu_create_menu( menu, config, gamestate )
247 menurender.repaint_full()
248 sound_mgr.setup( gamestate )
249 config.save()
250 elif code == MENU_SOUND_EFFECTS:
251 if config.sound_effects_on:
252 config.sound_effects_on = 0
253 else:
254 config.sound_effects_on = 1
255 general_menu_create_menu( menu, config, gamestate )
256 menurender.repaint_full()
257 sound_mgr.setup( gamestate )
258 config.save()
259 elif code == MENU_QUIT:
260 sys.exit(0)
262 return game_start
264 # ----------------------
266 def intro_draw_title():
267 screen.blit( intro_surface_title, (0,0) )
268 write_text( "Version " + troncode_version,
269 ( 0, 0, 0 ), 0.05, 0.88 )
270 write_text( "by Andy Balaam", ( 0, 0, 0 ), 0.06, 0.93 )
271 intro_draw_instructions()
273 def intro_draw_something( intro_mode ):
274 if intro_mode == INTRO_MODE_TITLE:
275 intro_draw_title()
276 elif intro_mode == INTRO_MODE_INSTR:
277 screen.blit( intro_surface_instr, (0,0) )
278 intro_draw_instructions()
279 elif intro_mode == INTRO_MODE_MUSIC:
280 screen.blit( intro_surface_music, (0,0) )
281 intro_draw_instructions()
282 pygame.display.update()
284 def intro_input( event, config, intro_mode ):
285 if event.type == QUIT:
286 sys.exit(0)
287 elif config.keys_volup.matches( event ):
288 config.volume = sound_mgr.increase_volume()
289 config.save()
290 elif config.keys_voldown.matches( event ):
291 config.volume = sound_mgr.decrease_volume()
292 config.save()
293 else:
294 if event.type == EVENTTYPE_TITLE_TICK:
295 intro_mode += 1
296 if intro_mode == INTRO_MODE_ENDED:
297 intro_mode = INTRO_MODE_TITLE
298 intro_draw_something( intro_mode )
299 elif config.keys_menu.matches( event ):
300 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
301 start_game = general_menu_screen( config, None )
302 if start_game:
303 intro_mode = INTRO_MODE_ENDED
304 else:
305 intro_draw_something( intro_mode )
306 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
307 elif config.keys_startgame.matches( event ):
308 intro_mode = INTRO_MODE_ENDED
309 return intro_mode
311 # ----------------------
313 def intro_mainloop( config ):
314 intro_draw_title()
316 pygame.display.update()
317 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
319 intro_mode = INTRO_MODE_TITLE
320 while intro_mode < INTRO_MODE_ENDED:
321 intro_mode = intro_input( pygame.event.wait(), config, intro_mode )
323 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
325 def draw_pixel( gamestate, surface, colour, x, y ):
326 col = mopelib.dim_colour( colour, gamestate.dim )
327 adj_x = screen_border[0] + scale * x
328 adj_y = screen_border[1] + scale * y
329 if scale < 1:
330 surface.set_at( ( int(adj_x), int(adj_y) ), col )
331 else:
332 pygame.draw.rect( surface, col,
333 (adj_x, adj_y, ceil_scale, ceil_scale ) )
335 def inlevel_screen_blit( gamestate ):
336 screen.blit( gamestate.offscreen_buff, (0,0) )
337 write_text_ingame( gamestate )
338 pygame.display.update()
340 def inlevel_redraw_screen( gamestate, arrows ):
341 gamestate.offscreen_buff = pygame.Surface( gamestate.config.screen_size )
342 gamestate.offscreen_buff.blit( ingame_surface_background, (0,0) )
343 for x, y, colour in gamestate.pixels_list:
344 draw_pixel( gamestate, gamestate.offscreen_buff, colour, x, y )
345 inlevel_draw_players( gamestate )
346 # TODO: draw arrows
347 inlevel_screen_blit( gamestate )
349 def inlevel_draw_players( gamestate ):
350 for player in gamestate.players:
351 x, y = gamestate.GetPosition( player )
352 draw_pixel( gamestate, gamestate.offscreen_buff, player.GetColour(),
353 x, y )
355 def inlevel_update_screen( gamestate ):
356 inlevel_draw_players( gamestate )
357 inlevel_screen_blit( gamestate )
361 # ----------------------
363 def inlevel_input( event, gamestate ):
364 if event.type == QUIT:
365 sys.exit(0)
366 elif( ( event.type == pygame.ACTIVEEVENT and event.state == 2 )
367 or config.keys_menu.matches( event ) ):
368 general_menu_screen( config, gamestate )
369 inlevel_redraw_screen( gamestate, False )
370 elif config.keys_volup.matches( event ):
371 config.volume = sound_mgr.increase_volume()
372 config.save()
373 elif config.keys_voldown.matches( event ):
374 config.volume = sound_mgr.decrease_volume()
375 config.save()
376 elif event.type == EVENTTYPE_INGAME_TICK:
377 gamestate.timer_tick()
378 gamestate.key_events = []
379 if gamestate.alive == INGAME_MOST_DEAD:
380 finishedlevel_mainloop( gamestate )
381 else:
382 inlevel_update_screen( gamestate )
383 else:
384 gamestate.key_events.append( event )
387 # ----------------------
389 def finishedgame_input( event, waiting ):
390 if event.type == QUIT:
391 sys.exit(0)
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_startgame.matches( event ):
399 waiting = False
400 return waiting
402 # ----------------------
404 class PlayerStatus:
405 def __init__( self, x, y, direction, player ):
406 self._x = x
407 self._y = y
408 self._dir = direction
409 self._colour = player.GetColour()
410 self._dead = False
412 def GetPosition( self ):
413 return ( self._x, self._y )
415 def GetDirection( self ):
416 return ( self._dir )
418 def SetDirection( self, direction ):
419 self._dir = direction
421 def IsDead( self ):
422 return self._dead
424 def Move( self, gamestate ):
425 """Moves the player one position depending on its direction, and
426 returns True if it hit anything, False otherwise."""
428 if self._dead:
429 return self._dead
431 if self._dir == DIR_UP:
432 self._y -= 1
433 elif self._dir == DIR_RIGHT:
434 self._x += 1
435 elif self._dir == DIR_DOWN:
436 self._y += 1
437 elif self._dir == DIR_LEFT:
438 self._x -= 1
440 self._dead = gamestate.AddPixel( self._x, self._y, self._colour )
442 return self._dead
444 class GameBoard:
445 def __init__( self, gamestate ):
446 self._gamestate = gamestate
448 def GetRelativePixel( self, start_pos, facing_dir,
449 offset_fwd, offset_right ):
450 """Given a position to stand in the arena, and a direction to face,
451 return the status (0 for empty, >0 for non-empty) of a pixel that
452 is offset_fwd pixels in front, and offset_right pixels to the right
453 (negative values may be used to go backwards or left respectively).
454 Pixels outside the arena also return >0."""
456 if facing_dir == DIR_UP:
457 found_pos = ( start_pos[0] + offset_right,
458 start_pos[1] - offset_fwd )
459 elif facing_dir == DIR_RIGHT:
460 found_pos = ( start_pos[0] + offset_fwd,
461 start_pos[1] + offset_right )
462 elif facing_dir == DIR_DOWN:
463 found_pos = ( start_pos[0] - offset_right,
464 start_pos[1] + offset_fwd )
465 elif facing_dir == DIR_LEFT:
466 found_pos = ( start_pos[0] - offset_fwd,
467 start_pos[1] - offset_right )
469 if ( found_pos[0] < 0 or
470 found_pos[0] >= self._gamestate.config.arena_size[0] or
471 found_pos[1] < 0 or
472 found_pos[1] >= self._gamestate.config.arena_size[1] ):
473 retval = 100
474 elif found_pos in self._gamestate.pixels_set:
475 retval = 1
476 else:
477 retval = 0
479 return retval
482 def TurnRight( self, direction ):
483 """Return the direction found by turning 90 degrees right from
484 the supplied direction."""
485 direction += 1
486 if direction > DIR_LEFT:
487 direction = DIR_UP
488 return direction
490 def TurnLeft( self, direction ):
491 """Return the direction found by turning 90 degrees left from
492 the supplied direction."""
493 direction -= 1
494 if direction < DIR_UP:
495 direction = DIR_LEFT
496 return direction
498 class GameState:
499 def __init__( self, config, classes ):
500 self.config = config
502 self.players = []
503 self.key_events = []
505 for cls in classes:
506 if "IsHuman" in cls.__dict__ and cls.IsHuman():
507 self.players.append( cls( config.keys_p1_up,
508 config.keys_p1_right, config.keys_p1_down,
509 config.keys_p1_left ) )
510 else:
511 self.players.append( cls() )
513 self.alive = INGAME_ALL_ALIVE
514 self.dim = 1
516 self.pixels_list = []
517 self.pixels_set = set()
518 self.create_initial_pixels()
520 self.statuses = {}
521 for player in self.players:
522 x = random.randint( config.starting_border, config.arena_size[0]
523 - config.starting_border )
524 y = random.randint( config.starting_border, config.arena_size[1]
525 - config.starting_border )
526 dr = random.randint( DIR_UP, DIR_LEFT )
527 self.statuses[player] = PlayerStatus( x, y, dr, player )
528 self.AddPixel( x, y, player.GetColour() )
530 self._gameboard = GameBoard( self )
532 def timer_tick( self ):
533 num_alive = 0
534 for player in self.players:
535 status = self.statuses[player]
536 if( "IsHuman" in player.__class__.__dict__ and
537 player.__class__.IsHuman() ):
538 new_dir = player.GetDirWithInput( status.GetDirection(),
539 self.key_events )
540 else:
541 new_dir = player.GetDir( status.GetPosition(),
542 status.GetDirection(), self._gameboard )
543 status.SetDirection( new_dir )
544 # TODO: copy pixels list for "security"
546 dead = self.statuses[player].Move( self )
547 if not dead:
548 num_alive += 1
550 if num_alive < 2:
551 self.alive = INGAME_MOST_DEAD
553 def create_initial_pixels( self ):
554 colour = (100, 100, 100)
555 size_x, size_y = config.arena_size
556 for x in range( size_x ):
557 self.AddPixel( x, 0, colour )
558 self.AddPixel( x, size_y - 1, colour )
559 for y in range( 1, size_y - 1 ):
560 self.AddPixel( 0, y, colour )
561 self.AddPixel( size_x - 1, y, colour )
563 def GetPosition( self, player ):
564 return self.statuses[player].GetPosition()
566 def AddPixel( self, x, y, colour ):
567 if ( x, y ) in self.pixels_set:
568 dead = True
569 else:
570 dead = False
571 self.pixels_set.add( ( x, y ) )
573 self.pixels_list.append( ( x, y, colour ) )
575 return dead
578 # ----------------------
580 def ingame_mainloop( config ):
581 gamestate = GameState( config, config.player_classes )
582 while gamestate.alive == INGAME_ALL_ALIVE:
583 inlevel_mainloop( config, gamestate )
584 return gamestate
586 # ----------------------
588 def inlevel_mainloop( config, gamestate ):
590 inlevel_redraw_screen( gamestate, True )
591 #time.sleep( 1 )
592 inlevel_redraw_screen( gamestate, False )
594 gc.disable()
595 pygame.time.set_timer( EVENTTYPE_INGAME_TICK, config.tick_time )
596 while gamestate.alive == INGAME_ALL_ALIVE:
597 inlevel_input( pygame.event.wait(), gamestate )
598 gc.enable()
599 mopelib.clear_events( EVENTTYPE_INGAME_TICK )
601 ingame_surface_background.fill( config.colour_background )
604 # ----------------------
606 def write_text( txt, colour, size, y_pos ):
607 ft = pygame.font.Font( None, int( config.screen_size[1] * size ) )
608 sf = ft.render( txt, True, colour )
609 screen.blit( sf, ( (config.screen_size[0] - sf.get_width() )/2,
610 (config.screen_size[1] - sf.get_height() ) * y_pos ) )
612 # ----------------------
614 def write_text_ingame( gamestate ):
615 #global ingame_font
617 #bgcol = mopelib.dim_colour( gamestate.cur_lev.background_colour,
618 # gamestate.dim )
619 #fgcol = mopelib.dim_colour( gamestate.cur_lev.text_colour, gamestate.dim )
621 #write_text_blank_font_coords( "Level: %d" % ( gamestate.level_num + 1 ),
622 # fgcol, ingame_font, bgcol, 0.3, 0.99 )
624 #write_text_blank_font_coords( "Time: %s" % gamestate.cur_lev.current_time,
625 # fgcol, ingame_font, bgcol, 0.7, 0.99 )
626 pass
629 def write_text_blank_font_coords( txt, colour, font, bgcolour, x, y ):
630 sf = font.render( txt, True, colour )
631 sf_bg = pygame.Surface( ( int( sf.get_width() * 1.29 ), sf.get_height() ) )
632 sf_bg.fill( bgcolour )
634 tlx = ( config.screen_size[0] - sf_bg.get_width() ) * x
635 tly = ( config.screen_size[1] - sf_bg.get_height() ) * y
637 dirty_rect = Rect( tlx, tly, sf_bg.get_width(), sf_bg.get_height() )
638 screen.blit( sf_bg, dirty_rect )
639 screen.blit( sf, ( tlx * 1.01, tly ) )
641 # ----------------------
643 def finishedlevel_input( event, waiting ):
644 if event.type == QUIT:
645 sys.exit(0)
646 elif config.keys_volup.matches( event ):
647 config.volume = sound_mgr.increase_volume()
648 config.save()
649 elif config.keys_voldown.matches( event ):
650 config.volume = sound_mgr.decrease_volume()
651 config.save()
652 elif config.keys_startgame.matches( event ):
653 waiting = False
654 return waiting
656 def finishedlevel_mainloop( gamestate ):
657 gamestate.dim = 0.5
658 inlevel_redraw_screen( gamestate, False )
659 waiting = True
660 while waiting:
661 waiting = finishedlevel_input( pygame.event.wait(), waiting )
663 def finishedgame_mainloop( config, gamestate ):
664 pass
665 #config.start_level = 0
666 # config.save()
668 # inlevel_redraw_screen( gamestate )
669 # write_text( "Congratulations!", (255,255,255), 0.125, 0.38 )
670 # write_text( "You won!", (255,255,255), 0.125, 0.52 )
671 # waiting = True
672 # write_text( "Press %s" % config.keys_startgame.name, (255,255,255),
673 # 0.05, 0.8 )
674 # pygame.display.update()
675 # while waiting:
676 # waiting = finishedgame_input( pygame.event.wait(), waiting )
678 # ingame_surface_background.fill( config.colour_background )
680 def sort_by_score( class2score ):
681 scorename_sorted = []
683 for cls in class2score.keys():
684 scorename_sorted.append( ( class2score[cls], cls.GetName() ) )
686 scorename_sorted.sort( reverse = True )
688 return scorename_sorted
690 def execute_tournament():
691 total_scores = {}
692 for cls in config.player_classes:
693 total_scores[cls] = 0
695 print
696 print " " * 30 + "=== Pairings ==="
697 print
699 pairs = []
700 num_classes = len( config.player_classes )
701 for i in range( num_classes ):
702 for j in range( i+1, num_classes ):
703 execute_match(
704 ( config.player_classes[i], config.player_classes[j] ),
705 total_scores, False )
707 print
708 print " " * 30 + "=== Medley ==="
709 print
711 execute_match( config.player_classes, total_scores, True )
713 print
714 print " " * 30 + "=== Total Scores ==="
715 print
717 scorename_sorted = sort_by_score( total_scores )
719 for score, name in scorename_sorted:
720 print "%30s: %d" % ( name, score )
721 print
724 def execute_match( classes, total_scores, newlines ):
725 match_scores = {}
726 for cls in classes:
727 match_scores[cls] = 0
729 for i in range( config.num_games ):
730 gamestate = GameState( config, classes )
732 while gamestate.alive == INGAME_ALL_ALIVE:
733 gamestate.timer_tick()
735 for player in gamestate.statuses.keys():
736 status = gamestate.statuses[player]
737 if not status.IsDead():
738 match_scores[player.__class__] += 1
740 scorename_sorted = sort_by_score( match_scores )
742 oldscore = -1
743 for score, name in scorename_sorted:
744 if oldscore != -1 and not newlines:
745 if oldscore == score:
746 print " drew",
747 else:
748 print " beat",
749 print "%- 4d %-30s" % ( score, name ),
750 else:
751 print "%30s% 4d" % ( name, score ),
752 oldscore = score
755 if newlines:
756 print
758 if not newlines:
759 print
761 for cls in classes:
762 total_scores[cls] += match_scores[cls]
764 def get_players( players_dir ):
765 ret_classes = []
767 player_re = re.compile( """^(\w*Player).py$""" )
768 for filename in os.listdir( players_dir ):
769 m = player_re.match( filename )
770 if m:
771 class_name = m.group( 1 )
772 cls = __import__( "players/" + class_name,
773 globals(), locals() ).__dict__[class_name]
774 ret_classes.append( cls )
776 return ret_classes
778 # ----------------------
779 # Execution starts here
780 # ----------------------
782 # Fixed constants
784 INTRO_MODE_TITLE = 0
785 INTRO_MODE_INSTR = 1
786 INTRO_MODE_MUSIC = 2
787 INTRO_MODE_ENDED = 3
789 MENU_START = 0
790 MENU_NUM_PLAYERS = 1
791 MENU_END = 2
792 MENU_MUSIC = 3
793 MENU_SOUND_EFFECTS = 4
794 MENU_QUIT = 6
795 MENU_CHANGE_PLAYER = 100 # Must go at end of this list
797 INGAME_ALL_ALIVE = 0
798 INGAME_MOST_DEAD = 1
799 INGAME_QUIT = 2
801 TITLE_TICK_TIME = 4000
803 EVENTTYPE_INGAME_TICK = pygame.USEREVENT
804 EVENTTYPE_TITLE_TICK = pygame.USEREVENT + 1
806 num_args = len( sys.argv )
807 #if num_args > 1:
808 # if sys.argv[1] == "--help":
809 # print "Usage:"
810 # print "troncode.py [install_dir] [config_file] [resolution] "
811 # print " [num_players] [--tournament]"
812 # print
813 # print "e.g. ./troncode.py . troncoderc.txt '(640,480)' 2 --tournament"
814 # print
815 # sys.exit( 0 )
817 # install_dir = sys.argv[1]
818 #else:
819 # install_dir = "."
821 #if num_args > 2:
822 # config_filename = sys.argv[2]
823 #else:
824 # config_filename = os.path.expanduser( "~/.troncode/config" )
826 config_filename = os.path.expanduser( "~/.troncode/config" )
827 install_dir = "."
829 config = TronCodeConfig( config_filename )
831 if num_args > 1 and sys.argv[1] == "--tournament":
832 run_tournament = True
833 config.num_games = 100
834 else:
835 run_tournament = False
837 config.install_dir = install_dir
838 config.unsaved.append( "install_dir" )
840 config.images_dir = os.path.join( install_dir, "images" )
841 config.unsaved.append( "images_dir" )
843 config.music_dir = os.path.join( install_dir, "music" )
844 config.unsaved.append( "music_dir" )
846 if num_args > 3:
847 config.screen_size = config.parse_value( sys.argv[3] )
849 config.arena_size = ( 200, 200 )
850 config.unsaved.append( "arena_size" )
852 config.starting_border = 35
853 config.unsaved.append( "starting_border" )
855 config.players_dir = "players"
856 config.unsaved.append( "players_dir" )
858 config.player_classes = get_players( config.players_dir )
859 config.unsaved.append( "player_classes" )
861 config.players = []
862 config.unsaved.append( "players" )
864 if run_tournament:
865 execute_tournament()
866 else:
867 pygame.init()
868 pygame.font.init()
869 pygame.mouse.set_visible( False )
871 window = pygame.display.set_mode( config.screen_size )
872 pygame.display.set_caption( 'troncode' )
873 screen = pygame.display.get_surface()
875 fixed_border = 5
876 scale = min(
877 ( float( config.screen_size[0] - fixed_border*2 )
878 / float( config.arena_size[0] ) ),
879 ( float( config.screen_size[1] - fixed_border*2 )
880 / float( config.arena_size[1] ) ) )
882 screen_border = ( float( config.screen_size[0] - config.arena_size[0]*scale )
883 / 2.0,
884 float( config.screen_size[1] - config.arena_size[1]*scale ) / 2.0, )
886 ceil_scale = math.ceil( scale )
888 # General initialisation
890 num_joysticks = pygame.joystick.get_count()
891 for j in range( num_joysticks ):
892 pygame.joystick.Joystick( j ).init()
894 intro_surface_title = mopelib.load_and_scale_image( "title.png", config )
895 intro_surface_instr = mopelib.load_and_scale_image( "instructions.png", config )
896 intro_surface_music = mopelib.load_and_scale_image( "music.png", config )
898 ingame_surface_background = pygame.Surface( screen.get_size() ).convert()
899 ingame_surface_background.fill( config.colour_background )
901 intro_mode = INTRO_MODE_TITLE
903 sound_mgr = TronCodeSoundManager( config.volume )
905 troncode_version = mopelib.read_version( config )
907 ingame_font = pygame.font.Font( None, int( config.screen_size[1] * 0.09 ) )
909 menurender = mopelib.MenuRenderer( screen, config, ingame_surface_background,
910 (128, 128, 128), (128, 255, 128), (128, 128, 128) )
912 while True:
913 sound_mgr.music_loud()
914 intro_mainloop( config )
915 sound_mgr.music_quiet()
916 gamestate = ingame_mainloop( config )
917 intro_mode = finishedgame_mainloop( config, gamestate )