I am a new programmer and I sill don't know how to write a clean code. please suggest me ways to improve my skill and how to write a proper code.
import pygame
import time
import random
pygame.init()
# defines the width and height of the display
display_width = 800
display_height = 600
white = (255, 255, 255)
d_white = (250, 250, 250)
black = (0, 0, 0)
teal = (0, 128, 128)
blue_black = (50, 50, 50)
game_display = pygame.display.set_mode((display_width, display_height))
factor = 10
food_x = random.randrange(5, display_width - 5)
food_y = random.randrange(5, display_height - 5)
print(food_x, food_y)
score = 60
clock = pygame.time.Clock()
class snake_body:
x = 0
y = 0
def __init__(self, x_position, y_position):
self.x = x_position
self.y = y_position
snake = [snake_body(20, 20), snake_body(20, 30), snake_body(20, 40), snake_body(20, 50), snake_body(20, 60),
snake_body(20, 70), snake_body(20, 80), snake_body(20, 90), snake_body(20, 100), snake_body(20, 110),
snake_body(20, 120), snake_body(20, 130), snake_body(20, 140), snake_body(20, 150), snake_body(20, 160),
snake_body(20, 170)]
# class food():
# x=0
# y=0
#
# def __init__(self):
def update_snake(score):
i = len(snake) - 1
while i > 0:
snake[i].x = snake[i - 1].x
snake[i].y = snake[i - 1].y
i -= 1
def check_death():
if snake[0].x < 1 or snake[0].x > display_width or snake[0].y < 1 or snake[0].y > display_height:
pygame.quit()
quit()
for i in range(1, len(snake)):
if snake[0].x == snake[i].x and snake[0].y == snake[i].y:
pygame.quit()
quit()
def drawsnake(snake):
for i in range(len(snake)):
pygame.draw.rect(game_display, teal, (snake[i].x, snake[i].y, factor, factor))
x = 0
y = 0
x_change = 0
y_change = 0
first_time = True
eat = True
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
first_time = False
if event.key == pygame.K_a:
if x_change is not 10:
x_change = -10
y_change = 0
elif event.key == pygame.K_d:
if x_change != -10:
x_change = 10
y_change = 0
elif event.key == pygame.K_w:
if y_change is not 10:
x_change = 0
y_change = -10
elif event.key == pygame.K_s:
if y_change != -10:
x_change = 0
y_change = 10
elif event.key == pygame.K_c:
x_change = 0
y_change = 0
if not first_time:
update_snake(score)
if score % 10 == 0 and eat:
snake.append(snake_body(snake[len(snake)-1].x, snake[len(snake)-1].y))
print(len(snake))
eat = False
snake[0].x += x_change
snake[0].y += y_change
if snake[0].x < food_x+10 and snake[0].x > food_x-10 and snake[0].y < food_y+10 and snake[0].y >food_y-10:
score = score + 10
food_x = random.randrange(5, display_width - 5)
food_y = random.randrange(5, display_height - 5)
eat = True
check_death()
game_display.fill(white)
pygame.draw.rect(game_display, black, (food_x, food_y, factor, factor))
drawsnake(snake)
pygame.display.update()
time.sleep(0.035)
clock.tick(60)
2 Answers 2
Here are some changes I would make:
Your snake_body
class (which should be called SnakeBody
, according to PEP8), does not need to initialize its attributes before the initialization, so you can just have:
class SnakeBody:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return "SnakeBody({self.x}, {self.y})".format(self=self)
I also gave it a __repr__
method so it prints nicer.
Currently, your full snake is just a list of body parts. While this is nice, you also have a few functions that just operate on a snake, so it would be nice to add these two things together and make snake a class as well. I would do something like this, which inherits from list
so the rest of your code stays basically unchanged, except that instead of draw_snake()
you now have snake.draw()
, etc:
class Snake(list):
def __init__(self, start_x, start_y, n):
list.__init__(self, [SnakeBody(start_x, start_y + i * factor)
for i in range(n)])
def move_head(self, dx, dy):
self[0].x += dx
self[0].y += dy
def update(self, score):
for i in range(len(self) - 1, 0, -1):
self[i].x = self[i - 1].x
self[i].y = self[i - 1].y
def check_death(self):
if not (1 <= self[0].x <= display_width and 1 <= self[0].y <= display_height):
return True
return any(body_part.x == self[0].x and body_part.y == self[0].y for body_part in self[1:])
def draw(self):
for body_part in self:
pygame.draw.rect(game_display, teal,
(body_part.x, body_part.y, factor, factor))
Note that I also simplified the check_death
function. It now returns True
if the snake needs to die. I used the fact that Python can do multiple comparisons, so a <= x <= b
is legal in Python.
The actual game code I would put into a main
function which you can call within a if __name__ == "__main__":
guard to allow importing this from another script. With these changes, your code becomes:
Final code:
import pygame
import time
import random
pygame.init()
# defines the width and height of the display
display_width = 800
display_height = 600
white = (255, 255, 255)
d_white = (250, 250, 250)
black = (0, 0, 0)
teal = (0, 128, 128)
blue_black = (50, 50, 50)
game_display = pygame.display.set_mode((display_width, display_height))
factor = 10
clock = pygame.time.Clock()
class SnakeBody:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return "SnakeBody({self.x}, {self.y})".format(self=self)
class Snake(list):
def __init__(self, start_x, start_y, n):
list.__init__(self, [SnakeBody(start_x, start_y + i * factor)
for i in range(n)])
def move_head(self, dx, dy):
self[0].x += dx
self[0].y += dy
def update(self, score):
for i in range(len(self) - 1, 0, -1):
self[i].x = self[i - 1].x
self[i].y = self[i - 1].y
def check_death(self):
if not (1 <= self[0].x <= display_width and 1 <= self[0].y <= display_height):
return True
return any(body_part.x == self[0].x and body_part.y == self[0].y for body_part in self[1:])
def draw(self):
for body_part in self:
pygame.draw.rect(game_display, teal,
(body_part.x, body_part.y, factor, factor))
def main():
food_x = random.randrange(5, display_width - 5)
food_y = random.randrange(5, display_height - 5)
print(food_x, food_y)
score = 60
snake = Snake(20, 20, 16)
x = 0
y = 0
x_change = 0
y_change = 0
first_time = True
eat = True
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
first_time = False
if event.key == pygame.K_a:
if x_change != 10:
x_change = -10
y_change = 0
elif event.key == pygame.K_d:
if x_change != -10:
x_change = 10
y_change = 0
elif event.key == pygame.K_w:
if y_change != 10:
x_change = 0
y_change = -10
elif event.key == pygame.K_s:
if y_change != -10:
x_change = 0
y_change = 10
elif event.key == pygame.K_c:
x_change = 0
y_change = 0
if not first_time:
snake.update(score)
if score % 10 == 0 and eat:
snake.append(snake_body(
snake[len(snake) - 1].x, snake[len(snake) - 1].y))
print(len(snake))
eat = False
snake.move_head(x_change, y_change)
if snake[0].x < food_x + 10 and snake[0].x > food_x - 10 and snake[0].y < food_y + 10 and snake[0].y > food_y - 10:
score += 10
food_x = random.randrange(5, display_width - 5)
food_y = random.randrange(5, display_height - 5)
eat = True
if snake.check_death():
pygame.quit()
quit()
# update game display
game_display.fill(white)
pygame.draw.rect(game_display, black, (food_x, food_y, factor, factor))
snake.draw()
pygame.display.flip()
time.sleep(0.035)
clock.tick(60)
if __name__ == "__main__":
main()
<br>
I suggest to use pygame.Rect
s and pygame.math.Vector2
to make your life easier and the code cleaner and more beautiful. The snake would be a list of rects and the velocity and position vectors. Rects have a colliderect
method with which you can see if it collides with another rect (e.g. the snake head with the body or the food rect).
In the update_snake
method you can iterate over the snake parts (rects) and then just set the part's topleft
attribute to the new position and the new pos to the previous position of the part at the same time (with the help of tuple unpacking). BTW, iterate over the list directly for part in snake:
instead of for i in range(len(snake)):
.
In the check_death
function you can use a rect with the size of the display and then check if it contains the head rect of the snake DISPLAY_RECT.contains(snake[0])
. And for collisions with the body any(snake[0].colliderect(part) for part in snake[1:])
.
import sys
import random
import pygame
from pygame.math import Vector2
pygame.init()
display_width = 800
display_height = 600
game_display = pygame.display.set_mode((display_width, display_height))
DISPLAY_RECT = pygame.Rect(0, 0, display_width, display_height)
WHITE = pygame.Color('white')
BLACK = pygame.Color('black')
TEAL = pygame.Color(0, 128, 128)
def update_snake(snake, new_pos):
for part in snake:
part.topleft, new_pos = new_pos, part.topleft
def check_death(snake):
touched_body = any(snake[0].colliderect(part) for part in snake[1:])
in_game_area = DISPLAY_RECT.contains(snake[0])
return touched_body or not in_game_area
def main():
clock = pygame.time.Clock()
score = 60
size = 10
snake = [pygame.Rect(20, y, size, size) for y in range(20, 171, 10)]
food = pygame.Rect(
random.randrange(0, display_width, 10),
random.randrange(0, display_height, 10),
size, size)
pos = Vector2(snake[0].topleft)
vel = Vector2(10, 0)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
vel = Vector2(-10, 0)
elif event.key == pygame.K_d:
vel = Vector2(10, 0)
elif event.key == pygame.K_w:
vel = Vector2(0, -10)
elif event.key == pygame.K_s:
vel = Vector2(0, 10)
elif event.key == pygame.K_c:
vel = Vector2(0, 0)
pos += vel
update_snake(snake, pos)
if snake[0].colliderect(food):
score += 10
food.topleft = (random.randrange(0, display_width, 10),
random.randrange(0, display_height, 10))
snake.append(snake[-1].copy())
print(len(snake), score)
if check_death(snake):
running = False
game_display.fill(WHITE)
pygame.draw.rect(game_display, BLACK, food)
for rect in snake:
pygame.draw.rect(game_display, TEAL, rect)
pygame.display.update()
pygame.time.wait(35)
clock.tick(60)
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()
-
1\$\begingroup\$ That is a very good advice I will try to implement it \$\endgroup\$Vidhyanshu jain– Vidhyanshu jain2017年04月25日 04:11:04 +00:00Commented Apr 25, 2017 at 4:11
-
\$\begingroup\$ I've changed the one liner in the
check_death
function, because I think it's more readable if the two expressions are assigned to names (and readability is one of our main concerns). Also, when a new snake part gets appended, you can just pass a copy of the last snake part/rectsnake.append(snake[-1].copy())
. \$\endgroup\$skrx– skrx2017年04月25日 18:17:01 +00:00Commented Apr 25, 2017 at 18:17