def get_control_points(interval): """ Returns all 4 control points of a bezier interval Args: interval (dict) : Holds one interval of the bezier curve that is two waypoints Returns: (misc.Vector, misc.Vector, misc.Vector, misc.Vector) If the interval holds position bezier (float, float, float, float) Else : the interval holds value bezier """ # If the interval is for position or vector if "to" in interval.keys(): st = Vector(interval["s"][0], interval["s"][1]) en = Vector(interval["e"][0], interval["e"][1]) to = Vector(interval["synfig_to"][0], interval["synfig_to"][1]) ti = Vector(interval["synfig_ti"][0], interval["synfig_ti"][1]) # If the interval is for real values else: st = interval["s"][0] en = interval["e"][0] to = interval["synfig_o"][0] ti = interval["synfig_i"][0] return st, to, ti, en
def handle_color(): """ Default linear tangent values for color interpolations Args: (None) Returns: (misc.Vector, misc.Vector) : out and in tangents for color parameter """ out_val = Vector(0.5, 0.5, "color") in_val = Vector(0.5, 0.5, "color") return out_val, in_val
def game_map(player, orc, air, candy): """Create a level and place the other objects in it.""" from misc import Vector from dungeon import Dungeon from registry import Registry registry = Registry() dungeon = Dungeon() dungeon.initialize(player, registry) level = dungeon.current_level # Place player and orc in this level player.place(level, Vector(0, 0)) orc.place(level, Vector(5, 5)) candy.place(level, Vector(1, 0)) air.place(level, Vector(0, 1)) return level
def _init_room(self, room): """Make the tiles in the map that correspond to the room walkable.""" for x in range(room.x1 + 1, room.x2): for y in range(room.y1 + 1, room.y2): pos = Vector(x, y) self.walkable[pos] = True self.transparent[pos] = True
def compute_path(self, pos1, pos2): """ Calculate a path between pos1 and pos2 in the game map. Args: pos1 (Vector): Vector representing the starting point. pos2 (Vector): Vector representing the destination. Returns: list(Vector): A list of vectors, where each vector represents the position of the next tile in the path. The list goes up to the pos2. """ # Get current walkable state pos1_walkable = self.walkable[pos1] pos2_walkable = self.walkable[pos2] # Set origin and destination to walkable for the actual computation to work self.walkable[pos1] = self.walkable[pos2] = True # Compute path path = self._map.compute_path(pos1.x, pos1.y, pos2.x, pos2.y) # Convert path to vectors path = [Vector(x, y) for x, y in [tile for tile in path]] # Revert origin and destination walkable state to its original state self.walkable[pos1] = pos1_walkable self.walkable[pos2] = pos2_walkable return path
def clamped_vector(p1, p2, p3, animated, i, lottie, ease): """ Function to generate the collective tangents i.e. x tangent and y tangent when clamped waypoints are used Args: p1 (misc.Vector) : First point in Co-ordinate System p2 (misc.Vector) : Second point in Co-ordinate System p3 (misc.Vector) : Third point in Co-ordinate System animated (lxml.etree._Element) : Synfig format animation i (int) : Iterator over animation ease (str) : Specifies if it is an ease in animation ease out Returns: (misc.Vector) : Clamped Vector is returned """ x_tan = clamped_tangent(p1.val1, p2.val1, p3.val1, animated, i) y_tan = clamped_tangent(p1.val2, p2.val2, p3.val2, animated, i) if isclose(x_tan, 0.0) or isclose(y_tan, 0.0): if ease == "in": ease_in(lottie) else: ease_out(lottie) return Vector(x_tan, y_tan, animated.attrib["type"])
def handle_key_input(cls): """ Use tdl's user input features to react to keyborad input. Returns: True if an action that consumes a turn was performed by the player, False otherwise. """ # TODO: Have key mapping in config file and use enums (or something similar) instead if not cls.user_input: return False key_char = cls.user_input.char move_direction = None # Vertical and horizontal movement if cls.user_input.key == 'UP' or key_char == 'k': move_direction = Vector(0, -1) elif cls.user_input.key == 'DOWN' or key_char == 'j': move_direction = Vector(0, 1) elif cls.user_input.key == 'LEFT' or key_char == 'h': move_direction = Vector(-1, 0) elif cls.user_input.key == 'RIGHT' or key_char == 'l': move_direction = Vector(1, 0) # Diagonal movement elif key_char == 'y': move_direction = Vector(-1, -1) elif key_char == 'u': move_direction = Vector(1, -1) elif key_char == 'b': move_direction = Vector(-1, 1) elif key_char == 'n': move_direction = Vector(1, 1) # Check if the action is a movement action if move_direction is not None: return cls.movement_action(move_direction) if cls.user_input.key == 'ENTER' and cls.user_input.alt: # Alt+Enter: toggle fullscreen tdl.set_fullscreen(not tdl.get_fullscreen()) return False elif cls.user_input.key == 'ESCAPE': # Exit game # TODO: Find more elegant way to terminate the program exit() elif cls.user_input.char == 'g': return cls.pickup_action() elif cls.user_input.char == 'e': return cls.interact_action() else: return False
def center(self): """ Get the center of this room object as a Vector. Returns: Vector: The center of this room object. """ center_x = int((self.x1 + self.x2) / 2) center_y = int((self.y1 + self.y2) / 2) return Vector(center_x, center_y)
def generate(self): """ Generate the level's layout. """ # Initialize map for x in range(self.width): for y in range(self.height): pos = Vector(x, y) self.walkable[pos] = False self.transparent[pos] = False for r in range(self.room_max_count): # Random width and height w = randint(self.room_min_size, self.room_max_size) h = randint(self.room_min_size, self.room_max_size) # Random position without going out of the boundaries of the map x = randint(0, self.width - w - 1) y = randint(0, self.height - h - 1) new_room = Room(x, y, w, h) # If it intersects with an existent room, start over for room in self.rooms: if new_room.intersect(room): break else: # Room is valid self._init_room(new_room) center = new_room.center() if not self.rooms: # First room, place up stairs self.up_stairs.place(self, center) else: # Connect room to previous room previous = self.rooms[-1].center() # Flip a coin if randint(0, 1) == 1: # First move horizontally, then vertically self._create_h_tunnel(previous.x, center.x, previous.y) self._create_v_tunnel(previous.y, center.y, center.x) else: # First move vertically, then horizontally self._create_v_tunnel(previous.y, center.y, previous.x) self._create_h_tunnel(previous.x, center.x, center.y) self.rooms.append(new_room) # Place down stairs random_room = choice(self.rooms) self.place_entity_randomly(self.down_stairs, random_room)
def _render_map(cls): """ Renders the current game map if necessary. """ cur_map = cls.dungeon.current_level if cls.dungeon.fov_recomputed: cls.dungeon.fov_recomputed = False # First clear the old console before re-draw cls.console.clear(fg=Colors.WHITE, bg=Colors.BLACK) for x in range(cur_map.width): for y in range(cur_map.height): pos = Vector(x, y) wall = not cur_map.transparent[pos] # If position is visible, draw a bright tile if cur_map.fov[pos]: if wall: cls.console.draw_char(x, y, None, fg=None, bg=Colors.WALL_VISIBLE) else: cls.console.draw_char(x, y, None, fg=None, bg=Colors.GROUND_VISIBLE) # Tiles in FOV will be remembered after they get out # of sight, out of mind :^) cur_map.explored[pos] = True # Position is not visible, but has been explored before elif cur_map.explored[pos]: if wall: cls.console.draw_char(x, y, None, fg=None, bg=Colors.WALL_DARK) else: cls.console.draw_char(x, y, None, fg=None, bg=Colors.GROUND_DARK)
def get_last_control_point(interval): """ Returns the last control point of a bezier interval Args: interval (dict) : Holds one interval of the bezier curve that is two waypoints Returns: (misc.Vector) If the interval holds position bezier (float) Else : the interval holds value bezier """ if len(interval["e"]) >= 2: en = Vector(interval["e"][0], interval["e"][1]) else: en = interval["e"][0] return en
def place_entity_randomly(self, entity, room, allow_overlap=False): """ Places the given entity randomly in a given room. """ # For now, naively assume that there's a free spot while True: # Get a random position inside the room x = randint(room.x1 + 1, room.x2 - 1) y = randint(room.y1 + 1, room.y2 - 1) position = Vector(x, y) # Check if there's already an entity in this spot if not allow_overlap and [ entity for entity in self.entities if entity.pos == position ]: continue # We found a valid spot, place the entity entity.place(self, position) return
class Entity(ABC): """ An entity is any object that can be visually represented in the game map. Entities are represented by a character that is displayed to the screen, and a color that is applied to said character. Note: This is an abstract class and cannot be instanciated directly, it must be sub-classed in order for it to be usable. Examples: * A chest containing items, traps, etc. * The player character. * Enemies. Args: key (Enum): An identifier for the entity in the registry. name (str): A name for the entity, doesn't have to be unique. type (str): Pseudo-type of this entity, can be one of 'player', 'npc', 'enemy', 'item', ... char (str): How the entity will be visually displayed. color (Colors): Color of the character that visually represents this entity. blocks (bool): Whether this entity is blocking or not. render_priority (RenderPriority): At what layer should this entity be rendered. """ pos = Vector(0, 0) game_map = None @abstractmethod def __init__(self, key, name, type, char, color, blocks, render_priority): self.key = key self.name = name self.type = type self.char = char self.color = color self.blocks = blocks self.render_priority = render_priority self.behavior = None def __repr__(self): return f"{self.key.name} '{self.name}' <{self.type}>@{self.pos}" def move(self, direction): """ Move this entity in the specified direction in the given map. Args: direction (Vector): Direction towards which to move this entity. """ old_pos = self.pos self.pos += direction if self.blocks: # Update blocked tile in the map self.game_map.walkable[old_pos] = True self.game_map.walkable[self.pos] = False def distance_to(self, dest): """ Calculate the distance between this entity and the destination position. Args: dest (Vector): Vector to calculate the distance to. Returns: float: The relative distance between this entity and the destination position. """ return (self.pos - dest).norm def move_towards(self, dest): """ Move towards the destination if there's a clear path. Args: dest (Vector): Position to move to. """ path = self.game_map.compute_path(self.pos, dest) if not path: # Don't do anything return next_tile = path[0] direction = next_tile - self.pos if self.game_map.walkable[ next_tile] and not self.game_map.get_blocking_entity_at_location( next_tile): self.move(direction) def place(self, game_map, position): """ Place the entity at a certain position in the given map. Args: game_map (Level): Level in which to place the entity. position (Vector): Position in the level where the entity should be placed. """ # If the entity was on another map, first remove it from there if self.game_map: self.game_map.entities.remove(self) self.game_map.walkable[self.pos] = True self.game_map = game_map self.pos = position game_map.entities.append(self) if self.blocks: game_map.walkable[position] = False def take_turn(self, target): """ The entity takes a turn, using the logic defined by its behavior. If the actor doesn't have a behavior, do nothing. Args: target (Entity): Target entity used by the behavior logic. """ if self.behavior is not None: self.behavior(self, target)
def _create_v_tunnel(self, y1, y2, x): """Create a vertical tunnel from y1 to y2 at a fixed x.""" for y in range(min(y1, y2), max(y1, y2) + 1): pos = Vector(x, y) self.walkable[pos] = True self.transparent[pos] = True
def _create_h_tunnel(self, x1, x2, y): """Create an horizontal tunnel from x1 to x2 at a fixed y.""" for x in range(min(x1, x2), max(x1, x2) + 1): pos = Vector(x, y) self.walkable[pos] = True self.transparent[pos] = True
def test_vector_operations(self): u = Vector(0.3, 2) v = Vector(3, -2.5) assert u + v == Vector(3.3, -0.5) assert u - v == Vector(-2.7, 4.5)
def test_vector_norm(self, x, y, result): tol = 1e-5 norm = Vector(x, y).norm assert abs(norm - result) < tol
def test_vector_normalized(self): tol = 1e-5 assert Vector(1, 0).normalized() == Vector(1, 0) assert Vector(0, -0.5).normalized() == Vector(0, -1) assert (Vector(-1, 1).normalized() - Vector(-sqrt(2), sqrt(2))).norm - 1. < tol
def test_vector_repr(self): assert repr(Vector(-0.5, 2)) == "(-0.5, 2)"