source: ubervaders/ubervaders.py @ 260

Revision 260, 22.8 KB checked in by shiml, 18 months ago (diff)

ubervaders: joystickfoo

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2# $Id: pyspaceinvaders.py 1.51 2004/08/19 11:57:50 jimb Exp $
3# Python Space Invaders.
4# Author:   Jim Brooks  http://www.jimbrooks.org
5# Date:     2004/08
6# License:  GNU General Public License (GPL)
7# Requires: Python 2.3, PyGame, SDL.
8# Notes:    See notes.txt.
9# ==============================================================================
10
11import os, sys, random
12import pygame
13from pygame.locals import *
14from pygame.rect import Rect
15if not pygame.font: print "Warning, fonts disabled"
16from libgame import *
17from libutils import *
18
19# Colors.
20RGBbg        = (0, 0, 0)
21RGBtext      = (210, 240, 255)
22RGBalive     = (0, 255, 0)
23RGBdead      = (255, 0, 0)
24RGBgameover  = RGBdead
25RGBgreen     = (0,255,0)
26RGBcyan      = (0,200,200)
27RGBgrey      = (64,64,64)
28
29# ------------------------------------------------------------------------------
30# Classes.
31# ------------------------------------------------------------------------------
32
33# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
34# Object class.
35class Object:
36    def __init__( self ):
37        self.valid    = True
38        self.hit      = 0
39        self.movement = ( 0, 0 )
40        return
41
42# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
43# Player class.
44class Player(Object):
45    def __init__( self , st ):
46        Object.__init__( self )
47        self.image       = pygame.image.load( "img/player.png" )
48        self.image2      = pygame.image.load( "img/playerb.png" )
49        self.imageHit    = pygame.image.load( "img/explosion.png" )
50        self.rect        = self.image.get_rect()
51        self.imageFlip   = False
52        self.step        = 7 * st.stride
53        self.fire        = False
54        self.fireLatency = 5
55        self.salvo       = 3
56        return
57
58    def Reset( self, st, scr ):
59        self.valid        = True
60        self.hit          = 0 # counts down
61        self.rect.centerx = scr.width / 2
62        self.rect.bottom  = st.ground
63        return       
64
65    def Hit( self ):
66        self.hit = 30
67        st.playerLives -= 1
68        if st.playerLives <= 0: # player out of lives?
69            st.GameOver()
70        return
71
72# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
73# Alien class.
74class Alien(Object):
75    # constants:
76    points    = 50
77    colCnt    = 10
78    rowCnt    = 4
79    totalCnt  = colCnt * rowCnt
80    # vars:
81    imageFlip = False
82    horzDir   = -1
83   
84    def __init__( self, fname, fname2 ):
85        Object.__init__( self )
86        self.image     = pygame.image.load(fname)
87        self.image2    = pygame.image.load(fname2)
88        self.imageHit  = pygame.image.load("img/explosion.png")
89        self.rect      = self.image.get_rect()
90        # reset vars
91        self.imageFlip = False
92        self.horzDir   = -1
93        return
94   
95    def Hit( self ):
96        self.hit = 5
97        return
98
99# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
100# Player missile class.
101class PlayerMissile(Object):
102    def __init__( self ):
103        Object.__init__( self )
104        self.image        = pygame.image.load( "img/missile_player.png" )
105        self.rect         = self.image.get_rect()
106        self.rect.centerx = st.player.rect.centerx
107        self.rect.centery = st.player.rect.centery - st.player.rect.height
108        return
109
110# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
111# Alien missile class.
112class AlienMissile(Object):
113    def __init__( self, alien ):
114        Object.__init__( self )
115        self.image        = pygame.image.load( "img/missile_alien.png" )
116        self.rect         = self.image.get_rect()
117        self.rect.centerx = alien.rect.centerx + random.randint( -8, 8 )
118        self.rect.centery = alien.rect.centery + alien.rect.height
119        return
120
121# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
122# Screen state
123class Screen:
124    # Initialize screen geometry and caption text in title bar.
125    def __init__( self, w, h, caption ):
126        self.width  = w
127        self.height = h
128        self.screen = pygame.display.set_mode( (w,h), pygame.FULLSCREEN )
129        self.font   = pygame.font.SysFont( None, 28 )
130        pygame.display.set_caption( caption )
131        return
132
133# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
134# Game state
135class State:
136    # Fundamental game states:
137    state_over     = -2  # enum
138    state_pause    = -1  # enum
139    state_stop     = 0   # enum
140    state_play     = 1   # enum
141    state          = state_stop
142
143    # Sprites:
144    player         = None
145    alienColumns   = []
146    playerMissiles = []
147    alienMissiles  = []
148
149    # Misc:
150    stride         = 1.0  # effective speed
151    tick           = 0
152    tempo          = 1
153    score          = 0
154    level          = 1
155    cheat          = 0    # 1: 999 lives 2: 999 lives + persistent gunfire
156    ground         = None
157    ceiling        = None
158    quitable       = 1
159   
160    # - - - - - - - - -
161    # Initialize game state.
162    # Pass Screen object as "scr".
163    def __init__( self, scr ):
164        self.Reset( scr, 1 )
165        return
166
167    # - - - - - - - - -
168    # Reset game state.
169    # Can advance level of difficulty.
170    def Reset( self, scr, level ):
171        self.tick     = 0
172        self.tempo    = 1
173        if self.state == self.state_over:
174            self.score    = 0
175        self.level    = level
176        if not self.cheat or level == 1:
177            self.playerLives = 5
178        self.ground   = scr.height - 6
179        self.ceiling  = 32
180
181        msg.Del( MsgListGameOver() )
182
183        # Create/reset player.
184        if self.player == None:
185            self.player = Player( self )
186        self.player.Reset( self, scr )
187
188        # Delete all aliens (invalidate then prune).
189        if len(self.alienColumns):
190            for col in range(Alien.colCnt):
191                for alien in self.alienColumns[col]:
192                    alien.valid = False
193            PruneListList(self.alienColumns)
194           
195        # Create a full population of aliens.
196        self.alienColumns = []
197        for col in range(Alien.colCnt):
198            self.alienColumns.append([])
199            for row in range(Alien.rowCnt):
200                if row == 0:
201                    fname  = "img/enemy3.png"
202                    fname2 = "img/enemy3b.png"
203                elif row == 1:
204                    fname  = "img/enemy2.png"
205                    fname2 = "img/enemy2b.png"
206                else:
207                    if col % 2 == row % 2:
208                        fname  = "img/enemy3.png"
209                        fname2 = "img/enemy3b.png"
210                    else:
211                        fname  = "img/enemy2.png"
212                        fname2 = "img/enemy2b.png"
213
214                alien = Alien(fname, fname2)
215                alien.rect.move_ip(col * 48 + 40, row * 48 + self.ceiling)
216                self.alienColumns[col].append(alien)
217
218        # Delete any missiles.
219        if len(self.playerMissiles):
220            for m in self.playerMissiles: m.valid = False
221            PruneList(self.playerMissiles)
222        if len(self.alienMissiles):
223            for m in self.alienMissiles:  m.valid = False
224            PruneList(self.alienMissiles)
225
226        return
227
228    # - - - - - - - - -
229    def GameOver( self ):
230        self.state = self.state_over
231        msg.Add( MsgListGameOver() )
232        splash.Enable(True)
233        return
234
235    # - - - - - - - - -
236    # Toggle pause if playing else do nothing.
237    def TogglePause( self ):
238        if self.state == self.state_play:
239            self.state = self.state_pause
240        elif self.state == self.state_pause:
241            self.state = self.state_play
242        return
243
244    # - - - - - - - - -
245    # For convenience, return a single list
246    # from the lists of alien columns.
247    # NOTE: The list returned is a derivation.
248    #       It shouldn't be modified (treat as read-only).
249    def AlienList( self ):
250        list = []
251        for col in range(Alien.colCnt):
252            for alien in self.alienColumns[col]:
253                list.append( alien )
254        return list
255
256# ------------------------------------------------------------------------------
257# Subroutines.
258# ------------------------------------------------------------------------------
259
260# Missile/missile collisions.
261# Detects if one missile has collided into any opposite missiles (plural).
262def MissileMissileCollision( missile, missileIdx, oppMissileList ):
263    if missile.valid:
264        oppMissileIdx = -1
265        for oppMissile in oppMissileList:
266            oppMissileIdx += 1
267            if oppMissile.valid and Collided( missile, oppMissile ):
268                missile.valid = False
269                oppMissile.valid = False
270    return
271
272# Has any alien collided into the player?
273def PlayerAlienCollision( alienList ):
274    for alien in alienList:
275        # Ignore further collisions while both are exploding.
276        if alien.valid and Collided( alien, st.player ):
277            if alien.hit <= 0 and st.player.hit <= 0:
278                alien.Hit()
279                st.player.Hit()
280    return
281
282# ------------------------------------------------------------------------------
283# Render.
284# ------------------------------------------------------------------------------
285
286def Render():
287    # Clear screen.
288    scr.screen.fill( RGBbg )
289
290    # Don't render if stopped.
291    if st.state != st.state_stop:
292        Render2()
293
294    # Display any text messages (over whatever was rendered).
295    msg.Render()
296
297    pygame.display.flip()
298
299    return
300
301def Render2():
302    # Blit player.
303    if st.player.hit <= 0:
304        if st.player.imageFlip:
305            scr.screen.blit( st.player.image,  st.player.rect )
306        else:
307            scr.screen.blit( st.player.image2, st.player.rect )
308    else:
309        scr.screen.blit( st.player.imageHit, st.player.rect )
310
311    # Blit aliens.
312    for alien in st.AlienList():
313        if alien.hit <= 0:
314            if Alien.imageFlip:
315                scr.screen.blit( alien.image, alien.rect )
316            else:
317                scr.screen.blit( alien.image2, alien.rect )
318        else:
319                scr.screen.blit( alien.imageHit, alien.rect )
320
321    # Blit player missiles.
322    for o in st.playerMissiles:
323        if o.valid and o.rect.centery > 0:
324            scr.screen.blit( o.image, o.rect )
325
326    # Blit alien missiles.
327    for o in st.alienMissiles:
328        if o.valid and o.rect.centery < scr.height:
329            scr.screen.blit( o.image, o.rect )
330
331    # Show status.
332    MsgShowScore()
333    MsgShowLevel()
334    MsgShowLives()
335
336    return
337
338# ------------------------------------------------------------------------------
339# Animate.
340# ------------------------------------------------------------------------------
341
342# Main animation function.
343# Assumes: invocation driven by timer tick.
344def Animate():
345    # Periodically prune invalid objects.
346    PruneList( st.playerMissiles )
347    PruneList( st.alienMissiles )
348    PruneListList( st.alienColumns )
349
350    # Animate only if playing.
351    if st.state == st.state_play:
352        alienList = st.AlienList()
353        AnimatePlayer( alienList )
354        AnimateAliens( alienList )
355        PlayerAlienCollision( alienList )
356
357    return
358
359# Animate player.
360def AnimatePlayer( alienList ):
361    # Animate exploding player (ok if not hit).
362    st.player.hit -= 1
363
364    # Don't let player move while exploding.
365    if st.player.hit <= 0:
366        # Continue player movement until key released.
367        if st.player.movement != (0,0):
368            if st.player.rect.left      + st.player.movement[0] > 0 \
369               and st.player.rect.right + st.player.movement[0] < scr.width:
370                st.player.rect.move_ip( st.player.movement[0], 0 )
371                st.player.imageFlip = not st.player.imageFlip
372        # Player's gun has a latency period and a limited salvo.
373        if st.player.fire and st.tick % st.player.fireLatency == 0 and len(st.playerMissiles) < st.player.salvo:
374            st.playerMissiles.append( PlayerMissile() )
375
376    # Animate missiles from player.
377    missileIdx = -1
378    for missile in st.playerMissiles:
379        missileIdx += 1
380        # Animate missile.
381        missile.rect.centery -= 10 * st.stride
382        # Off-screen?
383        if missile.rect.top < st.ceiling:
384            missile.valid = False # will be pruned later
385            continue
386        # Has player's missiles hit any alien?
387        # Exclude any hit alien from further collision-detection
388        # in order to allow player missiles to fly thru explosion
389        # to hit a higher alien.
390        for alien in alienList:
391            if Collided( missile, alien ) and alien.valid and alien.hit <= 0:
392                alien.Hit()
393                st.score += Alien.points
394                if st.cheat != 2: missile.valid = False
395        # Has this missile hit any opposite missile?
396        MissileMissileCollision( missile, missileIdx, st.alienMissiles )
397
398    return
399
400# Animate aliens (part 1).
401def AnimateAliens( alienList ):
402    # Animate aliens (unless player is hit/exploding).
403    if st.player.hit <= 0:
404        # Call actual routine.
405        (invaded,alienCnt) = AnimateAliens2( alienList )
406        # Game over if aliens have invaded (reached the ground).
407        # Or were all aliens destroyed?  If so, goto next level.
408        if invaded:
409            st.GameOver()
410        elif alienCnt == 0:
411            st.Reset( scr, st.level + 1 )
412    return
413
414# Animate aliens (part 2).
415# Returns: (invaded,alienCnt)
416# "invaded" true if any alien has reached the ground.
417def AnimateAliens2( alienList ):
418    invaded = False
419
420    # Animate the walking of aliens.
421    if st.tick % 5 == 0:
422        Alien.imageFlip = not Alien.imageFlip
423
424    # Only aliens at bottom of columns can fire.
425    for list in st.alienColumns:
426        if len(list):
427            alien = list[-1] # last element (alien at bottom of column)
428            if random.randint( 0, 1000 ) > (999 - min(20,2*st.tempo)):
429                st.alienMissiles.append( AlienMissile( alien ) )
430
431    # Animate missiles from aliens.
432    missileIdx = -1
433    for missile in st.alienMissiles:
434        missileIdx += 1
435        # Animate missile.
436        missile.rect.centery += 10 * st.stride
437        # Off-screen?
438        if missile.rect.bottom > st.ground:
439            missile.valid = False # will be pruned later
440            continue
441        # Has an alien missile hit the player?
442        # Ignore further hits while player explodes.
443        if Collided( missile, st.player ) and st.player.hit <= 0:
444            missile.valid = False
445            st.player.Hit()
446        # Has this missile hit any opposite missile?
447        MissileMissileCollision( missile, missileIdx, st.playerMissiles )
448
449    # Animate alien movement:
450    # - right-to-left
451    # - down
452    # - left-right
453    # - down
454    # - repeat
455
456    # Move all aliens left or right.
457    for alien in alienList:
458        alien.rect.centerx += Alien.horzDir * st.tempo * st.stride
459
460    # Has any alien hit the left/right edge of screen?
461    hitEdge = False
462    padding = 4
463    for alien in alienList:
464        if alien.rect.left - padding < 0 or alien.rect.right + padding > scr.width:
465            hitEdge = True
466            break
467    if hitEdge:
468        # Reverse horizontal direction of aliens.
469        Alien.horzDir = -Alien.horzDir
470        # Move all aliens downward one step.
471        for alien in alienList:
472            alien.rect.centery += 4 * st.stride
473            # Has this alien invaded (reached the ground)?
474            if alien.rect.bottom >= st.ground:
475                invaded = True
476
477    # Decrement alien.hit of every alien
478    # in order to animate exploding aliens.
479    for alien in alienList:
480        alien.hit -= 1
481        # Subtle: An alien is dead if .hit was decremented from 1 to 0.
482        # .hit is assigned a positive value if alien was struck.
483        if alien.hit == 0:
484            alien.valid = False
485
486    # Increase tempo (alien movement) as the amount of aliens decreases.
487    factor = Alien.totalCnt / 4
488    st.tempo = min(10,st.level)
489    if len(alienList) > 5:
490        st.tempo += 4 - int( len(alienList) / factor ) + 1
491    else:
492        st.tempo += 12 # very quick when few aliens survive
493
494    return (invaded,len(alienList))
495
496# ------------------------------------------------------------------------------
497# Specific messages.
498# ------------------------------------------------------------------------------
499
500# The reason these are functions is because (x,y) is computed dynamically
501# based on screen size.  The functions return a list that contains one tuple.
502
503def MsgListGameOver():
504    return [ ( "GAME OVER", scr.width/4 * 3, 4, RGBgameover, 0 ) ]
505#             ( "press 1 to restart!", scr.width/2, scr.height/2+32, RGBgameover, 1) ]
506
507def MsgShowScore():
508    msg.Show( ("SCORE: "+str(st.score), 4, 4, RGBtext, 0) )
509    return
510   
511def MsgShowLevel():
512    msg.Show( ("LEVEL: "+str(st.level), 16+scr.width/4.0*1.0, 4, RGBtext, 0) )
513    return
514   
515def MsgShowLives():
516    if st.playerLives:
517        rgb = RGBalive
518    else:
519        rgb = RGBdead
520    msg.Show( ("LIVES: "+str(st.playerLives), scr.width/4.0*2.0, 4, rgb, 0) )
521    return
522
523# ------------------------------------------------------------------------------
524# Splash.
525# ------------------------------------------------------------------------------
526
527class Splash:
528    def __init__( self ):
529        self.on = True
530        self.Enable( self.on )
531        return
532
533    def SplashList( self ):
534        x = scr.width/2
535        y = 8
536        return [ ( "UBERVADERS",                  x, y+32*1, RGBgreen, 1 ), \
537                 ( "1: start",                  x, y+32*3, RGBtext, 1 ), \
538                 ( "Left Arrow: left",          x, y+32*4, RGBtext, 1 ), \
539                 ( "Right Arrow: right",        x, y+32*5, RGBtext, 1 ), \
540                 ( "CTRL: fire",                x, y+32*6, RGBtext, 1 ), \
541                 ( "ESC: pause",                x, y+32*7, RGBtext, 1 ), \
542                 ( "based on",                  x, y+32*13, RGBgrey, 1 ), 
543                 ( "www.jimbrooks.org/web/python/pyspaceinvaders/",                  x, y+32*14, RGBgrey, 1 ) ]
544#                 ( "F5, F6: CHEAT, CHEAT MORE", x, y+32*8, RGBtext, 1 ), \
545#                 ( "Q: QUIT",                   x, y+32*9, RGBtext, 1 )  ]
546
547    def Enable( self, f ):
548        if f:
549            msg.Add( self.SplashList() )
550            self.on = True
551        else:
552            msg.Del( self.SplashList() )
553            self.on = False
554        return
555
556    # If stopped, ESC key does nothing (nothing to unpause).
557    def Toggle( self ):
558        if st.state != st.state_stop:
559            self.on = not self.on
560            self.Enable(self.on)
561        return
562
563
564class DummyJoystick:
565    def __init__(self):
566        pass
567    def init(self):
568        return None
569    def quit(self): 
570        return None
571    def get_init(self):
572        return True
573    def get_id(self):
574        return 0
575    def get_name(self):
576        return 'dummy'
577    def get_numaxes(self): 
578        return 2
579    def get_axis(self,dummy):
580        return 0
581    def get_numballs(self):
582        return 0
583    def get_ball(self,dummy):
584        return 0
585    def get_numbuttons(self): 
586        return 2
587    def get_button(self,dummy):
588        return False
589    def get_numhats(self):
590        return 0
591    def get_hat(self,dummy):
592        return 0,0
593
594
595# ------------------------------------------------------------------------------
596# Initialization.
597# ------------------------------------------------------------------------------
598
599random.seed()
600pygame.init()
601POLLCLOCK = USEREVENT + 1
602pygame.time.set_timer ( POLLCLOCK, 40 )
603scr    = Screen( 640, 480, "Python Space Invaders" )
604msg    = Msg( scr.screen, scr.font )
605st     = State( scr )
606splash = Splash()
607
608pygame.mouse.set_visible(False)
609
610## joystick-foo
611try:
612    pygame.joystick.init()
613    joy = pygame.joystick.Joystick(0)
614    joy.init()
615except:
616#    joy = DummyJoystick()
617    joy = None
618
619# ------------------------------------------------------------------------------
620# Event loop.
621# ------------------------------------------------------------------------------
622
623run = True
624
625while run:
626    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
627    # Process events.
628    event = pygame.event.wait()
629    # .......................................................
630    if event.type == pygame.QUIT:       
631        sys.exit()
632    # .......................................................
633    elif event.type == POLLCLOCK:
634        st.tick += 1
635        Animate()
636        Render()
637    # .......................................................
638   
639#    if joy.get_name() == 'dummy':
640    if joy == None:
641        if event.type == KEYDOWN:
642            # Start game?
643            if event.key == K_1:
644                st.Reset( scr, 1 )
645                st.state = st.state_play
646                splash.Enable( False )
647            # Quit?
648            elif event.key == K_k and st.quitable == 1:
649                pygame.mouse.set_visible(True)
650                run = False
651            # ESC?
652            elif event.key == K_ESCAPE:
653                splash.Toggle()
654                st.TogglePause()
655           
656            #----ignore other keys if not playing----
657            if st.state != st.state_play:
658                continue
659            #----ignore other keys if not playing----
660
661            # Move player left?
662            if event.key == K_a or event.key == K_LEFT:
663                st.player.movement = ( -st.player.step, 0 )
664            # Move player right?
665            elif event.key == K_d or event.key == K_RIGHT:
666                st.player.movement = ( st.player.step, 0 )
667            # Player fired gun?
668            elif event.key == K_RCTRL or event.key == K_LCTRL:
669                st.player.fire = True
670            # Cheat?
671    #        elif event.key == K_F5:
672    #            st.cheat = 1
673    #            st.playerLives = 999
674    #            st.player.salvo = 7
675    #        elif event.key == K_F6:
676    #           st.cheat = 2
677    #            st.playerLives = 999
678    #            st.player.salvo = 12
679        # .......................................................
680        elif event.type == KEYUP:
681            # Stop moving player?
682            if event.key == K_z or event.key == K_x or event.key == K_LEFT or event.key == K_RIGHT:
683                st.player.movement = ( 0, 0 )
684            # Player stopped firing gun?
685            elif event.key == K_RCTRL or event.key == K_LCTRL:
686                st.player.fire = False
687        # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
688    else:
689        # Start game?
690        if joy.get_button(1) == True:
691            st.Reset( scr, 1 )
692            st.state = st.state_play
693            splash.Enable( False )
694       
695        # Move player left?
696        if joy.get_axis(0) < -0.5:
697            st.player.movement = ( -st.player.step, 0 )
698        # Move player right?
699        elif joy.get_axis(0) > 0.5:
700            st.player.movement = ( st.player.step, 0 )
701        # Player fired gun?
702        elif joy.get_button(0) == True:
703            st.player.fire = True
704    # .......................................................
705        # Stop moving player?
706        elif abs(joy.get_axis(0)) < 0.5:
707            st.player.movement = ( 0, 0 )
708        elif joy.get_button(0) == False:
709            st.player.fire = False
710
Note: See TracBrowser for help on using the repository browser.