view mamba/snake.py @ 188:d76c069164c0

Much better interaction detection.
author Jeremy Thurgood <firxen@gmail.com>
date Wed, 14 Sep 2011 17:41:26 +0200
parents 3449b51ae944
children d488731886dc
line wrap: on
line source

"""The player snake object."""

from pygame.sprite import Group, spritecollide
from pygame.locals import BLEND_MULT

from mamba.constants import TILE_SIZE
from mamba.sprites import BaseSprite
from mamba.engine import SnakeDiedEvent
from mamba import mutators


class Snake(object):

    UP, DOWN, LEFT, RIGHT = [(0, -1), (0, 1), (-1, 0), (1, 0)]

    def __init__(self, tile_pos, orientation):
        self.segments = self.create_segments(tile_pos, orientation)
        self.segment_group = Group()
        self.segment_group.add(*reversed(self.segments))
        self.set_orientation(orientation)
        self.speed = 120.0  # pixel / s
        self.frac_ds = 0.0
        self.mutation = None

    head = property(fget=lambda self: self.segments[0])
    tail = property(fget=lambda self: self.segments[-1])

    def create_segments(self, tile_pos, orientation):
        tx, ty = tile_pos
        dx, dy = orientation
        segments = []
        for cls in [Head] + [Body] * 10 + [Tail]:
            segments.append(cls((tx, ty)))
        return segments

    def draw(self, surface):
        self.segment_group.draw(surface)

    def update(self, dt, world):
        ds = dt * self.speed + self.frac_ds
        ds, self.frac_ds = divmod(ds, 1)
        ds = int(ds)
        while True:
            tile_state = self.head.get_tile_state()
            shifted, ds = self.head.shift_head(ds)
            if not shifted:
                break
            self.head.set_orientation(self.orientation)
            for segment in self.segments[1:]:
                old_tile_state = segment.get_tile_state()
                segment.shift_tile(tile_state)
                tile_state = old_tile_state

        for segment in self.segments:
            segment.shift_pixels(ds)
            world.interact(segment)

    def set_orientation(self, orientation):
        self.orientation = orientation

    def crash(self):
        SnakeDiedEvent.post()

    def mutate(self, mutation):
        self.mutation = mutation

    def can_swim(self):
        return self.mutation == 'amphibious'


class Segment(BaseSprite):

    GREEN = mutators.Overlay("tiles/common/snake/green.png", BLEND_MULT)
    BLUE = mutators.BLUE
    RED = mutators.RED
    YELLOW = mutators.YELLOW

    _detail_mutators = ()

    is_head = False

    def __init__(self, image_name, tile_pos):
        super(Segment, self).__init__()
        self.set_base_image(image_name)
        self._colour_overlay = self.GREEN
        self.orientation = Snake.UP

        self.make_images()
        self.update_image()
        self.set_tile_pos(tile_pos)
        self.on_tiles = []

    def filter_collisions(self, group):
        collide = []
        tiles = spritecollide(self, group, False)
        for tile in tiles:
            if tile not in self.on_tiles:
                collide.append(tile)
        self.on_tiles = tiles
        return collide

    def set_base_image(self, image_name):
        self._base_image = "/".join(["snake", image_name])

    def make_images(self):
        self._images = {}
        for orientation, muts in [
            (Snake.RIGHT, (mutators.RIGHT,)),
            (Snake.LEFT, (mutators.LEFT,)),
            (Snake.UP, (mutators.UP,)),
            (Snake.DOWN, (mutators.DOWN,)),
            ]:
            all_muts = (self._colour_overlay,) + self._detail_mutators + muts
            self._images[orientation] = self.load_image(self._base_image,
                    all_muts)

    def update_image(self):
        self.image = self._images[self.orientation]

    def set_orientation(self, orientation):
        self.orientation = orientation
        self.update_image()

    def set_colour(self, colour_overlay):
        self._colour_overlay = colour_overlay
        self.make_images()
        self.update_image()

    def get_tile_state(self):
        return self.tile_pos, self.orientation

    def shift_tile(self, tile_state):
        """Shift this segment to the tile the other one was on.

        Also reset the position to be the center of the tile.
        """
        tile_pos, orientation = tile_state
        self.set_tile_pos(tile_pos)
        self.orientation = orientation

    def shift_pixels(self, distance):
        """Shift this segment a number of pixels."""
        dx, dy = self.orientation
        dx, dy = distance * dx, distance * dy
        self.rect = self.rect.move(dx, dy)


class Head(Segment):
    CLOSED = "snake-head"
    OPEN = "snake-head-mouth-open-r"
    EYE = mutators.Overlay("tiles/common/snake/snake-head-eye-r.png")
    TONGUE = mutators.Overlay("tiles/common/snake/snake-head-tongue-r.png")

    is_head = True

    def __init__(self, tile_pos):
        self._detail_mutators = (self.EYE,)
        super(Head, self).__init__(self.CLOSED, tile_pos)

    def mouth_open(self):
        self.set_base_image(self.OPEN)
        self.make_images()
        self.update_image()

    def mouth_closed(self):
        self.set_base_image(self.CLOSED)
        self.make_images()
        self.update_image()

    def tongue_out(self):
        self._detail_mutators = (self.EYE, self.TONGUE)
        self.make_images()
        self.update_image()

    def tongue_in(self):
        self._detail_mutators = (self.EYE,)
        self.make_images()
        self.update_image()

    def shift_head(self, ds):
        """Shift the head a number of pixels in the direction of it
        orientation.
        """
        dx, dy = self.orientation
        TX, TY = TILE_SIZE
        rx, ry = self.rect.left, self.rect.top
        tx, ty = self.tile_pos
        # WARNING: Tri-state logic ahead
        # (Tri-state logic is the mamba's natural habitat)
        if dx != 0:
            newdx = rx + ds * dx
            newtx = newdx / TX
            if newtx > tx:
                self.set_tile_pos((tx + 1, ty))
                ds = newdx - self.rect.left
                return True, ds
            elif newtx < tx - 1:
                self.set_tile_pos((tx - 1, ty))
                ds = self.rect.left - newdx
                return True, ds
        else:
            newdy = ry + ds * dy
            newty = newdy / TY
            if newty > ty:
                self.set_tile_pos((tx, ty + 1))
                ds = newdy - self.rect.top
                return True, ds
            elif newty < ty - 1:
                self.set_tile_pos((tx, ty - 1))
                ds = self.rect.top - newdy
                return True, ds
        return False, ds


class Body(Segment):
    def __init__(self, tile_pos):
        super(Body, self).__init__("snake-body", tile_pos)


class Tail(Segment):
    def __init__(self, tile_pos):
        super(Tail, self).__init__("snake-tail", tile_pos)