Example #1
0
    def __init__(self, filename: str, pos: tuple, height: float) -> None:
        """Initialise a new `Image` object.

        pos: a pair of floats that describe this image's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        height: the normalized height of the image.
        """
        validate(self.__init__, locals())

        surface_w, surface_h = pygame.display.get_surface().get_size()
        pos_x, pos_y = pos
        raw_img = pygame.image.load(filename)

        # When resizing the image to fit `height`, the image ratio must
        # be preserved.

        height_px = surface_h * height
        width_px = height_px / (raw_img.get_height() / raw_img.get_width())

        self.top_left = (
            surface_w*pos_x - width_px/2,
            surface_h*pos_y - height_px/2
            )

        self.image = pygame.transform.scale(
            raw_img,
            (int(width_px), int(height_px))
            )
Example #2
0
    def __init__(
            self,
            pos: tuple,
            height: float,
            text: str,
            font_type: str,
            alignment: str,
            colour: tuple) -> None:
        """Initialise a new `Label` object.

        pos: a pair of floats that describe this label's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        height: the normalized height of the label.
        font_type: the font family of the text.
        alignment: the alignment of the text in relation to the
            position. Valid values are 'centre' and 'left'.
        """
        validate(self.__init__, locals())

        surface_w, surface_h = pygame.display.get_surface().get_size()
        pos_x, pos_y = pos

        self.pos = (surface_w * pos_x, surface_h * pos_y)

        self.font = Font(
            pygame.font.match_font(font_type),
            int(surface_h * height)
            )

        self.text = text
        self.alignment = alignment
        self.colour = colour
Example #3
0
def point_to_direction(point: tuple) -> Direction:
    """Converts a vector to a `Direction`."""
    validate(point_to_direction, locals())

    if point == (0, 1):
        return NORTH

    elif point == (1, 1):
        return NORTHEAST

    elif point == (1, 0):
        return EAST

    elif point == (1, -1):
        return SOUTHEAST

    elif point == (0, -1):
        return SOUTH

    elif point == (-1, -1):
        return SOUTHWEST

    elif point == (-1, 0):
        return WEST

    elif point == (-1, 1):
        return NORTHWEST

    else:
        raise ValueError('point {} has incorrect form'.format(point))
Example #4
0
    def check_event(self, event: EventType) -> None:
        """Update this component using `event`."""
        validate(self.check_event, locals())

        level_length = len(self.world.current_level.tiles)

        # If there is a mouse motion or click event, set `self.hover`
        # and `self.pressed` to the correct values.

        if event.type == pygame.MOUSEMOTION:

            for x in range(level_length):
                for y in range(level_length):

                    if self.tile_rects[x][y].collidepoint(event.pos):
                        self.hover = (x, y)
                        return

            self.hover = None

        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:

            for x in range(level_length):
                for y in range(level_length):

                    if self.tile_rects[x][y].collidepoint(event.pos):
                        self.pressed = (x, y)
                        self.pressed_this_step = True
                        return
Example #5
0
def check_fill_rect(
        level: Level,
        point: tuple,
        width: int,
        height: int,
        type_: TileType) -> None:
    """Fills a rectangular area on `level`.

    The area is only filled if every tile in the area has a type of
    `SOLID_EARTH`. Returns true if successful, false otherwise.
    """
    validate(check_fill_rect, locals())

    x, y = point
    top_right = (x + width, y + height)
    level_length = len(level.tiles)

    # The bounds of the area need to be checked to ensure the whole
    # rectangle fits on the level.

    if (not point_within(level_length, point)
            or not point_within(level_length, top_right)):
        return False

    for i in range(x, x + width):
        for j in range(y, y + height):

            if level.tiles[i][j].type != SOLID_EARTH:
                return False

    for i in range(x, x + width):
        for j in range(y, y + height):
            level.tiles[i][j] = new_tile(type_)

    return True
Example #6
0
def direction_to_point(direction: Direction) -> tuple:
    """Converts a `Direction` to a vector."""
    validate(direction_to_point, locals())

    if direction is NORTH:
        return (0, 1)

    elif direction is NORTHEAST:
        return (1, 1)

    elif direction is EAST:
        return (1, 0)

    elif direction is SOUTHEAST:
        return (1, -1)

    elif direction is SOUTH:
        return (0, -1)

    elif direction is SOUTHWEST:
        return (-1, -1)

    elif direction is WEST:
        return (-1, 0)

    else:
        return (-1, 1)
Example #7
0
def remove_entity(level: Level, point: tuple) -> None:
    """Removes the entity on `level` at `point`."""
    validate(remove_entity, locals())

    x, y = point
    level.tiles[x][y].entity = None
    level.entities[level.entities.index(point)] = None
Example #8
0
    def render(self, surface: Surface) -> None:
        """Render this display to the given surface."""
        validate(self.render, locals())

        level_length = len(self.world.current_level.tiles)

        # Render each tile in the level using the appropriate icon.

        for x in range(level_length):
            for y in range(level_length):

                tile = self.world.current_level.tiles[x][y]
                rect = self.tile_rects[x][y]

                if tile.entity is not None:
                    icon = self.icons[tile.entity.icon_name].copy()
                else:
                    icon = self.icons[tile.type.name].copy()

                # If the tile is being hovered over, highlight it.

                if self.hover == (x, y):
                    colourise(icon, (64, 64, 64))

                surface.blit(icon, rect)
Example #9
0
    def render(self, surface: Surface) -> None:
        """Render this element to the given surface."""
        validate(self.render, locals())

        # Any messages that are over the character limit must be split
        # onto multiple lines.
        self.messages = [
            str_
            for msg in self.messages
            for str_ in textwrap.wrap(msg, width=self.chars_per_line)
            ]

        # If there are too many lines of messages, remove the ones at
        # the beginning of the list.
        while len(self.messages) > self.lines:
            del self.messages[0]

        # The colour of each line must be toggled between white and
        # grey to allow each line to be more easily distinguished.

        colours = itertools.cycle([(255, 255, 255), (190, 190, 190)])
        seq = zip(self.messages, self.y_positions, colours)

        for msg, y_pos, colour in seq:

            label = Label(
                pos=(self.left_pos, y_pos),
                height=self.font_height,
                text=msg,
                font_type='mono',
                alignment='left',
                colour=colour
                )

            label.render(surface)
Example #10
0
    def path_to(self, world: World, point: tuple) -> None:
        """
        Generate the necessary actions to move the hero to `point`.
        """
        validate(self.path_to, locals())

        x, y = point
        tile = world.current_level.tiles[x][y]

        if not tile.type.passable and tile.type is not CLOSED_DOOR:
            self.add_message('You cannot move there.')
            return

        elif world.hero == point:
            self.wait()
            return

        directions = renethack.entity.find_path(world.hero, point,
                                                world.current_level)

        moves = [Move(d) for d in directions]

        # If the target tile type is special, convert the last action
        # to a `Use`.

        if (tile.type is UP_STAIRS or tile.type is DOWN_STAIRS
                or tile.type is OPEN_DOOR):
            self.actions = moves[:-1]
            self.actions.append(Use(directions[-1]))

        else:
            self.actions = moves
Example #11
0
    def __init__(self, name: str, hit_points: int, defence: int, speed: int,
                 strength: int) -> None:
        """Initialise a new entity.

        name: display name
        hit_points: health
        defence: damage reduction
        speed: energy gain per turn
        strength: damage
        """
        validate(self.__init__, locals())

        self.name = name
        self.hit_points = hit_points
        self.max_hit_points = hit_points
        self.defence = defence
        self.speed = speed
        self.strength = strength

        self.level = 1
        self.experience = 0
        self.score = 0
        self.energy = 0
        self.hp_counter = 0
        self.actions = []
        self.messages = []
        self.icon_name = 'Hero'
Example #12
0
    def __init__(self, pos: tuple, height: float) -> None:
        """Initialise a new `TextBox` object.

        pos: a pair of floats that describe this text box's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        height: the normalized height of the text box.
        """
        validate(self.__init__, locals())

        surface_w, surface_h = pygame.display.get_surface().get_size()
        pos_x, pos_y = pos
        width = height*5
        left_pos = pos_x - width/2
        top_pos = pos_y - height/2

        self.underline_rect = Rect(
            surface_w * left_pos,
            surface_h * (top_pos + height*1.2),
            surface_w * width,
            surface_h * height * 0.05
            )

        # The text of the label must be updated each step, so
        # initially it is set empty.

        self.label = Label(
            pos=pos,
            height=height*0.8,
            text='',
            font_type='sans',
            alignment='centre',
            colour=(255, 255, 255)
            )
Example #13
0
def new_tile(type_: TileType) -> Tile:
    """Returns a new, empty `Tile` object."""
    validate(new_tile, locals())

    return Tile(
        type_,
        entity=None
        )
Example #14
0
def rand_hero(name: str) -> Hero:
    """Returns a new hero with random stats."""
    validate(rand_hero, locals())

    return Hero(name=name,
                hit_points=random.randint(7, 10),
                defence=random.randint(0, 1),
                speed=random.randint(50, 75),
                strength=random.randint(1, 2))
Example #15
0
def point_within(length: int, point: tuple) -> bool:
    """
    Check whether a point is within a square grid
    of length `length`.
    """
    validate(point_within, locals())

    x, y = point
    return 0 <= x < length and 0 <= y < length
Example #16
0
    def __init__(self, type: TileType, entity) -> None:
        """Initialise a new `Tile` object.

        `entity` can either be a `Monster` or a `Hero`.
        """
        validate(self.__init__, locals())

        self.type = type
        self.entity = entity
Example #17
0
    def render(self, surface: Surface) -> None:
        """Render this label to the given surface."""
        validate(self.render, locals())

        for c in self.components:
            c.render(surface)

        for x in range(self.bars):
            surface.fill((255, 255, 255), self.rects[x])
Example #18
0
    def __init__(self, tiles: list, entities: list) -> None:
        """Initialises a new `Level` object.

        tiles: a 2D grid of tiles that describe the layout of the
            level.
        entities: the list of points that currently contain entities
            on the level.
        """
        validate(self.__init__, locals())

        self.tiles = tiles
        self.entities = entities
Example #19
0
    def __init__(self, levels: list, hero: tuple) -> None:
        """Initialise a new `World` object.

        levels: the list of levels to use.
        hero: the point on the first level that the hero is at.
        """
        validate(self.__init__, locals())

        self.upper_levels = []
        self.current_level = levels[0]
        self.lower_levels = levels[1:]
        self.hero = hero
Example #20
0
def get_tiles(level: Level, type_: TileType) -> list:
    """Returns the list of points on `level` with tile type `type_`."""
    validate(get_tiles, locals())

    level_length = len(level.tiles)

    return [
        (x, y)
        for x in range(level_length)
        for y in range(level_length)
        if level.tiles[x][y].type is type_
        ]
Example #21
0
    def __init__(self, parent, point: tuple, target_point: tuple) -> None:
        validate(self.__init__, locals())

        self.parent = parent
        self.point = point

        x, y = self.point
        target_x, target_y = target_point

        self.cost = 0 if parent is None else parent.cost + 1
        self.remaining_cost = abs(x - target_x) + abs(y - target_y)
        self.final_cost = self.cost + self.remaining_cost
Example #22
0
def apply(config: Config) -> Surface:
    """Apply the given config and return the resulting surface object.

    `pygame.display.set_mode` is used to apply the config.
    """
    validate(apply, locals())

    flag = (pygame.FULLSCREEN
            | pygame.HWSURFACE
            | pygame.DOUBLEBUF if config.fullscreen else 0)

    pygame.mixer.music.set_volume(config.volume)
    return pygame.display.set_mode(config.resolution, flag)
Example #23
0
    def __init__(self, pos: tuple, width: float, hero: Hero) -> None:
        """Initialise a new `StatusDisplay` object.

        pos: a pair of floats that describe this display's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        width: the normalized width of the display.
        """
        validate(self.__init__, locals())

        surface_w, surface_h = pygame.display.get_surface().get_size()
        pos_x, pos_y = pos
        height = width*1.25
        top_pos = pos_y - height/2
        left_pos = pos_x - width/2
        text_height = height*0.1

        # Generate a sequence of labels, each with the same x position
        # and a different y position.
        labels = (
            Label(
                pos=(left_pos, top_pos + height*x),
                height=text_height,
                text='',
                font_type='mono',
                alignment='left',
                colour=(255, 255, 255)
                )
            for x in xrange(1/14, 1, 1/7)
            )

        self.name_label = next(labels)
        self.score_label = next(labels)
        self.level_label = next(labels)
        self.hp_label = next(labels)
        self.defence_label = next(labels)
        self.speed_label = next(labels)
        self.strength_label = next(labels)

        self.components = [
            self.name_label,
            self.score_label,
            self.level_label,
            self.hp_label,
            self.defence_label,
            self.speed_label,
            self.strength_label
            ]

        self.hero = hero
Example #24
0
    def vol_update(self, volume: float) -> float:
        """
        Update this component using `volume`. Returns the new volume.
        """
        validate(self.vol_update, locals())

        if self.left_button.pressed:
            volume = min_clamp(volume-0.1, 0.0)

        elif self.right_button.pressed:
            volume = max_clamp(volume+0.1, 1.0)

        self.bars = round(volume * 10)
        return volume
Example #25
0
    def __init__(self, pos: tuple, height: float) -> None:
        """Initialise a new `VolumeDisplay` object.

        pos: a pair of floats that describe this display's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        height: the normalized height of the display.
        """
        validate(self.__init__, locals())

        surface_w, surface_h = pygame.display.get_surface().get_size()
        pos_x, pos_y = pos
        width = height*6
        button_width = 0.15
        bar_width = (1 - 2*button_width)/10
        bar_rect_width = bar_width*0.5
        self.bars = 0

        self.left_button = Button(
            pos=(pos_x - width*(0.5 - button_width/2), pos_y),
            width=width*button_width,
            height=height,
            text='<'
            )

        self.right_button = Button(
            pos=(pos_x + width*(0.5 - button_width/2), pos_y),
            width=width*button_width,
            height=height,
            text='>'
            )

        self.components = [self.left_button, self.right_button]

        # Generate a list of rectangles that represent the current
        # volume, depending on which rectangles are rendered.
        self.rects = [
            Rect(
                surface_w * (pos_x - width/2 + width*x),
                surface_h * (pos_y - height/2),
                surface_w * width * bar_rect_width,
                surface_h * height
                )
            for x in xrange(
                button_width + (bar_width - bar_rect_width)/2,
                1-button_width,
                bar_width
                )
            ]
Example #26
0
    def step(self, ms_per_step: float) -> None:
        """Update this element."""
        validate(self.step, locals())

        self.name_label.text = self.hero.name
        self.score_label.text = 'Score: {}'.format(self.hero.score)
        self.level_label.text = 'Level {}'.format(self.hero.level)

        self.hp_label.text = 'Hit Points: {}/{}'.format(
            self.hero.hit_points, self.hero.max_hit_points)

        self.defence_label.text = 'Defence: {}'.format(self.hero.defence)
        self.speed_label.text = 'Speed: {}'.format(self.hero.speed)
        self.strength_label.text = 'Strength: {}'.format(self.hero.strength)
Example #27
0
    def check_event(self, event: EventType) -> None:
        """Check `event` with this element."""
        validate(self.check_event, locals())

        # If backspace is pressed, the last character must be removed.
        # If any other key is pressed, add its character to the label.

        if event.type == pygame.KEYDOWN:

            if event.key == pygame.K_BACKSPACE:
                self.label.text = self.label.text[:-1]

            else:
                self.label.text += event.unicode
Example #28
0
    def step(self, ms_per_step: float) -> None:
        """Step this component by `ms_per_step`."""
        validate(self.step, locals())

        # Only set `self.pressed` to `None` if the click event happened
        # during the last step, not this one.

        if self.pressed is not None:

            if self.pressed_this_step:
                self.pressed_this_step = False

            else:
                self.pressed = None
                del self.pressed_this_step
Example #29
0
    def step(self, ms_per_step: float) -> None:
        """Step this button by `ms_per_step`."""
        validate(self.step, locals())

        # Only set `self.pressed` to `False` if the click event
        # happened during the last step, not this one.

        if self.pressed:

            if self.pressed_this_step:
                self.pressed_this_step = False

            else:
                self.pressed = False
                del self.pressed_this_step
Example #30
0
    def __init__(self, pos: tuple, height: float, score: Score) -> None:
        """Initialise a new `ScoreDisplay` object.

        pos: a pair of floats that describe this display's position on
            the screen. The floats must be normalized, e.g. a value
            of (0.3, 0.6) means 30% of the screen width and 60% of
            the screen height. (0, 0) is the top left corner.
        height: the normalized height of the display.
        """
        validate(self.__init__, locals())

        pos_x, pos_y = pos
        line_height = height/3
        text_height = line_height*0.8
        self.components = []

        self.components.append(
            Label(
                pos=(pos_x, pos_y - line_height),
                height=text_height,
                text='Name: {}'.format(score.name),
                font_type='sans',
                alignment='left',
                colour=(255, 255, 255)
                )
            )

        self.components.append(
            Label(
                pos=pos,
                height=text_height,
                text='Level: {}'.format(score.level),
                font_type='sans',
                alignment='left',
                colour=(255, 255, 255)
                )
            )

        self.components.append(
            Label(
                pos=(pos_x, pos_y + line_height),
                height=text_height,
                text='Score: {}'.format(score.score),
                font_type='sans',
                alignment='left',
                colour=(255, 255, 255)
                )
            )