Exemple #1
0
class NonogramGUI(QtGui.QFrame):
    """ Implement QFrame, which is a subclass of QWidget """
    status_message = pyqtSignal(str)

    def __init__(self, parent):
        QtGui.QFrame.__init__(self, parent)

        self.dx = self.dy = 1
        self.widget_width_px = self.widget_height_px = 600

        self.nonogram_state = None
        self.set_nonogram(True)
        self.delay = 50
        self.mode = C.search_mode.A_STAR
        self.thread = SearchWorker(self)
        self.init_ui()

    def init_ui(self):
        """ Initialize the UI """
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        size = QtCore.QSize(self.widget_width_px, self.widget_height_px)
        self.setMinimumSize(size)
        self.parent().adjustSize()

    def level_loaded(self, nonogram):
        """ Called whenever a level is loaded, adjust Widget size """
        self.nonogram_state = NonogramState(nonogram, None)

        self.compute_tile_size()

    def compute_tile_size(self):
        """ Computes tile size based on widget size and nonogram """
        self.dx = self.dy = 1
        x, y = self.nonogram_state.state.x, self.nonogram_state.state.y

        self.dx *= (self.widget_width_px / float(x))
        self.dy *= (self.widget_height_px / float(y))

    def start_search(self):
        """ Start the search in the worker thread """
        self.status_message.emit(str('Search started'))
        search = NonogramBfs(self.nonogram_state.state, self)
        self.thread.search(search)

    def set_solution(self, solution):
        """ Receives a solution from search and sets the node """
        self.nonogram_state = NonogramState(
            solution.state,
            None,
            solution.domains,
            solution.solution_length
        )

    def paint(self, state):
        """ Receives a node and tells Qt to update the graphics """
        self.nonogram_state = state

        self.update()

    def paintEvent(self, _):  # pylint: disable=invalid-name
        """ Called by the Qt event loop when the widget should be updated """
        painter = QtGui.QPainter(self)
        self.draw_nonogram(painter)

    def resizeEvent(self, e):  # pylint: disable=invalid-name
        """ Handles widget resize and scales Nonogram """
        self.widget_width_px = e.size().width()
        self.widget_height_px = e.size().height()
        self.compute_tile_size()

    def draw_nonogram(self, painter):
        """ Draws a Nonogram, WHITE should be treated as an error. """
        colors = {
            C.colors.GREY: QtGui.QColor(130, 130, 130),  # Unset
            C.colors.PINK: QtGui.QColor(255, 255, 150),  # Drawn
            C.colors.YELLOW: QtGui.QColor(255, 0, 150),  # Space
            C.colors.WHITE: QtGui.QColor(255, 255, 255)  # No domains
        }

        for y, row in enumerate(self.nonogram_state.representation()):
            for x, element in enumerate(row):
                painter.setBrush(colors[int(element)])
                painter.drawRect(x*self.dx, y*self.dy, self.dx - 1, self.dy - 1)

    def set_nonogram(self, default=False):
        """ Load level with a QFileDialog """
        folder = res.nonograms.__path__[0]
        if default:
            path = folder + '/nono-heart-1.txt'
        else:
            path = QtGui.QFileDialog.getOpenFileName(
                self.window(), "Nonogram Selector", folder, "Text files (*.txt)"
            )
            if not path:
                return

        nonogram_file = open(path, 'r')
        contents = [line.strip() for line in nonogram_file.read().splitlines()]

        self.level_loaded(Nonogram(contents))

        filename = path.split('/')[-1]
        title = 'Module 3 - Nonogram - {}'.format(filename)
        self.parent().setWindowTitle(title)
        self.status_message.emit(str('Loaded: {}'.format(filename)))
        self.update()

    def set_delay(self, delay):
        """ Change delay """
        self.delay = delay
        self.status_message.emit('Delay: ' + str(delay))
Exemple #2
0
class NavigationGUI(QtGui.QFrame):
    # pylint: disable=too-many-instance-attributes
    """ Implement QFrame, which is a subclass of QWidget """
    status_message = pyqtSignal(str)

    def __init__(self, parent):
        QtGui.QFrame.__init__(self, parent)

        self.dx = self.dy = 0
        self.widget_width_px = self.widget_height_px = 600

        self.delay = 50
        self.diagonal = False
        self.mode = C.search_mode.A_STAR
        self.heuristics_type = 'euclidean'

        self.node = None
        self.opened = None
        self.closed = None
        self.set_map(True)
        self.thread = SearchWorker(self)
        self.init_ui()

    def init_ui(self):
        """ Initialize the UI """
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        minimum_size = QtCore.QSize(self.widget_width_px, self.widget_height_px)
        self.setMinimumSize(minimum_size)
        self.parent().adjustSize()

    def level_loaded(self, map_):
        """ Called whenever a level is loaded, adjust Widget size """
        self.node = NavigationState(map_)
        self.opened, self.closed = None, None
        self.compute_tile_size()

    def compute_tile_size(self):
        """ Computes tile size based on widget size and map """
        self.dx = self.widget_width_px / float(self.node.state.x_dim())
        self.dy = self.widget_height_px / float(self.node.state.y_dim())

    def start_search(self):
        """ Start the search in the worker thread """
        navigation = NavigationBfs(self.node.state, self)

        self.status_message.emit(str('Search started'))
        self.thread.search(navigation)

    def set_solution(self, solution):
        """ Receives a solution from search and sets the node """
        self.node = NavigationState(
            self.node.state,
            solution.visited,
            solution.current_pos
        )

    def set_opened_closed(self, opened, closed):
        """ Sets the opened and closed lists of states """
        self.opened = opened
        self.closed = closed

    def paint(self, node):
        """ Receives a node and tells Qt to update the graphics """
        self.node = node

        self.update()

    def paintEvent(self, _):  # pylint: disable=invalid-name
        """ Called by the Qt event loop when the widget should be updated """
        if self.node is None:
            return

        painter = QtGui.QPainter(self)
        self.paint_map(painter)

    def resizeEvent(self, e):  # pylint: disable=invalid-name
        """ Handles widget resize and scales Navigation """
        self.widget_width_px = e.size().width()
        self.widget_height_px = e.size().height()
        self.compute_tile_size()

    def is_visited(self, x, y):
        """ Checks whether (x,y) is visited in the current state """
        y_dim = self.node.state.y_dim()
        pos = [x, y_dim - y - 1]
        return pos in self.node.visited

    def is_on_frontier(self, x, y):
        """ Checks whether (x,y) is in the opened list """
        if not self.opened:
            return False

        y_dim = self.node.state.y_dim()

        for node in self.opened:
            if isinstance(node, tuple):
                node = node[1]  # Heapq for astar, normal list for BFS/DFS

            if [x, y_dim - y - 1] == node.visited[-1]:
                return True

        return False

    def is_closed(self, x, y):
        """ Checks whether (x,y) is in the closed list """
        if not self.closed:
            return False

        y_dim = self.node.state.y_dim()

        for node in self.closed:
            if [x, y_dim - y - 1] == node.visited[-1]:
                return True

        return False

    def get_color(self, x, y, visited=False, frontier=False, closed=False):
        # pylint: disable=too-many-arguments
        """ Return a QColor based on the tile and whether it is visited """

        if visited:
            color = {
                C.nav_tile.TILE:  QtGui.QColor(80, 80, 80),
                C.nav_tile.START: QtGui.QColor(153, 204, 255),
                C.nav_tile.GOAL:  QtGui.QColor(0, 255, 0)
            }[self.node.state.grid[y][x]]
        elif frontier:
            color = QtGui.QColor(255, 255, 80)
        elif closed:
            color = QtGui.QColor(210, 210, 210)
        else:
            color = {
                C.nav_tile.TILE:     QtGui.QColor(255, 255, 255),
                C.nav_tile.OBSTACLE: QtGui.QColor(255, 0, 0),
                C.nav_tile.START:    QtGui.QColor(0, 0, 255),
                C.nav_tile.GOAL:     QtGui.QColor(51, 102, 0)
            }[self.node.state.grid[y][x]]

        return color

    def draw(self, x, y, painter):
        """ Draws rectangles, either with a black border or without a border.
         If the tile is visited we draw a smaller rectangle on top.
        """
        color = self.get_color(x, y)
        dx, dy = self.dx, self.dy

        if self.node.state.grid[y][x] is C.nav_tile.OBSTACLE:
            painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1))
        else:
            painter.setPen(QtGui.QPen(color, 1))
        painter.setBrush(color)
        painter.drawRect((x * dx), (y * dy), dx - 1, dy - 1)

        is_visited = self.is_visited(x, y)
        is_on_frontier = self.is_on_frontier(x, y)
        is_closed = self.is_closed(x, y)
        if is_visited or is_on_frontier or is_closed:
            color = self.get_color(x, y, is_visited, is_on_frontier, is_closed)
            painter.setPen(QtGui.QPen(color, 1))
            painter.setBrush(color)
            painter.drawRect((x * dx) + 4, (y * dy) + 4, dx - 9, dy - 9)

    def paint_map(self, painter):
        """ Called by the paintEvent, we iterate over the map and draw tiles """
        for y in range(self.node.state.y_dim()):
            for x in range(self.node.state.x_dim()):
                self.draw(x, y, painter)

    def set_map(self, default=False):
        """ Load level with a QFileDialog """
        folder = res.maps.__path__[0]
        if default:
            path = folder + '/ex0.txt'
        else:
            path = QtGui.QFileDialog.getOpenFileName(
                self.window(), "Open Map File", folder, "Text files (*.txt)"
            )
            if not path:
                return

        map_file = open(path, 'r')
        contents = [line.strip() for line in map_file.read().splitlines()]

        self.level_loaded(Map(contents))

        filename = path.split('/')[-1]
        self.parent().setWindowTitle(
            'Module 1 - Navigation - {}'.format(filename)
        )
        self.status_message.emit(str('Loaded: {}'.format(filename)))
        self.update()

    def set_mode(self, mode):
        """ Chainable method for use in lambdas, change mode """
        self.mode = mode
        return True

    def set_delay(self, delay):
        """ Change delay """
        self.delay = delay
        self.status_message.emit('Delay: ' + str(delay))

    def set_diagonal(self, is_diagonal):
        """ Set diagonal mode """
        self.diagonal = is_diagonal
        self.status_message.emit('Diagonal mode: ' + str(is_diagonal))

    def set_heuristics_type(self, heuristics_type):
        """ Set heuristics """
        self.heuristics_type = heuristics_type
        message = 'Heuristics: ' + str(heuristics_type) + ' distance'
        self.status_message.emit(message)
Exemple #3
0
class GraphGUI(QtGui.QFrame):
    # pylint: disable=too-many-instance-attributes
    """ Implement QFrame, which is a subclass of QWidget """
    status_message = pyqtSignal(str)

    def __init__(self, parent):
        QtGui.QFrame.__init__(self, parent)

        self.dx = self.dy = 1
        self.widget_width_px = self.widget_height_px = 600
        # Adjustments for negative coordinates in graphs.
        self.nc_adjust_x = self.nc_adjust_y = 0
        self.total_height = 0
        self.vertex_radii = 5

        self.num_colors = 4
        self.delay = 50
        self.vertex_numbering = False
        self.node = None
        self.set_graph(True)
        self.mode = C.search_mode.A_STAR
        self.thread = SearchWorker(self)
        self.init_ui()

    def init_ui(self):
        """ Initialize the UI """
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        size = QtCore.QSize(self.widget_width_px, self.widget_height_px)
        self.setMinimumSize(size)
        self.parent().adjustSize()

    def level_loaded(self, graph):
        """ Called whenever a level is loaded, adjust Widget size """
        self.node = VertexColoringState(graph, None, self.num_colors)

        self.compute_tile_size()

    def compute_tile_size(self):
        """ Computes tile size based on widget size and graph """
        self.nc_adjust_x = self.nc_adjust_y = 0

        width_orig = self.node.state.width
        height_orig = self.node.state.height

        self.dx = self.dy = 1
        self.dx = self.widget_width_px / float(width_orig)
        self.dy = self.widget_height_px / float(height_orig)

        self.nc_adjust_x = self.node.state.min_x * self.dx
        self.nc_adjust_y = self.node.state.min_y * self.dy

    def start_search(self):
        """ Start the search in the worker thread """
        self.status_message.emit(str('Search started'))
        self.thread.search(VertexColoringBfs(self.node.state, self))

    def set_solution(self, solution):
        """ Receives a solution from search and sets the node """
        self.node = VertexColoringState(
            solution.state,
            None,
            self.num_colors,
            solution.domains,
            solution.solution_length
        )

    def paint(self, node):
        """ Receives a node and tells Qt to update the graphics """
        self.node = node
        self.update()

    def paintEvent(self, _): # pylint: disable=invalid-name
        """ Called by the Qt event loop when the widget should be updated """
        if self.node is None:
            return

        painter = QtGui.QPainter(self)
        self.paint_graph(painter)

    def resizeEvent(self, e): # pylint: disable=invalid-name
        """ Handles widget resize and scales Graph """
        self.widget_width_px = e.size().width()
        self.widget_height_px = e.size().height()
        self.compute_tile_size()

    def draw_vertex(self, vertex, painter, whites=False):
        """ Draws a vertex, either all the white nodes (with a domain length
        larger than 1) or all the colored nodes. Also draws optional
        vertex numbers. """
        x = (vertex[1] * self.dx) - self.nc_adjust_x
        y = (vertex[2] * self.dy) - self.nc_adjust_y
        y = self.widget_height_px - y
        point = QtCore.QPoint(x, y)

        colors = {
            C.graph_colors.RED: QtGui.QColor(255, 128, 0),
            C.graph_colors.GREEN: QtGui.QColor(0, 200, 0),
            C.graph_colors.BLUE: QtGui.QColor(0, 0, 255),
            C.graph_colors.ORANGE: QtGui.QColor(175, 0, 100),
            C.graph_colors.PINK: QtGui.QColor(255, 20, 147),
            C.graph_colors.YELLOW: QtGui.QColor(255, 255, 0),
            C.graph_colors.PURPLE: QtGui.QColor(238, 130, 238),
            C.graph_colors.BROWN: QtGui.QColor(222, 184, 135),
            C.graph_colors.CYAN: QtGui.QColor(0, 255, 255),
            C.graph_colors.DARK_BROWN: QtGui.QColor(120, 60, 0),
            C.graph_colors.WHITE: QtGui.QColor(255, 255, 255),
            C.graph_colors.BLACK: QtGui.QColor(0, 0, 0),
        }
        vertex_color = self.node.vertex_color(vertex[0])
        color = colors[vertex_color]

        painter.setPen(color)
        painter.setBrush(color)

        if whites and vertex_color is C.graph_colors.WHITE:
            painter.drawEllipse(point, self.vertex_radii, self.vertex_radii)
        elif not whites and vertex_color is not C.graph_colors.WHITE:
            painter.drawEllipse(point, self.vertex_radii, self.vertex_radii)

        if whites and self.vertex_numbering:
            painter.setPen(colors[C.graph_colors.BLACK])
            painter.drawText(x, y, str(vertex[0]))

    def draw_edge(self, edge, painter):
        """ Draws edge between two vertices """
        v1 = self.node.state.vertices[edge[0]]
        v2 = self.node.state.vertices[edge[1]]

        x1 = (v1[1] * self.dx) - self.nc_adjust_x
        y1 = (v1[2] * self.dy) - self.nc_adjust_y
        x2 = (v2[1] * self.dx) - self.nc_adjust_x
        y2 = (v2[2] * self.dy) - self.nc_adjust_y

        y1, y2 = self.widget_height_px - y1, self.widget_height_px - y2

        p1, p2 = QtCore.QPoint(x1, y1), QtCore.QPoint(x2, y2)

        painter.setPen(QtGui.QColor(120, 120, 120))
        painter.drawLine(p1, p2)

    def paint_graph(self, painter):
        """ The graph is painted by edges, unset and lastly colored vertices """
        for edge in self.node.state.edges:
            self.draw_edge(edge, painter)

        # Draw white vertices first
        for vertex in self.node.state.vertices:
            self.draw_vertex(vertex, painter, True)

        for vertex in reversed(self.node.state.vertices):
            self.draw_vertex(vertex, painter)

    def set_graph(self, default=False):
        """ Load level with a QFileDialog """
        folder = res.graphs.__path__[0]
        if default:
            path = folder + '/graph-color-1.txt'
        else:
            path = QtGui.QFileDialog.getOpenFileName(
                self.window(), "Open graph file", folder, "Text files (*.txt)"
            )
            if not path:
                return

        graph_file = open(path, 'r')
        contents = [line.strip() for line in graph_file.read().splitlines()]

        self.level_loaded(Graph(contents))

        filename = path.split('/')[-1]
        self.parent().setWindowTitle('Module 2 - A*-GAC - {}'.format(filename))
        self.status_message.emit(str('Loaded: {}'.format(filename)))
        self.update()

    def set_color(self, colors):
        """ Change color and notify user """
        self.num_colors = colors
        self.status_message.emit('Amount of colors available: ' + str(colors))

    def set_vertex_numbering(self, numbering):
        """ Boolean numbering decides if we show vertex numbers. """
        self.vertex_numbering = numbering
        if numbering:
            self.status_message.emit('Showing vertex numbers')
        else:
            self.status_message.emit('Hiding vertex numbers')
        self.update()

    def set_delay(self, delay):
        """ Change delay """
        self.delay = delay
        self.status_message.emit('Delay: ' + str(delay))