PyQt5实现求解三角板拼成残缺棋盘的所有情况(行列数,残缺数量可定)

https://www.bilibili.com/video/av41192590

原理,回溯法:

算法描述:

#1,随机选择一个空棋盘(不被三角板占用,不是残缺位置)
#2,然后讨论这个包含整个位置的所有可能情况,成为四种三角板中的任何一个位置(共12个),这样从由改点拼成的三角板最多有12种可能,设为n,n<=12
#3,这样无论从哪里开始拼图,都可以得到所有的解
#4,把这n种可能性都压入栈(和栈中元素不一样的才压入栈),栈元素是某个状态下的棋盘。栈中元素格式为(棋盘,当前已有三角板个数)
#5,取出栈中棋盘,回到步骤1
#6,如果栈某个元素三角板个数==(rows*cols-1)/3,,则作为可行解
#7,由于规则和顺序固定,所以应该不会有重复,免去了重复的计算过程

运行效果: 

PyQt5实现求解三角板拼成残缺棋盘的所有情况(行列数,残缺数量可定)_第1张图片

 

import random
import numpy as np
import cv2
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'E:\python\chessboard\main.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QPixmap,QPainter, QFont, QColor, QPen,QIcon,QBrush

import sys
from PyQt5.QtWidgets import QApplication,QMainWindow,QLabel,\
    QComboBox,QPushButton,QSlider,QMessageBox

class Ui_MainWindow(QMainWindow):
    def __init__(self):
        super(Ui_MainWindow, self).__init__()
        self.setGeometry(50,50,1050, 1000)

        self.comboBox = QtWidgets.QComboBox(self)
        self.comboBox.setGeometry(QtCore.QRect(20, 70, 111, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)

        self.comboBox.setFont(font)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("2")
        self.comboBox.addItem("4")
        self.comboBox.addItem("5")
        self.comboBox.addItem("7")
        self.comboBox.addItem("8")
        self.comboBox.addItem("10")

        self.label=QLabel(self)
        self.label.setGeometry(QtCore.QRect(20, 30, 131, 21))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(False)
        font.setWeight(50)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.comboBox_2=QComboBox(self)
        self.comboBox_2.setGeometry(QtCore.QRect(310, 70, 131, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)
        self.comboBox_2.setFont(font)
        self.comboBox_2.setObjectName("comboBox_2")
        self.label_2=QLabel(self)
        self.label_2.setGeometry(QtCore.QRect(310, 40, 131, 21))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(False)
        font.setWeight(50)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")

        self.label_3=QLabel(self)
        self.label_3.setGeometry(QtCore.QRect(0, 160, 601, 511))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label_3.setFont(font)
        self.label_3.setAutoFillBackground(True)
        self.label_3.setFrameShape(QtWidgets.QFrame.Box)
        self.label_3.setFrameShadow(QtWidgets.QFrame.Raised)
        self.label_3.setLineWidth(2)
        self.label_3.setScaledContents(True)
        self.label_3.setObjectName("label_3")

        self.comboBox_3=QComboBox(self)
        self.comboBox_3.setGeometry(QtCore.QRect(150, 70, 141, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)
        self.comboBox_3.setFont(font)
        self.comboBox_3.setAutoFillBackground(False)
        self.comboBox_3.setObjectName("comboBox_3")
        self.comboBox_3.addItem("1")
        self.comboBox_3.addItem("2")
        self.comboBox_3.addItem("3")
        self.comboBox_3.addItem("4")

        self.label_4=QLabel(self)
        self.label_4.setGeometry(QtCore.QRect(150, 40, 151, 21))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(False)
        font.setWeight(50)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")

        self.pushButton=QPushButton(self)
        self.pushButton.setGeometry(QtCore.QRect(460, 70, 131, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")


        self.label_5=QLabel(self)
        self.label_5.setGeometry(QtCore.QRect(810, 250, 151, 21))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(False)
        font.setWeight(50)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")

        self.label_v=QLabel(self)
        self.label_v.setGeometry(QtCore.QRect(960, 190, 35, 35))
        self.label_v.setFont(font)

        self.horizontalSlider=QSlider(self)
        self.horizontalSlider.setGeometry(QtCore.QRect(810, 200, 141, 31))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.horizontalSlider.setFont(font)
        self.horizontalSlider.setMinimum(10)
        self.horizontalSlider.setMaximum(100)
        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
        self.horizontalSlider.setTickPosition(QtWidgets.QSlider.TicksBothSides)
        self.horizontalSlider.setObjectName("horizontalSlider")


        self.label_6=QLabel(self)
        self.label_6.setGeometry(QtCore.QRect(820, 170, 141, 21))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(False)
        font.setWeight(50)
        self.label_6.setFont(font)
        self.label_6.setObjectName("label_6")

        self.comboBox_4=QComboBox(self)
        self.comboBox_4.setGeometry(QtCore.QRect(820, 280, 111, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.comboBox_4.setFont(font)
        self.comboBox_4.setObjectName("comboBox_4")

        self.pushButton_2=QPushButton(self)
        self.pushButton_2.setGeometry(QtCore.QRect(820, 340, 111, 41))
        self.pushButton_2.setObjectName("pushButton_2")

        self.label_count_tri_type = QLabel(self)
        self.label_count_tri_type.setGeometry(QtCore.QRect(820, 400, 111, 91))


        self.timer = QTimer(self)
        self.timer.timeout.connect(self.tick)


        self.comboBox_2.currentTextChanged['QString'].connect(self.resize_label)
        self.horizontalSlider.valueChanged.connect(self.show_v)
        self.comboBox.currentTextChanged['QString'].connect(self.yeild_n_combox_items)
        self.comboBox_3.currentTextChanged['QString'].connect(self.yeild_n_combox_items)
        self.pushButton_2.clicked.connect(self.timer_control)
        self.pushButton.clicked.connect(self.resolve_problem)

        self.results = []
        _translate = QtCore.QCoreApplication.translate

        self.comboBox.setItemText(0, _translate("MainWindow", "2"))
        self.comboBox.setItemText(1, _translate("MainWindow", "4"))
        self.comboBox.setItemText(2, _translate("MainWindow", "5"))
        self.comboBox.setItemText(3, _translate("MainWindow", "7"))
        self.comboBox.setItemText(4, _translate("MainWindow", "8"))
        self.comboBox.setItemText(5, _translate("MainWindow", "10"))
        self.label.setText(_translate("MainWindow", "选择棋盘行数"))
        self.label_2.setText(_translate("MainWindow", "选择棋盘列数"))
        self.label_3.setText(_translate("MainWindow", "TextLabel"))
        self.comboBox_3.setItemText(0, _translate("MainWindow", "1"))
        self.comboBox_3.setItemText(1, _translate("MainWindow", "2"))
        self.comboBox_3.setItemText(2, _translate("MainWindow", "3"))
        self.comboBox_3.setItemText(3, _translate("MainWindow", "4"))
        self.label_4.setText(_translate("MainWindow", "选择残缺位置数"))
        self.pushButton.setText(_translate("MainWindow", "覆盖棋盘求解"))
        self.label_5.setText(_translate("MainWindow", "演示第几种方案"))
        self.label_6.setText(_translate("MainWindow", "选择显示速度"))
        self.pushButton_2.setText(_translate("MainWindow", "开始演示"))
        self.label_v.setText(_translate("MainWindow", str(self.horizontalSlider.value())))
        self.yeild_n_combox_items()
        self.show()


    def draw_img(self,pix,_board,idx):

        my_painter=QPainter()
        my_painter.begin(pix)

        color_list=[QColor.fromRgb(255,255,255),QColor.fromRgb(255,228,181),QColor.fromRgb(	65,105,225),QColor.fromRgb(124,205,124),QColor.fromRgb(125,38,205)]
        for _r in range(_board.rows):
            for _c in range(_board.cols):

                cube_type = _board.history[idx][_r][_c]
                pen=QPen(QColor.fromRgb(255,255,255),3)
                my_painter.setPen(pen)

                my_painter.setBrush(QColor.fromRgb(255,255,255))
                if cube_type == -1:
                    my_painter.fillRect(_c*80,_r*80,80,80,QColor(0,0,0))
                    my_painter.drawLine(_c*80,_r*80,_c*80+80,_r*80+80)
                    my_painter.drawLine(_c*80+80,_r*80,_c*80,_r*80+80)
                    continue

                color=color_list[cube_type]


                my_painter.fillRect(_c*80,_r*80,80,80,color)

        my_painter.end()
        return pix

    def tick(self):

        pix=QPixmap(self.board_to_show.cols*80,self.board_to_show.rows*80)
        pix = self.draw_img(pix,self.board_to_show, self.ticks)
        self.label_3.setPixmap(pix)
        self.ticks+=1
        if self.ticks==len(self.board_to_show.history):
            self.timer.stop()
            temp_strs=["三角板" + str(idx+1)+ ":" +str(count)+"个\n" for idx,count in enumerate(self.board_to_show.count_tri_type)]
            strings=""
            for s in temp_strs:
                strings +=s
            self.label_count_tri_type.setText(strings)
    def timer_control(self):
        if self.comboBox_4.currentText()=='':
            msg_box = QMessageBox(self)
            msg_box.information(self, "警告", "没有要显示的方案", QMessageBox.Yes)
            return
        self.timer.start(10000/self.horizontalSlider.value())
        self.board_to_show=self.results[int(self.comboBox_4.currentIndex())]
        self.ticks=0
    def show_v(self):
        self.label_v.setText(str(self.horizontalSlider.value()))
        self.timer.setInterval(10000/self.horizontalSlider.value())
    def yeild_n_combox_items(self):
        self.comboBox_2.clear()
        _rows=int(self.comboBox.currentText())
        broken_count=int(self.comboBox_3.currentText())
        for i in range(10):
            result=_rows*i-broken_count
            if result>4 and result %3==0 and i>1 :
                self.comboBox_2.addItem(str(i))
        self.resize_label()
    def resize_label(self):
        if self.comboBox_2.currentText()==''or self.comboBox.currentText()=='':
            return
        self.label_3.setGeometry(0,160,int(self.comboBox_2.currentText())*80,int(self.comboBox.currentText())*80)
        self.label_3.clear()
    def resolve_problem(self):
        if self.comboBox_2.currentText()=='':
            msg_box=QMessageBox(self)
            msg_box.information(self, "警告",  "没有选择棋盘列数", QMessageBox.Yes)
            return
        rows = int(self.comboBox.currentText())

        cols = int(self.comboBox_2.currentText())
        self.label_3.setGeometry(0,160,80*cols,80*rows)

        board = Board(_arr=np.zeros((rows, cols), np.int), _cur_tris_count=0, _rows=rows, _cols=cols)
        broken_count=int(self.comboBox_3.currentText())
        for i in range(broken_count):
            broken_position=get_random_feasible_position(board)
            board.arr[broken_position[0], broken_position[1]] = -1

        total_triangular = (rows * cols - broken_count) / 3

        stack_of_state = [board]
        self.results = []
        self.n =0
        while len(stack_of_state) != 0:
            self.n+=1
            print(self.n)
            my_board = stack_of_state.pop()
            if my_board.cur_tris_count == total_triangular:
                self.results.append(my_board)
                continue

            row, col = get_first_feasible_position(my_board)
            temp_triss = []
            for k in range(4):
                shapes = []
                tris = [(row + _r, col + _c) for _r in [0, 1] for _c in [0, 1]]
                tris.pop(k)
                shapes.append(tris)
                tris = [(row + _r, col + _c) for _r in [0, 1] for _c in [-1, 0]]
                tris.pop(k)
                shapes.append(tris)
                tris = [(row + _r, col + _c) for _r in [-1, 0] for _c in [0, 1]]
                tris.pop(k)
                shapes.append(tris)
                tris = [(row + _r, col + _c) for _r in [-1, 0] for _c in [-1, 0]]
                tris.pop(k)
                shapes.append(tris)
                shapes.pop(k)
                temp_triss.append(shapes)

            for idx, tris in enumerate(temp_triss):
                for tri in tris:
                    if reasonable(my_board, tri):
                        temp_board = Board(my_board)
                        for r, c in tri:
                            temp_board.arr[r, c] = idx + 1
                        temp_board.history.append(temp_board.arr.copy())
                        temp_board.cur_tris_count += 1
                        temp_board.count_tri_type[idx]+=1
                        if temp_board is None:
                            print("a")
                        stack_of_state.append(temp_board)
        self.comboBox_4.clear()
        for i in range(len(self.results)):
            self.comboBox_4.addItem(str(i))
class Board:
    def __init__(self,_board=None,_arr=None,_cur_tris_count=None,_rows=None,_cols=None):
        if _board is not None:
            self.arr=_board.arr.copy()
            self.cur_tris_count=_board.cur_tris_count
            self.history=_board.history.copy()
            self.cols=_board.cols
            self.rows=_board.rows
            self.count_tri_type=_board.count_tri_type.copy()
        else:
            self.arr=_arr
            self.cur_tris_count=_cur_tris_count
            self.history=[_arr]
            self.rows=_rows
            self.cols=_cols
            self.count_tri_type=[0,0,0,0]
#获取一个空的位置
def get_first_feasible_position(_board):
    if _board is None:
        print("?")
    size=np.shape(_board.arr)
    for _r in range(size[0]):
        for _c in range( size[1]):
            if _board.arr[_r][_c]==0:
                return _r,_c
    return None
def get_random_feasible_position(_board):
    size=np.shape(_board.arr)
    temps=[]
    for _r in range(size[0]):
        for _c in range( size[1]):
            if _board.arr[_r][_c]==0:
                temps.append((_r,_c))

    return temps[random.randint(0,len(temps)-1)]
#判断在棋盘中新加指定三角形之后是否合理(不重叠,不覆盖到外面)
def reasonable(_board,_tri):
    for _r,_c in _tri:
        if _r<0 or _r>=_board.rows:
            return False
        if _c<0 or _c>=_board.cols:
            return False
        if _board.arr[_r][_c]!=0:
            return False
    return True


app=QApplication(sys.argv)
ui=Ui_MainWindow()
sys.exit(app.exec_())

#1,随机选择一个空棋盘(不被三角板占用,不是残缺位置)
#2,然后讨论这个包含整个位置的所有可能情况,成为四种三角板中的任何一个位置(共12个),这样从由改点拼成的三角板最多有12种可能,设为n,n<=12
#3,这样无论从哪里开始拼图,都可以得到所有的解
#4,把这n种可能性都压入栈(和栈中元素不一样的才压入栈),栈元素是某个状态下的棋盘。栈中元素格式为(棋盘,当前已有三角板个数)
#5,取出栈中棋盘,回到步骤1
#6,如果栈某个元素三角板个数==(rows*cols-1)/3,,则作为可行解
#7,由于规则和顺序固定,所以应该不会有重复,免去了重复的计算过程

 

你可能感兴趣的:(PyQt5,残缺棋盘,覆盖,三角板,pyqt,回溯法)