def __init__(self, text: Text, padding: Vector2D = Vector2D(20, 10), position: Vector2D = Vector2D.zero(), color: Color = None, onclick=None): super(Button, self).__init__() # look self.text = text self._position = position self.padding = padding self._size = Vector2D( self.text.rect.size[0] + self.padding.x, self.text.rect.size[1] + self.padding.y ) self.text.center(Vector2D.center(self.position, self.size)) # color if color is None: color = colors.TRANSPARENT self._mutable_color = color self._original_color = color # behaviour self.onhover = Hover(DIM_LIGHT, ORIGINAL_COLOR) self.onclick = onclick if self.onclick is None: self.onclick = lambda button: None # used to draw self.surface = self.make()
def get_viable_neigbours(self): vectors = [] size = Vector2D(len(self.grid), len(self.grid[0])) for x in [self.current.x - 1, self.current.x, self.current.x + 1]: for y in [self.current.y - 1, self.current.y, self.current.y + 1]: # index out of range if x < 0 or x > size.x - 1 or y < 0 or y > size.y - 1: continue # current pos if x == self.current.x and y == self.current.y: continue tile = self.grid[x][y] state = Tile.int_to_state(tile) if state in [Tile.UNVISITED, Tile.NEIGHBOURS, Tile.END]: vectors.append(Vector2D(x, y)) # update cost grid_cost = self.costgrid[x][y] calculated_cost = Vector2D(x, y).manhattan( self.current) + Vector2D(x, y).manhattan(self.end) if grid_cost == -1 or grid_cost > calculated_cost: self.costgrid[x][y] = calculated_cost self.visited[tuple([x, y])] = self.current if state != Tile.END: self.grid[x][y] = Tile.state_to_int(Tile.NEIGHBOURS) return vectors
def __init__(self, text: Text, size: Vector2D = None, position: Vector2D = Vector2D.zero(), color: Color = None, onclick=None): super(Button, self).__init__() # look self.text = text self.size = size if self.size is None: self.size = Vector2D(100, 35) self._position = position self.text.center(Vector2D.center(self.position, self.size)) # color if color is None: color = colors.TRANSPARENT self._mutable_color = color self._original_color = color # behaviour self.onhover = Hover(DIM_LIGHT, ORIGINAL_COLOR) self.onclick = onclick if self.onclick is None: self.onclick = lambda button: None
def generate_bars(self, sizes): """ creates the bars if they aren't already, else updates them :param sizes: sizes of bars :return: """ bar_width = self.surface.get_rect().size[0] / self.size for i, y in enumerate(sizes): try: bar = self.bars[y] bar.position = Vector2D.custom(self.surface, i * bar_width, y - 1, inverty=True) continue except KeyError: bar = Rectangle(Vector2D.custom(self.surface, i * bar_width, y - 1, inverty=True), Vector2D(bar_width, y), color=Color.lerp(y / self.max, colors.RED, colors.GREEN, colors.BLUE) if self.color is None else self.color) self.bars[y] = bar
def __init__(self, size: Vector2D, info_text: Text, position=Vector2D(0, 20), padding=Vector2D(0, 0)): self.size = size self.position = position self.padding = padding self.info_text = info_text self.info_text.text = 'Select start' self.tilepadding = Vector2D(0, 0) screen_size = Vector2D.tuple(pygame.display.get_surface().get_rect().size) space = Vector2D( screen_size.x - position.x - padding.x, screen_size.y - position.y - padding.y ) self.tilesize = Vector2D( int((space.x - (self.size.x * self.tilepadding.x)) / self.size.x), int((space.y - (self.size.y * self.tilepadding.y)) / self.size.y), ) self.grid = [] for x in range(size.x): l = [0] * size.y self.grid.append(l) self.tiles = [] for x in range(size.x): l = [None] * size.y self.tiles.append(l) self.remake_tiles(self.grid) self.drawable = Switch(True) self.mouse_left_down = Switch(False) self.mouse_left_down_type = None self.start = None self.end = None self.algorithm = None def onflip(val): if self.algorithm.solution_length == -1: self.info_text.text = 'No solution found' return self.info_text.text = 'Running' if val else f'Found solution of length {self.algorithm.solution_length}' self.running = Switch(False, onflip=onflip) self.misc = {}
def remake_tiles(self, positions): for gridposition in positions: x, y = gridposition position = Vector2D( y * self.tilesize.y + y * self.tilepadding.x + self.position.x + self.padding.x, x * self.tilesize.x + x * self.tilepadding.y + self.position.y + self.padding.y, ) tile = Tile(Tile.int_to_state(self.grid[x][y]), gridpos=Vector2D(x, y), position=position, size=self.tilesize) self.tiles[x][y] = tile
def update_tiles(self, grid): size = Vector2D(len(grid), len(grid[0])) for x in range(size.x): for y in range(size.y): position = Vector2D( y * self.tilesize.y + y * self.tilepadding.x + self.position.x + self.padding.x, x * self.tilesize.x + x * self.tilepadding.y + self.position.y + self.padding.y, ) tile = Tile(Tile.int_to_state(self.grid[x][y]), gridpos=Vector2D(x, y), position=position, size=self.tilesize) self.tiles[x][y] = tile
def center(self, position): size = self.surface.get_rect().size self.position = Vector2D(position.x - (size[0] / 2), position.y - (size[1] / 2)) self.autocenter = True
def clean_grid(self, types, to=Tile.UNVISITED): """ replaces all the state types in :param types: with state :param to: :param types: state types to replace :param to: replacement state """ if Tile.END in types: self.end = None self.info_text.text = 'Select end' if Tile.START in types: self.start = None self.info_text.text = 'Select start' to = Tile.state_to_int(to) size = Vector2D(len(self.grid), len(self.grid[0])) for x in range(size.x): for y in range(size.y): tile = self.grid[x][y] if Tile.int_to_state(tile) in types: self.grid[x][y] = to
def __init__(self, state, gridpos: Vector2D, size: Vector2D = None, position: Vector2D = Vector2D.zero(), onclick=None): super(Tile, self).__init__(Text(''), size, position, state, onclick) self.gridpos = gridpos
def print_grid(grid): size = Vector2D(len(grid), len(grid[0])) for x in range(size.x): print('[', end='') for y in range(size.y): value = grid[x][y] print(value, end='') if y != size.y - 1: print(', ', end='') print(']')
def event(self, event): if event.type == pygame.MOUSEBUTTONDOWN: for widget in self.widgets: # left button if event.button == 1: if widget.inbound(Vector2D.tuple(event.pos)): clicked = getattr(widget, 'clicked', None) if callable(clicked): clicked()
def print_grid(self): size = Vector2D(len(self.tiles), len(self.tiles[0])) for x in range(size.x): print('[', end='') for y in range(size.y): value = self.grid[x][y] print(value, end='') if y != size.y - 1: print(', ', end='') print(']')
def __init__(self, text, size=14, italic=False, position=Vector2D.zero(), color: Color = colors.BLACK, font: Font = Roboto.MEDIUM): super(Text, self).__init__() self._text = text self._color = color self.font = font.get(size, italic) self.surface = self.font.render(text, True, color) self.position = position self.autocenter = False
def __init__(self, grid): self.grid = grid self.start = None self.end = None self.walls = [] self.queue = [] self.parent = {} self.gcost = {} self.path = () self.current = None self.costgrid = [] # analysis size = Vector2D(len(self.grid), len(self.grid[0])) for x in range(size.x): self.costgrid.append([-1] * size.y) for y in range(size.y): tile = self.grid[x][y] if Tile.int_to_state(tile) == Tile.START: self.start = Vector2D(x, y) self.costgrid[x][y] = 0 elif Tile.int_to_state(tile) == Tile.END: self.end = Vector2D(x, y) elif Tile.int_to_state(tile) == Tile.WALL: self.walls.append(Vector2D(x, y)) self.current = self.start self.gcost[tuple(self.current)] = 0 self.solution_found = False self.solution_length = 0 self.heuristic_modifier = 1.2
def _update_change(self, bar_width, sizes, color): for i, y in sizes: bar = self.bars[y] bar.position = Vector2D.custom(self.surface, i * bar_width, y - 1, inverty=True) # highlight the bar bar.color = color self.previous_changed.append(y)
def text(self, text): self._text = text prev_rect = self.surface.get_rect() self.surface = self.font.render(self.text, True, self.color) # size changes depending on amount of text so position is changed self.position = Vector2D( self.position.x + (prev_rect.size[0] - self.surface.get_rect().size[0]) / 2, self.position.y)
def update(self): mouse_pos = Vector2D.tuple(pygame.mouse.get_pos()) for widget in self.widgets: mouse_is_over = widget.inbound(mouse_pos) if not widget.hover and mouse_is_over: widget.enter() if widget.hover and not mouse_is_over: widget.exit()
def remake_tiles(self, grid): """ recreates the whole tile grid for :param grid: :param grid: blueprint for recreation :return: None """ size = Vector2D(len(grid), len(grid[0])) for x in range(size.x): for y in range(size.y): position = Vector2D( y * self.tilesize.x + y * self.tilepadding.x + self.position.x + self.padding.x, x * self.tilesize.y + x * self.tilepadding.y + self.position.y + self.padding.y, ) tile = Tile(Tile.int_to_state(self.grid[x][y]), gridpos=Vector2D(x, y), position=position, padding=Vector2D.zero()) tile.size = self.tilesize self.tiles[x][y] = tile
def update(self): if self.running.get(): try: affected = self.algorithm.next() self.update_tiles(affected) except StopIteration: self.running.set(False) # not further actions allowed # when algorithms is running return else: # get current mouse position mouse_pos = Vector2D.tuple(pygame.mouse.get_pos()) # get tile in mouse position position tile = self.tile(mouse_pos) if tile is None: return # update tile to indicate hover if not tile.hover: tile.enter() # revert previous hover previous hover tile try: inbound = self.misc['over'] if tile.position != inbound.position: inbound.exit() except KeyError: pass self.misc['over'] = tile keys = pygame.key.get_pressed() if keys[pygame.K_LALT]: for key in key_map.keys(): # save if keys[key]: value = key_map[key] self.update_grid() self.save(value) break else: for key in key_map.keys(): # load if keys[key]: value = key_map[key] self.load(value) break
def update(self): mouse_pos = Vector2D.tuple(pygame.mouse.get_pos()) for widget in self.widgets: try: mouse_is_over = widget.inbound(mouse_pos) except NotImplementedError: continue if not widget.hover and mouse_is_over: widget.enter() if widget.hover and not mouse_is_over: widget.exit()
def update(self): if self.running.get(): try: affected = self.algorithm.next() self.remake_tiles(affected) except StopIteration: self.running.set(False) else: mouse_pos = Vector2D.tuple(pygame.mouse.get_pos()) for tile in self.all_tiles(): inbound = tile.inbound(mouse_pos) if not tile.hover and inbound: tile.enter() if tile.hover and not inbound: tile.exit()
def clean_grid(self, types, to=Tile.UNVISITED): if Tile.END in types: self.end = None self.info_text.text = 'Select end' if Tile.START in types: self.start = None self.info_text.text = 'Select start' to = Tile.state_to_int(to) size = Vector2D(len(self.grid), len(self.grid[0])) for x in range(size.x): for y in range(size.y): tile = self.grid[x][y] if Tile.int_to_state(tile) in types: self.grid[x][y] = to
def event(self, event): """ event handler :param event: event in consideration :return: None """ if event.type == pygame.MOUSEBUTTONDOWN: for widget in self.widgets: # left button if event.button == 1: try: if widget.inbound(Vector2D.tuple(event.pos)): clicked = getattr(widget, 'clicked', None) if callable(clicked): clicked() except NotImplementedError: continue
def get_viable_neigbours(self) -> List[Vector2D]: """ :return: vectors of all the viable neighbours(tiles that you can move to) of the current positon (self.current) """ vectors = [] size = Vector2D(len(self.grid), len(self.grid[0])) for x in [self.current.x - 1, self.current.x, self.current.x + 1]: for y in [self.current.y - 1, self.current.y, self.current.y + 1]: # index out of range if x < 0 or x > size.x - 1 or y < 0 or y > size.y - 1: continue # current pos if x == self.current.x and y == self.current.y: continue tile = self.grid[x][y] state = Tile.int_to_state(tile) if state in [Tile.UNVISITED, Tile.NEIGHBOURS, Tile.END]: vectors.append(Vector2D(x, y)) # update grid cost / path cost grid_cost = self.costgrid[x][y] g = self.g(self.current) + self.distance( self.current, Vector2D(x, y)) if grid_cost == -1: self.gcost[(x, y)] = self.g(self.current) + self.distance( self.current, Vector2D(x, y)) else: if self.gcost[(x, y)] > g: self.gcost[(x, y)] = g # update cost calculated_cost = self.g(Vector2D(x, y)) + self.h( Vector2D(x, y)) * self.heuristic_modifier if grid_cost == -1 or grid_cost > calculated_cost: self.costgrid[x][y] = calculated_cost self.parent[(x, y)] = self.current if state != Tile.END: self.grid[x][y] = Tile.state_to_int(Tile.NEIGHBOURS) return vectors
def tile(self, coord) -> Union[Tile, None]: """ :returns: tile depending on value in case of tuple :return: tile in pixel positon (x, y) """ # finding tile position = Vector2D.tuple(coord) x = (position.y - self.position.y - self.padding.y) / (self.tilesize.y + self.tilepadding.y) y = (position.x - self.position.x - self.padding.x) / (self.tilesize.x + self.tilepadding.x) ix = math.floor(x) iy = math.floor(y) # out of bounds if ix < 0 or ix >= self.size.x or iy < 0 or iy >= self.size.y: return return self.tiles[ix][iy]
def all_tiles(self): size = Vector2D(len(self.tiles), len(self.tiles[0])) for x in range(size.x): for y in range(size.y): yield self.tiles[x][y]
def draw(self, surface): size = Vector2D(len(self.tiles), len(self.tiles[0])) for x in range(size.x): for y in range(size.y): self.tiles[x][y].draw(surface)
def event(self, event): if event.type == pygame.KEYUP: if event.key == pygame.K_SPACE: if not self.drawable.get(): easygui.msgbox('Press either Left Ctrl or Left Shift to clear the current grid' '\nLeft Ctrl: Everything excluding walls' '\nLeft Shift: Everything including walls', 'Clear grid', ok_button='CLOSE') return self.update_grid() if self.start is None or self.end is None: easygui.msgbox('Starting point or End point not specified', 'Missing inputs', 'CLOSE') return # lock input and start the a* algorithm self.drawable.set(False) self.algorithm = AStarAlgorithm(self.grid) self.running.set(True) if event.key == pygame.K_LSHIFT: if self.running.get(): return self.update_grid() self.clean_grid([Tile.VISITED, Tile.START, Tile.END, Tile.PATH, Tile.NEIGHBOURS, Tile.WALL]) self.update_tiles(self.grid) self.drawable.set(True) if event.key == pygame.K_LCTRL: if self.running.get(): return self.update_grid() self.clean_grid([Tile.VISITED, Tile.START, Tile.END, Tile.PATH, Tile.NEIGHBOURS]) self.update_tiles(self.grid) self.drawable.set(True) if event.type == pygame.MOUSEBUTTONDOWN: # left if event.button == 1: self.mouse_left_down.set(True) if self.drawable.get(): # for all tiles check and initiate drawing for tile in self.all_tiles(): if tile.inbound(Vector2D.tuple(event.pos)): currentstate = tile.state if currentstate == Tile.WALL: self.mouse_left_down_type = Tile.UNVISITED elif currentstate == Tile.UNVISITED: self.mouse_left_down_type = Tile.WALL if self.mouse_left_down_type is not None: tile.state = self.mouse_left_down_type # right if event.button == 3: if self.drawable.get(): if self.start is None or self.end is None: for tile in self.all_tiles(): if tile.inbound(Vector2D.tuple(event.pos)): if self.start is None: self.start = tile tile.state = Tile.START self.info_text.text = 'Select end' else: self.end = tile tile.state = Tile.END self.info_text.text = 'Ready' else: print('Start and end has been selected') if event.type == pygame.MOUSEBUTTONUP: # left if event.button == 1: self.mouse_left_down.set(False) self.mouse_left_down_type = None if event.type == pygame.MOUSEMOTION: # left if self.mouse_left_down.get(): if self.drawable.get(): # for all tiles draw / change state for tile in self.all_tiles(): if tile.state == Tile.START or tile.state == Tile.END: continue if tile.inbound(Vector2D.tuple(event.pos)): if self.mouse_left_down_type is not None: tile.state = self.mouse_left_down_type
def position(self, value): self._position = value self.text.center(Vector2D.center(self.position, self.size))