Esempio n. 1
0
class Bonus(retro.Sprite):
    BONUS1 = types.SimpleNamespace(
        id=0,
        img=retro.Image(assets("bonus1.png")),
        color=(255, 184, 151),
        value=10,
        offset=(0, 0),
    )
    BONUS2 = types.SimpleNamespace(
        id=1,
        img=retro.Image(assets("bonus2.png")),
        color=(255, 136, 84),
        value=50,
        offset=(-6, -6),
    )

    def __new__(cls, pos, color):
        if color == cls.BONUS1.color: bonus = cls.BONUS1
        elif color == cls.BONUS2.color: bonus = cls.BONUS2
        else: return None

        self = retro.Sprite.__new__(cls)
        retro.Sprite.__init__(self, bonus.img)
        self.rect.topleft = numpy.add(pos, bonus.offset).tolist()
        self.id = bonus.id
        self.value = bonus.value

        return self

    def __init__(self, pos, color):
        pass
Esempio n. 2
0
class Maze(retro.Sprite):
    IMG = retro.Image(assets('maze.png'))
    RANGEW = range(0, IMG.rect().w, 16)
    RANGEH = range(0, IMG.rect().h, 16)
    WALLS = None
    BONUSES = None

    # Defer constants initialization to avoid circular dependency.
    def __new__(cls):
        if not cls.WALLS: cls.WALLS = Walls()
        if not cls.BONUSES: cls.BONUSES = Bonuses()
        return retro.Sprite.__new__(cls)

    def __init__(self):
        retro.Sprite.__init__(self, self.IMG.copy())
        self.bonuses = self.BONUSES.copy()
        self.walls = self.WALLS

    # Checks whether the (`i, `j`) coordinates are inside the maze.
    @classmethod
    def inside(cls, i, j):
        return (i in range(len(cls.RANGEW)) and j in range(len(cls.RANGEH)))

    @classmethod
    def tile_pos(cls, pos):
        return (pos[0] // 16, pos[1] // 16)

    # Returns a Maze iterator.
    # If `window` is specified, iterates over the neighborhood centered on
    # `window.center` and of reach `window.reach`.
    @classmethod
    def iterator(self, transpose=False, window=None):
        def windowit(range, i):
            minv = window.center[i] - window.reach
            maxv = window.center[i] + window.reach + 1
            if minv < 0:
                maxv += abs(minv)
                minv = 0
            if maxv >= len(range):
                minv -= maxv - len(range)
                maxv = len(range)
            return itertools.islice(enumerate(range), minv, maxv)

        it1, it2 = enumerate(self.RANGEW), enumerate(self.RANGEH)
        if window:
            it1, it2 = windowit(self.RANGEW, 0), windowit(self.RANGEH, 1)
        if transpose: it1, it2 = it2, it1
        for ix, jy in itertools.product(it1, it2):
            if transpose: ix, jy = jy, ix
            yield ix, jy

    def draw(self, target):
        retro.Sprite.draw(self, target)
        self.bonuses.draw(target)
Esempio n. 3
0
class Ghost(Entity):
    IMG = retro.Image(assets("ghost.png"))
    BONUS = 200

    def __init__(self, pos):
        Entity.__init__(
            self,
            sprite=retro.Sprite(
                image=self.IMG,
                animations=retro.Animations(
                    frame_size=(32, 32),
                    period=3,
                    WALK_L=([0], 0),
                    WALK_U=([0], 0),
                    WALK_R=([0], 0),
                    WALK_D=([0], 0),
                    FEAR_L=([1], 0),
                    FEAR_U=([1], 0),
                    FEAR_R=([1], 0),
                    FEAR_D=([1], 0),
                ),
            ),
            pos=pos,
            speed=2,
            curdir=[0, 0],
            nxtdir=random.choice(([-1, 0], [1, 0])),
        )

        self.state = State(self)

    @property
    def bounding_rect(self):
        return Entity.bounding_rect(self, 12)

    def next_dir(self):
        dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        opdir = numpy.negative(self.curdir).tolist()
        if opdir in dirs: dirs.remove(opdir)
        self.nxtdir = random.choice(dirs)

    def collide_maze(self, maze):
        if not self.nxtcol:
            self.curdir = self.nxtdir
        elif self.curcol is None:
            self.curdir = numpy.negative(self.curdir).tolist()

        elif self.curcol:
            self.next_dir()
            return True

        return False

    def kill(self):
        for g in self.groups:
            g.notify_kill()
        Entity.kill(self)

    def update(self, maze, player):
        self.state.update(player)
        if (self.curdir == self.nxtdir):
            self.next_dir()
        Entity.update(self, maze)
Esempio n. 4
0
class Player(Entity):
    IMG = retro.Image(assets("pacman.png"))

    def __init__(self, pos):
        Entity.__init__(
            self,
            sprite=retro.Sprite(
                image=self.IMG,
                animations=retro.Animations(
                    frame_size=(32, 32),
                    period=3,
                    STOP_L=([2], 0),
                    STOP_U=([3], 0),
                    STOP_R=([0], 0),
                    STOP_D=([4], 0),
                    WALK_L=([2, 1], 0),
                    WALK_U=([3, 1], 0),
                    WALK_R=([0, 1], 0),
                    WALK_D=([4, 1], 0),
                ),
            ),
            pos=pos,
            speed=4,
        )

        self.score = 0
        self.powerup = Powerup()

    @property
    def bounding_rect(self):
        return Entity.bounding_rect(self, 4)

    def collide_maze(self, maze):
        if self.curcol is None:
            if self.curdir[0]:
                self.rect.centerx = abs(self.rect.centerx - maze.rect.w)
            if self.curdir[1]:
                self.rect.centery = abs(self.rect.centery - maze.rect.h)
        elif not self.nxtcol:
            self.set_animation("WALK")
            self.curdir = self.nxtdir
        elif self.curcol:
            self.set_animation("STOP")
            return True
        return False

    def collide_bonus(self, maze):
        for i, j, b in maze.bonuses.neighborhood(self):
            if not b.rect.colliderect(self.bounding_rect): continue
            maze.bonuses.remove(i, j)
            if (b.id == b.BONUS2.id): self.powerup.start()
            self.score += b.value

    # Returns 0 if no collision happened,
    #         1 if the player killed a ghost,
    #        -1 if a ghost killed the player.
    def collide_ghost(self, ghosts):
        g = next((g for g in ghosts
                  if g.bounding_rect.colliderect(self.bounding_rect)), None)
        if not g: return 0

        if g.state != g.state.FEAR: return -1

        g.kill()
        self.score += self.powerup.mul * g.BONUS
        self.powerup.mul *= 2
        return 1

    def update(self, maze):
        self.collide_bonus(maze)
        Entity.update(self, maze)

    def draw_score(self, image):
        font = retro.Font(36)
        txt = retro.Sprite(
            font.render(
                text=f"SCORE: {self.score}",
                color=retro.WHITE,
                bgcolor=retro.BLACK,
            ))
        txt.rect.bottomleft = image.rect().bottomleft
        txt.draw(image)
Esempio n. 5
0
class Bonuses(list):
    IMG = retro.Image(assets('bonuses.png'))

    def __init__(self):
        self.count = 0
        list.__init__(self, [])
        for (i, x), (j, y) in Maze.iterator():
            if j == 0: self.append([])
            pos = (x + 6, y + 6)
            b = Bonus(pos, self.IMG[pos])
            self[i].append(b)
            if b: self.count += 1

    def copy(self):
        new = list.__new__(self.__class__)
        new.count = self.count
        list.__init__(new, [lst.copy() for lst in self])
        return new

    def iterator(self):
        for i, line in enumerate(self):
            for j, b in enumerate(line):
                yield i, j, b

    # Search inside growing neighborhoods until a bonus is found.
    def nearest(self, sprite):
        max_reach = max(len(Maze.RANGEW), len(Maze.RANGEH))

        for reach in range(0, max_reach):
            _, _, b = next(self.neighborhood(sprite, reach),
                           (None, None, None))
            if b: return b

        return None

    # Iterator yielding bonuses contained inside a neighborhood centered around
    # `sprite` and defined by a hollow rectangle of a specific `reach`.
    # examples:
    # reach = 0 | reach = 1 | reach = 2
    # ·····     | ·····     | ▫▫▫▫▫
    # ·····     | ·▫▫▫·     | ▫···▫
    # ··▫··     | ·▫s▫·     | ▫·s·▫
    # ·····     | ·▫▫▫·     | ▫···▫
    # ·····     | ·····     | ▫▫▫▫▫
    def neighborhood(self, sprite, reach=0):
        i, j = Maze.tile_pos(sprite.rect.center)
        if reach == 0: it = ((0, 0), )
        else:
            it = itertools.chain(
                itertools.product(range(-reach, reach + 1), (-reach, reach)),
                itertools.product((-reach, reach), range(-reach + 1, reach)),
            )

        for k, l in it:
            k += i
            l += j
            if not Maze.inside(k, l): continue
            b = self[k][l]
            if b: yield k, l, b

    def remove(self, i, j):
        self[i][j] = None
        self.count -= 1

    def draw(self, image):
        for _, _, b in self.iterator():
            if b: image.draw_img(b.image, b.rect)