суббота, 21 июля 2012 г.

Simple maze solver with python and pyside

I'm practicing in Python, PySide and some algorithms. You can find a simple maze generator/solver below.





from PySide.QtCore import *
from PySide.QtGui import *
import sys
import random
 
class Maze(QWidget):
    def __init__(self, width, height, boxsize=7):
        QWidget.__init__(self)
 
        self.setMazeSize(width, height, boxsize)
        self.generateMazeDFS()
 
    def setMazeSize(self, width, height, boxsize):
        self.maze_width = width
        self.maze_height = height
        self.boxsize = boxsize
        self.setFixedSize(width * boxsize + 10, height * boxsize + 10)
 
    def generateMazeDumb(self, width=None, height=None, sparseness=None):
        if width is None: width = self.maze_width
        if height is None: height = self.maze_height
        if sparseness is None: sparseness = 2
 
        self.vwall = [[random.randint(0,sparseness) for x in range(0, width+1)] for y in range(0, height)]
        self.hwall = [[random.randint(0,sparseness) for x in range(0, width)] for y in range(0, height+1)]
        for x in range(0, width):
            self.hwall[0][x] = 0
            self.hwall[height][x] = 0
        for y in range(0, height):
            self.vwall[y][0] = 0
            self.vwall[y][width] = 0
 
    def generateMazeDFS(self, width=None, height=None, seed_point=None):
        if width is None: width = self.maze_width
        if height is None: height = self.maze_height
        if seed_point is None: seed_point = (random.randint(0, width-1), random.randint(0, height-1))
 
        self.vwall = [[0 for x in range(0, width+1)] for y in range(0, height)]
        self.hwall = [[0 for x in range(0, width)] for y in range(0, height+1)]
 
        maze = [[0 for x in range(0, self.maze_width)] for y in range(0, self.maze_height)]
 
        path = [seed_point]
        while True:
            possible_moves = []
            x, y = path[-1]
 
            if x > 0 and maze[y][x-1] == 0: possible_moves.append('left')
            if x < width-1 and maze[y][x+1] == 0: possible_moves.append('right')
            if y > 0 and maze[y-1][x] == 0: possible_moves.append('up')
            if y < height-1 and maze[y+1][x] == 0: possible_moves.append('down')
 
            if len(possible_moves) > 0:
                next_move = random.choice(possible_moves)
                maze[y][x] = 1
                if next_move == 'left':
                    self.vwall[y][x] = 1
                    path.append((x-1, y))
                    continue
                elif next_move == 'right':
                    self.vwall[y][x+1] = 1
                    path.append((x+1, y))
                    continue
                elif next_move == 'up':
                    self.hwall[y][x] = 1
                    path.append((x, y-1))
                    continue
                elif next_move == 'down':
                    self.hwall[y+1][x] = 1
                    path.append((x, y+1))
                    continue
            else:
                maze[y][x] = -1
                path.pop()
                if len(path) == 0:
                    break
 
    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        qp.fillRect(0, 0, self.maze_width * self.boxsize + 10, self.maze_height * self.boxsize + 10, Qt.black)
 
        qp.translate(QPoint(5.5, 5.5))
        qp.setPen(Qt.white)
        b = self.boxsize
 
        # vertical walls
        y = 0
        for row in self.vwall:
            x = 0
            for w in row:
                if w == 0: qp.drawLine(x*b, y*b, x*b, (y+1)*b)
                x += 1
            y += 1
 
        # horizontal walls
        y = 0
        for row in self.hwall:
            x = 0
            for w in row:
                if w == 0: qp.drawLine(x*b, y*b, (x+1)*b, y*b)
                x += 1
            y += 1
 
        # path
        prev = None
        for point in self.path:
            x1 = point[0]
            y1 = point[1]
            if prev:
                x2 = prev[0]
                y2 = prev[1]
                qp.fillRect(b*min(x1,x2) + 2, b*min(y1,y2) + 2, b*abs(x1-x2)+b-4, b*abs(y1-y2)+b-4, Qt.green)
            else:
                qp.fillRect(x*b+2, y*b+2, b-4, b-4, Qt.green)
            prev = point
 
        # visited cells
        y = 0
        for row in self.maze:
            x = 0
            for m in row:
                if m == -1: qp.fillRect(x*b+2, y*b+2, b-3, b-3, Qt.gray)
                x += 1
            y += 1
 
        # target
        x, y = self.target
        qp.fillRect(x*b+2, y*b+2, b-3, b-3, Qt.red)
 
        # status
        if self.status:
            qp.setPen(Qt.white)
            qp.setFont(QFont("sans", 72, QFont.Normal))
            qp.drawText(QRect(0, 0, self.maze_width * b, self.maze_height * b),
                Qt.AlignCenter | Qt.AlignVCenter,
                self.status)
 
        qp.end()
 
    def step(self):
        self.step_num += 1
        x, y = self.path[-1]
 
        if (x, y) == self.target:
            self.status = "win"
            return False
 
        if self.step_select == 'random':
            order_list = ['u', 'r', 'd', 'l']
            random.shuffle(order_list)
        else:
            order_list = self.step_select
 
        for order in order_list:
            if order == 'u':
                # try step up
                if y > 0 and self.hwall[y][x] != 0 and self.maze[y-1][x] == 0:
                    self.path.append((x, y-1))
                    self.maze[y-1][x] = self.step_num
                    return True
            elif order == 'r':
                # try step right
                if x < self.maze_width-1 and self.vwall[y][x+1] != 0 and self.maze[y][x+1] == 0:
                    self.path.append((x+1, y))
                    self.maze[y][x+1] = self.step_num
                    return True
            elif order == 'd':
                # try step down
                if y < self.maze_height-1 and self.hwall[y+1][x] != 0 and self.maze[y+1][x] == 0:
                    self.path.append((x, y+1))
                    self.maze[y+1][x] = self.step_num
                    return True
            elif order == 'l':
                # try step left
                if x > 0 and self.vwall[y][x] != 0 and self.maze[y][x-1] == 0:
                    self.path.append((x-1, y))
                    self.maze[y][x-1] = self.step_num
                    return True
 
        # step back
        self.maze[y][x] = -1
        self.path.pop()
 
        if len(self.path) == 0:
            self.status = "loss"
            return False
 
        return True
 
    def init(self):
        self.maze = [[0 for x in range(0, self.maze_width)] for y in range(0, self.maze_height)]
        self.path = [(0, 0)]
        self.step_num = 1
        x, y = self.path[0]
        self.maze[y][x] = 1
        self.target = (self.maze_width-1, self.maze_height-1)
        self.status = False
 
 
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("Maze")
 
        self.maze = Maze(150, 80, 7)
 
        hbox = QHBoxLayout()
        hbox.setContentsMargins(0, 0, 10, 0)
        hbox.addWidget(self.maze)
        vbox = QVBoxLayout()
        vbox.setContentsMargins(0, 10, 0, 0)
        hbox.addLayout(vbox)
 
        self.le_w = QLineEdit("150")
        vbox.addWidget(QLabel("maze width:"))
        vbox.addWidget(self.le_w)
        self.le_h = QLineEdit("80")
        vbox.addWidget(QLabel("maze height:"))
        vbox.addWidget(self.le_h)
        self.le_b = QLineEdit("7")
        vbox.addWidget(QLabel("cell size:"))
        vbox.addWidget(self.le_b)
 
        self.dm_sparse = QLineEdit("2")
        vbox.addWidget(QLabel("Dumb mode sparseness:"))
        vbox.addWidget(self.dm_sparse)
 
        self.maze_type = QButtonGroup()
        rb_1 = QRadioButton("Dumb random maze")
        rb_1.setChecked(True)
        rb_2 = QRadioButton("DFS maze generator")
        self.maze_type.addButton(rb_1, 0)
        self.maze_type.addButton(rb_2, 1)
        vbox.addWidget(rb_1)
        vbox.addWidget(rb_2)
 
        self.btn_generate = QPushButton("Generate")
        self.btn_generate.clicked.connect(self.generateMaze)
        vbox.addWidget(self.btn_generate)
 
        self.step_order = QLineEdit("random")
        self.step_order.setPlaceholderText("step order")
        vbox.addWidget(self.step_order)
 
        self.btn_solve = QPushButton("Solve")
        self.btn_solve.clicked.connect(self.startSolving)
        vbox.addWidget(self.btn_solve)
 
        self.btn_stop = QPushButton("Stop")
        self.btn_stop.setEnabled(False)
        self.btn_stop.clicked.connect(self.stopSolving)
        vbox.addWidget(self.btn_stop)
 
        vbox.addStretch()
 
        proxy_widget = QWidget()
        proxy_widget.setLayout(hbox)
        self.setCentralWidget(proxy_widget)
 
        self.maze.init()
 
    def doStep(self):
        for k in range(0, 50):
            if not self.maze.step():
                self.timer.stop()
                self.stopSolving()
                break
        self.maze.repaint()
 
    def startSolving(self):
        self.btn_solve.setEnabled(False)
        self.btn_stop.setEnabled(True)
        self.btn_generate.setEnabled(False)
        self.maze.init()
 
        self.maze.step_select = self.step_order.text()
        if self.maze.step_select == '':
            self.maze.step_select = 'random'
 
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.doStep)
        self.timer.setInterval(50)
        self.timer.start()
 
    def stopSolving(self):
        self.btn_stop.setEnabled(False)
        self.btn_solve.setEnabled(True)
        self.btn_generate.setEnabled(True)
        self.timer.stop()
 
    def generateMaze(self):
        w = max(2, int(self.le_w.text()))
        h = max(2, int(self.le_h.text()))
        b = max(5, int(self.le_b.text()))
        sparseness = max(0, int(self.dm_sparse.text()))
 
        self.maze.setMazeSize(w, h, b)
        self.maze.init()
 
        if self.maze_type.checkedId() == 0:
            self.maze.generateMazeDumb(sparseness=sparseness)
        elif self.maze_type.checkedId() == 1:
            self.maze.generateMazeDFS()
        self.maze.repaint()
 
 
app = QApplication(sys.argv)
 
mw = MainWindow()
mw.show()
 
sys.exit(app.exec_())
 


2 комментария:

  1. thank you so much for your post, but could you add more comment to your code?

    ОтветитьУдалить
  2. I don't see any place that should be commented. If you look for deep-first search algorithm, try searching wikipedia. It has nice short description of DFS.

    ОтветитьУдалить