Beispiel #1
0
    def __init__(self, resource_dirs=None, **params):
        params['width'] = 450
        params['height'] = 300
        params['focusable'] = False
        super(GameWindow, self).__init__(**params)
        self.surface = pygame.Surface((450, 300))
        self.surface.fill((0, 0, 0))
        self.grid = False
        self.shadows = pygame.sprite.RenderUpdates()
        self.hilights = pygame.sprite.RenderUpdates()
        self.sprites = SortedUpdates()
        self.overlays = pygame.sprite.RenderUpdates()
        self.overlays_sprites = {}
        self.animated_background = pygame.sprite.RenderUpdates()
        self.animated_background_sprites = {}
        self._tileset = "tileset"
        self._sprite_cache = TileCache(32, 32, resource_dirs=resource_dirs)
        self.map_cache = TileCache(MAP_TILE_WIDTH, MAP_TILE_HEIGHT,
                                   resource_dirs=resource_dirs)
        # Separate cache for shadows, as convert_alpha() ruins their
        # transparency.
        self._shadow_cache = TileCache(32, 32, resource_dirs=resource_dirs,
                                       convert_alpha=False)
        self._time_sprite = None
        self._clones = {}
        self._gates = {}
        self._crates = {}
        self.active_animation = False
        self._gevent_seq = []
        self._gevent_queue = Queue.Queue()
        self.level = None
        self._event_handler = {
            # play
            'move-up': functools.partial(self._move, Direction.NORTH),
            'move-down': functools.partial(self._move, Direction.SOUTH),
            'move-left': functools.partial(self._move, Direction.WEST),
            'move-right': functools.partial(self._move, Direction.EAST),
            'add-player-clone': self._player_clone,
            'remove-player-clone': self._player_clone,
            'field-activated': self._field_state_change,
            'field-deactivated': self._field_state_change,
            'time-paradox': self._time_paradox,
            'time-jump': self._time_jump,
            'game-complete': self._game_complete,
            'end-of-turn': self._end_of_turn,
            'goal-obtained': lambda *x: self.goal.kill(),
            'goal-lost': lambda *x: self.animated_background.add(self.goal),
            'jump-moveable': self._jump_moveable,

            # edit
            'new-map': self._new_map,
            'replace-tile': self._replace_tile,
            'remove-special-field': self._remove_special_field,
            'add-crate': self._add_remove_crate,
            'remove-crate': self._add_remove_crate,
        }
Beispiel #2
0
    def _new_map(self, *args):
        self.shadows = pygame.sprite.RenderUpdates()
        self.sprites = SortedUpdates()
        self.animated_background = pygame.sprite.RenderUpdates()
        self.overlays_sprites = {}
        self.animated_background_sprites = {}
        self._clones = {}
        self._gates = {}
        self._crates = {}

        level = self.level

        # Render the level map
        self._make_background()

        for field in level.iter_fields():
            # Crates looks best in 32x32, gates and buttons in 24x16
            #   - if its "on top of" a field 32x32 usually looks best.
            #   - if it is (like) a field, 24x16 is usually better
            # - use sprite_cache and map_cache accordingly.
            crate = level.get_crate_at(field.position)
            if crate:
                self._add_crate(crate)
            self._init_field(field)

        try:
            iter_clones = level.iter_clones
        except AttributeError:
            # Not a playable map, no clones to place
            iter_clones = None

        if iter_clones:
            for clone in iter_clones():
                self._new_clone(clone)

        self.repaint()
Beispiel #3
0
class GameWindow(gui.Widget):
    """The main game object."""

    def __init__(self, resource_dirs=None, **params):
        params['width'] = 450
        params['height'] = 300
        params['focusable'] = False
        super(GameWindow, self).__init__(**params)
        self.surface = pygame.Surface((450, 300))
        self.surface.fill((0, 0, 0))
        self.grid = False
        self.shadows = pygame.sprite.RenderUpdates()
        self.hilights = pygame.sprite.RenderUpdates()
        self.sprites = SortedUpdates()
        self.overlays = pygame.sprite.RenderUpdates()
        self.overlays_sprites = {}
        self.animated_background = pygame.sprite.RenderUpdates()
        self.animated_background_sprites = {}
        self._tileset = "tileset"
        self._sprite_cache = TileCache(32, 32, resource_dirs=resource_dirs)
        self.map_cache = TileCache(MAP_TILE_WIDTH, MAP_TILE_HEIGHT,
                                   resource_dirs=resource_dirs)
        # Separate cache for shadows, as convert_alpha() ruins their
        # transparency.
        self._shadow_cache = TileCache(32, 32, resource_dirs=resource_dirs,
                                       convert_alpha=False)
        self._time_sprite = None
        self._clones = {}
        self._gates = {}
        self._crates = {}
        self.active_animation = False
        self._gevent_seq = []
        self._gevent_queue = Queue.Queue()
        self.level = None
        self._event_handler = {
            # play
            'move-up': functools.partial(self._move, Direction.NORTH),
            'move-down': functools.partial(self._move, Direction.SOUTH),
            'move-left': functools.partial(self._move, Direction.WEST),
            'move-right': functools.partial(self._move, Direction.EAST),
            'add-player-clone': self._player_clone,
            'remove-player-clone': self._player_clone,
            'field-activated': self._field_state_change,
            'field-deactivated': self._field_state_change,
            'time-paradox': self._time_paradox,
            'time-jump': self._time_jump,
            'game-complete': self._game_complete,
            'end-of-turn': self._end_of_turn,
            'goal-obtained': lambda *x: self.goal.kill(),
            'goal-lost': lambda *x: self.animated_background.add(self.goal),
            'jump-moveable': self._jump_moveable,

            # edit
            'new-map': self._new_map,
            'replace-tile': self._replace_tile,
            'remove-special-field': self._remove_special_field,
            'add-crate': self._add_remove_crate,
            'remove-crate': self._add_remove_crate,
        }

    @property
    def pending_animation(self):
        return self.active_animation or not self._gevent_queue.empty()

    @property
    def tileset(self):
        return self._tileset

    @tileset.setter
    def tileset(self, ntile):
        self._make_background(tileset=ntile)
        self._tileset = ntile
        self.repaint()

    def use_level(self, level, grid=None):
        """Set the level as the current one."""

        if grid is not None:
            self.grid = grid
        self.level = level
        self._gevent_seq = []
        self._gevent_queue = Queue.Queue()
        level.add_event_listener(self._new_event)
        self._new_map()

    def _new_event(self, e):
        self._gevent_seq.append(e)
        if e.event_type == "end-of-event-sequence":
            # flush
            self._gevent_queue.put(self._gevent_seq)
            self._gevent_seq = []

    def _make_background(self, tileset=None):
        self.overlays = pygame.sprite.RenderUpdates()

        # Render the level map
        if tileset is None:
            tileset = self._tileset

        background, overlays = make_background(self.level,
                                               map_cache=self.map_cache,
                                               tileset=tileset,
                                               grid=self.grid)

        self.surface.fill((0, 0, 0))
        self.surface.blit(background, (0,0))

        self._add_overlay(overlays)

    def _new_map(self, *args):
        self.shadows = pygame.sprite.RenderUpdates()
        self.sprites = SortedUpdates()
        self.animated_background = pygame.sprite.RenderUpdates()
        self.overlays_sprites = {}
        self.animated_background_sprites = {}
        self._clones = {}
        self._gates = {}
        self._crates = {}

        level = self.level

        # Render the level map
        self._make_background()

        for field in level.iter_fields():
            # Crates looks best in 32x32, gates and buttons in 24x16
            #   - if its "on top of" a field 32x32 usually looks best.
            #   - if it is (like) a field, 24x16 is usually better
            # - use sprite_cache and map_cache accordingly.
            crate = level.get_crate_at(field.position)
            if crate:
                self._add_crate(crate)
            self._init_field(field)

        try:
            iter_clones = level.iter_clones
        except AttributeError:
            # Not a playable map, no clones to place
            iter_clones = None

        if iter_clones:
            for clone in iter_clones():
                self._new_clone(clone)

        self.repaint()

    def _add_crate(self, crate):
        c_sprite = MoveableSprite(crate.position, self._sprite_cache['crate'], c_depth=1)
        self._crates[crate] = c_sprite
        self.sprites.add(c_sprite)

    def _add_remove_crate(self, evt):
        if evt.event_type == 'add-crate':
            self._add_crate(evt.source)
        else:
            _kill_sprite(self._crates, evt.source)

    def _init_field(self, field):
        ani_bg = None
        if field.symbol == '-' or field.symbol == '_':
            ani_bg = Sprite(field.position, self.map_cache['campfiregate'])
            self._gates[field.position] = ani_bg
            if field.symbol == '-':
                ani_bg.state = 1
        if field.symbol == 'b':
            ani_bg = Sprite(field.position, self.map_cache['stonebutton'])
        if field.symbol == 'o':
            ani_bg = Sprite(field.position, self.map_cache['onetimebutton'])
        if field.symbol == 'p':
            ani_bg = Sprite(field.position, self.map_cache['onetimepassage'])
        if field.symbol == 'P':
            ani_bg = Sprite(field.position, self.map_cache['pallet'])
        if field.symbol == 'S':
            ani_bg = Sprite(field.position, self.map_cache['timemachine'])
            if self._time_sprite is None:
                self._time_sprite = TimeSprite(field.position,
                                               self._sprite_cache["clock"],
                                               c_depth =1)
            else:
                self._time_sprite.pos = field.position
        if field.symbol == 'G':
            ani_bg = Sprite(field.position, self.map_cache['coingoal'])
            self.goal = ani_bg
        if ani_bg:
            self.animated_background.add(ani_bg)
            self.animated_background_sprites[field.position] = ani_bg
        return

    def _add_overlay(self, overlays):
        # Add the overlays for the level map
        for (x, y), image in overlays.iteritems():
            overlay = pygame.sprite.Sprite(self.overlays)
            overlay.image = image.subsurface(0, 0, MAP_TILE_WIDTH, MAP_TILE_HEIGHT/2)
            overlay.rect = image.get_rect().move(Position(x*MAP_TILE_WIDTH,
                                                          y * MAP_TILE_HEIGHT - MAP_TILE_HEIGHT/2))
            self.overlays_sprites[Position(x,y)] = overlay

    def _remove_special_field(self, evt):
        f = evt.source
        _kill_sprite(self.animated_background_sprites, f.position)

    def _replace_tile(self, evt):
        f = evt.source

        # Kill the old animations on this field (if any)
        _kill_sprite(self._gates, f.position)
        _kill_sprite(self.animated_background_sprites, f.position)
        _kill_sprite(self.overlays_sprites, f.position)

        # Kill "nearby" overlays - they should only be "south", but mah
        for d in (Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST):
            dpos = f.position.dir_pos(d)
            _kill_sprite(self.overlays_sprites, dpos)

        # FIXME: remove old overlay
        overlays = update_background(self.map_cache[self._tileset], self.surface, self.level, f,
                                     fixup=True, grid=self.grid)
        self._add_overlay(overlays)
        ani_bg = None

        self._init_field(f)
        self.repaint()

    def _move(self, d, event):
        """Start walking in specified direction."""

        actor = None
        if event.source in self._crates:
            actor = self._crates[event.source]
        else:
            actor = self._clones[event.source][0]

        if d == Direction.NO_ACT or not event.success:
            actor.animation = actor.do_nothing_animation()
            return
        pos = actor.pos
        target = pos.dir_pos(d)
        actor.direction = d
        actor.animation = actor.walk_animation()
        self.repaint()

    def _field_state_change(self, event):
        src_pos = event.source.position
        # determine the new state from the event name.  While it may be tempting
        # to use the state of the source, the problem is that we may be processing
        # an "old" event from our queue.  Thus the source may have already changed
        # state again.  So to make sure the animation looks correct, use the state
        # at the time of the event and not the source's current state.
        nstate = 0
        if event.event_type == "field-activated":
            nstate = 1
        if src_pos in self._gates:
            self._gates[src_pos].state = nstate
        if event.source.symbol == "b" or event.source.symbol == "o" or event.source.symbol == "p":
            b = self.animated_background_sprites[src_pos]
            if b:
                b.state = nstate

    def _new_clone(self, clone):
        sprite = PlayerSprite(clone, self._sprite_cache['player'])
        # Ensure the sprite is created at the start location.
        # - The player may have enqueued a lot of actions, so the model puts
        #   the clone at its current position.  However, the animation just
        #   reached the time jump, so the player should see the clone at
        #   the start location.  As the animation catches up, the clone will
        #   eventually reach its intented location.
        sprite.pos = self.level.start_location.position
        shadow = Shadow(sprite, self._shadow_cache["shadow"][0][0])
        self._clones[clone] = (sprite, shadow)
        self.sprites.add(sprite)
        self.shadows.add(shadow)

    def _player_clone(self, event):
        if event.event_type == "add-player-clone":
            self._new_clone(event.source)
        elif event.source in self._clones:
            csprite, shadow = self._clones[event.source]
            del self._clones[event.source]
            csprite.kill()
            shadow.kill()

        self.repaint()

    def _time_jump(self, *args):
        self._time_sprite.animation = self._time_sprite.time_jump_animation()
        self.sprites.add(self._time_sprite)

    def process_game_events(self):
        if self.active_animation or self._gevent_queue.empty():
            # if the game event queue is empty just skip the code below.
            return
        try:
            seq = self._gevent_queue.get_nowait()
            print "Event seq [%s]" % (", ".join(x.event_type for x in seq))
            for e in seq:
                if e.event_type not in self._event_handler:
                    continue
                self._event_handler[e.event_type](e)
            self.active_animation = True
        except Queue.Empty:
            pass # expected

    def _time_paradox(self, e):
        self.repaint()

    def _jump_moveable(self, event):
        actor = None
        if event.source in self._crates:
            actor = self._crates[event.source]
        elif event.source in self._clones:
            actor = self._clones[event.source][0]
        actor.pos = event.source.position
        self.repaint()

    def _end_of_turn(self, _):
        self.repaint()

    def _game_complete(self, _):
        self.repaint()

    def make_hilight(self, lpos, color="yellow"):
        hilight = pygame.Surface((MAP_TILE_WIDTH, MAP_TILE_HEIGHT))
        hilight.fill(pygame.Color(color))
        hilight.set_alpha(0x80)
        s = Sprite(lpos, ((hilight,),))
        self.hilights.add(s)
        return s


    def paint(self, s):
        # Draw the whole screen initially
        s.blit(self.surface, (0, 0))
        if self.level:
            self.update(s)

    def update(self, s):
        if not self.level:
            return

        # Don't clear shadows and overlays, only sprites.
        self.sprites.clear(s, self.surface)
        self.animated_background.clear(s, self.surface)
        self.hilights.clear(s, self.surface)

        self.sprites.update()
        self.animated_background.update()
        has_animation = lambda x: self._clones[x][0].animation is not None
        active_animation = any(itertools.ifilter(has_animation,
                                                 self._clones))
        if self._time_sprite and self._time_sprite.animation is not None:
            active_animation = True
        if not active_animation:
            self.active_animation = False

        self.shadows.update()

        # Draw the "animated" background (gates).  These may be dirty even
        # if no actor approcated it (eg. via buttons)
        dirty = self.animated_background.draw(s)

        # Don't add shadows to dirty rectangles, as they already fit inside
        # sprite rectangles.  But draw them after the animated background
        # (or the background would hide them)
        self.shadows.draw(s)

        # Draw hilights on top of backgrounds...
        dirty.extend(self.hilights.draw(s))

        # Draw actors (and crates) on top of everything
        dirty.extend(self.sprites.draw(s))


        # Don't add ovelays to dirty rectangles, only the places where
        # sprites are need to be updated, and those are already dirty.
        self.overlays.draw(s)

        return dirty