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)
\$\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>
\$\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()
\$\endgroup\$
2
  • 1
    \$\begingroup\$ That is a very good advice I will try to implement it \$\endgroup\$ 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\$
    – skrx
    Apr 25, 2017 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.