You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

277 lines
9.1 KiB

# SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
from uasyncio import sleep_ms
from common import dis
from display import Display, FontSmall
from settings import Settings
from ux import KeyInputHandler
import utime
import random
# Game State Machine
READY_TO_PLAY = 1
THE_GAMES_AFOOT = 2
GAME_OVER = 3
TRYING_TO_QUIT = 4
SLITHER_RIGHT = 1
SLITHER_LEFT = 2
SLITHER_UP = 3
SLITHER_DOWN = 4
BLOCK_SIZE = 10
MIN_X = 0
MIN_Y = 30
MAX_X = 230
MAX_Y = 240
class Snack:
def __init__(self, dis):
self.dis = dis
self.x = random.randrange(MIN_X, MAX_X, BLOCK_SIZE)
self.y = random.randrange(MIN_Y, MAX_Y, BLOCK_SIZE)
self.size = BLOCK_SIZE
def newPosition(self):
self.x = random.randrange(MIN_X, MAX_X - BLOCK_SIZE, BLOCK_SIZE)
self.y = random.randrange(MIN_Y, MAX_Y - BLOCK_SIZE, BLOCK_SIZE)
def draw(self):
# self.dis.draw_rect(self.x, self.y, self.size, self.size, 5)
self.dis.icon(self.x, self.y, 'fruit')
class Snake:
def __init__(self, dis, length):
self.dis = dis
self.length = length
self.x=[]
self.y=[]
self.size = BLOCK_SIZE
self.step = BLOCK_SIZE
self.direction = SLITHER_RIGHT
# Set starting position in center screen
for i in range(0,self.length):
self.x.append((MAX_X + MIN_X) // 2 + BLOCK_SIZE // 2)
self.y.append((MAX_Y + MIN_Y) // 2 + BLOCK_SIZE // 2)
def update(self):
# Advance position of tail segments
for i in range(self.length - 1,0,-1):
self.x[i] = self.x[i-1]
self.y[i] = self.y[i-1]
# update head position
if self.direction == SLITHER_RIGHT:
self.x[0] = self.x[0] + self.step
elif self.direction == SLITHER_LEFT:
self.x[0] = self.x[0] - self.step
elif self.direction == SLITHER_UP:
self.y[0] = self.y[0] - self.step
elif self.direction == SLITHER_DOWN:
self.y[0] = self.y[0] + self.step
def addSegment(self):
self.x.append(self.x[self.length - 1])
self.y.append(self.y[self.length - 1])
self.length += 1
def move(self, direction):
self.direction = direction
def draw(self):
for i in range(0, self.length):
self.dis.draw_rect(self.x[i], self.y[i], self.size, self.size, 5)
def isCollision(self,x,y):
for i in range(0, self.length):
if (x == self.x[i] and y == self.y[i]):
return True
return False
class Game:
def __init__(self):
self.dis = dis
self.font = FontSmall
self.running = True
self.pending_direction = SLITHER_RIGHT
self.prev_time = 0
self.state = READY_TO_PLAY
self.score = 0
self.speed = 100;
self.highscore = 0
self.snake = Snake(self.dis, 3)
self.snack = Snack(self.dis)
self.input = KeyInputHandler(down='udplrxy', up='xy')
# ensure initial snack spawn isn't on snake
while (self.snake.isCollision(self.snack.x, self.snack.y)):
self.snack.newPosition()
def isCollision(self,x1,y1,x2,y2):
if x1 == x2 and y1 == y2:
return True
return False
def move(self, direction):
self.pending_direction = direction
def render(self):
self.dis.clear()
self.dis.draw_header('Score: {}'.format(self.score), left_text = str(self.highscore))
# draw arena bounding box
self.dis.draw_rect(MIN_X, MIN_Y, MAX_X, MAX_Y, 2)
# rendering of snake, snacks, etc
if self.state == READY_TO_PLAY:
self.snake.draw()
if self.state == THE_GAMES_AFOOT:
self.snake.draw()
self.snack.draw()
# rendering of game over screen
if self.state == GAME_OVER:
POPUP_WIDTH = 180
POPUP_HEIGHT = 100
POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2)
POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2)
self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4)
self.dis.text(None, Display.HALF_HEIGHT - 3 * self.font.leading // 4 - 9, 'GAME OVER!')
self.dis.text(None, Display.HALF_HEIGHT - 9, 'Score: ' + str(self.score))
self.dis.text(None, Display.HALF_HEIGHT + 3 * self.font.leading // 4 - 9, 'Highscore: ' + str(self.highscore))
# rendering quit confirmation screen
if self.state == TRYING_TO_QUIT:
POPUP_WIDTH = 180
POPUP_HEIGHT = 200
POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2)
POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2)
self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4)
self.dis.text(None, Display.HALF_HEIGHT - 3 * self.font.leading // 4 - 9, 'Are you sure you')
self.dis.text(None, Display.HALF_HEIGHT - 9, 'want to quit? ')
# rendering of footer
if self.state == READY_TO_PLAY or self.state == GAME_OVER:
right_btn = 'START'
left_btn = 'BACK'
elif self.state == THE_GAMES_AFOOT:
right_btn = ''
left_btn = "BACK"
elif self.state == TRYING_TO_QUIT:
right_btn = 'YES'
left_btn = 'NO'
self.dis.draw_footer(left_btn, right_btn, self.input.is_pressed('x'), self.input.is_pressed('y'))
self.dis.show()
# tracking game logic and do collision checking.
def update(self, now):
if (now - self.prev_time > self.speed):
self.prev_time = now
if self.state == THE_GAMES_AFOOT:
self.snake.direction = self.pending_direction
self.snake.update()
# snake is updated but not drawn yet, check for collisions....
# if snek leaves game arena, wrap to other side.
if (self.snake.x[0] > MIN_X + MAX_X - BLOCK_SIZE):
self.snake.x[0] = MIN_X
elif (self.snake.x[0] < MIN_X):
self.snake.x[0] = MIN_X + MAX_X - BLOCK_SIZE
elif (self.snake.y[0] > MIN_Y + MAX_Y - BLOCK_SIZE):
self.snake.y[0] = MIN_Y
elif (self.snake.y[0] < MIN_Y):
self.snake.y[0] = MIN_Y + MAX_Y - BLOCK_SIZE
# if snek eat snac
if (self.isCollision(self.snake.x[0], self.snake.y[0], self.snack.x, self.snack.y)):
self.snake.addSegment()
self.snack.newPosition()
while (self.snake.isCollision(self.snack.x, self.snack.y)):
self.snack.newPosition()
self.score += 1
self.speed = 0.98 * self.speed
# if snek eat snek
for i in range(1, self.snake.length):
if self.isCollision(self.snake.x[0], self.snake.y[0], self.snake.x[i], self.snake.y[i]):
self.state = GAME_OVER
if self.score > self.highscore:
self.highscore = self.score
def start(self):
self.state = THE_GAMES_AFOOT
self.score = 0
self.snake.length = 3
for i in range(0,self.snake.length):
self.snake.x[i] = (MAX_X + MIN_X) // 2 + BLOCK_SIZE // 2
self.snake.y[i] = (MAX_Y + MIN_Y) // 2 + BLOCK_SIZE // 2
self.prev_time = utime.ticks_ms()
async def snake_game():
# Game functions and settings
s = Settings()
s.load()
game = Game()
game.highscore = s.get('snake_highscore', 0)
input = KeyInputHandler(down='udplrxy', up='xy')
while game.running:
event = await input.get_event()
if event != None:
# Handle key event and update game state
key, event_type = event
if event_type == 'down':
if key == 'l':
game.move(SLITHER_LEFT)
elif key == 'r':
game.move(SLITHER_RIGHT)
elif key == 'u':
game.move(SLITHER_UP)
elif key == 'd':
game.move(SLITHER_DOWN)
elif event_type == 'up':
if key == 'x':
if game.state == THE_GAMES_AFOOT:
game.state = TRYING_TO_QUIT
elif game.state == TRYING_TO_QUIT:
game.state = THE_GAMES_AFOOT
elif game.state == READY_TO_PLAY or game.state == GAME_OVER:
game.running = False
elif key == 'y':
if game.state == READY_TO_PLAY or game.state == GAME_OVER:
game.start()
elif game.state == TRYING_TO_QUIT:
game.running = False
game.update(utime.ticks_ms())
game.render()
await sleep_ms(1)
s.set('snake_highscore', game.highscore)
s.save()
return None