4797b4de11
For this, xrange was replaced by range and for imports of the own library lib.x was used instead of x.
501 lines
16 KiB
Python
501 lines
16 KiB
Python
from __future__ import print_function
|
|
from lib.board import Board
|
|
from lib.apple import Apple
|
|
from mock import MagicMock, patch
|
|
from random import randint
|
|
from lib.snake import Snake
|
|
from tinydb import TinyDB, Query
|
|
import math
|
|
import pygame
|
|
import sys
|
|
|
|
|
|
try:
|
|
from sense_hat import SenseHat
|
|
except ImportError:
|
|
print("[Game][error] An error occurred importing 'sense_hat.SenseHat'")
|
|
mock = MagicMock()
|
|
mock.clear.return_value = True
|
|
mock.set_pixel.return_value = True
|
|
mock.show_message.return_value = True
|
|
|
|
with patch.dict('sys.modules', {'sense_hat': mock, 'sense_hat.SenseHat': mock.RTIMU}):
|
|
from sense_hat import SenseHat
|
|
|
|
|
|
class Game:
|
|
BLOCK_SIZE = 36
|
|
|
|
COLUMNS = 8
|
|
ROWS = 8
|
|
|
|
FPS = None
|
|
|
|
COLORS = [
|
|
# 0 - Black
|
|
(0, 0, 0),
|
|
# 1 - Green
|
|
(0, 255, 0),
|
|
# 2 - Red
|
|
(255, 0, 0),
|
|
# 3 - Purple
|
|
(128, 0, 128),
|
|
# 4 - Blue
|
|
(0, 0, 255),
|
|
# 5 - Orange
|
|
(255, 165, 0),
|
|
# 6 - Cyan
|
|
(0, 255, 255),
|
|
# 7 - Yellow
|
|
(255, 255, 0),
|
|
# 8 - Dark Grey
|
|
(35, 35, 35),
|
|
# 9 - White
|
|
(255, 255, 255)
|
|
]
|
|
|
|
SHAPES = [
|
|
# Snake
|
|
[
|
|
[1],
|
|
],
|
|
|
|
# Apple
|
|
[
|
|
[2]
|
|
],
|
|
]
|
|
|
|
WIDTH = None
|
|
HEIGHT = None
|
|
|
|
COUNTDOWN = None
|
|
|
|
SCORE_INCREMENT = None
|
|
|
|
SCORE = 0
|
|
APPLES = 0
|
|
|
|
INTERVAL = None
|
|
INTERVAL_INCREMENT = None
|
|
|
|
LEVEL = 1
|
|
LEVEL_INCREMENT = None
|
|
|
|
PAUSED = False
|
|
|
|
GAMEOVER = False
|
|
|
|
BACKGROUND_GRID = None
|
|
|
|
SNAKE_MOVED = True
|
|
|
|
pygame = None
|
|
pygame_font = None
|
|
pygame_screen = None
|
|
sensehat = None
|
|
db = None
|
|
|
|
board = None
|
|
snake = None
|
|
apple = None
|
|
|
|
def __init__(self, columns, rows, fps, countdown, interval, score_increment, level_increment, interval_increment, pygame_instance=None):
|
|
self.COLUMNS = columns
|
|
self.ROWS = rows
|
|
self.FPS = fps
|
|
self.COUNTDOWN = countdown
|
|
self.INTERVAL = interval
|
|
self.SCORE_INCREMENT = score_increment
|
|
self.LEVEL_INCREMENT = level_increment
|
|
self.INTERVAL_INCREMENT = interval_increment
|
|
|
|
if pygame_instance is None:
|
|
self.pygame = pygame
|
|
else:
|
|
self.pygame = pygame_instance
|
|
|
|
self.sensehat = SenseHat()
|
|
self.db = TinyDB('data/database.json')
|
|
|
|
try:
|
|
self.WIDTH = self.BLOCK_SIZE * self.COLUMNS + 150
|
|
self.HEIGHT = self.BLOCK_SIZE * self.ROWS
|
|
|
|
self.BACKGROUND_GRID = [
|
|
[8 if x % 2 == y % 2 else 0 for x in range(self.COLUMNS)]
|
|
for y in range(self.ROWS)
|
|
]
|
|
|
|
self.pygame.init()
|
|
self.pygame.key.set_repeat(0, 0)
|
|
self.pygame_font = self.pygame.font.Font(self.pygame.font.get_default_font(), 12)
|
|
self.pygame_screen = self.pygame.display.set_mode((self.WIDTH, self.HEIGHT), 0, 24)
|
|
self.pygame.event.set_blocked(self.pygame.MOUSEMOTION)
|
|
|
|
self.board = Board(self.COLUMNS, self.ROWS)
|
|
self.__generate_snake()
|
|
self.__generate_apple()
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred initialising game")
|
|
|
|
def start(self, run_once=False):
|
|
print("[Game][info] Starting game")
|
|
|
|
try:
|
|
pygame_wait = True
|
|
|
|
while pygame_wait:
|
|
for event in self.pygame.event.get():
|
|
if event.type == self.pygame.KEYDOWN:
|
|
if event.key == self.pygame.K_RETURN:
|
|
pygame_wait = False
|
|
elif event.key == self.pygame.K_ESCAPE:
|
|
self.quit()
|
|
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
self.__display_message("Press to start")
|
|
self.pygame.display.update()
|
|
|
|
self.sensehat.clear()
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred starting game")
|
|
|
|
self.__countdown()
|
|
self.__loop()
|
|
self.finish()
|
|
|
|
if run_once is not True:
|
|
self.start()
|
|
|
|
def __countdown(self):
|
|
print("[Game][info] Starting game countdown")
|
|
|
|
try:
|
|
seconds = 0
|
|
|
|
while True:
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
remaining = (self.COUNTDOWN - seconds)
|
|
|
|
if seconds > self.COUNTDOWN:
|
|
break
|
|
|
|
if seconds == self.COUNTDOWN:
|
|
self.__display_message("Go!")
|
|
else:
|
|
self.__display_message("%d!" % remaining)
|
|
|
|
seconds += 1
|
|
self.sensehat.clear()
|
|
self.pygame.display.update()
|
|
self.pygame.time.wait(1000)
|
|
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
self.sensehat.clear()
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred starting game countdown")
|
|
|
|
def __loop(self):
|
|
print("[Game][info] Starting game loop")
|
|
|
|
try:
|
|
self.pygame.time.set_timer(pygame.USEREVENT + 1, self.INTERVAL)
|
|
|
|
key_actions = {
|
|
'ESCAPE': lambda: self.quit(),
|
|
'LEFT': lambda: self.__direction_left(),
|
|
'RIGHT': lambda: self.__direction_right(),
|
|
'DOWN': lambda: self.__direction_down(),
|
|
'UP': lambda: self.__direction_up(),
|
|
'p': lambda: self.toggle_pause(),
|
|
}
|
|
|
|
pygame_clock = self.pygame.time.Clock()
|
|
|
|
while not self.GAMEOVER:
|
|
self.sensehat.clear()
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
|
|
if self.PAUSED:
|
|
self.__display_message("Paused")
|
|
else:
|
|
self.__draw_line(
|
|
((self.BLOCK_SIZE * self.COLUMNS) + 1, 0),
|
|
((self.BLOCK_SIZE * self.COLUMNS) + 1, (self.HEIGHT - 1)),
|
|
self.COLORS[9]
|
|
)
|
|
|
|
self.__display_message(
|
|
"Score: %d\n\nLevel: %d\n\nApples: %d" % (self.SCORE, self.LEVEL, self.APPLES),
|
|
((self.BLOCK_SIZE * self.COLUMNS) + self.BLOCK_SIZE, 2),
|
|
self.COLORS[9],
|
|
self.COLORS[0],
|
|
False
|
|
)
|
|
|
|
self.__draw_matrix(self.BACKGROUND_GRID, (0, 0), None, False)
|
|
self.__draw_matrix(self.board.coordinates(), (0, 0), None, False)
|
|
self.__draw_matrix(self.snake.coordinates(self.COLUMNS, self.ROWS), (0, 0))
|
|
self.__draw_matrix(self.apple.coordinates(), (self.apple.x(), self.apple.y()))
|
|
|
|
self.pygame.display.update()
|
|
|
|
for event in self.pygame.event.get():
|
|
if event.type == self.pygame.USEREVENT + 1:
|
|
self.__move()
|
|
elif event.type == self.pygame.QUIT:
|
|
self.quit()
|
|
elif event.type == self.pygame.KEYDOWN:
|
|
for key in key_actions:
|
|
if event.key == eval("self.pygame.K_" + key):
|
|
key_actions[key]()
|
|
|
|
pygame_clock.tick(self.FPS)
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred during game loop")
|
|
|
|
def __generate_snake(self):
|
|
print("[Game][info] Generating snake")
|
|
|
|
self.snake = Snake(self.SHAPES[0], int(math.floor(self.COLUMNS / 2)), int(math.floor(self.ROWS / 2)))
|
|
|
|
def __generate_apple(self):
|
|
print("[Game][info] Generating apple")
|
|
|
|
apple_collision = True
|
|
offset_x, offset_y = (0, 0)
|
|
|
|
while apple_collision:
|
|
offset_x = randint(0, self.ROWS - 1)
|
|
offset_y = randint(0, self.COLUMNS - 1)
|
|
|
|
apple_collision = self.board.check_collision(self.snake, (offset_x, offset_y))
|
|
|
|
self.apple = Apple(self.SHAPES[1], offset_x, offset_y)
|
|
|
|
def __display_message(self, message, coordinates=None, color=COLORS[9], background_color=COLORS[0], sensehat=True):
|
|
print("[Game][info] Displaying message")
|
|
|
|
if sensehat:
|
|
self.sensehat.show_message(message, text_colour=color, scroll_speed=0.05)
|
|
|
|
for i, line in enumerate(message.splitlines()):
|
|
message_image = self.pygame_font.render(line, False, color, background_color)
|
|
|
|
if coordinates is not None:
|
|
position_x, position_y = coordinates
|
|
else:
|
|
message_image_center_x, message_image_center_y = message_image.get_size()
|
|
message_image_center_x //= 2
|
|
message_image_center_y //= 2
|
|
position_x = self.WIDTH // 2 - message_image_center_x
|
|
position_y = self.HEIGHT // 2 - message_image_center_y
|
|
|
|
self.pygame_screen.blit(
|
|
message_image,
|
|
(position_x, position_y + i * 22)
|
|
)
|
|
|
|
def __draw_line(self, start_position, end_position, color=COLORS[9], sensehat=True):
|
|
print("[Game][info] Drawing line")
|
|
|
|
if sensehat:
|
|
start_x, start_y = start_position
|
|
end_x, end_y = start_position
|
|
|
|
for i in range(start_y, end_y):
|
|
for j in range(start_x, end_x):
|
|
if start_x == end_x or start_y == end_y:
|
|
self.sensehat.set_pixel(j, i, color)
|
|
|
|
self.pygame.draw.line(self.pygame_screen, color, start_position, end_position)
|
|
|
|
def __draw_matrix(self, matrix, offset, color=None, sensehat=True):
|
|
print("[Game][info] Drawing matrix")
|
|
|
|
offset_x, offset_y = offset
|
|
|
|
for y, row in enumerate(matrix):
|
|
for x, val in enumerate(row):
|
|
if val:
|
|
if color is None:
|
|
shape_color = self.COLORS[val]
|
|
else:
|
|
shape_color = color
|
|
|
|
if sensehat:
|
|
self.sensehat.set_pixel((offset_x + x), (offset_y + y), shape_color)
|
|
|
|
self.pygame.draw.rect(
|
|
self.pygame_screen,
|
|
shape_color,
|
|
self.pygame.Rect((offset_x + x) * self.BLOCK_SIZE, (offset_y + y) * self.BLOCK_SIZE, self.BLOCK_SIZE, self.BLOCK_SIZE),
|
|
0
|
|
)
|
|
|
|
def __count_clear_apples(self, apples):
|
|
print("[Game][info] Counting cleared apples")
|
|
|
|
if apples > 0:
|
|
self.APPLES += apples
|
|
self.SCORE += self.SCORE_INCREMENT * self.LEVEL
|
|
|
|
if self.APPLES >= self.LEVEL * self.LEVEL_INCREMENT:
|
|
self.LEVEL += 1
|
|
delay = self.INTERVAL - self.INTERVAL_INCREMENT * (self.LEVEL - 1)
|
|
delay = 100 if delay < 100 else delay
|
|
self.pygame.time.set_timer(self.pygame.USEREVENT + 1, delay)
|
|
|
|
def __move(self):
|
|
print("[Game][info] Moving snake %s" % (self.snake.direction()))
|
|
|
|
if not self.GAMEOVER and not self.PAUSED:
|
|
new_x, new_y = self.snake.head()
|
|
|
|
if self.snake.direction() == self.snake.DIRECTION_UP:
|
|
new_y = self.snake.y() - 1
|
|
elif self.snake.direction() == self.snake.DIRECTION_DOWN:
|
|
new_y = self.snake.y() + 1
|
|
elif self.snake.direction() == self.snake.DIRECTION_LEFT:
|
|
new_x = self.snake.x() - 1
|
|
elif self.snake.direction() == self.snake.DIRECTION_RIGHT:
|
|
new_x = self.snake.x() + 1
|
|
|
|
if self.board.check_collision(self.snake, (new_x, new_y)):
|
|
self.GAMEOVER = True
|
|
|
|
return False
|
|
|
|
tail_x, tail_y = self.snake.tail()
|
|
self.snake.set_position((new_x, new_y))
|
|
cleared_apples = 0
|
|
|
|
if new_x == self.apple.x() and new_y == self.apple.y():
|
|
self.snake.add_segment((tail_x, tail_y))
|
|
cleared_apples += 1
|
|
self.__generate_apple()
|
|
|
|
self.__count_clear_apples(cleared_apples)
|
|
self.SNAKE_MOVED = True
|
|
|
|
return True
|
|
|
|
def __direction_up(self):
|
|
print("[Game][info] Event direction up")
|
|
|
|
if self.snake.direction() != self.snake.DIRECTION_DOWN and self.SNAKE_MOVED is True:
|
|
self.snake.set_direction(self.snake.DIRECTION_UP)
|
|
self.SNAKE_MOVED = False
|
|
|
|
def __direction_down(self):
|
|
print("[Game][info] Event direction down")
|
|
|
|
if self.snake.direction() != self.snake.DIRECTION_UP and self.SNAKE_MOVED is True:
|
|
self.snake.set_direction(self.snake.DIRECTION_DOWN)
|
|
self.SNAKE_MOVED = False
|
|
|
|
def __direction_left(self):
|
|
print("[Game][info] Event direction left")
|
|
|
|
if self.snake.direction() != self.snake.DIRECTION_RIGHT and self.SNAKE_MOVED is True:
|
|
self.snake.set_direction(self.snake.DIRECTION_LEFT)
|
|
self.SNAKE_MOVED = False
|
|
|
|
def __direction_right(self):
|
|
print("[Game][info] Event direction right")
|
|
|
|
if self.snake.direction() != self.snake.DIRECTION_LEFT and self.SNAKE_MOVED is True:
|
|
self.snake.set_direction(self.snake.DIRECTION_RIGHT)
|
|
self.SNAKE_MOVED = False
|
|
|
|
def toggle_pause(self):
|
|
print("[Game][info] Toggling paused state")
|
|
|
|
self.PAUSED = not self.PAUSED
|
|
|
|
def get_score(self):
|
|
print("[Game][info] Calculating score")
|
|
|
|
return self.SCORE
|
|
|
|
def print_score(self, high_score=False):
|
|
print("[Game][info] Printing score")
|
|
|
|
score = self.get_score()
|
|
|
|
try:
|
|
self.sensehat.clear()
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
|
|
if high_score:
|
|
self.__display_message("Game Over!\n\nHigh score: %d" % score)
|
|
self.pygame.display.update()
|
|
else:
|
|
self.__display_message("Game Over!\n\nYour score: %d!" % self.get_score())
|
|
self.pygame.display.update()
|
|
|
|
self.pygame.time.wait(3000)
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred printing score")
|
|
|
|
def finish(self):
|
|
print("[Game][info] Finishing game")
|
|
|
|
score = self.get_score()
|
|
|
|
self.pygame.display.update()
|
|
|
|
if self.db.contains(Query().score >= score):
|
|
self.print_score()
|
|
else:
|
|
self.print_score(True)
|
|
|
|
self.db.insert({'score': score})
|
|
self.reset()
|
|
|
|
def quit(self):
|
|
print("[Game][info] Quitting game")
|
|
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
self.sensehat.clear()
|
|
self.__display_message("Exiting...")
|
|
self.pygame.display.update()
|
|
sys.exit()
|
|
|
|
def reset(self):
|
|
print("[Game][info] Resetting game")
|
|
|
|
self.PAUSED = False
|
|
self.GAMEOVER = False
|
|
self.SCORE = 0
|
|
self.APPLES = 0
|
|
self.LEVEL = 1
|
|
self.SNAKE_MOVED = True
|
|
|
|
self.board = Board(self.COLUMNS, self.ROWS)
|
|
self.snake = None
|
|
self.apple = None
|
|
self.__generate_snake()
|
|
self.__generate_apple()
|
|
|
|
self.sensehat.clear()
|
|
self.pygame.time.set_timer(pygame.USEREVENT + 1, 0)
|
|
self.pygame.display.update()
|
|
|
|
def cleanup(self):
|
|
print("[Game][info] Game clean up")
|
|
|
|
try:
|
|
self.sensehat.clear()
|
|
self.pygame_screen.fill(self.COLORS[0])
|
|
self.pygame.display.update()
|
|
self.pygame.quit()
|
|
except AttributeError:
|
|
print("[Game][error] An error occurred cleaning up")
|
|
|
|
def __exit__(self):
|
|
print("[Game][info] Game exit")
|
|
|
|
self.cleanup()
|