# HG changeset patch # User Emmanuel Gil Peyrot # Date 1376713793 -7200 # Node ID 78e1c3864e730737d2217a45028efe4876f0a366 # Parent 3a33ed7f3b85e7a4304af323fb4c21cc9236d02f Make pytouhou.game.game an extension type. diff --git a/pytouhou/game/bullet.pxd b/pytouhou/game/bullet.pxd --- a/pytouhou/game/bullet.pxd +++ b/pytouhou/game/bullet.pxd @@ -1,15 +1,17 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.utils.interpolator cimport Interpolator cdef class Bullet(Element): cdef public unsigned long state, flags, frame, sprite_idx_offset, damage cdef public double dx, dy, angle, speed cdef public bint player_bullet, was_visible, grazed - cdef public object target, _game, _bullet_type + cdef public object target, _bullet_type cdef public tuple hitbox cdef public list attributes cdef Interpolator speed_interpolator + cdef Game _game cdef bint is_visible(self, unsigned int screen_width, unsigned int screen_height) cpdef set_anim(self, sprite_idx_offset=*) diff --git a/pytouhou/game/bullet.pyx b/pytouhou/game/bullet.pyx --- a/pytouhou/game/bullet.pyx +++ b/pytouhou/game/bullet.pyx @@ -22,7 +22,7 @@ LAUNCHING, LAUNCHED, CANCELLED = range(3 cdef class Bullet(Element): def __init__(self, pos, bullet_type, unsigned long sprite_idx_offset, - double angle, double speed, attributes, unsigned long flags, target, game, + double angle, double speed, attributes, unsigned long flags, target, Game game, bint player_bullet=False, unsigned long damage=0, hitbox=None): cdef double launch_mult diff --git a/pytouhou/game/effect.pyx b/pytouhou/game/effect.pyx --- a/pytouhou/game/effect.pyx +++ b/pytouhou/game/effect.pyx @@ -15,6 +15,8 @@ from pytouhou.game.sprite cimport Sprite from pytouhou.vm.anmrunner import ANMRunner +from pytouhou.game.game cimport Game + cdef class Effect(Element): def __init__(self, pos, index, anm): @@ -35,7 +37,7 @@ cdef class Effect(Element): cdef class Particle(Effect): - def __init__(self, pos, index, anm, long amp, game, bint reverse=False, long duration=24): + def __init__(self, pos, index, anm, long amp, Game game, bint reverse=False, long duration=24): Effect.__init__(self, pos, index, anm) self.frame = 0 diff --git a/pytouhou/game/enemy.pxd b/pytouhou/game/enemy.pxd --- a/pytouhou/game/enemy.pxd +++ b/pytouhou/game/enemy.pxd @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.utils.interpolator cimport Interpolator cdef class Enemy(Element): @@ -9,8 +10,9 @@ cdef class Enemy(Element): cdef public dict laser_by_id cdef public list aux_anm cdef public Interpolator interpolator, speed_interpolator - cdef public object _game, _anms, process + cdef public object _anms, process + cdef Game _game cdef double[2] hitbox_half_size cpdef play_sound(self, index) @@ -34,7 +36,7 @@ cdef class Enemy(Element): cpdef set_pos(self, x, y, z) cpdef move_to(self, duration, x, y, z, formula) cpdef stop_in(self, duration, formula) - cpdef bint is_visible(self, long screen_width, long screen_height) + cdef bint is_visible(self, long screen_width, long screen_height) cdef void check_collisions(self) cdef void handle_callbacks(self) cpdef update(self) diff --git a/pytouhou/game/enemy.pyx b/pytouhou/game/enemy.pyx --- a/pytouhou/game/enemy.pyx +++ b/pytouhou/game/enemy.pyx @@ -22,7 +22,7 @@ from pytouhou.game.effect cimport Effect cdef class Enemy(Element): - def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, game): + def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, Game game): Element.__init__(self) self._game = game @@ -272,7 +272,7 @@ cdef class Enemy(Element): formula) - cpdef bint is_visible(self, long screen_width, long screen_height): + cdef bint is_visible(self, long screen_width, long screen_height): cdef double tw, th if self.sprite is not None: diff --git a/pytouhou/game/game.pxd b/pytouhou/game/game.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/game/game.pxd @@ -0,0 +1,28 @@ +from pytouhou.game.effect cimport Effect +from pytouhou.game.player cimport Player + +cdef class Game: + cdef public long width, height, nb_bullets_max, stage, rank, difficulty, difficulty_counter, difficulty_min, difficulty_max, frame, last_keystate + cdef public list bullet_types, laser_types, item_types, players, enemies, effects, bullets, lasers, cancelled_bullets, players_bullets, players_lasers, items, labels, faces, texts, hints, bonus_list + cdef public object interface, boss, msg_runner, prng, sfx_player + cdef public double continues + cdef public Effect spellcard_effect + cdef public tuple spellcard + cdef public bint time_stop, msg_wait + cdef public unsigned short deaths_count, next_bonus + + cpdef modify_difficulty(self, long diff) + cpdef drop_bonus(self, double x, double y, long _type, end_pos=*) + cdef void autocollect(self, Player player) + cpdef cancel_bullets(self) + cpdef new_particle(self, pos, long anim, long amp, long number=*, bint reverse=*, long duration=*) + cpdef new_label(self, pos, str text) + cdef void update_background(self) + cdef void update_enemies(self) + cdef void update_msg(self, long keystate) except * + cdef void update_players(self, long keystate) except * + cdef void update_effects(self) + cdef void update_hints(self) + cdef void update_faces(self) + cdef void update_bullets(self) + cdef void cleanup(self) diff --git a/pytouhou/game/game.py b/pytouhou/game/game.pyx rename from pytouhou/game/game.py rename 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 = 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 = 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 = 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 diff --git a/pytouhou/game/item.pxd b/pytouhou/game/item.pxd --- a/pytouhou/game/item.pxd +++ b/pytouhou/game/item.pxd @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.game.player cimport Player from pytouhou.utils.interpolator cimport Interpolator @@ -12,10 +13,10 @@ cdef class Indicator(Element): cdef class Item(Element): cdef public object _item_type - cdef object _game cdef unsigned long frame cdef long _type cdef double angle, speed + cdef Game _game cdef Player player cdef Indicator indicator cdef Interpolator speed_interpolator, pos_interpolator diff --git a/pytouhou/game/item.pyx b/pytouhou/game/item.pyx --- a/pytouhou/game/item.pyx +++ b/pytouhou/game/item.pyx @@ -33,7 +33,7 @@ cdef class Indicator(Element): cdef class Item(Element): - def __init__(self, start_pos, long _type, item_type, game, double angle=pi/2, Player player=None, end_pos=None): + def __init__(self, start_pos, long _type, item_type, Game game, double angle=pi/2, Player player=None, end_pos=None): Element.__init__(self, start_pos) self._game = game diff --git a/pytouhou/game/player.pxd b/pytouhou/game/player.pxd --- a/pytouhou/game/player.pxd +++ b/pytouhou/game/player.pxd @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game cdef class PlayerState: cdef public double x, y @@ -8,8 +9,8 @@ cdef class PlayerState: cdef class Player(Element): cdef public PlayerState state - cdef public object _game cdef public long death_time + cdef public Game _game cdef object anm cdef tuple speeds diff --git a/pytouhou/game/player.pyx b/pytouhou/game/player.pyx --- a/pytouhou/game/player.pyx +++ b/pytouhou/game/player.pyx @@ -55,7 +55,7 @@ cdef class PlayerState: cdef class Player(Element): - def __init__(self, PlayerState state, game, anm): + def __init__(self, PlayerState state, Game game, anm): Element.__init__(self) self._game = game @@ -117,7 +117,7 @@ cdef class Player(Element): bullets = self._game.players_bullets lasers = self._game.players_lasers - nb_bullets_max = self._game.nb_bullets_max + nb_bullets_max = self._game.nb_bullets_max if self.fire_time % 5 == 0: self.play_sound('plst00') @@ -231,7 +231,7 @@ cdef class Player(Element): self.fire_time -= 1 if self.death_time: - time = self._game.frame - self.death_time + time = self._game.frame - self.death_time if time == 6: # too late, you are dead :( self.state.touchable = False if self.state.power > 16: @@ -277,6 +277,10 @@ cdef class Player(Element): self.sprite.fade(26, 96) self.sprite.scale_in(26, 0., 2.5) + #TODO: the next two branches could be happening at the same frame. + elif time == 31: + self._game.cancel_bullets() + elif time == 32: self.state.x = float(self._game.width) / 2. #TODO self.state.y = float(self._game.width) #TODO @@ -296,10 +300,7 @@ cdef class Player(Element): self.sprite.blendfunc = 0 self.sprite.changed = True - if time > 30: - self._game.cancel_bullets() - - if time > 90: # start the bullet hell again + elif time == 91: # start the bullet hell again self.death_time = 0 self.anmrunner.run_frame()