Mercurial > mamba
view mamba/snake.py @ 340:5456db312f7f
Curated and uncurated levels.
author | Simon Cross <hodgestar@gmail.com> |
---|---|
date | Fri, 16 Sep 2011 22:20:48 +0200 |
parents | cd544a311f11 |
children | 80ba03bec6ad |
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.sound import load_sound, play_sound from mamba import mutators INITIAL_SEGMENT_COUNT = 4 class Snake(object): def __init__(self, tile_pos, orientation): load_sound('crash', 'crash.ogg') 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 self._orientation_changes = [] 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._pop_orientation_queue() 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 send_new_direction(self, orientation): self._orientation_changes.append(orientation) # Cap queue length: self._orientation_changes = self._orientation_changes[:3] def _pop_orientation_queue(self): while self._orientation_changes: orientation = self._orientation_changes.pop(0) if ((0 == orientation[0] == self.head.orientation[0]) or (0 == orientation[1] == self.head.orientation[1])): continue self.set_orientation(orientation) break def set_orientation(self, orientation): self.orientation = orientation self.head.set_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('You hit yourself!') def crash(self, reason='You hit something'): play_sound('crash') SnakeDiedEvent.post(reason) def exit_level(self): LevelCompletedEvent.post() def mutate(self, mutation): self.mutation = mutation self.tail.show_mutation(mutation) def can_swim(self): return self.mutation == 'amphibious' def flame_retardant(self): return self.mutation == 'flame-retardant' def adjust_speed(self, delta): self.speed += delta self.speed = max(self.speed, 60) self.speed = min(self.speed, 180) 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.set_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): INDICATORS = { "flame-retardant": mutators.Overlay("snake/snake-tail-fire-r"), "amphibious": mutators.Overlay("snake/snake-tail-puddle-r"), } def __init__(self, tile_pos): super(Tail, self).__init__("snake-tail-r", tile_pos) def show_mutation(self, mutation): self._detail_mutators = (self.INDICATORS[mutation],) self.make_images() self.update_image()