When I started building my own version of the classic Snake game using Python and Tkinter, I was pretty excited. But like any good coding adventure, it wasn’t long before I hit my first real wall an IndexError
. Specifically:
IndexError: list index out of range
I wasn’t sure what I had done wrong. I was trying to randomly place food on the 10×10 grid, but the game would crash before even starting. It took me a bit of digging, but here’s what I found and how I fixed it.
The Problem
I was getting the error at this line in my code:
self.square_list[self.foody][self.foodx].config(bg='red')
This line is supposed to place the red-colored food square on a random cell. But for some reason, it was trying to access a cell that didn’t exist.
The Cause
The issue was with how I built the self.square_list
. It’s a 2D list meant to represent the grid, where each row is a list of square widgets. But here’s the mistake I made:
for row, rowList in enumerate(self.game):
for column, columnEntry in enumerate(self.game):
square = tkinter.Label(self.screen, text=' ', relief='raised')
square.grid(row=row, column=column)
square.config(bg='black')
self.square_list[row].append(square) # This line fails!
The problem? I never actually initialized self.square_list[row]
. I had created an empty list of rows but didn’t fill them in correctly.
The Fix
Here’s how I corrected the logic:
for row in range(10):
square_row = []
for column in range(10):
square = tkinter.Label(self.screen, text=' ', relief='raised')
square.grid(row=row, column=column)
square.config(bg='black')
square_row.append(square)
self.square_list.append(square_row)
By properly building each square_row
and appending it to self.square_list
, I made sure every [row][column]
reference would actually exist.
Other Issues I Found and Fix
Direction Binding Were Broken
Initially, I wrote:
self.screen.bind('w', self.up()) #
This actually calls the function right away instead of assigning it to the keypress. The fix:
self.screen.bind('w', lambda e: self.up())
self.screen.bind('s', lambda e: self.down())
self.screen.bind('a', lambda e: self.left())
self.screen.bind('d', lambda e: self.right())
Now the functions are only called when the player presses the keys.
Infinite Loop in __init__
Freezes GUI
Another major issue was using a while True:
loop in the constructor:
while True:
time.sleep(self.delay)
self.update()
This blocked the GUI thread, causing the window to freeze. Instead, I used after()
to schedule updates:
self.run_game()
def run_game(self):
self.update()
self.screen.after(int(self.delay * 1000), self.run_game)
Practice Functionalities I Added
Once the basics were stable, I added a few features to improve the game and sharpen my Python skills.
Game Over on Wall Collision
import tkinter.messagebox
if self.head.x > 9 or self.head.x < 0 or self.head.y > 9 or self.head.y < 0:
tkinter.messagebox.showinfo("Game Over", "You hit the wall!")
self.screen.destroy()
Game Over on Self-Collision
for segment in self.segments:
if self.head.x == segment.x and self.head.y == segment.y:
tkinter.messagebox.showinfo("Game Over", "You hit yourself!")
self.screen.destroy()
Score Tracking
In the constructor:
self.score = 0
self.score_label = tkinter.Label(self.screen, text="Score: 0", fg="white", bg="black")
self.score_label.grid(row=0, column=10)
And when food is eaten:
self.score += 1
self.score_label.config(text=f"Score: {self.score}")
Final Thought
Fixing the IndexError was just the beginning of making my Snake game run properly. Along the way, I learned how easy it is to accidentally misuse lists, how to properly bind keys in Tkinter, and why event-driven programming is critical in GUI development.