501 lines
16 KiB
Python
501 lines
16 KiB
Python
from __future__ import print_function
|
|
from board import Board
|
|
from apple import Apple
|
|
from mock import MagicMock, patch
|
|
from random import randint
|
|
from 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 xrange(self.COLUMNS)]
|
|
for y in xrange(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 xrange(start_y, end_y):
|
|
for j in xrange(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()
|