1

I am trying to develop a graphical sudoku game/solver as a way to learn more about OOP principals, Python, and GUIs. I have decided to use PyQt5, mostly because I understand the concept more than I do that of Pygame. I do have one issue with PyQt5. I have been loading my GUI application as a library and calling it's functions from the terminal, in order to see the errors when they occur. It helps a great deal with debugging. My issue is that it often closes the Python terminal which was used to run the application immediately after printing the errors. Is there any way to prevent this from happening?

To reproduce, download GSudoku.py and sudoku.py or copy and paste their code in an editor and save them with the correct names. Place them in the same directory and import GSudoku from your python shell. Call GSudoku.Game(), then click on any square twice. Should throw an error and should boot you from the python shell.

Full source is available at the GitHub repo. Here is GSudoku.py. This version throws an attribute error.:

import sudoku
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget

class sudokuApp(QMainWindow):
    def __init__(self):
        super(sudokuApp, self).__init__()
        self.setWindowTitle('Sudoku')
        self.createUI()
        self.resizeUI()
        self.activateUI()
        self.b1 = sudoku.board()
        self.activeGrid = 'no active grid'
        self.possibleInputs = [1, 2, 3, 4, 5, 6, 7, 8, 9]

    def createUI(self):    #Create all the buttons, etcetera
        #nine columns of buttons, nine buttons to a column
        self.grids = [[QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)],
                      [QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self), QPushButton(self)]]

        self.setupBtn = QPushButton(self)
        self.solveBtn = QPushButton(self)
        self.clearBtn = QPushButton(self)
        self.setupBtn.setText('Setup')
        self.solveBtn.setText('Solve')
        self.clearBtn.setText('Clear')

    def resizeUI(self, desiredScale = 1):    #make them all the right size and scale them properly.
        self.scale = desiredScale
        self.gridwidth = 40 * self.scale
        self.gridheight = 40 * self.scale
        self.spacing = 5 * self.scale
        self.boardWidth = self.spacing + (9 * (self.spacing + self.gridwidth))
        self.boardHeight = self.spacing + (9 * (self.spacing + self.gridheight))

        for i in range(9):
            for j in range(9):
                grid = self.grids[i][j]
                grid.setGeometry(self.spacing + (j * (self.spacing + self.gridwidth)), self.spacing + (i * (self.spacing + self.gridheight)), self.gridwidth, self.gridheight)
        self.setupBtn.setGeometry(self.spacing, self.boardHeight, (self.scale * 80), (self.scale * 30))
        self.solveBtn.setGeometry((self.spacing * 2) + (self.scale * 80), self.boardHeight, (self.scale * 80), (self.scale * 30))
        self.clearBtn.setGeometry((self.spacing * 3) + (self.scale * 160), self.boardHeight, (self.scale * 80), (self.scale * 30))
        self.setFixedSize(self.boardWidth, self.boardHeight + (self.scale * 30) + self.spacing)

    def activateUI(self):
        for i in range(9):
            for j in range(9):
                grid = self.grids[i][j]
                grid.clicked.connect(self.selectGrid)
                grid.setCheckable(True)
        self.setupBtn.clicked.connect(self.setup)

    def setup(self):
        self.b1.setup()
        for i in range(9):
            for j in range(9):
                val = self.b1.rawread(i, j)
                if val != 0:
                    self.grids[i][j].setText(str(val))
                    self.grids.setEnabled(False)
                elif val == 0:
                    self.grids[i][j].setText('')

    def selectGrid(self):
        for i in range(9):
            for j in range(9):
                grid = self.grids[i][j]
                if grid.isChecked():
                    x = i
                    y = j
                    grid.setCheckable(False)    #reset the grid's checkable state, thereby unchecking it.
                    grid.setCheckable(True)
        self.activeGrid = (x, y)

    def editGrid(self, key):
        x, y = self.activeGrid
        #for i in self.possibleInputs:
        print(key)

    def keyPressEvent(self, event):    #This is the keypress detector. I use this to determine input to edit grids.
        try:
            print(event.key())
            key = event.key() - 48
            print(key)
            x, y = self.activeGrid
            if len(sudoku.findall(self.possibleInputs, key)) == 1:    #if the input is within the legal range
                self.grids[x][y].setText(str(key))
                sudoku.rawplace(x, y, key)
        except:
            pass

def Game():
    app = QApplication([])
    gameInstance = sudokuApp()
    gameInstance.show()
    app.exec_()

Here is sudoku.py. This is fully functional.

import random as rnd

class row():
    def __init__(self):
        self.grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]
        print('created row')

class col():
    def __init__(self):
        self.grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]

class sub():
    def __init__(self):
        self.grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]

class board():
    mode = 'easy'
    won = False
    def __init__(self):
        self.rows = [row(), row(), row(), row(), row(), row() ,row(), row(), row()]
        self.cols = [col(), col(), col(), col(), col(), col(), col(), col(), col()]
        self.subs = [sub(), sub(), sub(), sub(), sub(), sub(), sub(), sub(), sub()]
        print('Board created.')

    def setup(self):
        if self.mode == 'easy':
            numtoremove = 40
        if self.mode == 'medium':
            numtoremove = 50
        if self.mode == 'hard':
            numtoremove = 60

        for i in self.rows:
            for j in i.grid:
                j = 0

        for i in range(9):
            for j in range(9):
                self.rawplace(i, j, rnd.randint(1, 9))
        self.check()
        for i in range(rnd.randint(numtoremove, numtoremove + 10)):
            self.rawplace(rnd.randint(0, 8), rnd.randint(0, 8), 0)

    def clear(self):
        for i in self.rows:
            for j in i.grid:
                j = 0
        for i in self.cols:
            for j in i.grid:
                j = 0
        for i in self.subs:
            for j in i.grid:
                j = 0

    def check(self):
        self.error = False
        for i in self.rows:
            #print('checking {}'.format(str(i)))
            for j in range(1, 10):
                instances = findall(i.grid, j)
                #print(instances, end = ' ')
                #print(len(instances))
                if len(instances) > 1:    #check for multiple instances and throw an error
                    error = True
                    i.grid[instances[len(instances) - 1]] = 0

                if len(instances) == 0:    #check to see if all grids are filled and set the won variable accordingly
                    self.won = True
                else:
                    self.won = False
        self.carry('rows', 'cols')
        self.carry('rows', 'subs')

        for i in self.cols:
            #print('checking {}'.format(str(i)))
            for j in range(1, 10):
                instances = findall(i.grid, j)
                #print(instances, end = ' ')
                #print(len(instances))
                if len(instances) > 1:    #check for multiple instances and throw an error
                    error = True
                    i.grid[instances[len(instances) - 1]] = 0

                if len(instances) == 0:    #check to see if all grids are filled and set the won variable accordingly
                    self.won = True
                else:
                    self.won = False
        self.carry('cols', 'rows')
        self.carry('rows', 'subs')

        for i in self.subs:
            #print('checking {}'.format(str(i)))
            for j in range(1, 10):
                instances = findall(i.grid, j)
                #print(instances, end = ' ')
                #print(len(instances))
                if len(instances) > 1:    #check for multiple instances and throw an error
                    error = True
                    i.grid[instances[len(instances) - 1]] = 0

                if len(instances) == 0:    #check to see if all grids are filled and set the won variable accordingly
                    self.won = True
                else:
                    self.won = False
        self.carry('subs', 'rows')
        self.carry('rows', 'cols')

        if self.error:
            self.won = False
            self.check()

    def printboard(self, mode = 'rows'):
        if mode == 'rows':
            for i in self.rows:
                for j in range(len(i.grid)):
                    print(i.grid[j], end = ' ')
                print()
        if mode == 'cols':
            for i in range(9):
                for j in self.cols:
                    print(j.grid[i], end = ' ')
                print()
        if mode == 'pretty':
            lnctr = 0
            print('-------------------------')
            for i in self.rows:
                print('| ' + str(i.grid[0]) + ' ' + str(i.grid[1]) + ' ' + str(i.grid[2]) + ' | ' +
                str(i.grid[3]) + ' ' + str(i.grid[4]) + ' ' + str(i.grid[5]) + ' | ' +
                str(i.grid[6]) + ' ' + str(i.grid[7]) + ' ' + str(i.grid[8]) + ' |')
                lnctr += 1
                if lnctr in [3, 6]:
                    print('|-------+-------+-------|')
            print('-------------------------')

    def place(self, xloc, yloc, val):
        try:
            #print(str(xloc) + ', ' + str(yloc) + ', ' + str(val))
            return self.rawplace(int(xloc) - 1, int(yloc) - 1, int(val))
        except:
            return False

    def rawplace(self, xloc, yloc, val):
        #print("function: 'rawplace'")
        try:
            if xloc > 8 or xloc < 0 or yloc > 8 or yloc < 0:
                #print('Out of range')
                return False
            else:
                self.rows[yloc].grid[xloc] = val
                self.carry('rows', 'cols')
                self.carry('rows', 'subs')
                return True
        except:
            return False

    def read(self, xloc, yloc):
        try:
            return self.rawread(int(xloc - 1), int(yloc - 1))
        except:
                return False

    def rawread(self, xloc, yloc):
        try:
            if xloc > 8 or xloc < 0 or yloc > 8 or yloc < 0:
                #print('Out of range')
                return False
            else:
                return self.rows[yloc].grid[xloc]
        except:
            return False


    def carry(self, set1, set2):
        if set1 == 'rows' and set2 == 'cols':
            for x in range(9):    #for each grid in the board
                for y in range(9):
                    self.cols[x].grid[y] = self.rows[y].grid[x]    #set that col.grid equal to that row.grid
        if set1 == 'cols' and set2 == 'rows':
            for x in range(9):    #for each grid in the board
                for y in range(9):
                    self.rows[y].grid[x] = self.cols[x].grid[y]    #set that row.grid equal to that col.grid
        if set1 == 'rows' and set2 == 'subs':                      #I couldn't come up with an algorithm here, so I listed the lines and made a script to mirror them for the subs - rows function.
            self.subs[0].grid[0] = self.rows[0].grid[0]    #first row
            self.subs[0].grid[1] = self.rows[0].grid[1]
            self.subs[0].grid[2] = self.rows[0].grid[2]
            self.subs[0].grid[3] = self.rows[1].grid[0]
            self.subs[0].grid[4] = self.rows[1].grid[1]
            self.subs[0].grid[5] = self.rows[1].grid[2]
            self.subs[0].grid[6] = self.rows[2].grid[0]
            self.subs[0].grid[7] = self.rows[2].grid[1]
            self.subs[0].grid[8] = self.rows[2].grid[2]

            self.subs[1].grid[0] = self.rows[0].grid[3]
            self.subs[1].grid[1] = self.rows[0].grid[4]
            self.subs[1].grid[2] = self.rows[0].grid[5]
            self.subs[1].grid[3] = self.rows[1].grid[3]
            self.subs[1].grid[4] = self.rows[1].grid[4]
            self.subs[1].grid[5] = self.rows[1].grid[5]
            self.subs[1].grid[6] = self.rows[2].grid[3]
            self.subs[1].grid[7] = self.rows[2].grid[4]
            self.subs[1].grid[8] = self.rows[2].grid[5]

            self.subs[2].grid[0] = self.rows[0].grid[6]
            self.subs[2].grid[1] = self.rows[0].grid[7]
            self.subs[2].grid[2] = self.rows[0].grid[8]
            self.subs[2].grid[3] = self.rows[1].grid[6]
            self.subs[2].grid[4] = self.rows[1].grid[7]
            self.subs[2].grid[5] = self.rows[1].grid[8]
            self.subs[2].grid[6] = self.rows[2].grid[6]
            self.subs[2].grid[7] = self.rows[2].grid[7]
            self.subs[2].grid[8] = self.rows[2].grid[8]

            self.subs[3].grid[0] = self.rows[3].grid[0]    #second row
            self.subs[3].grid[1] = self.rows[3].grid[1]
            self.subs[3].grid[2] = self.rows[3].grid[2]
            self.subs[3].grid[3] = self.rows[4].grid[0]
            self.subs[3].grid[4] = self.rows[4].grid[1]
            self.subs[3].grid[5] = self.rows[4].grid[2]
            self.subs[3].grid[6] = self.rows[5].grid[0]
            self.subs[3].grid[7] = self.rows[5].grid[1]
            self.subs[3].grid[8] = self.rows[5].grid[2]

            self.subs[4].grid[0] = self.rows[3].grid[3]
            self.subs[4].grid[1] = self.rows[3].grid[4]
            self.subs[4].grid[2] = self.rows[3].grid[5]
            self.subs[4].grid[3] = self.rows[4].grid[3]
            self.subs[4].grid[4] = self.rows[4].grid[4]
            self.subs[4].grid[5] = self.rows[4].grid[5]
            self.subs[4].grid[6] = self.rows[5].grid[3]
            self.subs[4].grid[7] = self.rows[5].grid[4]
            self.subs[4].grid[8] = self.rows[5].grid[5]

            self.subs[5].grid[0] = self.rows[3].grid[6]
            self.subs[5].grid[1] = self.rows[3].grid[7]
            self.subs[5].grid[2] = self.rows[3].grid[8]
            self.subs[5].grid[3] = self.rows[4].grid[6]
            self.subs[5].grid[4] = self.rows[4].grid[7]
            self.subs[5].grid[5] = self.rows[4].grid[8]
            self.subs[5].grid[6] = self.rows[5].grid[6]
            self.subs[5].grid[7] = self.rows[5].grid[7]
            self.subs[5].grid[8] = self.rows[5].grid[8]

            self.subs[6].grid[0] = self.rows[6].grid[0]    #third row
            self.subs[6].grid[1] = self.rows[6].grid[1]
            self.subs[6].grid[2] = self.rows[6].grid[2]
            self.subs[6].grid[3] = self.rows[7].grid[0]
            self.subs[6].grid[4] = self.rows[7].grid[1]
            self.subs[6].grid[5] = self.rows[7].grid[2]
            self.subs[6].grid[6] = self.rows[8].grid[0]
            self.subs[6].grid[7] = self.rows[8].grid[1]
            self.subs[6].grid[8] = self.rows[8].grid[2]

            self.subs[7].grid[0] = self.rows[6].grid[3]
            self.subs[7].grid[1] = self.rows[6].grid[4]
            self.subs[7].grid[2] = self.rows[6].grid[5]
            self.subs[7].grid[3] = self.rows[7].grid[3]
            self.subs[7].grid[4] = self.rows[7].grid[4]
            self.subs[7].grid[5] = self.rows[7].grid[5]
            self.subs[7].grid[6] = self.rows[8].grid[3]
            self.subs[7].grid[7] = self.rows[8].grid[4]
            self.subs[7].grid[8] = self.rows[8].grid[5]

            self.subs[8].grid[0] = self.rows[6].grid[6]
            self.subs[8].grid[1] = self.rows[6].grid[7]
            self.subs[8].grid[2] = self.rows[6].grid[8]
            self.subs[8].grid[3] = self.rows[7].grid[6]
            self.subs[8].grid[4] = self.rows[7].grid[7]
            self.subs[8].grid[5] = self.rows[7].grid[8]
            self.subs[8].grid[6] = self.rows[8].grid[6]
            self.subs[8].grid[7] = self.rows[8].grid[7]
            self.subs[8].grid[8] = self.rows[8].grid[8]

        if set1 == 'subs' and set2 == 'rows':
            self.rows[0].grid[0] = self.subs[0].grid[0]    #first row
            self.rows[0].grid[1] = self.subs[0].grid[1]
            self.rows[0].grid[2] = self.subs[0].grid[2]
            self.rows[1].grid[0] = self.subs[0].grid[3]
            self.rows[1].grid[1] = self.subs[0].grid[4]
            self.rows[1].grid[2] = self.subs[0].grid[5]
            self.rows[2].grid[0] = self.subs[0].grid[6]
            self.rows[2].grid[1] = self.subs[0].grid[7]
            self.rows[2].grid[2] = self.subs[0].grid[8]

            self.rows[0].grid[3] = self.subs[1].grid[0]
            self.rows[0].grid[4] = self.subs[1].grid[1]
            self.rows[0].grid[5] = self.subs[1].grid[2]
            self.rows[1].grid[3] = self.subs[1].grid[3]
            self.rows[1].grid[4] = self.subs[1].grid[4]
            self.rows[1].grid[5] = self.subs[1].grid[5]
            self.rows[2].grid[3] = self.subs[1].grid[6]
            self.rows[2].grid[4] = self.subs[1].grid[7]
            self.rows[2].grid[5] = self.subs[1].grid[8]

            self.rows[0].grid[6] = self.subs[2].grid[0]
            self.rows[0].grid[7] = self.subs[2].grid[1]
            self.rows[0].grid[8] = self.subs[2].grid[2]
            self.rows[1].grid[6] = self.subs[2].grid[3]
            self.rows[1].grid[7] = self.subs[2].grid[4]
            self.rows[1].grid[8] = self.subs[2].grid[5]
            self.rows[2].grid[6] = self.subs[2].grid[6]
            self.rows[2].grid[7] = self.subs[2].grid[7]
            self.rows[2].grid[8] = self.subs[2].grid[8]

            self.rows[3].grid[0] = self.subs[3].grid[0]    #second row
            self.rows[3].grid[1] = self.subs[3].grid[1]
            self.rows[3].grid[2] = self.subs[3].grid[2]
            self.rows[4].grid[0] = self.subs[3].grid[3]
            self.rows[4].grid[1] = self.subs[3].grid[4]
            self.rows[4].grid[2] = self.subs[3].grid[5]
            self.rows[5].grid[0] = self.subs[3].grid[6]
            self.rows[5].grid[1] = self.subs[3].grid[7]
            self.rows[5].grid[2] = self.subs[3].grid[8]

            self.rows[3].grid[3] = self.subs[4].grid[0]
            self.rows[3].grid[4] = self.subs[4].grid[1]
            self.rows[3].grid[5] = self.subs[4].grid[2]
            self.rows[4].grid[3] = self.subs[4].grid[3]
            self.rows[4].grid[4] = self.subs[4].grid[4]
            self.rows[4].grid[5] = self.subs[4].grid[5]
            self.rows[5].grid[3] = self.subs[4].grid[6]
            self.rows[5].grid[4] = self.subs[4].grid[7]
            self.rows[5].grid[5] = self.subs[4].grid[8]

            self.rows[3].grid[6] = self.subs[5].grid[0]
            self.rows[3].grid[7] = self.subs[5].grid[1]
            self.rows[3].grid[8] = self.subs[5].grid[2]
            self.rows[4].grid[6] = self.subs[5].grid[3]
            self.rows[4].grid[7] = self.subs[5].grid[4]
            self.rows[4].grid[8] = self.subs[5].grid[5]
            self.rows[5].grid[6] = self.subs[5].grid[6]
            self.rows[5].grid[7] = self.subs[5].grid[7]
            self.rows[5].grid[8] = self.subs[5].grid[8]

            self.rows[6].grid[0] = self.subs[6].grid[0]    #third row
            self.rows[6].grid[1] = self.subs[6].grid[1]
            self.rows[6].grid[2] = self.subs[6].grid[2]
            self.rows[7].grid[0] = self.subs[6].grid[3]
            self.rows[7].grid[1] = self.subs[6].grid[4]
            self.rows[7].grid[2] = self.subs[6].grid[5]
            self.rows[8].grid[0] = self.subs[6].grid[6]
            self.rows[8].grid[1] = self.subs[6].grid[7]
            self.rows[8].grid[2] = self.subs[6].grid[8]

            self.rows[6].grid[3] = self.subs[7].grid[0]
            self.rows[6].grid[4] = self.subs[7].grid[1]
            self.rows[6].grid[5] = self.subs[7].grid[2]
            self.rows[7].grid[3] = self.subs[7].grid[3]
            self.rows[7].grid[4] = self.subs[7].grid[4]
            self.rows[7].grid[5] = self.subs[7].grid[5]
            self.rows[8].grid[3] = self.subs[7].grid[6]
            self.rows[8].grid[4] = self.subs[7].grid[7]
            self.rows[8].grid[5] = self.subs[7].grid[8]

            self.rows[6].grid[6] = self.subs[8].grid[0]
            self.rows[6].grid[7] = self.subs[8].grid[1]
            self.rows[6].grid[8] = self.subs[8].grid[2]
            self.rows[7].grid[6] = self.subs[8].grid[3]
            self.rows[7].grid[7] = self.subs[8].grid[4]
            self.rows[7].grid[8] = self.subs[8].grid[5]
            self.rows[8].grid[6] = self.subs[8].grid[6]
            self.rows[8].grid[7] = self.subs[8].grid[7]
            self.rows[8].grid[8] = self.subs[8].grid[8]

def findall(listin, x):
    occurences = []
    for i in range(len(listin)):
        if listin[i] == x:
            occurences.append(i)
    return occurences

Python 3.7.4, installed under Visual Studio 2019 Windows 10 Home

AwesomeCronk
  • 421
  • 6
  • 16
  • 1
    provide a [mre] – eyllanesc Dec 25 '19 at 02:29
  • I have posted the entire source in a GitHub repo. Just download GSudoku.py and sudoku.py and put them in the same directory. Import GSudoku and call GSudoku.Game(). Repo: https://github.com/AwesomeCronk/Sudoku – AwesomeCronk Dec 25 '19 at 03:04
  • FYI, the "official" python bindings are PySide2. But your problem is not with Python, but how it's run from the terminal. Open an actual cmd prompt and run if from there. – Keith Dec 25 '19 at 03:18
  • @Keith I modified the python and now it doesn't boot me. Rather than the issue with the buttons, the new version has issues with key inputs, due to me trying to use PyQt4 code in it. Those errors don't boot me. I will try using the CMD hosted Python and see if that works. – AwesomeCronk Dec 25 '19 at 03:35
  • @AwesomeCronk A resource as important as the MRE should not depend on an external resource as a link since if it breaks it makes your question useless to the community, instead edit your question and place the MRE there, otherwise your question can be closed – eyllanesc Dec 25 '19 at 03:55
  • @AwesomeCronk Try [using an excepthook](https://stackoverflow.com/a/33741755/984421). – ekhumoro Dec 25 '19 at 17:37
  • @eyllanesc Thank you for that information. I edited the question and inserted the MRE. – AwesomeCronk Dec 25 '19 at 19:59
  • @ekhumoro Thank you! I will research this and implement it somehow. – AwesomeCronk Dec 25 '19 at 20:00

0 Answers0