Issue
So, I was watching a video from youtube on making the game Snake on Python with the pygame module.. and it was quite confusing.. and now I'm having an issue.. The pygame window opened and closed suddenly and there was no errors in the output.
I ran this pile of code :
import os
os.environ['DISPLAY'] = ': 0.0'
import pygame
pygame.init()
import random
from enum import Enum
from collections import namedtuple
font = pygame.font.Font('PermanentMarker-Regular.ttf', 25)
#font = pygame.font.SysFont('arial', 25)
class Direction(Enum):
RIGHT = 1
LEFT = 2
UP = 3
DOWN = 4
Point = namedtuple('Point', ('x','y'))
WHITE = (255, 255, 255)
RED = (247,33,25)
BLUE = (0,0,255)
CYAN = (255, 255, 1)
BLACK = [0,0,0]
BLOCK_SIZE = 20
SPEED = 40
class SnakeGame:
def __init__(self, w=640, h=480):
self.w = w
self.h = h
# init display
self.display = pygame.display.set_mode((self.w, self.h))
pygame.display.set_caption("Snake")
self.clock = pygame.time.Clock()
# init game state
self.direction = Direction.RIGHT
self.head = Point(self.w/2, self.h/2)
self.snake = [self.head, Point(self.head.x-BLOCK_SIZE, self.head.y), Point(self.head.x-(2*BLOCK_SIZE), self.head.y)]
self.score = 0
self.food = None
self._place_food()
def _place_food(self):
x = random.randint(0, (self.w-BLOCK_SIZE)//BLOCK_SIZE) * BLOCK_SIZE
y = random.randint(0, (self.h-BLOCK_SIZE)//BLOCK_SIZE) * BLOCK_SIZE
self.food = Point(x, y)
if self.food in self.snake:
self._place_food()
self.head = Point(x,y)
def _move(self, direction):
x = self.head.x
y = self.head.y
if direction == Direction.RIGHT:
x += BLOCK_SIZE
elif direction == Direction.LEFT:
x -= BLOCK_SIZE
elif direction == Direction.UP:
y -= BLOCK_SIZE
elif direction == Direction.DOWN:
y += BLOCK_SIZE
def _is_collision(self):
# hits boundary
if self.head.x > self.w - BLOCK_SIZE or self.head.x < 0 or self.head.y > self.h - BLOCK_SIZE or self.head.y < 0:
return True
# hits itself
if self.head in self.snake[1:]:
return True
return False
pygame.display.flip()
def play_step(self):
# collect user input
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.direction = Direction.LEFT
elif event.key == pygame.K_RIGHT:
self.direction = Direction.RIGHT
elif event.key == pygame.K_UP:
self.direction = Direction.UP
elif event.key == pygame.K_DOWN:
self.direction = Direction.DOWN
#2. move
self._move(self.direction)
self.snake.insert(0, self.head)
#3. check if game over
game_over = False
if self._is_collision():
game_over = True
#4. place new food or move snake
if self.head == self.food:
self.score += 1
self._place_food()
else:
self.snake.pop()
#5. update ui and clock
self._update_ui()
self.clock.tick(SPEED)
#6. return game over and score
return game_over, self.score
def _update_ui(self):
self.display.fill(BLACK)
for pt in self.snake:
pygame.draw.rect(self.display, CYAN, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.display, BLUE, pygame.Rect(pt.x+4, pt.y+4, 12, 12))
pygame.draw.rect(self.display, RED, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE))
text = font.render("Score:", str(self.score), True, WHITE)
self.display.blit(text, [0,0])
if __name__ == '__main__':
game = SnakeGame()
# game loop
while True:
game_over, score = game.play_step()
if game_over == True:
break
print("Final score {}".format(score))
# break if game over
pygame.quit()
The issue is probably because I misplaced a method or did something in the script..
Solution
There are 4 issues:
- Update the
head
attribute after in themove()
method:
class SnakeGame:
# [...]
def _move(self, direction):
x = self.head.x
y = self.head.y
if direction == Direction.RIGHT:
x += BLOCK_SIZE
elif direction == Direction.LEFT:
x -= BLOCK_SIZE
elif direction == Direction.UP:
y -= BLOCK_SIZE
elif direction == Direction.DOWN:
y += BLOCK_SIZE
self.head = Point(x, y) # <--- this is missing
- You have to concatenate the strings for the score text (see TypeError: Invalid foreground RGBA argument):
text = font.render("Score:", str(self.score), True, WHITE)
text = font.render("Score:" + str(self.score), True, WHITE)
- Update the display after drawing the scene:
def _update_ui(self):
self.display.fill(BLACK)
for pt in self.snake:
pygame.draw.rect(self.display, CYAN, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.display, BLUE, pygame.Rect(pt.x+4, pt.y+4, 12, 12))
pygame.draw.rect(self.display, RED, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE))
text = font.render("Score:" + str(self.score), True, WHITE)
self.display.blit(text, [0,0])
pygame.display.flip() # <--- this is missing
- Use
pygame.time.Clock
to control the frames per second and thus the game speed.
The method tick()
of a pygame.time.Clock
object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick()
:
This method should be called once per frame.
if __name__ == '__main__':
game = SnakeGame()
# game loop
clock = pygame.time.Clock()
while True:
clock.tick(10)
game_over, score = game.play_step()
if game_over == True:
break
print("Final score {}".format(score))
Answered By - Rabbid76