class SegmentTool(Tool): def setup(self): self.pos1 = None def mousedown(self, x, y): self.pos1 = Position(round(x), round(y)) def mousemove(self, x, y): if self.pos1: pos2 = self._closest_position(round(x), round(y)) self.canvas.redraw() if self.pos1.pos() != pos2.pos(): segment = self._make_segment(self.pos1, pos2) segment.draw(self.canvas) def mouseup(self, x, y): if self.pos1: pos2 = self._closest_position(round(x), round(y)) if self.pos1.pos() != pos2.pos(): segment = self._make_segment(self.pos1, pos2) self.level.walls.append(segment) self.canvas.redraw() self.pos1 = None def _closest_position(self, x, y): if abs(x - self.pos1.x) > abs(y - self.pos1.y): return Position(x, self.pos1.y) else: return Position(self.pos1.x, y) def _make_segment(self, pos1, pos2): raise NotImplementedError
def _remove_walls(self, x1, y1, x2, y2): to_remove = [] for wall in self.level.walls: if wall.intersects(Position(x1, y1), Position(x2, y2)): to_remove.append(wall) for wall in to_remove: self.level.walls.remove(wall)
def _segment(self): num_segments = round(Position(self.x1, self.y1).distance(Position(self.x2, self.y2))) self.segments = [] for i in range(num_segments): self.segments.append(WallSegment((self.x1 + (self.x2 - self.x1) * (i / num_segments), self.y1 + (self.y2 - self.y1) * (i / num_segments)), (self.x1 + (self.x2 - self.x1) * ((i+1) / num_segments), self.y1 + (self.y2 - self.y1) * ((i+1) / num_segments)), self))
def mousedown(self, x, y): door = self._get_door(x, y) button = self._get_button(x, y) if door and button: if door.center().distance(Position(x, y)) < button.distance( Position(x, y)): self.door = door else: self.button = button elif door: self.door = door elif button: self.button = button
def mousemove(self, x, y): if self.door: self.canvas.redraw() button = self._get_button(x, y) if button: self._draw_trigger(self.door.center(), button) else: self._draw_trigger(self.door.center(), Position(x, y)) elif self.button: self.canvas.redraw() door = self._get_door(x, y) if door: self._draw_trigger(self.button, door.center()) else: self._draw_trigger(self.button, Position(x, y))
def _closest_position(self, x, y): x = round(x * 2) * 0.5 y = round(y * 2) * 0.5 if x == self.pos1.x and y == self.pos1.y: return Position(x, y) if abs(x - self.pos1.x) > abs(y - self.pos1.y): if x > self.pos1.x: return Position(self.pos1.x + 1, self.pos1.y) else: return Position(self.pos1.x - 1, self.pos1.y) else: if y > self.pos1.y: return Position(self.pos1.x, self.pos1.y + 1) else: return Position(self.pos1.x, self.pos1.y - 1)
def add_neighbor(self, node, direction, pos, next_pos): next_node = self.nodes.get(next_pos) x1, y1 = pos x2, y2 = next_pos p1 = Position(x1 + 0.5, y1 + 0.5) p2 = Position(x2 + 0.5, y2 + 0.5) for wall in self.level.possible_intersections(p1, p2): intersection = wall.intersects(p1, p2) # If we've already explored this node, add the appropriate connections. # If not, then if there's no wall between the two nodes, add it to our frontier. # If there is a wall, leave it to be discovered (and potentially connected) # later. if intersection: # Need special handling so we can determine which segments to create portals on if isinstance(wall, PortalWall): for segment in wall.segments: if segment.intersects(p1, p2): node.add_neighbor(direction, None, segment, intersection) elif isinstance(wall, Wall): node.add_neighbor(direction, None, wall, intersection) elif next_node: if isinstance(wall, Door) or isinstance(wall, Grill): path_from = True path_to = True elif isinstance(wall, Ledge) and intersection > 0: path_from = True path_to = False elif isinstance(wall, Ledge) and intersection < 0: path_from = False path_to = True node.add_neighbor(direction, (next_node if path_from else None), wall, intersection) next_node.add_neighbor((direction + 2) % 4, (node if path_to else None), wall, -intersection) return True # No wall intersection if next_node: node.add_neighbor(direction, next_node, None, 0) next_node.add_neighbor((direction + 2) % 4, node, None, 0) return True else: return False
def _remove_entities(self, x, y): to_remove = [] for e in self.level.entities: if e.x is not None and e.y is not None and e.distance( Position(x, y)) < 0.2: to_remove.append(e) for e in to_remove: self.level.remove_entity(e)
def __init__(self, app, level, width, height): self.level = level self.width = width self.height = height self.scale = None super().__init__(app, width=width, height=height) self.pan_from = None self.pan_orig = None self.pan_offset = Position(0, 0) self.calculate_transform() self.path_lines = [] self.bind('<Configure>', self._resize) self.bind('<Button-2>', self._pan_start) self.bind('<B2-Motion>', self._pan_move) self.bind('<ButtonRelease-2>', self._pan_end)
class PortalTool(Tool): def setup(self): self.pos1 = None def mousedown(self, x, y): self.pos1 = Position(round(x * 2) * 0.5, round(y * 2) * 0.5) def mousemove(self, x, y): if self.pos1: pos2 = self._closest_position(x, y) self.canvas.redraw() if self.pos1.pos() != pos2.pos(): portal = self._make_portal(self.pos1, pos2) portal.draw(self.canvas) def mouseup(self, x, y): if self.pos1: pos2 = self._closest_position(x, y) if self.pos1.pos() != pos2.pos(): portal = self._make_portal(self.pos1, pos2) self.level.add_entity(portal) self.canvas.redraw() self.pos1 = None def _closest_position(self, x, y): x = round(x * 2) * 0.5 y = round(y * 2) * 0.5 if x == self.pos1.x and y == self.pos1.y: return Position(x, y) if abs(x - self.pos1.x) > abs(y - self.pos1.y): if x > self.pos1.x: return Position(self.pos1.x + 1, self.pos1.y) else: return Position(self.pos1.x - 1, self.pos1.y) else: if y > self.pos1.y: return Position(self.pos1.x, self.pos1.y + 1) else: return Position(self.pos1.x, self.pos1.y - 1) def _make_portal(self, pos1, pos2): raise NotImplementedError
def center(self): min_x = self.nodes[0].x max_x = self.nodes[0].x min_y = self.nodes[0].y max_y = self.nodes[0].y for node in self.nodes: min_x = min(min_x, node.x) max_x = max(max_x, node.x) min_y = min(min_y, node.y) max_y = max(max_y, node.y) return Position((min_x + max_x) * 0.5, (min_y + max_y) * 0.5)
def __init__(self, x, y, name=None): objects.Button.__init__(self, name) Position.__init__(self, x, y) self.objects = set()
def _closest_position(self, x, y): if abs(x - self.pos1.x) > abs(y - self.pos1.y): return Position(x, self.pos1.y) else: return Position(self.pos1.x, y)
def mousedown(self, x, y): self.pos1 = Position(round(x), round(y))
def mousedown(self, x, y): self.pos1 = Position(round(x * 2) * 0.5, round(y * 2) * 0.5)
def _get_door(self, x, y): for wall in self.level.walls: if isinstance( wall, Door) and wall.center().distance(Position(x, y)) < 0.5: return wall
def _get_button(self, x, y): for entity in self.level.entities: if isinstance(entity, Button) and entity.distance(Position(x, y)) < 0.5: return entity
class LevelCanvas(tk.Canvas): def __init__(self, app, level, width, height): self.level = level self.width = width self.height = height self.scale = None super().__init__(app, width=width, height=height) self.pan_from = None self.pan_orig = None self.pan_offset = Position(0, 0) self.calculate_transform() self.path_lines = [] self.bind('<Configure>', self._resize) self.bind('<Button-2>', self._pan_start) self.bind('<B2-Motion>', self._pan_move) self.bind('<ButtonRelease-2>', self._pan_end) def set_scale(self, scale): self.scale = float(scale) self.calculate_transform() self.redraw() def calculate_transform(self): (x_min, y_min, x_max, y_max) = self.level.bounds center_x = (x_min + x_max) * 0.5 center_y = (y_min + y_max) * 0.5 level_width = x_max - x_min or 20 level_height = y_max - y_min or 20 scale = self.scale or min((self.width - 50) / level_width, (self.height - 50) / level_height) # A stack of Transforms self.transform = [ Translate(-center_x, -center_y), Dilate(scale), Translate(self.width * 0.5, self.height * 0.5), Translate(self.pan_offset.x, self.pan_offset.y), ] self.inverse_transform = [t.inverse() for t in reversed(self.transform)] def transform_point(self, pos): return functools.reduce((lambda p, t: t.apply(p)), self.transform, pos) def preimage_point(self, pos): return functools.reduce((lambda p, t: t.apply(p)), self.inverse_transform, pos) def create_line(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_line(x1_new, y1_new, x2_new, y2_new, **options) def create_rectangle(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_rectangle(x1_new, y1_new, x2_new, y2_new, **options) def create_oval(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_oval(x1_new, y1_new, x2_new, y2_new, **options) def redraw(self): self.delete('all') self.level.draw(self) def _resize(self, event): self.width = event.width self.height = event.height self.calculate_transform() self.redraw() def _pan_start(self, event): self.pan_from = Position(event.x, event.y) self.pan_orig = Position(*self.pan_offset.pos()) def _pan_move(self, event): self.pan_offset.x = self.pan_orig.x + event.x - self.pan_from.x self.pan_offset.y = self.pan_orig.y + event.y - self.pan_from.y self.calculate_transform() self.redraw() def _pan_end(self, event): self._pan_move(event) self.pan_from = None self.pan_orig = None
def mousedown(self, x, y): p = Position(round(x * 2) * 0.5, round(y * 2) * 0.5) self.level.goal.move_to(p) self.canvas.redraw()
def _pan_start(self, event): self.pan_from = Position(event.x, event.y) self.pan_orig = Position(*self.pan_offset.pos())
def __init__(self, x, y, name=None): Position.__init__(self, x, y) Object.__init__(self, name)