I made a Snake game in Python 3 with pygame. Please do critique it as much as possible.
IMPORTANT NOTE: I updated the code at 3:30 3/23
# main.py
# [My Name]
import pygame
from pygame.locals import *
from vector import *
import better_exceptions
import random
import colr
import sys
SCREENSIZE = (1664, 960)
CELLSIZE = 64
assert not SCREENSIZE[0] % CELLSIZE, 'CELLSIZE must be a multiple of SCREENSIZE[0]'
assert not SCREENSIZE[1] % CELLSIZE, 'CELLSIZE must be a multiple of SCREENSIZE[1]'
TITLE = 'Snake'
FPS = 60
LINECOLOR = colr.white()
LINEWIDTH = 2
BGCOLOR = colr.gray()
SNAKECOLOR = colr.green()
SNAKEHEADCOLOR = colr.blue()
SNAKETICKRATE = 200 # Move the snake once every _ milliseconds
SNAKEGROWRATE = 1 # Amount of places to grow for each food eaten
SCREEN = pygame.display.set_mode(SCREENSIZE)
UP = (0, -1)
RIGHT = (1, 0)
LEFT = (-1, 0)
DOWN = (0, 1)
class Snake(object):
def __init__(self, headPos, initSize):
self.spots = [headPos]
self.direction = UP
self.currentDirection = self.direction
self.timer = 0
self.tickrate = SNAKETICKRATE
self.headColor = SNAKEHEADCOLOR
self.bodyColor = SNAKECOLOR
self.growRate = SNAKEGROWRATE
self.queue = 1
self.grow(initSize)
@property
def head(self):
return self.spots[-1]
@staticmethod
def getWorldCoords(spot):
spotWorld = [spot[i] for i in range(2)]
return spotWorld
def lose(self):
leave()
def isOutsideMap(self, spot=None):
spot = self.getWorldCoords(spot)
# Check only one spot
for i in (0, 1):
if not 0 <= spot[i] < SCREENSIZE[i]:
return True
return False
def isCollidingWithSelf(self, spot):
# Check only one spot
if spot in self.spots:
return True
return False
def redir(self, direction):
if not tuple((-direction[i] for i in range(2))
) == tuple(self.currentDirection):
self.direction = direction
def move(self, keepOld=False):
newSpot = [self.head[i] + self.direction[i] for i in range(2)]
self.currentDirection = self.direction
if self.isOutsideMap(newSpot) or self.isCollidingWithSelf(newSpot):
self.lose()
else:
self.spots.append(newSpot)
if not keepOld:
del self.spots[0]
def smartMove(self):
if 0 < self.queue:
self.move(True)
self.queue -= 1
else:
self.move()
if tuple(self.head) == tuple(foodSpawner.food):
self.grow(self.growRate)
foodSpawner.eat()
def grow(self, amnt=1):
self.queue += amnt
def draw(self, deltaTime):
self.timer += deltaTime
if self.tickrate < self.timer:
self.smartMove()
self.timer = 0
for spot in self.spots:
color = self.bodyColor
if tuple(spot) == tuple(self.head):
color = self.headColor
drawCellAtLocation(spot, color)
class FoodSpawner(object):
def __init__(self, noFoodCheck=lambda: []):
self.noFoodCheck = noFoodCheck
self.food = self.genPos()
self.color = colr.orange()
def genPos(self):
return random.choice(list(filter(lambda x: x not in map(tuple, self.noFoodCheck()), allCells())))
def eat(self):
self.food = self.genPos()
def draw(self):
drawCellAtLocation(self.food, self.color)
def allCells():
return [(x, y) for y in range(SCREENSIZE[1] // CELLSIZE)
for x in range(SCREENSIZE[0] // CELLSIZE)]
def drawCellAtLocation(spot, color=None):
color = color or colr.white()
spotRect = pygame.Rect([spot[i] * CELLSIZE + LINEWIDTH for i in range(2)],
[CELLSIZE - LINEWIDTH for _ in range(2)])
pygame.draw.rect(SCREEN, color, spotRect)
def leave():
sys.exit()
def draw(deltaTime):
SCREEN.fill(BGCOLOR)
drawGrid()
snake.draw(deltaTime)
foodSpawner.draw()
keybindings = {
K_UP: lambda: snake.redir(UP),
K_RIGHT: lambda: snake.redir(RIGHT),
K_LEFT: lambda: snake.redir(LEFT),
K_DOWN: lambda: snake.redir(DOWN),
}
def event(ev):
if ev.type == KEYDOWN:
try:
keybindings[ev.key]()
except KeyError:
pass
def drawGrid():
for x in range(0, SCREENSIZE[0], CELLSIZE):
pygame.draw.line(SCREEN, LINECOLOR, (x, 0),
(x, SCREENSIZE[1]), LINEWIDTH)
for y in range(0, SCREENSIZE[1], CELLSIZE):
pygame.draw.line(SCREEN, LINECOLOR, (0, y),
(SCREENSIZE[0], y), LINEWIDTH)
fonts = {}
def getFont(self, size):
if size not in fonts:
fonts[size] = pygame.font.Font(None, size)
return fonts[size]
texts = {}
def getText(self, text, size=12, color=None):
color = color or colr.black()
if (text, size, color) not in texts:
fonts[(text, size, color)] = getFont(size).render(text, True, color)
return fonts[(text, size, color)]
def roundedRect(surface, rect, color, radius=0.4):
""" Draw a rounded rectangle """
rect = pygame.Rect(rect)
color = pygame.Color(*color)
alpha = color.a
color.a = 0
pos = rect.topleft
rect.topleft = 0, 0
rectangle = pygame.Surface(rect.size, SRCALPHA)
circle = pygame.Surface([min(rect.size) * 3] * 2, SRCALPHA)
pygame.draw.ellipse(circle, (0, 0, 0), circle.get_rect(), 0)
circle = pygame.transform.smoothscale(
circle, [int(min(rect.size) * radius)] * 2)
radius = rectangle.blit(circle, (0, 0))
radius.bottomright = rect.bottomright
rectangle.blit(circle, radius)
radius.topright = rect.topright
rectangle.blit(circle, radius)
radius.bottomleft = rect.bottomleft
rectangle.blit(circle, radius)
rectangle.fill((0, 0, 0), rect.inflate(-radius.w, 0))
rectangle.fill((0, 0, 0), rect.inflate(0, -radius.h))
rectangle.fill(color, special_flags=BLEND_RGBA_MAX)
rectangle.fill((255, 255, 255, alpha), special_flags=BLEND_RGBA_MIN)
return surface.blit(rectangle, pos)
snake = Snake((5, 5), 500)
foodSpawner = FoodSpawner(lambda: snake.spots)
clock = pygame.time.Clock()
while True:
deltaTime = clock.tick(FPS)
draw(deltaTime)
for ev in pygame.event.get():
if ev.type == QUIT:
leave()
else:
event(ev)
pygame.display.update()
pygame.display.flip()