5
\$\begingroup\$

I have written a Snake game using Python's Tkinter library.

I am a beginner to programming. I learned some python alone and in school. However, most of what we did in school is algorithms/data structures and problem-solving (school program geared towards mathematics). Not much coding.

Lately, I picked up Python again. I was introduced to OOP in Python and used Tkinter before deciding to give Snake a try. This is my first quasi-real application.

Any comments, recommendations, bad/good habits are welcome.

from tkinter import*
from random import randint
# constants that go in the making of the grid used for the snake's movment
GRADUATION = 40
PIXEL = 10
STEP = 2 * PIXEL
WD = PIXEL * GRADUATION
HT = PIXEL * GRADUATION
# constants that go into specifying the shapes' sizes
OB_SIZE_FACTOR = 1
SN_SIZE_FACTOR = 0.9
OB_SIZE = PIXEL * OB_SIZE_FACTOR
SN_SIZE = PIXEL * SN_SIZE_FACTOR
# color constants
BG_COLOR = 'black'
OB_COLOR = 'red'
SN_COLOR = 'white'
# a dictionary to ease access to a shape's type in the Shape class
SN = 'snake'
OB = 'obstacle'
SIZE = {SN: SN_SIZE, OB: OB_SIZE}
# constants for keyboard input
UP = 'Up'
DOWN = 'Down'
RIGHT = 'Right'
LEFT = 'Left'
# a dictionary to ease access to 'directions'
DIRECTIONS = {UP: [0, -1], DOWN: [0, 1], RIGHT: [1, 0], LEFT: [-1, 0]}
AXES = {UP: 'Vertical', DOWN: 'Vertical', RIGHT: 'Horizontal', LEFT: 'Horizontal'}
# refresh time for the perpetual motion
REFRESH_TIME = 100
class Master(Canvas):
 """create the game canvas, the snake, the obstacle, keep track of the score"""
 def __init__(self, boss=None):
 super().__init__(boss)
 self.configure(width=WD, height=HT, bg=BG_COLOR)
 self.running = 0
 self.snake = None
 self.obstacle = None
 self.direction = None
 self.current = None
 self.score = Scores(boss)
 def start(self):
 """start snake game"""
 if self.running == 0:
 self.snake = Snake(self)
 self.obstacle = Obstacle(self)
 self.direction = RIGHT
 self.current = Movement(self, RIGHT)
 self.current.begin()
 self.running = 1
 def clean(self):
 """restarting the game"""
 if self.running == 1:
 self.score.reset()
 self.current.stop()
 self.running = 0
 self.obstacle.delete()
 for block in self.snake.blocks:
 block.delete()
 def redirect(self, event):
 """taking keyboard inputs and moving the snake accordingly"""
 if 1 == self.running and \
 event.keysym in AXES.keys() and\
 AXES[event.keysym] != AXES[self.direction]:
 self.current.flag = 0
 self.direction = event.keysym
 self.current = Movement(self, event.keysym) # a new instance at each turn to avoid confusion/tricking
 self.current.begin() # program gets tricked if the user presses two arrow keys really quickly
class Scores:
 """Objects that keep track of the score and high score"""
 def __init__(self, boss=None):
 self.counter = StringVar(boss, '0')
 self.maximum = StringVar(boss, '0')
 def increment(self):
 score = int(self.counter.get()) + 1
 maximum = max(score, int(self.maximum.get()))
 self.counter.set(str(score))
 self.maximum.set(str(maximum))
 def reset(self):
 self.counter.set('0')
class Shape:
 """This is a template to make obstacles and snake body parts"""
 def __init__(self, can, a, b, kind):
 self.can = can
 self.x, self.y = a, b
 self.kind = kind
 if kind == SN:
 self.ref = Canvas.create_rectangle(self.can,
 a - SN_SIZE, b - SN_SIZE,
 a + SN_SIZE, b + SN_SIZE,
 fill=SN_COLOR,
 width=2)
 elif kind == OB:
 self.ref = Canvas.create_oval(self.can,
 a - OB_SIZE, b - OB_SIZE,
 a + SN_SIZE, b + SN_SIZE,
 fill=OB_COLOR,
 width=2)
 def modify(self, a, b):
 self.x, self.y = a, b
 self.can.coords(self.ref,
 a - SIZE[self.kind], b - SIZE[self.kind],
 a + SIZE[self.kind], b + SIZE[self.kind])
 def delete(self):
 self.can.delete(self.ref)
class Obstacle(Shape):
 """snake food"""
 def __init__(self, can):
 """only create the obstacles where there is no snake body part"""
 self.can = can
 p = int(GRADUATION/2 - 1)
 n, m = randint(0, p), randint(0, p)
 a, b = PIXEL * (2 * n + 1), PIXEL * (2 * m + 1)
 while [a, b] in [[block.x, block.y] for block in self.can.snake.blocks]:
 n, m = randint(0, p), randint(0, p)
 a, b = PIXEL * (2 * n + 1), PIXEL * (2 * m + 1)
 super().__init__(can, a, b, OB)
class Block(Shape):
 """snake body part"""
 def __init__(self, can, a, y):
 super().__init__(can, a, y, SN)
class Snake:
 """a snake keeps track of its body parts"""
 def __init__(self, can):
 """initial position chosen by me"""
 self.can = can
 a = PIXEL + 2 * int(GRADUATION/4) * PIXEL
 self.blocks = [Block(can, a, a), Block(can, a, a + STEP)]
 def move(self, path):
 """an elementary step consisting of putting the tail of the snake in the first position"""
 a = (self.blocks[-1].x + STEP * path[0]) % WD
 b = (self.blocks[-1].y + STEP * path[1]) % HT
 if a == self.can.obstacle.x and b == self.can.obstacle.y: # check if we find food
 self.can.score.increment()
 self.can.obstacle.delete()
 self.blocks.append(Block(self.can, a, b))
 self.can.obstacle = Obstacle(self.can)
 elif [a, b] in [[block.x, block.y] for block in self.blocks]: # check if we hit a body part
 self.can.clean()
 else:
 self.blocks[0].modify(a, b)
 self.blocks = self.blocks[1:] + [self.blocks[0]]
class Movement:
 """object that enters the snake into a perpetual state of motion in a predefined direction"""
 def __init__(self, can, direction):
 self.flag = 1
 self.can = can
 self.direction = direction
 def begin(self):
 """start the perpetual motion"""
 if self.flag > 0:
 self.can.snake.move(DIRECTIONS[self.direction])
 self.can.after(REFRESH_TIME, self.begin)
 def stop(self):
 """stop the perpetual movement"""
 self.flag = 0
if __name__ == "__main__":
 root = Tk()
 root.title("Snake Game")
 game = Master(root)
 game.grid(column=1, row=0, rowspan=3)
 root.bind("<Key>", game.redirect)
 buttons = Frame(root, width=35)
 Button(buttons, text='Start', command=game.start).grid()
 Button(buttons, text='Stop', command=game.clean).grid()
 Button(buttons, text='Quit', command=root.destroy).grid()
 buttons.grid(column=0, row=0)
 scoreboard = Frame(root, width=35, height=2 * HT / 5)
 Label(scoreboard, text='Game Score').grid()
 Label(scoreboard, textvariable=game.score.counter).grid()
 Label(scoreboard, text='High Score').grid()
 Label(scoreboard, textvariable=game.score.maximum).grid()
 scoreboard.grid(column=0, row=2)
 root.mainloop()

A quick pic

toolic
14.6k5 gold badges29 silver badges204 bronze badges
asked Jan 25, 2018 at 21:06
\$\endgroup\$
1
  • 1
    \$\begingroup\$ You might be interested in this question and its answer. \$\endgroup\$ Commented Jan 25, 2018 at 23:06

1 Answer 1

2
\$\begingroup\$

Overview

The code layout is good, and you used meaningful names for classes, functions and variables.

UX

It is not obvious what the user should do when the GUI opens up. You should display some simple instructions in the GUI, such as:

Use arrow keys on keyboard to move right, left, etc.

Naming

Generally-speaking, you used meaningful names for many variables. However, some names are ambiguous or don't convey much meaning. For example, can should be canvas.

Docstrings

No need to mention "objects" when describing a class:

class Scores:
 """Objects that keep track of the score and high score"""

The docstring is simpler as:

 """Keep track of the score and high score"""

Boolean

The variable named running would be better as a boolean type since it only takes on values of 0 and 1. Also, conventionally, booleans use an "is_" prefix in the name. Checks of the variable:

 if self.running == 1:

can be simplified as:

 if self.is_running:

Consider making flag a boolean as well. Also, the name flag is not very descriptive; perhaps change it to something like is_moving.

Documentation

Add a docstring to the top of the file to summarize what the code does.

"""
Snake game using Python's Tkinter
"""

Namespace

The following line imports a lot of unused items into the current scope:

from tkinter import*

It is best to import only what is needed to avoid namespace pollution.

One common option is to use the following import line:

import tkinter as tk

This does not import any items by defualt, and you will need to prefix everything that is needed with tk.. For example, change:

root = Tk()

to:

root = tk.Tk()

The advantage to this approach is that it is obvious from reading the code where things like Canvas come from when referenced as tk.Canvas.

answered Dec 12, 2024 at 12:57
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.