patternpythonModerate
Classic Snake game using Python, Tkinter, and threading
Viewed 0 times
tkintersnakeclassicthreadinggamepythonusingand
Problem
I have just installed Ubuntu and am re-familiarizing myself with Python. I learned the basics in late 2012-early 2013 and I'm practicing with it in order to get better at programming concepts and practice.
```
'''
Snake Game
implements gameplay of classic snake
game with Tkinter
Author: Tracy Lynn Wesley
'''
import threading
import random
import os.path
from Tkinter import *
WIDTH = 500
HEIGHT = 500
class Snake(Frame):
def __init__(self):
Frame.__init__(self)
#Set up the main window frame as a grid
self.master.title("Snake Try to beat the high score! ")
self.grid()
#Set up main frame for game as a grid
frame1 = Frame(self)
frame1.grid()
#Add a canvas to frame1 as self.canvas member
self.canvas = Canvas(frame1, width = WIDTH, height = HEIGHT, bg ="white")
self.canvas.grid(columnspan = 3)
self.canvas.focus_set()
self.canvas.bind("", self.create)
self.canvas.bind("", self.create)
#Create a "New Game" button
newGame = Button(frame1, text = "New Game", command = self.new_game)
newGame.grid(row = 1, column = 0, sticky = E)
#Create a label to show user his/her score
self.score_label = Label(frame1)
self.score_label.grid(row = 1, column = 1)
self.high_score_label = Label(frame1)
self.high_score_label.grid(row = 1, column = 2)
#Direction label (for debugging purpose)
#self.direction_label = Label(frame1, text = "Direction")
#self.direction_label.grid(row = 1, column = 2)
self.new_game()
def new_game(self):
self.canvas.delete(ALL)
self.canvas.create_text(WIDTH/2,HEIGHT/2-50,text="Welcome to Snake!"\
+ "\nPress arrow keys or click in the window"\
+ " to start moving!", tag="welcome_text")
rectWidth = WIDTH/25
#Initialize snake to 3 rectangles
rect
```
'''
Snake Game
implements gameplay of classic snake
game with Tkinter
Author: Tracy Lynn Wesley
'''
import threading
import random
import os.path
from Tkinter import *
WIDTH = 500
HEIGHT = 500
class Snake(Frame):
def __init__(self):
Frame.__init__(self)
#Set up the main window frame as a grid
self.master.title("Snake Try to beat the high score! ")
self.grid()
#Set up main frame for game as a grid
frame1 = Frame(self)
frame1.grid()
#Add a canvas to frame1 as self.canvas member
self.canvas = Canvas(frame1, width = WIDTH, height = HEIGHT, bg ="white")
self.canvas.grid(columnspan = 3)
self.canvas.focus_set()
self.canvas.bind("", self.create)
self.canvas.bind("", self.create)
#Create a "New Game" button
newGame = Button(frame1, text = "New Game", command = self.new_game)
newGame.grid(row = 1, column = 0, sticky = E)
#Create a label to show user his/her score
self.score_label = Label(frame1)
self.score_label.grid(row = 1, column = 1)
self.high_score_label = Label(frame1)
self.high_score_label.grid(row = 1, column = 2)
#Direction label (for debugging purpose)
#self.direction_label = Label(frame1, text = "Direction")
#self.direction_label.grid(row = 1, column = 2)
self.new_game()
def new_game(self):
self.canvas.delete(ALL)
self.canvas.create_text(WIDTH/2,HEIGHT/2-50,text="Welcome to Snake!"\
+ "\nPress arrow keys or click in the window"\
+ " to start moving!", tag="welcome_text")
rectWidth = WIDTH/25
#Initialize snake to 3 rectangles
rect
Solution
If you haven't read it already I'd highly recommend that you check out PEP8 as it is a great starting point for a lot of questions about Python code conventions. Some of what I write here will be repeated there.
Tightly coupled functions
One issue with your design is that your functions are designed such that you must call them in a particular sequence in order to get the results you want.
Commented out code
Use your version control software to manage your changes in code instead of commenting out code. If you are not using version control then you should strongly consider learning how as this is one of the most valuable productivity boosters you can get with development.
If you want logging perhaps look into the standard library logging module.
Documentation
You have used comments fairly extensively in the code here which definitely helps when reading it.
Also I noticed you put a docstring for the module which is good to see! Putting some docstrings in the rest of your code will help other people read it as it gives you a standardized place to look for documentation on different functions/classes/methods.
For example:
Prefer named "constants" over multiple variables
"magic variables" are often less clear than explicit named variables.
When I'm reading this code I have to guess from the context that the
The code is much more clear if you give a named variable:
I notice a similar situation with the directions, create a variable for the various different directions instead of hardcoding strings everywhere. For example in a lot of places in the code you have lines such as:
Personally I'd prefer to define something such as
This makes means if you change the type of value stored in
Going further I'd consider making some sort dictionary to store the keycode along with the associated action that needs to be done if you start getting more keyboard keys being used in your program. I'll explain this in more depth if you make a follow up question.
Checking for non-empty sequences
The pythonic way to do this is explained in PEP8.
instead of:
do:
This makes your code shorter and improves readability.
duplicated code
Any time you see code that does essentially the same thing you should consider re-writing it. Python is highly amenable to the don't-repeat-yourself idea so keep that in mind.
First of all I'd clean up the indentation here to be consistent, but once we have done that we see that all of this is essentially doing the same thing. I would therefore break this into a function:
```
def expand_snake(self, x_direction, y_direction):
"""Expand the snake in the given directions as per the parameters."""
Tightly coupled functions
One issue with your design is that your functions are designed such that you must call them in a particular sequence in order to get the results you want.
init calls new_game calls move which creates the game loop thread in _move. While this might be somewhat appropriate in this case it is usually indicative of bad design. Generally speaking the more you can make your functions not depend on side-effects the better as it allows you more effective code reuse opportunities. Additionally if a precondition for a function is that another function must be called you really need to document that clearly, as this could be a large source of confusion (and hence bugs) for other people (including the future you) who read/maintain the codebase in the future.Commented out code
Use your version control software to manage your changes in code instead of commenting out code. If you are not using version control then you should strongly consider learning how as this is one of the most valuable productivity boosters you can get with development.
If you want logging perhaps look into the standard library logging module.
Documentation
You have used comments fairly extensively in the code here which definitely helps when reading it.
Also I noticed you put a docstring for the module which is good to see! Putting some docstrings in the rest of your code will help other people read it as it gives you a standardized place to look for documentation on different functions/classes/methods.
For example:
def new_game(self):
"""Creates a new game. Sets up canvas and 1initial game conditions."""Prefer named "constants" over multiple variables
"magic variables" are often less clear than explicit named variables.
if event.keycode == 111:
self.direction = "up"When I'm reading this code I have to guess from the context that the
111 is the keycode for the up key. I have to read ahead and look at the context to figure this out.The code is much more clear if you give a named variable:
UP_KEY_CODE = 111
if event.keycode == UP_KEY_CODE:I notice a similar situation with the directions, create a variable for the various different directions instead of hardcoding strings everywhere. For example in a lot of places in the code you have lines such as:
if self.direction == "left":Personally I'd prefer to define something such as
LEFT_DIRECTION = "left" then compare like so:if self.direction == LEFT_DIRECTION:This makes means if you change the type of value stored in
self.direction in the future it's very easy to make changes. Currently you would have to track down all the different strings and change them. Having used other languages I think this is a very good example of where an enumeration type is useful but others might consider that somewhat un-pythonic, so do whatever you think is most readable.Going further I'd consider making some sort dictionary to store the keycode along with the associated action that needs to be done if you start getting more keyboard keys being used in your program. I'll explain this in more depth if you make a follow up question.
Checking for non-empty sequences
The pythonic way to do this is explained in PEP8.
instead of:
if len(coordinates) > 0:
if self.dot != None:
elif self.game_over == True:do:
if coordinates:
if self.dot:
elif self.game_over:This makes your code shorter and improves readability.
duplicated code
Any time you see code that does essentially the same thing you should consider re-writing it. Python is highly amenable to the don't-repeat-yourself idea so keep that in mind.
if self.direction == "left":
self.canvas.move("rect1",-w,0)
self.canvas.after(100)
self.canvas.move("rect1",-w,0)
self.canvas.move("rect2",-w,0)
elif self.direction == "down":
self.canvas.move("rect1",0,w)
self.canvas.after(100)
self.canvas.move("rect1",0,w)
self.canvas.move("rect2",0,w)
elif self.direction == "right":
self.canvas.move("rect1",w,0)
self.canvas.after(100)
self.canvas.move("rect1",w,0)
self.canvas.move("rect2",w,0)
elif self.direction == "up":
self.canvas.move("rect1",0,-w)
self.canvas.after(100)
self.canvas.move("rect1",0,-w)
self.canvas.move("rect2",0,-w)First of all I'd clean up the indentation here to be consistent, but once we have done that we see that all of this is essentially doing the same thing. I would therefore break this into a function:
```
def expand_snake(self, x_direction, y_direction):
"""Expand the snake in the given directions as per the parameters."""
Code Snippets
def new_game(self):
"""Creates a new game. Sets up canvas and 1initial game conditions."""if event.keycode == 111:
self.direction = "up"UP_KEY_CODE = 111
if event.keycode == UP_KEY_CODE:if self.direction == "left":if self.direction == LEFT_DIRECTION:Context
StackExchange Code Review Q#94290, answer score: 10
Revisions (0)
No revisions yet.