1
\$\begingroup\$

I created and finished a Snake using pygame (in Python) utilising feedback I had from a previous code review a year or two ago. I'm pretty happy with the current state and havn't identified any bugs but would love any feedback on this, in terms of logic, coding principals, best practice, or anything else etc.

import pygame, random, time
CELL_SIZE_PX = 15 #the size of each logical 'square' in the game (snake, food etc) in pixels
GRID_WIDTH = 20 # total number of cells wide game is
GRID_HEIGHT = 20 # total number of cells tall game is
GRID_SIZE = (GRID_HEIGHT * CELL_SIZE_PX,GRID_WIDTH * CELL_SIZE_PX) #creates tuple for easier reference of game size
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PINK = (255, 0, 255)
BLUE = (0, 255, 255)
NUMBER_OF_FOOD = 1 #defines how many food icons appear at any moment
FOODLIST = set() #init a set for holding where food is 
TICK_SPEED = 15 #speed of the game
class Snake:
 def __init__(self,start: tuple, length: int, direction: tuple): #start = tuple containing y,x of the head of the snake, length = how many segments, direction = which way it will start moving, as a tuple which determines direction by adding to the location tuple
 self.segments = [] #holds snake location as tuples
 for i in range(length):
 self.segments.append(start) #snake is bunched up on starting square
 self.directionModifer = direction #a tuple that is used to determine the next square to move to based on the key presses. this tuple contains a value to add to the existing lead square tuple.
 def grow(self): #adds extra segment to snake at end of the list
 self.segments.append(self.segments[-1])
 
 def move(self): #updates the self.segments list location to move the snake
 tempList = [] #list to hold updated location
 headLocation = self.segments[0] # the current snake lead square is stored at position zero in the list
 
 newHeadLocation = (headLocation[0]+self.directionModifer[0],headLocation[1]+self.directionModifer[1]) #determines next location for head based on which way the snake is facing
 tempList.append(newHeadLocation) # writes new head location of snake to temporary list
 for index, segment in enumerate(self.segments):
 tempList.append(segment) #appends remaining snake segments to temp list, essentially pushing them down the list by 1 position. 
 del tempList[-1] #removes last segment location as the snake didn't get longer since this temp list is 1 item too long (due to adding new head location)
 self.segments = tempList
class Display():
 def __init__(self):
 self.display = pygame.display.set_mode(GRID_SIZE) #set display size
 
 self.font = pygame.font.Font(pygame.font.get_default_font(), 25) #set fonts use for score message
 
 def show(self,snake: Snake): #draws all items to screen
 self.display.fill(WHITE)
 self.draw_food()
 self.draw_snake(snake)
 pygame.display.update()
 
 def draw_food(self): #draws a pink square for each tuple in FOODLIST set
 for i in FOODLIST: 
 pygame.draw.rect(
 self.display, PINK,
 pygame.Rect(
 i[1] * CELL_SIZE_PX-1,
 i[0] * CELL_SIZE_PX-1,
 CELL_SIZE_PX-1,
 CELL_SIZE_PX-1,
 )
 )
 def draw_snake(self,snake: Snake): #draws blue square for each snake segment from passed snake object
 for i in snake.segments:
 pygame.draw.rect(
 self.display, BLUE,
 pygame.Rect(
 i[1] * CELL_SIZE_PX-1,
 i[0] * CELL_SIZE_PX-1,
 CELL_SIZE_PX-1,
 CELL_SIZE_PX-1,
 )
 )
 def show_score(self,score: int): #shows score (number of segments) and counts down to next game 
 
 for i in reversed(range(4)):
 self.display.fill(WHITE)
 score_text = self.font.render('Score: {0}'.format(score), True, BLACK)
 score_rect = score_text.get_rect(center=(GRID_SIZE[1]/2, GRID_SIZE[0]/3))
 self.display.blit(score_text, score_rect)
 
 countdown_text = self.font.render(str(i+1), True, BLACK)
 countdown_rect = countdown_text.get_rect(center=(GRID_SIZE[1]/2, GRID_SIZE[0]/2))
 self.display.blit(countdown_text, countdown_rect)
 
 pygame.display.update()
 time.sleep(1)
 
def create_food(snake: Snake): #builds list of all empty space and choses one at random for food location
 emptySpace = [] #list to hold areas taht are not occupied by snakes
 for y in range(GRID_HEIGHT): #iterate through all possible locations in the game
 for x in range(GRID_WIDTH):
 tup = (y,x) #build a tuple based of each coordinate location
 if tup not in snake.segments: #as long as the location is not in a snake segment...
 emptySpace.append(tup) # add coordinate to list of potentional food locations 
 
 for i in range(NUMBER_OF_FOOD - len(FOODLIST)): #this is added in case more than 1 food is wanted
 chosenLocation = random.choice(emptySpace) #choose a random valid location
 FOODLIST.add(chosenLocation) #append it to food list
 
 
def session(screen: Display):
 snake1 = Snake((5,5),5,(0,1)) #init new snake
 clock = pygame.time.Clock() #init clock
 create_food(snake1) #generate starting food icons
 pygame.event.clear() #clears events to prevent unexpected direction change at start of game
 
 while True:
 clock.tick(TICK_SPEED)
 snake1.move() #snake moves every tick, so update logical list containing segments
 
 for event in pygame.event.get(): # returns list of events and clears queue
 if event.type == pygame.QUIT:
 pygame.quit()
 exit()
 if event.type == pygame.KEYDOWN: #checks key is pressed
 if event.key == pygame.K_UP and snake1.directionModifer != (1,0): #if UP is pressed and direction modifier does not reference DOWN, so that snake cannot go back on itself
 snake1.directionModifer = (-1,0)
 break #necessary to prevent game reading rapid inputs and causing snake to double back on itself and crash
 elif event.key == pygame.K_RIGHT and snake1.directionModifer != (0,-1): #if RIGHT is pressed and direction modifier does not reference LEFT, so that snake cannot go back on itself
 snake1.directionModifer = (0,1)
 break #necessary to prevent game reading rapid inputs and causing snake to double back on itself and crash
 elif event.key == pygame.K_DOWN and snake1.directionModifer != (-1,0): #if DOWN is pressed and direction modifier does not reference UP, so that snake cannot go back on itself
 snake1.directionModifer = (1,0)
 break #necessary to prevent game reading rapid inputs and causing snake to double back on itself and crash
 elif event.key == pygame.K_LEFT and snake1.directionModifer != (0,1): #if LEFT is pressed and direction modifier does not reference RIGHT, so that snake cannot go back on itself
 snake1.directionModifer = (0,-1)
 break #necessary to prevent game reading rapid inputs and causing snake to double back on itself and crash
 
 if snake1.segments[0] in FOODLIST: #check if snake contacts any food
 FOODLIST.remove(snake1.segments[0]) #removes this food from the set
 create_food(snake1) #adds new food
 snake1.grow()
 head = snake1.segments[0] #get snake head tuple
 if head[0] < 0 or head[0] >= GRID_HEIGHT or head[1] < 0 or head[1] >= GRID_WIDTH or head in snake1.segments[1:]: #checks for border collison
 return len(snake1.segments) #returns to main with score
 
 screen.show(snake1) #updates visuals
 
def main():
 pygame.init()
 pygame.display.set_caption("Snake")
 try:
 screen = Display()
 
 while True:
 score = session(screen)
 screen.show_score(score)
 
 finally:
 pygame.quit()
if __name__ == '__main__':
 main()
ggorlen
4,1672 gold badges19 silver badges28 bronze badges
asked Oct 14, 2022 at 22:48
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

Pretty okay!

You have the start of type hints, but tuple needs to be filled out with an element type, i.e. start: tuple[int, int].

Snake.__init__ needs that comment to be moved from inline to a """ """ docstring on the first line in the function.

Use snake_case for your member variable names and locals, i.e. direction_modifier.

index, segment in enumerate(self.segments) doesn't use index, so delete the enumerate.

Don't append () to class definitions.

Functions like show() need to return-hint -> None.

for i in FOODLIST needs to be for y, x in FOODLIST to unpack the tuples and avoid [1], [0].

('Score: {0}'.format(score) is better-expressed with string interpolation, i.e. f'Score: {score}'.

FOODLIST is not a constant; don't make it look like one. The best place for this would be a non-display Game logic class.

Don't reversed(range(4)); instead use range(3, -1, -1).

When constructing Snake((5, 5), 5, (0, 1)), those parameters would be much clearer as named parameters.

answered Oct 15, 2022 at 13:22
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I've been through your feedback and incorporated into my code, thank you :) \$\endgroup\$ Commented Oct 15, 2022 at 23:17

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.