2
\$\begingroup\$

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)
asked Apr 21, 2017 at 7:14
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

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>
answered Apr 21, 2017 at 10:19
\$\endgroup\$
0
1
\$\begingroup\$

I suggest to use pygame.Rects 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()
answered Apr 25, 2017 at 0:45
\$\endgroup\$
2
  • 1
    \$\begingroup\$ That is a very good advice I will try to implement it \$\endgroup\$ Commented 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/rect snake.append(snake[-1].copy()). \$\endgroup\$ Commented Apr 25, 2017 at 18: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.