diff pytouhou/game/game.pyx @ 447:78e1c3864e73

Make pytouhou.game.game an extension type.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 17 Aug 2013 06:29:53 +0200
parents pytouhou/game/game.py@3a33ed7f3b85
children 3bc37791f0a2
line wrap: on
line diff
copy from pytouhou/game/game.py
copy to pytouhou/game/game.pyx
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.pyx
@@ -12,22 +12,22 @@
 ## GNU General Public License for more details.
 ##
 
-from itertools import chain
-
 from pytouhou.vm.msgrunner import MSGRunner
 
+from pytouhou.game.element cimport Element
+from pytouhou.game.bullet cimport Bullet
 from pytouhou.game.bullet import LAUNCHED, CANCELLED
-from pytouhou.game.enemy import Enemy
-from pytouhou.game.item import Item
-from pytouhou.game.effect import Effect, Particle
+from pytouhou.game.enemy cimport Enemy
+from pytouhou.game.item cimport Item
+from pytouhou.game.effect cimport Particle
 from pytouhou.game.text import Text
 from pytouhou.game.face import Face
 
 
-class Game(object):
-    def __init__(self, players, stage, rank, difficulty, bullet_types,
-                 laser_types, item_types, nb_bullets_max=None, width=384,
-                 height=448, prng=None, interface=None, continues=0,
+cdef class Game:
+    def __init__(self, players, long stage, long rank, long difficulty, bullet_types,
+                 laser_types, item_types, long nb_bullets_max=0, long width=384,
+                 long height=448, prng=None, interface=None, double continues=0,
                  hints=None):
         self.width, self.height = width, height
 
@@ -66,6 +66,7 @@ class Game(object):
                            1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 2]
         self.prng = prng
         self.frame = 0
+        self.sfx_player = None
 
         self.spellcard_effect = None
 
@@ -77,14 +78,14 @@ class Game(object):
 
 
     def msg_sprites(self):
-        return [face for face in self.faces if face] if self.msg_runner and not self.msg_runner.ended else []
+        return [face for face in self.faces if face is not None] if self.msg_runner is not None and not self.msg_runner.ended else []
 
 
     def lasers_sprites(self):
-        return [laser for laser in self.players_lasers if laser]
+        return [laser for laser in self.players_lasers if laser is not None]
 
 
-    def modify_difficulty(self, diff):
+    cpdef modify_difficulty(self, long diff):
         self.difficulty_counter += diff
         while self.difficulty_counter < 0:
             self.difficulty -= 1
@@ -108,23 +109,26 @@ class Game(object):
         self.spellcard_effect = None
 
 
-    def drop_bonus(self, x, y, _type, end_pos=None):
-        player = self.players[0] #TODO
+    cpdef drop_bonus(self, double x, double y, long _type, end_pos=None):
         if _type > 6:
             return
         if len(self.items) >= self.nb_bullets_max:
             return #TODO: check
         item_type = self.item_types[_type]
-        item = Item((x, y), _type, item_type, self, end_pos=end_pos)
-        self.items.append(item)
+        self.items.append(Item((x, y), _type, item_type, self, end_pos=end_pos))
 
 
-    def autocollect(self, player):
+    cdef void autocollect(self, Player player):
+        cdef Item item
+
         for item in self.items:
             item.autocollect(player)
 
 
-    def cancel_bullets(self):
+    cpdef cancel_bullets(self):
+        cdef Bullet bullet
+        #TODO: cdef Laser laser
+
         for bullet in self.bullets:
             bullet.cancel()
         for laser in self.lasers:
@@ -132,6 +136,9 @@ class Game(object):
 
 
     def change_bullets_into_star_items(self):
+        cdef Player player
+        cdef Bullet bullet
+
         player = self.players[0] #TODO
         item_type = self.item_types[6]
         self.items.extend(Item((bullet.x, bullet.y), 6, item_type, self, player=player)
@@ -144,6 +151,9 @@ class Game(object):
 
 
     def change_bullets_into_bonus(self):
+        cdef Player player
+        cdef Bullet bullet
+
         player = self.players[0] #TODO
         score = 0
         bonus = 2000
@@ -157,6 +167,8 @@ class Game(object):
 
 
     def kill_enemies(self):
+        cdef Enemy enemy
+
         for enemy in self.enemies:
             if enemy.boss:
                 pass # Bosses are immune to 96
@@ -168,13 +180,13 @@ class Game(object):
                 enemy.death_callback = -1
 
 
-    def new_effect(self, pos, anim, anm=None, number=1):
+    def new_effect(self, pos, long anim, anm=None, long number=1):
         number = min(number, self.nb_bullets_max - len(self.effects))
         for i in xrange(number):
             self.effects.append(Effect(pos, anim, anm or self.etama[1]))
 
 
-    def new_particle(self, pos, anim, amp, number=1, reverse=False, duration=24):
+    cpdef new_particle(self, pos, long anim, long amp, long number=1, bint reverse=False, long duration=24):
         number = min(number, self.nb_bullets_max - len(self.effects))
         for i in xrange(number):
             self.effects.append(Particle(pos, anim, self.etama[1], amp, self, reverse=reverse, duration=duration))
@@ -191,7 +203,7 @@ class Game(object):
         self.msg_runner.run_iteration()
 
 
-    def new_label(self, pos, text):
+    cpdef new_label(self, pos, str text):
         label = Text(pos, self.interface.ascii_anm, text=text, xspacing=8, shift=48)
         label.set_timeout(60, effect='move')
         self.labels.append(label)
@@ -217,7 +229,7 @@ class Game(object):
         return face
 
 
-    def run_iter(self, keystate):
+    def run_iter(self, long keystate):
         # 1. VMs.
         for runner in self.ecl_runners:
             runner.run_iter()
@@ -227,12 +239,11 @@ class Game(object):
             self.modify_difficulty(+100)
 
         # 3. Filter out destroyed enemies
-        self.enemies = [enemy for enemy in self.enemies if not enemy.removed]
-        self.effects = [effect for effect in self.effects if not effect.removed]
-        self.bullets = [bullet for bullet in self.bullets if not bullet.removed]
-        self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if not bullet.removed]
-        self.items = [item for item in self.items if not item.removed]
-
+        self.enemies = filter_removed(self.enemies)
+        self.effects = filter_removed(self.effects)
+        self.bullets = filter_removed(self.bullets)
+        self.cancelled_bullets = filter_removed(self.cancelled_bullets)
+        self.items = filter_removed(self.items)
 
         # 4. Let's play!
         # In the original game, updates are done in prioritized functions called "chains"
@@ -240,7 +251,7 @@ class Game(object):
 
         # Pri 6 is background
         self.update_background() #TODO: Pri unknown
-        if self.msg_runner:
+        if self.msg_runner is not None:
             self.update_msg(keystate) # Pri ?
             keystate &= ~3 # Remove the ability to attack (keystates 1 and 2).
         self.update_players(keystate) # Pri 7
@@ -262,30 +273,37 @@ class Game(object):
         self.frame += 1
 
 
-    def update_background(self):
+    cdef void update_background(self):
         if self.time_stop:
-            return None
+            return
         if self.spellcard_effect is not None:
             self.spellcard_effect.update()
         #TODO: update the actual background here?
 
 
-    def update_enemies(self):
+    cdef void update_enemies(self):
+        cdef Enemy enemy
+
         for enemy in self.enemies:
             enemy.update()
 
 
-    def update_msg(self, keystate):
-        if any((keystate & k and not self.last_keystate & k) for k in (1, 256)):
+    cdef void update_msg(self, long keystate) except *:
+        cdef long k
+
+        if any([(keystate & k and not self.last_keystate & k) for k in (1, 256)]):
             self.msg_runner.skip()
         self.msg_runner.skipping = bool(keystate & 256)
         self.last_keystate = keystate
         self.msg_runner.run_iteration()
 
 
-    def update_players(self, keystate):
+    cdef void update_players(self, long keystate) except *:
+        cdef Bullet bullet
+        cdef Player player
+
         if self.time_stop:
-            return None
+            return
 
         for bullet in self.players_bullets:
             bullet.update()
@@ -299,26 +317,34 @@ class Game(object):
         #TODO: give extra lives to the player
 
 
-    def update_effects(self):
+    cdef void update_effects(self):
+        cdef Element effect
+
         for effect in self.effects:
             effect.update()
 
 
-    def update_hints(self):
+    cdef void update_hints(self):
         for hint in self.hints:
             if hint['Count'] == self.frame and hint['Base'] == 'start':
                 self.new_hint(hint)
 
 
-    def update_faces(self):
+    cdef void update_faces(self):
         for face in self.faces:
             if face:
                 face.update()
 
 
-    def update_bullets(self):
+    cdef void update_bullets(self):
+        cdef Player player
+        cdef Bullet bullet
+        cdef Item item
+        cdef double bhalf_width, bhalf_height
+
         if self.time_stop:
-            return None
+            return
+
         for bullet in self.cancelled_bullets:
             bullet.update()
 
@@ -326,32 +352,34 @@ class Game(object):
             bullet.update()
 
         for laser in self.players_lasers:
-            if laser:
+            if laser is not None:
                 laser.update()
 
         for item in self.items:
             item.update()
 
         for player in self.players:
-            if not player.state.touchable:
+            player_state = player.state
+
+            if not player_state.touchable:
                 continue
 
-            px, py = player.state.x, player.state.y
-            phalf_size = player.sht.hitbox
+            px, py = player_state.x, player_state.y
+            phalf_size = <double>player.sht.hitbox
             px1, px2 = px - phalf_size, px + phalf_size
             py1, py2 = py - phalf_size, py + phalf_size
 
-            ghalf_size = player.sht.graze_hitbox
+            ghalf_size = <double>player.sht.graze_hitbox
             gx1, gx2 = px - ghalf_size, px + ghalf_size
             gy1, gy2 = py - ghalf_size, py + ghalf_size
 
             for laser in self.lasers:
                 if laser.check_collision((px, py)):
-                    if player.state.invulnerable_time == 0:
+                    if player_state.invulnerable_time == 0:
                         player.collide()
                 elif laser.check_grazing((px, py)):
-                    player.state.graze += 1 #TODO
-                    player.state.score += 500 #TODO
+                    player_state.graze += 1 #TODO
+                    player_state.score += 500 #TODO
                     player.play_sound('graze')
                     self.modify_difficulty(+6) #TODO
                     self.new_particle((px, py), 9, 192) #TODO
@@ -368,14 +396,14 @@ class Game(object):
                 if not (bx2 < px1 or bx1 > px2
                         or by2 < py1 or by1 > py2):
                     bullet.collide()
-                    if player.state.invulnerable_time == 0:
+                    if player_state.invulnerable_time == 0:
                         player.collide()
 
                 elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2
                         or by2 < gy1 or by1 > gy2):
                     bullet.grazed = True
-                    player.state.graze += 1
-                    player.state.score += 500 # found experimentally
+                    player_state.graze += 1
+                    player_state.score += 500 # found experimentally
                     player.play_sound('graze')
                     self.modify_difficulty(+6)
                     self.new_particle((px, py), 9, 192) #TODO: find the real size and range.
@@ -383,10 +411,10 @@ class Game(object):
                     # 12 pixels of the player, in the axis of the “collision”.
 
             #TODO: is it the right place?
-            if py < 128 and player.state.power >= 128: #TODO: check py.
+            if py < 128 and player_state.power >= 128: #TODO: check py.
                 self.autocollect(player)
 
-            ihalf_size = player.sht.item_hitbox
+            ihalf_size = <double>player.sht.item_hitbox
             for item in self.items:
                 bx, by = item.x, item.y
                 bx1, bx2 = bx - ihalf_size, bx + ihalf_size
@@ -397,7 +425,12 @@ class Game(object):
                     item.on_collect(player)
 
 
-    def cleanup(self):
+    cdef void cleanup(self):
+        cdef Enemy enemy
+        cdef Bullet bullet
+        cdef Item item
+        cdef long i
+
         # Filter out non-visible enemies
         for enemy in self.enemies:
             if enemy.is_visible(self.width, self.height):
@@ -406,25 +439,41 @@ class Game(object):
                 # Filter out-of-screen enemy
                 enemy.removed = True
 
-        self.enemies = [enemy for enemy in self.enemies if not enemy.removed]
+        self.enemies = filter_removed(self.enemies)
 
-        # Update cancelled bullets
-        self.cancelled_bullets = [b for b in chain(self.cancelled_bullets,
-                                                   self.bullets,
-                                                   self.players_bullets)
-                                    if b.state == CANCELLED and not b.removed]
         # Filter out-of-scren bullets
-        self.bullets = [bullet for bullet in self.bullets
-                            if not bullet.removed and bullet.state != CANCELLED]
-        self.players_bullets = [bullet for bullet in self.players_bullets
-                            if not bullet.removed and bullet.state != CANCELLED]
-        for i, laser in enumerate(self.players_lasers):
-            if laser and laser.removed:
-                self.players_lasers[i] = None
-        self.effects = [effect for effect in self.effects if not effect.removed]
+        cancelled_bullets = []
+        bullets = []
+        players_bullets = []
+
+        for bullet in self.cancelled_bullets:
+            if bullet.state == CANCELLED and not bullet.removed:
+                cancelled_bullets.append(bullet)
+
+        for bullet in self.bullets:
+            if not bullet.removed:
+                if bullet.state == CANCELLED:
+                    cancelled_bullets.append(bullet)
+                else:
+                    bullets.append(bullet)
+
+        for bullet in self.players_bullets:
+            if not bullet.removed:
+                if bullet.state == CANCELLED:
+                    cancelled_bullets.append(bullet)
+                else:
+                    players_bullets.append(bullet)
+
+        self.cancelled_bullets = cancelled_bullets
+        self.bullets = bullets
+        self.players_bullets = players_bullets
 
         # Filter “timed-out” lasers
-        self.lasers = [laser for laser in self.lasers if not laser.removed]
+        for i, laser in enumerate(self.players_lasers):
+            if laser is not None and laser.removed:
+                self.players_lasers[i] = None
+
+        self.lasers = filter_removed(self.lasers)
 
         # Filter out-of-scren items
         items = []
@@ -435,9 +484,18 @@ class Game(object):
                 self.modify_difficulty(-3)
         self.items = items
 
-        self.labels = [label for label in self.labels if not label.removed]
+        self.effects = filter_removed(self.effects)
+        self.labels = filter_removed(self.labels)
 
         # Disable boss mode if it is dead/it has timeout
         if self.boss and self.boss._enemy.removed:
             self.boss = None
 
+cdef list filter_removed(list elements):
+    cdef Element element
+
+    filtered = []
+    for element in elements:
+        if not element.removed:
+            filtered.append(element)
+    return filtered