Initial commit: you can play single player against an "always-turn-right" opponent.
[troncode.git] / troncode.py
blob8bc79b3a45e559abceeb0f3aa9b31695b838ea26
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 sys
30 import time
32 from mopelib import mopelib
33 from player_human import HumanPlayer
34 from player_null import NullPlayer
35 from player_turnright import TurnRightPlayer
36 from troncode_values import *
38 # ----------------------
39 class TronCodeConfig( mopelib.Config ):
41 def default_config( self ):
42 self.num_players = 2
43 self.tick_time = 20
45 self.screen_size = ( 640, 480 )
46 self.colour_background = ( 15, 45, 15 )
48 self.volume = 50
50 self.music_on = 1
51 self.sound_effects_on = 1
53 self.keys_menu = mopelib.MyInputEvent( "Escape" )
54 self.keys_menu.add( pygame.KEYDOWN, pygame.K_ESCAPE )
55 self.keys_menu.add( pygame.JOYBUTTONDOWN, 8 ) # GP2X Start
56 self.keys_menu.add( pygame.JOYBUTTONDOWN, 9 ) # GP2X Select
58 self.keys_return = mopelib.MyInputEvent( "Return" )
59 self.keys_return.add( pygame.KEYDOWN, pygame.K_RETURN )
60 self.keys_return.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
62 self.keys_startgame = mopelib.MyInputEvent( "any key" )
63 self.keys_startgame.add_all( pygame.KEYDOWN )
64 self.keys_startgame.add_all( pygame.JOYBUTTONDOWN )
65 self.keys_startgame.add_all( pygame.MOUSEBUTTONDOWN )
67 self.keys_up = mopelib.MyInputEvent( "up" )
68 self.keys_up.add( pygame.KEYDOWN, ord( 'q' ) )
69 self.keys_up.add( pygame.KEYDOWN, pygame.K_UP )
70 self.keys_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
71 self.keys_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
73 self.keys_right = mopelib.MyInputEvent( "right" )
74 self.keys_right.add( pygame.KEYDOWN, ord( 'p' ) )
75 self.keys_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
76 self.keys_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
77 self.keys_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
79 self.keys_down = mopelib.MyInputEvent( "down" )
80 self.keys_down.add( pygame.KEYDOWN, ord( 'a' ) )
81 self.keys_down.add( pygame.KEYDOWN, pygame.K_DOWN )
82 self.keys_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
83 self.keys_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
85 self.keys_left = mopelib.MyInputEvent( "left" )
86 self.keys_left.add( pygame.KEYDOWN, ord( 'o' ) )
87 self.keys_left.add( pygame.KEYDOWN, pygame.K_LEFT )
88 self.keys_left.add( pygame.JOYBUTTONDOWN, 2 ) # GP2X Joy left
89 self.keys_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
91 self.keys_volup = mopelib.MyInputEvent( "+" )
92 self.keys_volup.add( pygame.KEYDOWN, ord( '+' ) )
93 self.keys_volup.add( pygame.KEYDOWN, ord( '=' ) )
94 self.keys_volup.add( pygame.JOYBUTTONDOWN, 16 ) # GP2X volume + button
96 self.keys_voldown = mopelib.MyInputEvent( "-" )
97 self.keys_voldown.add( pygame.KEYDOWN, ord( '-' ) )
98 self.keys_voldown.add( pygame.JOYBUTTONDOWN, 17 ) # GP2X volume - button
100 self.keys_p1_up = mopelib.MyInputEvent( "p1_up" )
101 self.keys_p1_up.add( pygame.KEYDOWN, pygame.K_UP )
102 self.keys_p1_up.add( pygame.JOYBUTTONDOWN, 0 ) # GP2X Joy up
104 self.keys_p1_right = mopelib.MyInputEvent( "p1_right" )
105 self.keys_p1_right.add( pygame.KEYDOWN, pygame.K_RIGHT )
106 self.keys_p1_right.add( pygame.JOYBUTTONDOWN, 6 ) # GP2X Joy right
108 self.keys_p1_down = mopelib.MyInputEvent( "p1_down" )
109 self.keys_p1_down.add( pygame.KEYDOWN, pygame.K_DOWN )
110 self.keys_p1_down.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy down
112 self.keys_p1_left = mopelib.MyInputEvent( "p1_left" )
113 self.keys_p1_left.add( pygame.KEYDOWN, pygame.K_LEFT )
114 self.keys_p1_left.add( pygame.JOYBUTTONDOWN, 4 ) # GP2X Joy left
116 self.keys_p2_up = mopelib.MyInputEvent( "p2_up" )
117 self.keys_p2_up.add( pygame.KEYDOWN, ord( '2' ) )
118 self.keys_p2_up.add( pygame.JOYBUTTONDOWN, 15 ) # GP2X Y button
120 self.keys_p2_right = mopelib.MyInputEvent( "p2_right" )
121 self.keys_p2_right.add( pygame.KEYDOWN, ord( 'e' ) )
122 self.keys_p2_right.add( pygame.JOYBUTTONDOWN, 13 ) # GP2X B button
124 self.keys_p2_down = mopelib.MyInputEvent( "p2_down" )
125 self.keys_p2_down.add( pygame.KEYDOWN, ord( 'w' ) )
126 self.keys_p2_down.add( pygame.JOYBUTTONDOWN, 14 ) # GP2X X button
128 self.keys_p2_left = mopelib.MyInputEvent( "p2_left" )
129 self.keys_p2_left.add( pygame.KEYDOWN, ord( 'q' ) )
130 self.keys_p2_left.add( pygame.JOYBUTTONDOWN, 12 ) # GP2X A button
132 self.keys_p3_up = mopelib.MyInputEvent( "p3_up" )
133 self.keys_p3_up.add( pygame.KEYDOWN, ord( 'i' ) )
135 self.keys_p3_right = mopelib.MyInputEvent( "p3_right" )
136 self.keys_p3_right.add( pygame.KEYDOWN, ord( 'l' ) )
138 self.keys_p3_down = mopelib.MyInputEvent( "p3_down" )
139 self.keys_p3_down.add( pygame.KEYDOWN, ord( 'k' ) )
141 self.keys_p3_left = mopelib.MyInputEvent( "p3_left" )
142 self.keys_p3_left.add( pygame.KEYDOWN, ord( 'j' ) )
144 self.keys_p4_up = mopelib.MyInputEvent( "p4_up" )
145 self.keys_p4_up.add( pygame.KEYDOWN, pygame.K_KP8 )
147 self.keys_p4_right = mopelib.MyInputEvent( "p4_right" )
148 self.keys_p4_right.add( pygame.KEYDOWN, pygame.K_KP6 )
150 self.keys_p4_down = mopelib.MyInputEvent( "p4_down" )
151 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP5 )
152 self.keys_p4_down.add( pygame.KEYDOWN, pygame.K_KP2 )
154 self.keys_p4_left = mopelib.MyInputEvent( "p4_left" )
155 self.keys_p4_left.add( pygame.KEYDOWN, pygame.K_KP4 )
158 # ----------------------
160 class TronCodeSoundManager( mopelib.SoundManager ):
162 def __init__( self, volume ):
163 mopelib.SoundManager.__init__( self, config )
165 #self.add_sample_group( "waddles", ["waddle1"] )
167 # ----------------------
169 def intro_draw_instructions():
170 write_text( "Press %s for menu, or %s to start" % (
171 config.keys_menu.name, config.keys_startgame.name ),
172 (0, 0, 0), 0.05, 0.99 )
174 # ----------------------
176 def general_menu_create_menu( menu, config, gamestate ):
177 menu.items = []
178 if gamestate == None: # We are on a title screen - Start Game option
179 menu.add_item( "Start game", MENU_START )
180 menu.add_item( "Number of players: %d" % (config.num_players),
181 MENU_NUM_PLAYERS )
182 for player_num in range( config.num_players ):
183 cls_name = "--unknown--"
184 if player_num < len( config.player_classes ):
185 cls_name = config.player_classes[player_num].GetName()
186 menu.add_item( "P%d: %s" % ( player_num, cls_name ),
187 MENU_CHANGE_PLAYER + player_num )
188 else:
189 menu.add_item( "Continue", MENU_START )
190 menu.add_item( "End game", MENU_END )
192 tmp_str = "Music: "
193 if config.music_on:
194 tmp_str += "on"
195 else:
196 tmp_str += "off"
197 menu.add_item( tmp_str, MENU_MUSIC )
199 tmp_str = "Effects: "
200 if config.sound_effects_on:
201 tmp_str += "on"
202 else:
203 tmp_str += "off"
204 menu.add_item( tmp_str, MENU_SOUND_EFFECTS )
206 menu.add_item( "Quit troncode", MENU_QUIT )
208 return menu
210 def general_menu_screen( config, gamestate ):
212 if gamestate == None:
213 menu_title = "troncode"
214 else:
215 menu_title = "troncode paused"
217 menu = mopelib.Menu()
218 general_menu_create_menu( menu, config, gamestate )
219 menurender.set_menu( menu, menu_title )
220 menurender.repaint_full()
222 game_start = False
224 waiting = True
225 while waiting:
226 event = pygame.event.wait()
227 if event.type == QUIT:
228 sys.exit(0)
229 elif config.keys_menu.matches( event ):
230 waiting = False
231 elif config.keys_down.matches( event ):
232 menurender.move_down()
233 elif config.keys_up.matches( event ):
234 menurender.move_up()
235 elif config.keys_return.matches( event ):
236 code = menu.get_selected_item().code
237 if code == MENU_START:
238 game_start = True
239 waiting = False
240 elif code == MENU_END:
241 gamestate.alive = INGAME_QUIT
242 waiting = False
243 elif code == MENU_MUSIC:
244 if config.music_on == 1:
245 config.music_on = 0
246 else:
247 config.music_on = 1
248 general_menu_create_menu( menu, config, gamestate )
249 menurender.repaint_full()
250 sound_mgr.setup( gamestate )
251 config.save()
252 elif code == MENU_SOUND_EFFECTS:
253 if config.sound_effects_on:
254 config.sound_effects_on = 0
255 else:
256 config.sound_effects_on = 1
257 general_menu_create_menu( menu, config, gamestate )
258 menurender.repaint_full()
259 sound_mgr.setup( gamestate )
260 config.save()
261 elif code == MENU_QUIT:
262 sys.exit(0)
264 return game_start
266 # ----------------------
268 def intro_draw_title():
269 screen.blit( intro_surface_title, (0,0) )
270 write_text( "Version " + troncode_version,
271 ( 0, 0, 0 ), 0.05, 0.88 )
272 write_text( "by Andy Balaam", ( 0, 0, 0 ), 0.06, 0.93 )
273 intro_draw_instructions()
275 def intro_draw_something( intro_mode ):
276 if intro_mode == INTRO_MODE_TITLE:
277 intro_draw_title()
278 elif intro_mode == INTRO_MODE_INSTR:
279 screen.blit( intro_surface_instr, (0,0) )
280 intro_draw_instructions()
281 elif intro_mode == INTRO_MODE_MUSIC:
282 screen.blit( intro_surface_music, (0,0) )
283 intro_draw_instructions()
284 pygame.display.update()
286 def intro_input( event, config, intro_mode ):
287 if event.type == QUIT:
288 sys.exit(0)
289 elif config.keys_volup.matches( event ):
290 config.volume = sound_mgr.increase_volume()
291 config.save()
292 elif config.keys_voldown.matches( event ):
293 config.volume = sound_mgr.decrease_volume()
294 config.save()
295 else:
296 if event.type == EVENTTYPE_TITLE_TICK:
297 intro_mode += 1
298 if intro_mode == INTRO_MODE_ENDED:
299 intro_mode = INTRO_MODE_TITLE
300 intro_draw_something( intro_mode )
301 elif config.keys_menu.matches( event ):
302 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
303 start_game = general_menu_screen( config, None )
304 if start_game:
305 intro_mode = INTRO_MODE_ENDED
306 else:
307 intro_draw_something( intro_mode )
308 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
309 elif config.keys_startgame.matches( event ):
310 intro_mode = INTRO_MODE_ENDED
311 return intro_mode
313 # ----------------------
315 def intro_mainloop( config ):
316 intro_draw_title()
318 pygame.display.update()
319 pygame.time.set_timer( EVENTTYPE_TITLE_TICK, TITLE_TICK_TIME )
321 intro_mode = INTRO_MODE_TITLE
322 while intro_mode < INTRO_MODE_ENDED:
323 intro_mode = intro_input( pygame.event.wait(), config, intro_mode )
325 mopelib.clear_events( EVENTTYPE_TITLE_TICK )
327 def draw_pixel( gamestate, surface, colour, x, y ):
328 col = mopelib.dim_colour( colour, gamestate.dim )
329 adj_x = screen_border[0] + scale * x
330 adj_y = screen_border[1] + scale * y
331 if scale < 1:
332 surface.set_at( ( int(adj_x), int(adj_y) ), col )
333 else:
334 pygame.draw.rect( surface, col,
335 (adj_x, adj_y, ceil_scale, ceil_scale ) )
337 def inlevel_screen_blit( gamestate ):
338 screen.blit( gamestate.offscreen_buff, (0,0) )
339 write_text_ingame( gamestate )
340 pygame.display.update()
342 def inlevel_redraw_screen( gamestate, arrows ):
343 gamestate.offscreen_buff = pygame.Surface( gamestate.config.screen_size )
344 gamestate.offscreen_buff.blit( ingame_surface_background, (0,0) )
345 for x, y, colour in gamestate.pixels_list:
346 draw_pixel( gamestate, gamestate.offscreen_buff, colour, x, y )
347 inlevel_draw_players( gamestate )
348 # TODO: draw arrows
349 inlevel_screen_blit( gamestate )
351 def inlevel_draw_players( gamestate ):
352 for player in gamestate.players:
353 x, y = gamestate.GetPosition( player )
354 draw_pixel( gamestate, gamestate.offscreen_buff, player.GetColour(),
355 x, y )
357 def inlevel_update_screen( gamestate ):
358 inlevel_draw_players( gamestate )
359 inlevel_screen_blit( gamestate )
363 # ----------------------
365 def inlevel_input( event, gamestate ):
366 if event.type == QUIT:
367 sys.exit(0)
368 elif( ( event.type == pygame.ACTIVEEVENT and event.state == 2 )
369 or config.keys_menu.matches( event ) ):
370 general_menu_screen( config, gamestate )
371 inlevel_redraw_screen( gamestate, False )
372 elif config.keys_volup.matches( event ):
373 config.volume = sound_mgr.increase_volume()
374 config.save()
375 elif config.keys_voldown.matches( event ):
376 config.volume = sound_mgr.decrease_volume()
377 config.save()
378 elif event.type == EVENTTYPE_INGAME_TICK:
379 gamestate.timer_tick()
380 gamestate.key_events = []
381 if gamestate.alive == INGAME_SOMEONE_DEAD:
382 finishedlevel_mainloop( gamestate )
383 else:
384 inlevel_update_screen( gamestate )
385 else:
386 gamestate.key_events.append( event )
389 # ----------------------
391 def finishedgame_input( event, waiting ):
392 if event.type == QUIT:
393 sys.exit(0)
394 elif config.keys_volup.matches( event ):
395 config.volume = sound_mgr.increase_volume()
396 config.save()
397 elif config.keys_voldown.matches( event ):
398 config.volume = sound_mgr.decrease_volume()
399 config.save()
400 elif config.keys_startgame.matches( event ):
401 waiting = False
402 return waiting
404 # ----------------------
406 class PlayerStatus:
407 def __init__( self, x, y, direction, player ):
408 self._x = x
409 self._y = y
410 self._dir = direction
411 self._colour = player.GetColour()
412 self._dead = False
414 def GetPosition( self ):
415 return ( self._x, self._y )
417 def GetDirection( self ):
418 return ( self._dir )
420 def SetDirection( self, direction ):
421 self._dir = direction
423 def Move( self, gamestate ):
424 """Moves the player one position depending on its direction, and
425 returns True if it hit anything, False otherwise."""
427 if self._dir == DIR_UP:
428 self._y -= 1
429 elif self._dir == DIR_RIGHT:
430 self._x += 1
431 elif self._dir == DIR_DOWN:
432 self._y += 1
433 elif self._dir == DIR_LEFT:
434 self._x -= 1
436 self._dead = gamestate.AddPixel( self._x, self._y, self._colour )
438 return self._dead
440 class GameBoard:
441 def __init__( self, gamestate ):
442 self._gamestate = gamestate
444 def GetRelativePixel( self, start_pos, facing_dir,
445 offset_fwd, offset_right ):
446 """Given a position to stand in the arena, and a direction to face,
447 return the status (0 for empty, >0 for non-empty) of a pixel that
448 is offset_fwd pixels in front, and offset_right pixels to the right
449 (negative values may be used to go backwards or left respectively).
450 Pixels outside the arena also return >0."""
452 if facing_dir == DIR_UP:
453 found_pos = ( start_pos[0] + offset_right,
454 start_pos[1] - offset_fwd )
455 elif facing_dir == DIR_RIGHT:
456 found_pos = ( start_pos[0] + offset_fwd,
457 start_pos[1] + offset_right )
458 elif facing_dir == DIR_DOWN:
459 found_pos = ( start_pos[0] - offset_right,
460 start_pos[1] + offset_fwd )
461 elif facing_dir == DIR_LEFT:
462 found_pos = ( start_pos[0] - offset_fwd,
463 start_pos[1] - offset_right )
465 if ( found_pos[0] < 0 or
466 found_pos[0] >= self._gamestate.config.arena_size[0] or
467 found_pos[1] < 0 or
468 found_pos[1] >= self._gamestate.config.arena_size[1] ):
469 retval = 100
470 elif found_pos in self._gamestate.pixels_set:
471 retval = 1
472 else:
473 retval = 0
475 return retval
478 def TurnRight( self, direction ):
479 """Return the direction found by turning 90 degrees right from
480 the supplied direction."""
481 direction += 1
482 if direction > DIR_LEFT:
483 direction = DIR_UP
484 return direction
486 def TurnLeft( self, direction ):
487 """Return the direction found by turning 90 degrees left from
488 the supplied direction."""
489 direction -= 1
490 if direction < DIR_UP:
491 direction = DIR_LEFT
492 return direction
494 class GameState:
495 def __init__( self, config ):
496 self.config = config
498 self.players = []
500 for cls in config.player_classes:
501 if "IsHuman" in cls.__dict__ and cls.IsHuman():
502 self.players.append( cls( config.keys_p1_up,
503 config.keys_p1_right, config.keys_p1_down,
504 config.keys_p1_left ) )
505 else:
506 self.players.append( cls() )
508 self.alive = INGAME_ALL_ALIVE
509 self.dim = 1
511 self.pixels_list = []
512 self.pixels_set = set()
513 self.create_initial_pixels()
515 self.statuses = {}
516 for player in self.players:
517 x = random.randint( config.starting_border, config.arena_size[0]
518 - config.starting_border )
519 y = random.randint( config.starting_border, config.arena_size[1]
520 - config.starting_border )
521 dr = random.randint( DIR_UP, DIR_LEFT )
522 self.statuses[player] = PlayerStatus( x, y, dr, player )
523 self.AddPixel( x, y, player.GetColour() )
525 self._gameboard = GameBoard( self )
527 def timer_tick( self ):
528 any_dead = False
529 for player in self.players:
530 status = self.statuses[player]
531 if( "IsHuman" in player.__class__.__dict__ and
532 player.__class__.IsHuman() ):
533 new_dir = player.GetDirWithInput( status.GetDirection(),
534 self.key_events )
535 else:
536 new_dir = player.GetDir( status.GetPosition(),
537 status.GetDirection(), self._gameboard )
538 status.SetDirection( new_dir )
539 # TODO: copy pixels list for "security"
541 dead = self.statuses[player].Move( self )
542 if dead:
543 any_dead = True
545 if any_dead:
546 self.alive = INGAME_SOMEONE_DEAD
548 def create_initial_pixels( self ):
549 colour = (200, 200, 200)
550 size_x, size_y = config.arena_size
551 for x in range( size_x ):
552 self.AddPixel( x, 0, colour )
553 self.AddPixel( x, size_y - 1, colour )
554 for y in range( 1, size_y - 1 ):
555 self.AddPixel( 0, y, colour )
556 self.AddPixel( size_x - 1, y, colour )
558 def GetPosition( self, player ):
559 return self.statuses[player].GetPosition()
561 def AddPixel( self, x, y, colour ):
562 if ( x, y ) in self.pixels_set:
563 dead = True
564 else:
565 dead = False
566 self.pixels_set.add( ( x, y ) )
568 self.pixels_list.append( ( x, y, colour ) )
570 return dead
573 # ----------------------
575 def ingame_mainloop( config ):
576 gamestate = GameState( config )
577 gamestate.key_events = []
578 while gamestate.alive == INGAME_ALL_ALIVE:
579 inlevel_mainloop( config, gamestate )
580 return gamestate
582 # ----------------------
584 def inlevel_mainloop( config, gamestate ):
586 inlevel_redraw_screen( gamestate, True )
587 time.sleep( 1 )
588 inlevel_redraw_screen( gamestate, False )
590 gc.disable()
591 pygame.time.set_timer( EVENTTYPE_INGAME_TICK, config.tick_time )
592 while gamestate.alive == INGAME_ALL_ALIVE:
593 inlevel_input( pygame.event.wait(), gamestate )
594 gc.enable()
595 mopelib.clear_events( EVENTTYPE_INGAME_TICK )
597 ingame_surface_background.fill( config.colour_background )
600 # ----------------------
602 def write_text( txt, colour, size, y_pos ):
603 ft = pygame.font.Font( None, int( config.screen_size[1] * size ) )
604 sf = ft.render( txt, True, colour )
605 screen.blit( sf, ( (config.screen_size[0] - sf.get_width() )/2,
606 (config.screen_size[1] - sf.get_height() ) * y_pos ) )
608 # ----------------------
610 def write_text_ingame( gamestate ):
611 #global ingame_font
613 #bgcol = mopelib.dim_colour( gamestate.cur_lev.background_colour,
614 # gamestate.dim )
615 #fgcol = mopelib.dim_colour( gamestate.cur_lev.text_colour, gamestate.dim )
617 #write_text_blank_font_coords( "Level: %d" % ( gamestate.level_num + 1 ),
618 # fgcol, ingame_font, bgcol, 0.3, 0.99 )
620 #write_text_blank_font_coords( "Time: %s" % gamestate.cur_lev.current_time,
621 # fgcol, ingame_font, bgcol, 0.7, 0.99 )
622 pass
625 def write_text_blank_font_coords( txt, colour, font, bgcolour, x, y ):
626 sf = font.render( txt, True, colour )
627 sf_bg = pygame.Surface( ( int( sf.get_width() * 1.29 ), sf.get_height() ) )
628 sf_bg.fill( bgcolour )
630 tlx = ( config.screen_size[0] - sf_bg.get_width() ) * x
631 tly = ( config.screen_size[1] - sf_bg.get_height() ) * y
633 dirty_rect = Rect( tlx, tly, sf_bg.get_width(), sf_bg.get_height() )
634 screen.blit( sf_bg, dirty_rect )
635 screen.blit( sf, ( tlx * 1.01, tly ) )
637 # ----------------------
639 def finishedlevel_input( event, waiting ):
640 if event.type == QUIT:
641 sys.exit(0)
642 elif config.keys_volup.matches( event ):
643 config.volume = sound_mgr.increase_volume()
644 config.save()
645 elif config.keys_voldown.matches( event ):
646 config.volume = sound_mgr.decrease_volume()
647 config.save()
648 elif config.keys_startgame.matches( event ):
649 waiting = False
650 return waiting
652 def finishedlevel_mainloop( gamestate ):
653 gamestate.dim = 0.5
654 inlevel_redraw_screen( gamestate, False )
655 waiting = True
656 while waiting:
657 waiting = finishedlevel_input( pygame.event.wait(), waiting )
659 def finishedgame_mainloop( config, gamestate ):
660 pass
661 #config.start_level = 0
662 # config.save()
664 # inlevel_redraw_screen( gamestate )
665 # write_text( "Congratulations!", (255,255,255), 0.125, 0.38 )
666 # write_text( "You won!", (255,255,255), 0.125, 0.52 )
667 # waiting = True
668 # write_text( "Press %s" % config.keys_startgame.name, (255,255,255),
669 # 0.05, 0.8 )
670 # pygame.display.update()
671 # while waiting:
672 # waiting = finishedgame_input( pygame.event.wait(), waiting )
674 # ingame_surface_background.fill( config.colour_background )
677 # ----------------------
678 # Execution starts here
679 # ----------------------
681 # Fixed constants
683 INTRO_MODE_TITLE = 0
684 INTRO_MODE_INSTR = 1
685 INTRO_MODE_MUSIC = 2
686 INTRO_MODE_ENDED = 3
688 MENU_START = 0
689 MENU_NUM_PLAYERS = 1
690 MENU_END = 2
691 MENU_MUSIC = 3
692 MENU_SOUND_EFFECTS = 4
693 MENU_QUIT = 6
694 MENU_CHANGE_PLAYER = 100 # Must go at end of this list
696 INGAME_ALL_ALIVE = 0
697 INGAME_SOMEONE_DEAD = 1
698 INGAME_QUIT = 2
700 TITLE_TICK_TIME = 4000
702 EVENTTYPE_INGAME_TICK = pygame.USEREVENT
703 EVENTTYPE_TITLE_TICK = pygame.USEREVENT + 1
705 num_args = len( sys.argv )
706 if num_args > 1:
707 if sys.argv[1] == "--help":
708 print "Usage:"
709 print "troncode.py [install_dir] [config_file] [resolution] "
710 print " [num_players] [--tournament]"
711 print
712 print "e.g. ./troncode.py . troncoderc.txt '(640,480)' 2 --tournament"
713 print
714 sys.exit( 0 )
716 install_dir = sys.argv[1]
717 else:
718 install_dir = "."
720 if num_args > 2:
721 config_filename = sys.argv[2]
722 else:
723 config_filename = os.path.expanduser( "~/.troncode/config" )
725 config = TronCodeConfig( config_filename )
727 config.install_dir = install_dir
728 config.unsaved.append( "install_dir" )
730 config.images_dir = os.path.join( install_dir, "images" )
731 config.unsaved.append( "images_dir" )
733 config.music_dir = os.path.join( install_dir, "music" )
734 config.unsaved.append( "music_dir" )
736 pygame.init()
737 pygame.font.init()
739 pygame.mouse.set_visible( False )
741 if num_args > 3:
742 config.screen_size = config.parse_value( sys.argv[3] )
744 window = pygame.display.set_mode( config.screen_size )
745 pygame.display.set_caption( 'duckmaze' )
746 screen = pygame.display.get_surface()
748 config.arena_size = ( 200, 200 )
749 config.unsaved.append( "arena_size" )
751 config.starting_border = 35
752 config.unsaved.append( "starting_border" )
754 config.player_classes = []
755 config.unsaved.append( "player_classes" )
756 config.player_classes.append( TurnRightPlayer )
757 config.player_classes.append( HumanPlayer )
759 config.players = []
760 config.unsaved.append( "players" )
762 fixed_border = 5
763 scale = min(
764 ( float( config.screen_size[0] - fixed_border*2 )
765 / float( config.arena_size[0] ) ),
766 ( float( config.screen_size[1] - fixed_border*2 )
767 / float( config.arena_size[1] ) ) )
769 screen_border = ( float( config.screen_size[0] - config.arena_size[0]*scale )
770 / 2.0,
771 float( config.screen_size[1] - config.arena_size[1]*scale ) / 2.0, )
773 ceil_scale = math.ceil( scale )
775 # General initialisation
777 num_joysticks = pygame.joystick.get_count()
778 for j in range( num_joysticks ):
779 pygame.joystick.Joystick( j ).init()
781 intro_surface_title = mopelib.load_and_scale_image( "title.png", config )
782 intro_surface_instr = mopelib.load_and_scale_image( "instructions.png", config )
783 intro_surface_music = mopelib.load_and_scale_image( "music.png", config )
785 ingame_surface_background = pygame.Surface( screen.get_size() ).convert()
786 ingame_surface_background.fill( config.colour_background )
788 intro_mode = INTRO_MODE_TITLE
790 sound_mgr = TronCodeSoundManager( config.volume )
792 troncode_version = mopelib.read_version( config )
794 ingame_font = pygame.font.Font( None, int( config.screen_size[1] * 0.09 ) )
796 menurender = mopelib.MenuRenderer( screen, config, ingame_surface_background,
797 (128, 128, 128), (128, 255, 128), (128, 128, 128) )
799 while True:
800 sound_mgr.music_loud()
801 intro_mainloop( config )
802 sound_mgr.music_quiet()
803 gamestate = ingame_mainloop( config )
804 intro_mode = finishedgame_mainloop( config, gamestate )