Last active
July 18, 2023 11:05
-
-
Save SimonKocurek/85ff49d41208fbdccd65d3dac5968b42 to your computer and use it in GitHub Desktop.
Snake game on a Micro:bit controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from microbit import * | |
from random import randrange | |
class State: | |
def __init__(self): | |
self.last_frame_time = 0 | |
self.speed = 500 | |
self.snake_pos = [(2, 2)] | |
self.food_pos = (0, 0) | |
self.directions = ((1, 0), (0, 1), (-1, 0), (0, -1)) | |
self.direction = 0 | |
self.board_max_x = 5 | |
self.board_max_y = 5 | |
self.game_lost = False | |
def main(): | |
state = State() | |
spawn_food(state) | |
while not state.game_lost: | |
wait_for_next_step(state) | |
handle_input(state) | |
move_snake(state) | |
render(state) | |
display.scroll('Score: ' + str(len(state.snake_pos))) | |
restart_on_button_press() | |
def handle_input(state): | |
if button_a.was_pressed(): | |
if state.direction == 0: | |
state.direction = len(state.directions) | |
state.direction -= 1 | |
elif button_b.was_pressed(): | |
state.direction = (state.direction + 1) % len(state.directions) | |
def wait_for_next_step(state): | |
current_time = running_time() | |
time_to_next_step = state.speed - (current_time - state.last_frame_time) | |
while time_to_next_step > 0: | |
# we cannot trust sleep to work with 100ms precision | |
# but it's still better than going in while loop over and over again | |
if time_to_next_step > 100: | |
sleep(time_to_next_step - 100) | |
current_time = running_time() | |
time_to_next_step = state.speed - (current_time - state.last_frame_time) | |
state.last_frame_time = current_time | |
def move_snake(state): | |
# Add new head and remove last tail | |
state.snake_pos.insert(0, get_next_snake_point(state)) | |
state.snake_pos.pop() | |
# Head ate part of tail | |
if state.snake_pos[0] in state.snake_pos[1:]: | |
state.game_lost = True | |
# Ate food | |
if state.food_pos == state.snake_pos[0]: | |
state.snake_pos.append(state.snake_pos[0]) | |
spawn_food(state) | |
def get_next_snake_point(state): | |
old_x, old_y = state.snake_pos[0] | |
change_x, change_y = state.directions[state.direction] | |
new_x = (old_x + change_x) % state.board_max_x | |
if new_x < 0: | |
new_x = state.board_max_x - 1 | |
new_y = (old_y + change_y) % state.board_max_y | |
if new_y < 0: | |
new_y = state.board_max_y - 1 | |
return (new_x, new_y) | |
def spawn_food(state): | |
# Just doing state.food_pos = (random.randrange(5), random.randrange(5)) would be problematic, | |
# when there are no longer many free spaces. So instead we create a set of all free spaces and | |
# choose random one from those | |
all_places = set([(x, y) for x in range(state.board_max_x) for y in range(state.board_max_y)]) | |
snake_places = set(state.snake_pos) | |
free_places = list(all_places.difference(snake_places)) | |
chosen_place_idx = randrange(len(free_places)) | |
state.food_pos = free_places[chosen_place_idx] | |
def render(state): | |
# we don't use display.clear() and display.set_pixel() since that would cause | |
# flickering (lights would turn off for a brief moment) | |
board = ['0' * state.board_max_x] * state.board_max_y | |
image = Image(':'.join(board)) | |
image.set_pixel(state.food_pos[0], state.food_pos[1], 4) | |
render_snake(state, image) | |
display.show(image) | |
def render_snake(state, image): | |
# We first render tail with lower brightness so that we can tell what is head | |
# and what is tail in case the snake goes in a straight line... habing head | |
# exactly before it's tail | |
snake_tail = state.snake_pos[-1] | |
image.set_pixel(snake_tail[0], snake_tail[1], 6) | |
for snake_part in state.snake_pos[1:]: | |
image.set_pixel(snake_part[0], snake_part[1], 8) | |
# In case of len(state.snake_pos) == 1, lower brightness of tail gets overriden by | |
# higher brightness of head | |
snake_head = state.snake_pos[0] | |
image.set_pixel(snake_head[0], snake_head[1], 9) | |
def restart_on_button_press(): | |
# Reset button presses | |
button_a.was_pressed() | |
button_b.was_pressed() | |
# We have no support for interrupts unfortunately, so waiting it is... | |
while not (button_a.was_pressed() or button_b.was_pressed()): | |
sleep(1000) | |
reset() | |
main() |
Author
SimonKocurek
commented
Nov 7, 2020
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment