1

This is a weird issue which I cannot solve myself. Basically I am making Minesweeper using python 3.6, and Tkinter. The game plays fine, up until about halfway through a hard game (it happens in the other games if they get drawn out longer), where you click a piece, and then an error comes up in the console. A little while later, after being able to play a few more moves, the game crashes. Excuse the 'temporary' code, it's not my best work as it was never meant to be seen by anyone!

(I haven't included all code from the program)

from tkinter import *
import random

root = Tk()

buttons = []

images = {"X": PhotoImage(file="Mine.gif"), "X/": PhotoImage(file="MineSlash.gif"), "F": PhotoImage(file="Flag.gif")}
colors = {1:"#0008f9", 2:"#10cc00", 3:"#ff4f1e", 4:"#4b00a8", 5:"#bc0000", 6:"#00cbdd"}

board = []

mines = []

buttons = []

minesLeft = 10

numMines = 10

boardSize = 8

dead = False

minesLeftLabel = Label(font="Verdana 10 bold")

startingButtons = []

        
def ButtonCreate():
    Button(root, text="Restart", width=8, height=1, font="Verdana 10 bold", command=Restart).grid(row=0, column=0, columnspan=3)
    global minesLeftLabel
    minesLeftLabel.grid(row=0, column=4, columnspan=2)
    minesLeftLabel.config(text=str(minesLeft))
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            button = Button(root, text=board[y][x], bg="#eaeaea", width=2, height=1, font="Verdana 10 bold")
            button.bind('<Button-1>', lambda event, x=x, y=y: MinePressed(x, y, False))
            button.bind('<Button-3>', lambda event, x=x, y=y: MinePressed(x, y, True))
            button.grid(row=y + 2, column=x)
            tempList.append(button)
        buttons.append(tempList)

def CheckNeighbours(x, y):
    buttons[y][x].config(text=" ", fg="grey", bg="#e5e5e5", relief=SUNKEN)
    for yPos in range(-1, 2):
        for xPos in range(-1, 2):
            if y + yPos >= 0 and y + yPos <= boardSize - 1 and x + xPos >= 0 and x + xPos <= boardSize - 1:
                if mines[y + yPos][x + xPos] != 0:
                    board[y + yPos][x + xPos] = mines[y + yPos][x + xPos]
                    buttons[y + yPos][x + xPos].config(text=board[y + yPos][x + xPos], fg=colors[board[y + yPos][x + xPos]], bg="#e5e5e5", width=2, height=1, relief=SUNKEN)
                elif board[y + yPos][x + xPos] == " " and mines[y + yPos][x + xPos] == 0:
                    board[y + yPos][x + xPos] = mines[y + yPos][x + xPos]
                    CheckNeighbours(x + xPos, y + yPos)
    return

def MinePressed(x, y, flag):
    if dead != True:
        if flag:
            global minesLeft
            global minesLeftLabel
            if board[y][x] == " ":
                board[y][x] = "F"
                minesLeft -= 1
                minesLeftLabel.config(text=str(minesLeft))
                buttons[y][x].config(image=images["F"], width=22, height=22)
            elif board[y][x] == "F":
                board[y][x] = " "
                minesLeft += 1
                minesLeftLabel.config(text=str(minesLeft))
                buttons[y][x].config(text=board[y][x], image="", width=2, height=1)
        else:
            if board[y][x] != "F":
                board[y][x] = mines[y][x]
                if board[y][x] == "X":
                    GameOver()
                    buttons[y][x].config(image=images["X"], bg="red", width=21, height=21, relief=SUNKEN)
                elif board[y][x] == 0:
                    CheckNeighbours(x, y)
                else:
                    buttons[y][x].config(text=board[y][x], fg=colors[board[y][x]], bg="#e5e5e5", relief=SUNKEN)
        root.update_idletasks()
        root.mainloop()

def Restart():
    ResetBoards()
    global dead
    dead = False
    global buttons
    buttons = []
    CreateMines()
    MineCalculations()
    CreateWindow()


def ResetBoards():
    global board
    board = []
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            tempList.append(" ")
        board.append(tempList)
    global mines
    mines = []
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            tempList.append(0)
        mines.append(tempList)

def BoardSize(i):
    global boardSize
    boardSize = i
    global numMines
    numMines = int(boardSize * boardSize * 0.18)
    global minesLeft
    minesLeft = numMines
    ResetBoards()
    CreateMines()
    MineCalculations()
    CreateWindow()

def CreateWindow():
    root.resizable(width=FALSE, height=FALSE)
    root.geometry('{}x{}'.format(28 * boardSize, 28 * (boardSize + 1)))
    for i in startingButtons:
        i.destroy()
    ButtonCreate()
    
def SelectSize():
    root.resizable(width=FALSE, height=FALSE)
    root.geometry('{}x{}'.format(150, 150))

    global startingButtons
    button1 = Button(text="Beginner", font="Verdana 10 bold", anchor=N, command=lambda i=8: BoardSize(i))
    button1.place(relx=0.5, rely=0.2, anchor=CENTER)
    button2 = Button(text="Intermediate", font="Verdana 10 bold", anchor=N, command=lambda i=16: BoardSize(i))
    button2.place(relx=0.5, rely=0.45, anchor=CENTER)
    button3 = Button(text="Hard", font="Verdana 10 bold", anchor=N, command=lambda i=24: BoardSize(i))
    button3.place(relx=0.5, rely=0.7, anchor=CENTER)
    startingButtons.append(button1)
    startingButtons.append(button2)
    startingButtons.append(button3)

SelectSize()
input()

The other weird thing is that it throws up 2 slightly different errors, but they are still part of the same issue. Here are the two problems:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\idlelib\run.py", line 137, in main
    seq, request = rpc.request_queue.get(block=True, timeout=0.05)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\queue.py", line 172, in get
    raise Empty
queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1698, in __call__
    args = self.subst(*args)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1428, in _substitute
    e.type = EventType(T)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\enum.py", line 291, in __call__
    return cls.__new__(cls, value)
RecursionError: maximum recursion depth exceeded while calling a Python object

And the other one:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\idlelib\run.py", line 137, in main
    seq, request = rpc.request_queue.get(block=True, timeout=0.05)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\queue.py", line 172, in get
    raise Empty
queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 108, in Restart
    CreateWindow()
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 153, in CreateWindow
    ButtonCreate()
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 35, in ButtonCreate
    Button(root, text="Restart", width=8, height=1, font="Verdana 10 bold", command=Restart).grid(row=0, column=0, columnspan=3)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 2363, in __init__
    Widget.__init__(self, master, 'button', cnf, kw)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 2293, in __init__
    (widgetName, self._w) + extra + self._options(cnf))
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1320, in _options
    v = self._register(v)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1356, in _register
    f = CallWrapper(func, subst, self).__call__
RecursionError: maximum recursion depth exceeded

I've looked up this issue as much as I can, and it's either something to do with the root.mainloop() calling, or the recursive function (CheckNeighbours) doing something wrong, or the fact I'm not using classes. Help is appreciated :)

EDIT - The program seems to throw a slightly different error each time. However, all of them end in the maximum recursion depth exceeded error

5
  • If you are hitting a maximum recursion error, it's almost certainly because of your recursive CheckNeighbours function. If you have an 8x8 board, the maximum number of spots on the board is 64. The default recursion limit is 1000, meaning you are "checking a neighbor" 1000 times when it's impossible for there to be more than 64 neighbors to check. Commented Feb 4, 2017 at 23:01
  • I see where you're going. I have solved this problem since (as I have posted a solution), but in my case there are more than 64 neighbours to check. This program is not optimised as of yet, therefore, a piece checks all 8 neighbours surrounding it, and if one of them has no value (is a blank spot), then it does the same for that tile, it will check all eight neighbours (apart from the neighbour which called the function on that tile). Commented Feb 5, 2017 at 12:53
  • (cont) However, all that being said, it never actually goes over 1000 recursions in one call, it builds up over time, even if the recursion of a previous set of neighbour checking has finished. Weird integration of recursion in Python, but as said in the solution the best way around this would simply be to make it into a loop (which would require a bit of extra work because of the way I have made the recursive function). Thanks for the help Commented Feb 5, 2017 at 13:05
  • python's implementation is no different than any other programming language. The behavior is due to your code, not python's implementation of recursion. Commented Feb 5, 2017 at 15:10
  • Just like to add that it turns out the mainloop() was the issue. It was the reason the stack was never deleted or cleared. You were indeed right :) Commented Feb 5, 2017 at 22:14

1 Answer 1

1

The problem, or at least one problem, is that you are calling mainloop more than once, and you are calling it from an event handler. That is what is ultimately causing your infinite recursion.

As the name implies, mainloop is an infinite loop itself. It runs until the main window is destroyed. When you press a key or button, mainloop is what runs the command associated with the key or button. If that command is MinePressed, it will eventually cause mainloop to be called again from inside the original call to mainloop.

Because mainloop never exits (because the root window is never destroyed), you have an infinite loop calling an infinite loop calling an infinite loop, ..., and none of those inner loops ever exit. Eventually you run out of stack space when you get a thousand copies of mainloop running.

You need to move root.mainloop() out of MinePressed and put it as the last line in your file, e.g.:

SelectSize()
input()
root.mainloop()

Though, maybe it goes before input() -- I have no idea what that line of code is for.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot! That explains why the stack space was not being cleared, thus stacking my recursion loops on top of each other until it hit the max recursion limit. The reason the input() was there was to actually make it so that program wouldn't close, but now I understand what mainloop() does, it can completely replace the input() statement! Thanks for the help!
I'm now having problems with something that's hard to explain. When I click on a button, it should go to relief=SUNKEN mode, I've checked, it even executes that line, and every other command in that line (the line just above 'root.update_idletasks()' in 'MinePressed'). However, it doesn't stay sunken unless I keep the root.mainloop() there. The weirdest part is, every button that gets updated via the CheckNeighbours() function, and gets relief=SUNKEN called on it successfully stays sunken. Help is appreciated
Fixed the previous issue as well. Simply put 'root.mainloop()' at the end of the file, removed all other 'root.mainloop()' and 'root.update_idletasks()', and changed this line: button.bind('<Button-1>', lambda event, x=x, y=y: MinePressed(x, y, False)) to this: button.config(command=lamda x=x, y=y: MinePressed(x, y, False)) Thanks for all the help Bryan

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.