view mamba/snake.py @ 255:59166ae6e864

fire works
author Adrianna Pińska <adrianna.pinska@gmail.com>
date Thu, 15 Sep 2011 01:32:50 +0200
parents 8198492745b1
children d0ed88799c44
line wrap: on
line source

"""The player snake object."""

import random

from pygame.sprite import Group, spritecollide

from mamba.constants import TILE_SIZE, UP, DOWN, LEFT, RIGHT
from mamba.sprites import BaseSprite, tile_sizify
from mamba.engine import SnakeDiedEvent, LevelCompletedEvent
from mamba import mutators


INITIAL_SEGMENT_COUNT = 4


class Snake(object):

    def __init__(self, tile_pos, orientation):
        self.segments = self.create_segments(tile_pos, orientation)
        self.pending_segments = []  # segments waiting to be added
        self.segment_group = Group()
        self.segment_group.add(*reversed(self.segments))
        self.orientation = orientation
        self.speed = 120.0  # pixel / s
        self.frac_ds = 0.0
        self.mutation = None
        self.coiled = True

    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] * INITIAL_SEGMENT_COUNT + [Tail]:
            segments.append(cls((tx, ty)))
        return segments

    def add_segment(self, segment=None):
        if segment is None:
            segment = Body((0, 0))
            segment.set_colour(self.head.colour)
        self.pending_segments.append(segment)

    def remove_segment(self, segment=None):
        if segment is None:
            if len(self.segments) < 3:
                return
            segment = self.segments[-2]
        try:
            idx = self.segments.index(segment)
        except IndexError:
            return
        self.shiftup_segments(idx, segment.get_tile_state())
        del self.segments[idx]
        segment.kill()

    def shiftup_segments(self, idx, state):
        for segment in self.segments[idx:]:
            next_state = segment.get_tile_state()
            segment.shift_tile(state)
            state = next_state

    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 shifted:
                self.coiled = False
                self.head.shifted_tile()
                self.head.set_orientation(self.orientation)
            else:
                break
            if self.pending_segments:
                new_segment = self.pending_segments.pop(0)
                self.segments.insert(1, new_segment)
                self.segment_group.add(new_segment)
                new_segment.shift_tile(tile_state)
                for segment in self.segments[2:]:
                    segment.shift_tile(segment.get_tile_state())
            else:
                self.shiftup_segments(1, tile_state)

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

    def set_orientation(self, orientation):
        # Don't allow the snake to go back on itself
        # immediately. More creative self-inflicted
        # deaths are allowed though.
        if (abs(self.head.orientation[0] - orientation[0]) < 2
                and abs(self.head.orientation[1] - orientation[1]) < 2):
            self.orientation = orientation

    def check_self_crash(self):
        if self.coiled:
            return
        collides = spritecollide(self.head, self.segment_group, False)
        if [s for s in collides if s not in self.segments[:2]]:
            self.crash()

    def crash(self):
        SnakeDiedEvent.post()

    def exit_level(self):
        LevelCompletedEvent.post()

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

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

    def flame_retardant(self):
        return self.mutation == 'flame-retardant'


class Segment(BaseSprite):

    GREEN = mutators.SNAKE_GREEN
    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 = self.GREEN
        self.orientation = 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 [
            (RIGHT, (mutators.RIGHT,)),
            (LEFT, (mutators.LEFT,)),
            (UP, (mutators.UP,)),
            (DOWN, (mutators.DOWN,)),
            ]:
            all_muts = (self.colour,) + 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 = colour_overlay
        self.make_images()
        self.update_image()

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

    def get_distance(self):
        rx, ry = self.rect.topleft
        x, y = tile_sizify(self.tile_pos)
        return max(abs(rx - x), abs(ry - y))

    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_tile_and_pixels(self, tile_state):
        ds = self.get_distance()
        self.shift_tile(tile_state)
        self.shift_pixels(ds)

    def shifted_tile(self):
        pass

    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)

    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 Head(Segment):
    CLOSED = "snake-head"
    OPEN = "snake-head-mouth-open-r"
    EYE = mutators.Overlay("snake/snake-head-eye-r")
    TONGUE = mutators.Overlay("snake/snake-head-tongue-r")

    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_close(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 shifted_tile(self):
        if random.random() < 0.02:
            self.mouth_open()
            self.tongue_out()
        else:
            self.mouth_close()
            self.tongue_in()


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)