3
\$\begingroup\$

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()
asked Mar 22, 2017 at 13:54
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Nice game. Here are some nits and tips:

  1. For readability and clarity, put each of your classes in a separate file and import them from a runner script or game script.

  2. Why the need to do this?

    def getWorldCoords(spot):
     spotWorld = [spot[i] for i in range(2)]
     return spotWorld
    

    You can write it like so:

    def getWorldCoords(spot):
     return spot[:2]
    

    Be aware that if you are modifying the objects in the list, you should use the copy to make a deep copy of them if they are not primitives.

  3. Try to make your code less verbose.

    def isCollidingWithSelf(self, spot):
     return spot in self.spots
    
  4. Unneeded list comprehensions.

    if not tuple((-direction[i] for i in range(2))
     ) == tuple(self.currentDirection):
     self.direction = direction
    

    This is extremely verbose and difficult to read.

    if (-direction[0], -direction[1]) != tuple(self.currentDirection):
     self.direction = direction
    
  5. Avoid using sys.exit(). Try to have your program exit normally. You can just set a condition in your while loop.

  6. Comments should not state the obvious. Use comments to document things like complex algorithms.

    def roundedRect(surface, rect, color, radius=0.4):
     """ Draw a rounded rectangle """
    

    This comment doesn't really add much value.

  7. Enclose your main code using an if __name__ == '__main__': guard. Avoid putting it in the global scope.

Happy coding!

answered Jul 28, 2017 at 18:42
\$\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.