|
@@ -0,0 +1,177 @@
|
|
|
|
|
+from typing import List
|
|
|
|
|
+import pygame
|
|
|
|
|
+from random import randint
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class Pos:
|
|
|
|
|
+ def __init__(self, x, y):
|
|
|
|
|
+ self.x, self.y = int(x), int(y)
|
|
|
|
|
+
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ def random(cls):
|
|
|
|
|
+ return cls(randint(0, Game.MAP.x - 1), randint(0, Game.MAP.y - 1))
|
|
|
|
|
+
|
|
|
|
|
+ def copy(self):
|
|
|
|
|
+ return Pos(self.x, self.y)
|
|
|
|
|
+
|
|
|
|
|
+ def __eq__(self, other):
|
|
|
|
|
+ return self.x == other.x and self.y == other.y
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class Colours:
|
|
|
|
|
+ BACKGROUND = (0, 0, 0)
|
|
|
|
|
+ FOREGROUND = (255, 0, 0)
|
|
|
|
|
+ SNAKE = (0, 255, 0)
|
|
|
|
|
+ FOOD = (0, 0, 255)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class Game:
|
|
|
|
|
+ MAP = Pos(20, 20)
|
|
|
|
|
+ CUBE = 20
|
|
|
|
|
+ FPS = 60
|
|
|
|
|
+ SPEED = 120
|
|
|
|
|
+ DEBUG = False
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self):
|
|
|
|
|
+ start_pos = Pos(self.MAP.x / 2, self.MAP.y / 2)
|
|
|
|
|
+ self.snake = Snake(start_pos)
|
|
|
|
|
+ self.dead = False
|
|
|
|
|
+ self.running = True
|
|
|
|
|
+ self.score = 0
|
|
|
|
|
+
|
|
|
|
|
+ self.screen = pygame.display.set_mode((self.MAP.y * self.CUBE, self.MAP.x * self.CUBE))
|
|
|
|
|
+ self.clock = pygame.time.Clock()
|
|
|
|
|
+ self.timer = 0.0
|
|
|
|
|
+ self.font = pygame.font.SysFont('Arial', 20, True, False)
|
|
|
|
|
+
|
|
|
|
|
+ def proc_events(self, e):
|
|
|
|
|
+ if e.type == pygame.QUIT:
|
|
|
|
|
+ self.running = False
|
|
|
|
|
+ if e.type != pygame.KEYDOWN:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ if e.key == pygame.K_SPACE and self.dead:
|
|
|
|
|
+ start_pos = Pos(self.MAP.x / 2, self.MAP.y / 2)
|
|
|
|
|
+ self.snake = Snake(start_pos)
|
|
|
|
|
+ self.dead = False
|
|
|
|
|
+
|
|
|
|
|
+ if e.key == pygame.K_UP:
|
|
|
|
|
+ if self.snake.dir != 1:
|
|
|
|
|
+ self.snake.dir_next = 0
|
|
|
|
|
+
|
|
|
|
|
+ elif e.key == pygame.K_DOWN:
|
|
|
|
|
+ if self.snake.dir != 0:
|
|
|
|
|
+ self.snake.dir_next = 1
|
|
|
|
|
+
|
|
|
|
|
+ elif e.key == pygame.K_LEFT:
|
|
|
|
|
+ if self.snake.dir != 3:
|
|
|
|
|
+ self.snake.dir_next = 2
|
|
|
|
|
+
|
|
|
|
|
+ elif e.key == pygame.K_RIGHT:
|
|
|
|
|
+ if self.snake.dir != 2:
|
|
|
|
|
+ self.snake.dir_next = 3
|
|
|
|
|
+
|
|
|
|
|
+ elif e.key == pygame.K_d:
|
|
|
|
|
+ self.DEBUG = not self.DEBUG
|
|
|
|
|
+
|
|
|
|
|
+ elif e.key == pygame.K_ESCAPE:
|
|
|
|
|
+ self.running = False
|
|
|
|
|
+
|
|
|
|
|
+ def loop(self):
|
|
|
|
|
+ for event in pygame.event.get():
|
|
|
|
|
+ self.proc_events(event)
|
|
|
|
|
+
|
|
|
|
|
+ if self.dead:
|
|
|
|
|
+ text = self.font.render(f"SCORE: {self.score}", True, Colours.FOREGROUND)
|
|
|
|
|
+ self.screen.blit(text, (self.MAP.x * self.CUBE / 2, self.MAP.y * self.CUBE / 2))
|
|
|
|
|
+ pygame.display.flip()
|
|
|
|
|
+ self.clock.tick(30)
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ if 0 > self.snake.head.x or self.snake.head.x >= self.MAP.x or \
|
|
|
|
|
+ 0 > self.snake.head.y or self.snake.head.y >= self.MAP.y:
|
|
|
|
|
+ self.dead = True
|
|
|
|
|
+
|
|
|
|
|
+ self.screen.fill(Colours.BACKGROUND)
|
|
|
|
|
+ pygame.draw.rect(
|
|
|
|
|
+ self.screen, Colours.FOOD,
|
|
|
|
|
+ (self.snake.food.y * self.CUBE, self.snake.food.x * self.CUBE, self.CUBE - 1, self.CUBE - 1), 0)
|
|
|
|
|
+
|
|
|
|
|
+ for c in self.snake.body:
|
|
|
|
|
+ pygame.draw.rect(
|
|
|
|
|
+ self.screen, Colours.SNAKE,
|
|
|
|
|
+ (c.y * self.CUBE, c.x * self.CUBE, self.CUBE - 1, self.CUBE - 1), 0)
|
|
|
|
|
+
|
|
|
|
|
+ self.timer += self.clock.get_time()
|
|
|
|
|
+ if self.timer >= self.SPEED:
|
|
|
|
|
+ self.timer = 0.0
|
|
|
|
|
+ if self.snake.dir_next == 0:
|
|
|
|
|
+ self.snake.head.x -= 1
|
|
|
|
|
+ elif self.snake.dir_next == 1:
|
|
|
|
|
+ self.snake.head.x += 1
|
|
|
|
|
+ elif self.snake.dir_next == 2:
|
|
|
|
|
+ self.snake.head.y -= 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.snake.head.y += 1
|
|
|
|
|
+ self.snake.dir = self.snake.dir_next
|
|
|
|
|
+
|
|
|
|
|
+ if self.snake.collide(self.snake.head):
|
|
|
|
|
+ self.dead = True
|
|
|
|
|
+
|
|
|
|
|
+ if self.snake.head == self.snake.food:
|
|
|
|
|
+ self.snake.add_food()
|
|
|
|
|
+ self.snake.size += 1
|
|
|
|
|
+ self.score += 1
|
|
|
|
|
+
|
|
|
|
|
+ self.snake.move()
|
|
|
|
|
+
|
|
|
|
|
+ if self.DEBUG:
|
|
|
|
|
+ text1 = self.font.render(
|
|
|
|
|
+ f"X: {self.snake.head.x:02d} Y: {self.snake.head.y:02d} "
|
|
|
|
|
+ f"S: {self.snake.size} FPS: {self.clock.get_fps():.1f}",
|
|
|
|
|
+ True, Colours.FOREGROUND)
|
|
|
|
|
+ text2 = self.font.render(
|
|
|
|
|
+ f"F X: {self.snake.food.x:02d} Y: {self.snake.food.y:02d} S: {self.score}",
|
|
|
|
|
+ True, Colours.FOREGROUND)
|
|
|
|
|
+ self.screen.blit(text1, (10, 10))
|
|
|
|
|
+ self.screen.blit(text2, (10, 32))
|
|
|
|
|
+
|
|
|
|
|
+ pygame.display.flip()
|
|
|
|
|
+ self.clock.tick(60)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class Snake:
|
|
|
|
|
+ def __init__(self, start: Pos, size=3, dir=0):
|
|
|
|
|
+ self.size: int = size
|
|
|
|
|
+ self.head: Pos = start
|
|
|
|
|
+ self.body: List[Pos] = [start.copy()]
|
|
|
|
|
+ self.dir: int = dir # Direction 0=N, 1=S, 2=W, 3=E
|
|
|
|
|
+ self.dir_next: int = dir # Next direction
|
|
|
|
|
+ self.food: Pos = None
|
|
|
|
|
+ self.add_food()
|
|
|
|
|
+
|
|
|
|
|
+ def add_food(self):
|
|
|
|
|
+ self.food = Pos.random()
|
|
|
|
|
+ while self.collide(self.food):
|
|
|
|
|
+ self.food = Pos.random()
|
|
|
|
|
+
|
|
|
|
|
+ def collide(self, pos: Pos) -> bool:
|
|
|
|
|
+ for l in self.body:
|
|
|
|
|
+ if pos == l:
|
|
|
|
|
+ return True
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ def move(self):
|
|
|
|
|
+ self.body.append(self.head.copy())
|
|
|
|
|
+ if len(self.body) > self.size:
|
|
|
|
|
+ self.body.pop(0)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == '__main__':
|
|
|
|
|
+ pygame.init()
|
|
|
|
|
+ pygame.display.set_caption("Snake")
|
|
|
|
|
+ game = Game()
|
|
|
|
|
+ game.DEBUG = True
|
|
|
|
|
+ while game.running:
|
|
|
|
|
+ game.loop()
|
|
|
|
|
+ pygame.quit()
|