Updated version number.
[troncode.git] / troncode.py
blobf115c443305668543e4d3c35c2eb93433c0b8bce
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 = 2
44 self.screen_size = ( 810, 810 )
45 #self.screen_size = ( 410, 410 )
46 #self.screen_size = ( 640, 480 )
47 self.colour_background = ( 15, 45, 15 )
49 self.volume = 50
51 self.music_on = 1
52 self.sound_effects_on = 1
54 self.keys_menu = mopelib.MyInputEvent( "Escape" )
55 self.keys_menu.add( pygame.KEYDOWN, pygame.K_ESCAPE )
56 self.keys_menu.add( pygame.JOYBUTTONDOWN, 8 ) # GP2X Start
57 self.keys_menu.add( pygame.JOYBUTTONDOWN, 9 ) # GP2X Select
59 self.keys_return = mopelib.MyInputEvent( "Return" )
60 self.keys_return.add( pygame.KEYDOWN, pygame.K_RETURN )
61 self.keys_return.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
63 self.keys_startgame = mopelib.MyInputEvent( "any key" )
64 self.keys_startgame.add_all( pygame.KEYDOWN )
65 self.keys_startgame.add_all( pygame.JOYBUTTONDOWN )
66 self.keys_startgame.add_all( pygame.MOUSEBUTTONDOWN )
68 self.keys_up = mopelib.MyInputEvent( "up" )
69 self.keys_up.add( pygame.KEYDOWN, ord( 'q' ) )
70 self.keys_up.add( pygame.KEYDOWN, pygame.K_UP )
71 self.keys_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
72 self.keys_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
74 self.keys_right = mopelib.MyInputEvent( "right" )
75 self.keys_right.add( pygame.KEYDOWN, ord( 'p' ) )
76 self.keys_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
77 self.keys_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
78 self.keys_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
80 self.keys_down = mopelib.MyInputEvent( "down" )
81 self.keys_down.add( pygame.KEYDOWN, ord( 'a' ) )
82 self.keys_down.add( pygame.KEYDOWN, pygame.K_DOWN )
83 self.keys_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
84 self.keys_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
86 self.keys_left = mopelib.MyInputEvent( "left" )
87 self.keys_left.add( pygame.KEYDOWN, ord( 'o' ) )
88 self.keys_left.add( pygame.KEYDOWN, pygame.K_LEFT )
89 self.keys_left.add( pygame.JOYBUTTONDOWN, 2 ) # GP2X Joy left
90 self.keys_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
92 self.keys_volup = mopelib.MyInputEvent( "+" )
93 self.keys_volup.add( pygame.KEYDOWN, ord( '+' ) )
94 self.keys_volup.add( pygame.KEYDOWN, ord( '=' ) )
95 self.keys_volup.add( pygame.JOYBUTTONDOWN, 16 ) # GP2X volume + button
97 self.keys_voldown = mopelib.MyInputEvent( "-" )
98 self.keys_voldown.add( pygame.KEYDOWN, ord( '-' ) )
99 self.keys_voldown.add( pygame.JOYBUTTONDOWN, 17 ) # GP2X volume - button
101 self.keys_p1_up = mopelib.MyInputEvent( "p1_up" )
102 self.keys_p1_up.add( pygame.KEYDOWN, pygame.K_UP )
103 self.keys_p1_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
105 self.keys_p1_right = mopelib.MyInputEvent( "p1_right" )
106 self.keys_p1_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
107 self.keys_p1_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
109 self.keys_p1_down = mopelib.MyInputEvent( "p1_down" )
110 self.keys_p1_down.add( pygame.KEYDOWN, pygame.K_DOWN )
111 self.keys_p1_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
113 self.keys_p1_left = mopelib.MyInputEvent( "p1_left" )
114 self.keys_p1_left.add( pygame.KEYDOWN, pygame.K_LEFT )
115 self.keys_p1_left.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy left
117 self.keys_p2_up = mopelib.MyInputEvent( "p2_up" )
118 self.keys_p2_up.add( pygame.KEYDOWN, ord( '2' ) )
119 self.keys_p2_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
121 self.keys_p2_right = mopelib.MyInputEvent( "p2_right" )
122 self.keys_p2_right.add( pygame.KEYDOWN, ord( 'e' ) )
123 self.keys_p2_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
125 self.keys_p2_down = mopelib.MyInputEvent( "p2_down" )
126 self.keys_p2_down.add( pygame.KEYDOWN, ord( 'w' ) )
127 self.keys_p2_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
129 self.keys_p2_left = mopelib.MyInputEvent( "p2_left" )
130 self.keys_p2_left.add( pygame.KEYDOWN, ord( 'q' ) )
131 self.keys_p2_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
133 self.keys_p3_up = mopelib.MyInputEvent( "p3_up" )
134 self.keys_p3_up.add( pygame.KEYDOWN, ord( 'i' ) )
136 self.keys_p3_right = mopelib.MyInputEvent( "p3_right" )
137 self.keys_p3_right.add( pygame.KEYDOWN, ord( 'l' ) )
139 self.keys_p3_down = mopelib.MyInputEvent( "p3_down" )
140 self.keys_p3_down.add( pygame.KEYDOWN, ord( 'k' ) )
142 self.keys_p3_left = mopelib.MyInputEvent( "p3_left" )
143 self.keys_p3_left.add( pygame.KEYDOWN, ord( 'j' ) )
145 self.keys_p4_up = mopelib.MyInputEvent( "p4_up" )
146 self.keys_p4_up.add( pygame.KEYDOWN, pygame.K_KP8 )
148 self.keys_p4_right = mopelib.MyInputEvent( "p4_right" )
149 self.keys_p4_right.add( pygame.KEYDOWN, pygame.K_KP6 )
151 self.keys_p4_down = mopelib.MyInputEvent( "p4_down" )
152 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP5 )
153 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP2 )
155 self.keys_p4_left = mopelib.MyInputEvent( "p4_left" )
156 self.keys_p4_left.add( pygame.KEYDOWN, pygame.K_KP4 )
159 # ----------------------
161 class TronCodeSoundManager( mopelib.SoundManager ):
163 def __init__( self, volume ):
164 mopelib.SoundManager.__init__( self, config )
166 #self.add_sample_group( "waddles", ["waddle1"] )
168 # ----------------------
170 def intro_draw_instructions():
171 write_text( "Press %s for menu, or %s to start" % (
172 config.keys_menu.name, config.keys_startgame.name ),
173 (0, 0, 0), 0.05, 0.99 )
175 # ----------------------
177 def general_menu_create_menu( menu, config, gamestate ):
178 menu.items = []
179 if gamestate == None: # We are on a title screen - Start Game option
180 menu.add_item( "Start game", MENU_START )
181 menu.add_item( "Number of players: %d" % (config.num_players),
182 MENU_NUM_PLAYERS )
183 for player_num in range( config.num_players ):
184 cls_name = "--unknown--"
185 if player_num < len( config.player_classes ):
186 cls_name = config.player_classes[player_num].GetName()
187 menu.add_item( "P%d: %s" % ( player_num, cls_name ),
188 MENU_CHANGE_PLAYER + player_num )
189 else:
190 menu.add_item( "Continue", MENU_START )
191 menu.add_item( "End game", MENU_END )
193 tmp_str = "Music: "
194 if config.music_on:
195 tmp_str += "on"
196 else:
197 tmp_str += "off"
198 menu.add_item( tmp_str, MENU_MUSIC )
200 tmp_str = "Effects: "
201 if config.sound_effects_on:
202 tmp_str += "on"
203 else:
204 tmp_str += "off"
205 menu.add_item( tmp_str, MENU_SOUND_EFFECTS )
207 menu.add_item( "Quit troncode", MENU_QUIT )
209 return menu
211 def general_menu_screen( config, gamestate ):
213 if gamestate == None:
214 menu_title = "troncode"
215 else:
216 menu_title = "troncode paused"
218 menu = mopelib.Menu()
219 general_menu_create_menu( menu, config, gamestate )
220 menurender.set_menu( menu, menu_title )
221 menurender.repaint_full()
223 game_start = False
225 waiting = True
226 while waiting:
227 event = pygame.event.wait()
228 if event.type == QUIT:
229 sys.exit(0)
230 elif config.keys_menu.matches( event ):
231 waiting = False
232 elif config.keys_down.matches( event ):
233 menurender.move_down()
234 elif config.keys_up.matches( event ):
235 menurender.move_up()
236 elif config.keys_return.matches( event ):
237 code = menu.get_selected_item().code
238 if code == MENU_START:
239 game_start = True
240 waiting = False
241 elif code == MENU_END:
242 gamestate.alive = INGAME_QUIT
243 waiting = False
244 elif code == MENU_MUSIC:
245 if config.music_on == 1:
246 config.music_on = 0
247 else:
248 config.music_on = 1
249 general_menu_create_menu( menu, config, gamestate )
250 menurender.repaint_full()
251 sound_mgr.setup( gamestate )
252 config.save()
253 elif code == MENU_SOUND_EFFECTS:
254 if config.sound_effects_on:
255 config.sound_effects_on = 0
256 else:
257 config.sound_effects_on = 1
258 general_menu_create_menu( menu, config, gamestate )
259 menurender.repaint_full()
260 sound_mgr.setup( gamestate )
261 config.save()
262 elif code == MENU_QUIT:
263 sys.exit(0)
265 return game_start
267 # ----------------------
269 def intro_draw_title():
270 screen.blit( intro_surface_title, (0,0) )
271 write_text( "Version " + troncode_version,
272 ( 0, 0, 0 ), 0.05, 0.88 )
273 write_text( "by Andy Balaam", ( 0, 0, 0 ), 0.06, 0.93 )
274 intro_draw_instructions()
276 def intro_draw_something( intro_mode ):
277 if intro_mode == INTRO_MODE_TITLE:
278 intro_draw_title()
279 elif intro_mode == INTRO_MODE_INSTR:
280 screen.blit( intro_surface_instr, (0,0) )
281 intro_draw_instructions()
282 elif intro_mode == INTRO_MODE_MUSIC:
283 screen.blit( intro_surface_music, (0,0) )
284 intro_draw_instructions()
285 pygame.display.update()
287 def intro_input( event, config, intro_mode ):
288 if event.type == QUIT:
289 sys.exit(0)
290 elif config.keys_volup.matches( event ):
291 config.volume = sound_mgr.increase_volume()
292 config.save()
293 elif config.keys_voldown.matches( event ):
294 config.volume = sound_mgr.decrease_volume()
295 config.save()
296 else:
297 if event.type == EVENTTYPE_TITLE_TICK:
298 intro_mode += 1
299 if intro_mode == INTRO_MODE_ENDED:
300 intro_mode = INTRO_MODE_TITLE
301 intro_draw_something( intro_mode )
302 elif config.keys_menu.matches( event ):
303 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
304 start_game = general_menu_screen( config, None )
305 if start_game:
306 intro_mode = INTRO_MODE_ENDED
307 else:
308 intro_draw_something( intro_mode )
309 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
310 elif config.keys_startgame.matches( event ):
311 intro_mode = INTRO_MODE_ENDED
312 return intro_mode
314 # ----------------------
316 def intro_mainloop( config ):
317 intro_draw_title()
319 pygame.display.update()
320 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
322 intro_mode = INTRO_MODE_TITLE
323 while intro_mode < INTRO_MODE_ENDED:
324 intro_mode = intro_input( pygame.event.wait(), config, intro_mode )
326 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
328 def draw_pixel( gamestate, surface, colour, x, y ):
329 col = mopelib.dim_colour( colour, gamestate.dim )
330 adj_x = screen_border[0] + scale * x
331 adj_y = screen_border[1] + scale * y
332 if scale < 1:
333 surface.set_at( ( int(adj_x), int(adj_y) ), col )
334 else:
335 pygame.draw.rect( surface, col,
336 (adj_x, adj_y, ceil_scale, ceil_scale ) )
338 def inlevel_screen_blit( gamestate ):
339 screen.blit( gamestate.offscreen_buff, (0,0) )
340 write_text_ingame( gamestate )
341 pygame.display.update()
343 def inlevel_redraw_screen( gamestate, arrows ):
344 gamestate.offscreen_buff = pygame.Surface( gamestate.config.screen_size )
345 gamestate.offscreen_buff.blit( ingame_surface_background, (0,0) )
346 for x, y, colour in gamestate.pixels_list:
347 draw_pixel( gamestate, gamestate.offscreen_buff, colour, x, y )
348 inlevel_draw_players( gamestate )
349 # TODO: draw arrows
350 inlevel_screen_blit( gamestate )
352 def inlevel_draw_players( gamestate ):
353 for player in gamestate.players:
354 x, y = gamestate.GetPosition( player )
355 draw_pixel( gamestate, gamestate.offscreen_buff, player.GetColour(),
356 x, y )
358 def inlevel_update_screen( gamestate ):
359 inlevel_draw_players( gamestate )
360 inlevel_screen_blit( gamestate )
364 # ----------------------
366 def inlevel_input( event, gamestate ):
367 if event.type == QUIT:
368 sys.exit(0)
369 elif( ( event.type == pygame.ACTIVEEVENT and event.state == 2 )
370 or config.keys_menu.matches( event ) ):
371 general_menu_screen( config, gamestate )
372 inlevel_redraw_screen( gamestate, False )
373 elif config.keys_volup.matches( event ):
374 config.volume = sound_mgr.increase_volume()
375 config.save()
376 elif config.keys_voldown.matches( event ):
377 config.volume = sound_mgr.decrease_volume()
378 config.save()
379 elif event.type == EVENTTYPE_INGAME_TICK:
380 gamestate.timer_tick()
381 gamestate.key_events = []
382 if gamestate.alive == INGAME_MOST_DEAD:
383 finishedlevel_mainloop( gamestate )
384 else:
385 inlevel_update_screen( gamestate )
386 else:
387 gamestate.key_events.append( event )
390 # ----------------------
392 def finishedgame_input( event, waiting ):
393 if event.type == QUIT:
394 sys.exit(0)
395 elif config.keys_volup.matches( event ):
396 config.volume = sound_mgr.increase_volume()
397 config.save()
398 elif config.keys_voldown.matches( event ):
399 config.volume = sound_mgr.decrease_volume()
400 config.save()
401 elif config.keys_startgame.matches( event ):
402 waiting = False
403 return waiting
405 # ----------------------
407 class PlayerStatus:
408 def __init__( self, x, y, direction, player ):
409 self._x = x
410 self._y = y
411 self._dir = direction
412 self._colour = player.GetColour()
413 self._dead = False
415 def GetPosition( self ):
416 return ( self._x, self._y )
418 def GetDirection( self ):
419 return ( self._dir )
421 def SetDirection( self, direction ):
422 self._dir = direction
424 def IsDead( self ):
425 return self._dead
427 def Move( self, gamestate ):
428 """Moves the player one position depending on its direction, and
429 returns True if it hit anything, False otherwise."""
431 if self._dead:
432 return self._dead
434 if self._dir == DIR_UP:
435 self._y -= 1
436 elif self._dir == DIR_RIGHT:
437 self._x += 1
438 elif self._dir == DIR_DOWN:
439 self._y += 1
440 elif self._dir == DIR_LEFT:
441 self._x -= 1
443 self._dead = gamestate.AddPixel( self._x, self._y, self._colour )
445 return self._dead
447 class GameBoard( AbstractGameBoard ):
448 def __init__( self, gamestate ):
449 self._gamestate = gamestate
451 def GetRelativePixel( self, start_pos, facing_dir,
452 offset_fwd, offset_right ):
453 """Given a position to stand in the arena, and a direction to face,
454 return the status (0 for empty, >0 for non-empty) of a pixel that
455 is offset_fwd pixels in front, and offset_right pixels to the right
456 (negative values may be used to go backwards or left respectively).
457 Pixels outside the arena also return >0."""
459 if facing_dir == DIR_UP:
460 found_pos = ( start_pos[0] + offset_right,
461 start_pos[1] - offset_fwd )
462 elif facing_dir == DIR_RIGHT:
463 found_pos = ( start_pos[0] + offset_fwd,
464 start_pos[1] + offset_right )
465 elif facing_dir == DIR_DOWN:
466 found_pos = ( start_pos[0] - offset_right,
467 start_pos[1] + offset_fwd )
468 elif facing_dir == DIR_LEFT:
469 found_pos = ( start_pos[0] - offset_fwd,
470 start_pos[1] - offset_right )
472 if ( found_pos[0] < 0 or
473 found_pos[0] >= self._gamestate.config.arena_size[0] or
474 found_pos[1] < 0 or
475 found_pos[1] >= self._gamestate.config.arena_size[1] ):
476 retval = 100
477 elif found_pos in self._gamestate.pixels_set:
478 retval = 1
479 else:
480 retval = 0
482 return retval
484 def GetPlayerPositions( self, pos_to_exclude = None ):
485 """Returns a list of pairs (pos, dir) for each player on screen.
486 Excludes the player at the position specified if pos_to_exclude
487 is not None."""
489 ret = []
491 for player in self._gamestate.statuses.keys():
492 status = self._gamestate.statuses[player]
493 pos = status.GetPosition()
494 if pos_to_exclude != pos:
495 ret.append( ( pos, status.GetDirection() ) )
497 return ret
499 def TurnRight( self, direction ):
500 """Return the direction found by turning 90 degrees right from
501 the supplied direction."""
502 direction += 1
503 if direction > DIR_LEFT:
504 direction = DIR_UP
505 return direction
507 def TurnLeft( self, direction ):
508 """Return the direction found by turning 90 degrees left from
509 the supplied direction."""
510 direction -= 1
511 if direction < DIR_UP:
512 direction = DIR_LEFT
513 return direction
515 def class_is_human( cls ):
516 return "IsHuman" in cls.__dict__ and cls.IsHuman()
518 class GameState:
519 def __init__( self, config, classes ):
520 self.config = config
522 self.players = []
523 self.key_events = []
525 for cls in classes:
526 if class_is_human( cls ):
527 self.players.append( cls( config.keys_p1_up,
528 config.keys_p1_right, config.keys_p1_down,
529 config.keys_p1_left ) )
530 else:
531 self.players.append( cls() )
533 self.alive = INGAME_ALL_ALIVE
534 self.dim = 1
536 self.pixels_list = []
537 self.pixels_set = set()
538 self.create_initial_pixels()
540 self.statuses = {}
541 for player in self.players:
542 x = random.randint( config.starting_border, config.arena_size[0]
543 - config.starting_border )
544 y = random.randint( config.starting_border, config.arena_size[1]
545 - config.starting_border )
546 dr = random.randint( DIR_UP, DIR_LEFT )
547 self.statuses[player] = PlayerStatus( x, y, dr, player )
548 self.AddPixel( x, y, player.GetColour() )
550 self._gameboard = GameBoard( self )
552 def timer_tick( self ):
553 num_alive = 0
554 for player in self.players:
555 status = self.statuses[player]
556 if class_is_human( player.__class__ ):
557 new_dir = player.GetDirWithInput( status.GetDirection(),
558 self.key_events )
559 else:
560 new_dir = player.GetDir( status.GetPosition(),
561 status.GetDirection(), self._gameboard )
562 status.SetDirection( new_dir )
563 # TODO: copy pixels list for "security"
565 dead = self.statuses[player].Move( self )
566 if not dead:
567 num_alive += 1
569 if num_alive < 2:
570 self.alive = INGAME_MOST_DEAD
572 def create_initial_pixels( self ):
573 colour = (100, 100, 100)
574 size_x, size_y = config.arena_size
575 for x in range( size_x ):
576 self.AddPixel( x, 0, colour )
577 self.AddPixel( x, size_y - 1, colour )
578 for y in range( 1, size_y - 1 ):
579 self.AddPixel( 0, y, colour )
580 self.AddPixel( size_x - 1, y, colour )
582 def GetPosition( self, player ):
583 return self.statuses[player].GetPosition()
585 def AddPixel( self, x, y, colour ):
586 if ( x, y ) in self.pixels_set:
587 dead = True
588 else:
589 dead = False
590 self.pixels_set.add( ( x, y ) )
592 self.pixels_list.append( ( x, y, colour ) )
594 return dead
597 # ----------------------
599 def ingame_mainloop( config ):
600 gamestate = GameState( config, config.player_classes )
601 while gamestate.alive == INGAME_ALL_ALIVE:
602 inlevel_mainloop( config, gamestate )
603 return gamestate
605 # ----------------------
607 def inlevel_mainloop( config, gamestate ):
609 inlevel_redraw_screen( gamestate, True )
610 #time.sleep( 1 )
611 inlevel_redraw_screen( gamestate, False )
613 gc.disable()
614 pygame.time.set_timer( EVENTTYPE_INGAME_TICK, config.tick_time )
615 while gamestate.alive == INGAME_ALL_ALIVE:
616 inlevel_input( pygame.event.wait(), gamestate )
617 gc.enable()
618 mopelib.clear_events( EVENTTYPE_INGAME_TICK )
620 ingame_surface_background.fill( config.colour_background )
623 # ----------------------
625 def write_text( txt, colour, size, y_pos ):
626 ft = pygame.font.Font( None, int( config.screen_size[1] * size ) )
627 sf = ft.render( txt, True, colour )
628 screen.blit( sf, ( (config.screen_size[0] - sf.get_width() )/2,
629 (config.screen_size[1] - sf.get_height() ) * y_pos ) )
631 # ----------------------
633 def write_text_ingame( gamestate ):
634 #global ingame_font
636 #bgcol = mopelib.dim_colour( gamestate.cur_lev.background_colour,
637 # gamestate.dim )
638 #fgcol = mopelib.dim_colour( gamestate.cur_lev.text_colour, gamestate.dim )
640 #write_text_blank_font_coords( "Level: %d" % ( gamestate.level_num + 1 ),
641 # fgcol, ingame_font, bgcol, 0.3, 0.99 )
643 #write_text_blank_font_coords( "Time: %s" % gamestate.cur_lev.current_time,
644 # fgcol, ingame_font, bgcol, 0.7, 0.99 )
645 pass
648 def write_text_blank_font_coords( txt, colour, font, bgcolour, x, y ):
649 sf = font.render( txt, True, colour )
650 sf_bg = pygame.Surface( ( int( sf.get_width() * 1.29 ), sf.get_height() ) )
651 sf_bg.fill( bgcolour )
653 tlx = ( config.screen_size[0] - sf_bg.get_width() ) * x
654 tly = ( config.screen_size[1] - sf_bg.get_height() ) * y
656 dirty_rect = Rect( tlx, tly, sf_bg.get_width(), sf_bg.get_height() )
657 screen.blit( sf_bg, dirty_rect )
658 screen.blit( sf, ( tlx * 1.01, tly ) )
660 # ----------------------
662 def finishedlevel_input( event, waiting ):
663 if event.type == QUIT:
664 sys.exit(0)
665 elif config.keys_volup.matches( event ):
666 config.volume = sound_mgr.increase_volume()
667 config.save()
668 elif config.keys_voldown.matches( event ):
669 config.volume = sound_mgr.decrease_volume()
670 config.save()
671 elif config.keys_startgame.matches( event ):
672 waiting = False
673 return waiting
675 def finishedlevel_mainloop( gamestate ):
676 gamestate.dim = 0.5
677 inlevel_redraw_screen( gamestate, False )
678 waiting = True
679 while waiting:
680 waiting = finishedlevel_input( pygame.event.wait(), waiting )
682 def finishedgame_mainloop( config, gamestate ):
683 pass
684 #config.start_level = 0
685 # config.save()
687 # inlevel_redraw_screen( gamestate )
688 # write_text( "Congratulations!", (255,255,255), 0.125, 0.38 )
689 # write_text( "You won!", (255,255,255), 0.125, 0.52 )
690 # waiting = True
691 # write_text( "Press %s" % config.keys_startgame.name, (255,255,255),
692 # 0.05, 0.8 )
693 # pygame.display.update()
694 # while waiting:
695 # waiting = finishedgame_input( pygame.event.wait(), waiting )
697 # ingame_surface_background.fill( config.colour_background )
699 def sort_by_score( class2score ):
700 scorename_sorted = []
702 for cls in class2score.keys():
703 scorename_sorted.append( ( class2score[cls], cls.GetName() ) )
705 scorename_sorted.sort( reverse = True )
707 return scorename_sorted
709 def execute_tournament( config ):
710 total_scores = {}
712 classes = []
713 for cls in config.player_classes:
714 if not class_is_human( cls ):
715 classes.append( cls )
716 total_scores[cls] = 0
718 print
719 print " " * 30 + "=== Pairings ==="
720 print
722 pairs = []
723 num_classes = len( classes )
724 for i in range( num_classes ):
725 for j in range( i+1, num_classes ):
726 execute_match(
727 ( classes[i], classes[j] ),
728 total_scores, False )
730 print
731 print " " * 30 + "=== Medley ==="
732 print
734 execute_match( classes, total_scores, True )
736 print
737 print " " * 30 + "=== Total Scores ==="
738 print
740 scorename_sorted = sort_by_score( total_scores )
742 for score, name in scorename_sorted:
743 print "%30s % 4d" % ( name, score )
744 print
747 def execute_match( classes, total_scores, newlines ):
748 match_scores = {}
749 for cls in classes:
750 match_scores[cls] = 0
752 for i in range( config.num_games ):
753 gamestate = GameState( config, classes )
755 while gamestate.alive == INGAME_ALL_ALIVE:
756 gamestate.timer_tick()
758 for player in gamestate.statuses.keys():
759 status = gamestate.statuses[player]
760 if not status.IsDead():
761 match_scores[player.__class__] += 1
763 scorename_sorted = sort_by_score( match_scores )
765 oldscore = -1
766 for score, name in scorename_sorted:
767 if oldscore != -1 and not newlines:
768 if oldscore == score:
769 print " drew",
770 else:
771 print " beat",
772 print "%- 4d %-30s" % ( score, name ),
773 else:
774 print "%30s% 4d" % ( name, score ),
775 oldscore = score
778 if newlines:
779 print
781 if not newlines:
782 print
784 for cls in classes:
785 total_scores[cls] += match_scores[cls]
787 def get_players( players_dir ):
788 ret_classes = []
790 player_re = re.compile( """^(\w*Player).py$""" )
791 for filename in os.listdir( players_dir ):
792 m = player_re.match( filename )
793 if m:
794 class_name = m.group( 1 )
795 cls = __import__( "players." + class_name,
796 globals(), locals() ).__dict__[class_name].__dict__[class_name]
797 ret_classes.append( cls )
799 return ret_classes
801 # ----------------------
802 # Execution starts here
803 # ----------------------
805 # Fixed constants
807 INTRO_MODE_TITLE = 0
808 INTRO_MODE_INSTR = 1
809 INTRO_MODE_MUSIC = 2
810 INTRO_MODE_ENDED = 3
812 MENU_START = 0
813 MENU_NUM_PLAYERS = 1
814 MENU_END = 2
815 MENU_MUSIC = 3
816 MENU_SOUND_EFFECTS = 4
817 MENU_QUIT = 6
818 MENU_CHANGE_PLAYER = 100 # Must go at end of this list
820 INGAME_ALL_ALIVE = 0
821 INGAME_MOST_DEAD = 1
822 INGAME_QUIT = 2
824 TITLE_TICK_TIME = 4000
826 EVENTTYPE_INGAME_TICK = pygame.USEREVENT
827 EVENTTYPE_TITLE_TICK = pygame.USEREVENT + 1
829 num_args = len( sys.argv )
830 #if num_args > 1:
831 # if sys.argv[1] == "--help":
832 # print "Usage:"
833 # print "troncode.py [install_dir] [config_file] [resolution] "
834 # print " [num_players] [--tournament]"
835 # print
836 # print "e.g. ./troncode.py . troncoderc.txt '(640,480)' 2 --tournament"
837 # print
838 # sys.exit( 0 )
840 # install_dir = sys.argv[1]
841 #else:
842 # install_dir = "."
844 #if num_args > 2:
845 # config_filename = sys.argv[2]
846 #else:
847 # config_filename = os.path.expanduser( "~/.troncode/config" )
849 config_filename = os.path.expanduser( "~/.troncode/config" )
850 install_dir = "."
852 config = TronCodeConfig( config_filename )
854 if num_args > 1 and sys.argv[1] == "--tournament":
855 run_tournament = True
856 config.num_games = 100
857 else:
858 run_tournament = False
860 config.install_dir = install_dir
861 config.unsaved.append( "install_dir" )
863 config.images_dir = os.path.join( install_dir, "images" )
864 config.unsaved.append( "images_dir" )
866 config.music_dir = os.path.join( install_dir, "music" )
867 config.unsaved.append( "music_dir" )
869 if num_args > 3:
870 config.screen_size = config.parse_value( sys.argv[3] )
872 config.arena_size = ( 200, 200 )
873 config.unsaved.append( "arena_size" )
875 config.starting_border = 35
876 config.unsaved.append( "starting_border" )
878 config.players_dir = "players"
879 config.unsaved.append( "players_dir" )
881 config.player_classes = get_players( config.players_dir )
882 config.unsaved.append( "player_classes" )
884 config.players = []
885 config.unsaved.append( "players" )
887 if run_tournament:
888 execute_tournament( config )
889 else:
890 pygame.init()
891 pygame.font.init()
892 pygame.mouse.set_visible( False )
894 window = pygame.display.set_mode( config.screen_size )
895 pygame.display.set_caption( 'troncode' )
896 screen = pygame.display.get_surface()
898 fixed_border = 5
899 scale = min(
900 ( float( config.screen_size[0] - fixed_border*2 )
901 / float( config.arena_size[0] ) ),
902 ( float( config.screen_size[1] - fixed_border*2 )
903 / float( config.arena_size[1] ) ) )
905 screen_border = ( float( config.screen_size[0] - config.arena_size[0]*scale )
906 / 2.0,
907 float( config.screen_size[1] - config.arena_size[1]*scale ) / 2.0, )
909 ceil_scale = math.ceil( scale )
911 # General initialisation
913 num_joysticks = pygame.joystick.get_count()
914 for j in range( num_joysticks ):
915 pygame.joystick.Joystick( j ).init()
917 intro_surface_title = mopelib.load_and_scale_image( "title.png", config )
918 intro_surface_instr = mopelib.load_and_scale_image( "instructions.png", config )
919 intro_surface_music = mopelib.load_and_scale_image( "music.png", config )
921 ingame_surface_background = pygame.Surface( screen.get_size() ).convert()
922 ingame_surface_background.fill( config.colour_background )
924 intro_mode = INTRO_MODE_TITLE
926 sound_mgr = TronCodeSoundManager( config.volume )
928 troncode_version = mopelib.read_version( config )
930 ingame_font = pygame.font.Font( None, int( config.screen_size[1] * 0.09 ) )
932 menurender = mopelib.MenuRenderer( screen, config, ingame_surface_background,
933 (128, 128, 128), (128, 255, 128), (128, 128, 128) )
935 while True:
936 sound_mgr.music_loud()
937 intro_mainloop( config )
938 sound_mgr.music_quiet()
939 gamestate = ingame_mainloop( config )
940 intro_mode = finishedgame_mainloop( config, gamestate )