A simple snake game running on micro:bit chip.
The snake is intialized with length of 2, where its body coordinates are (2, 3), (2, 3) and running on a 5 x 5 LED map on the microbit chip. The food location is randomly assigned to the coordinate which is not a part of snake's body. The game is over when the snake hits the wall (out of bounce) or crash with its own body.
0 stands for LED off.
- Snake's body: 9
- food: 5
- Button A: LEFT TURN
- Button B: RIGHT TURN
This project is done by python. You can flash the code to the chip either from web or via pip package uflash
.
To install needed package:
pip install uflash
To flash the python script to the chip
uflash snake.py
import time
import random
from collections import namedtuple
from microbit import display, button_a, button_b, Image
- The
microbit
library is exclusively for microbit chip, which is used for control of display (LED) and buttons (button_a
,button_b
), other than that,Image
class is for creating a object as input of display function. random
library is used for randomly generate new food on maptime
is used for game clock and game frame (refresh rate on snake's movement).collections
'namedtuple
is used for improvement of tuple readability.
# Map constant
COL_LEN = 5 # row and column number in micro:bit
MAP_LEN = COL_LEN ** 2
# Point in Map, data structure
point = namedtuple('point', ['x', 'y'])
# Image: LED opacity settings.
LIGHT, MEDIUM, DENSE = "0", "5", "9"
# CLOCK: clock delay time and frequency
CLOCK = 0.002 # Period 2 ms
FRAME = 50 # How fast the snake goes
COL_LEN
- length of LED columns, in microbit, it is 5.MAP_LEN
- squared ofCOL_LEN
. Althought we can treat LED map as 5 x 5 matrix, but in this project, I will transform 2-D array to 1-D array representation, in which, length of 25.point
- named tuple, we can now refer object's 0-index as x, and 1-index as y.LIGHT
,MEDIUM
,DENSE
- Opagueness of the LED light in a point. Scale from 1 to 9.CLOCK
- also treat as game period, is 2 ms.FRAME
- 50 frames, in every 50 frames, snake moves 1 step.
Game manager that keeps the game's status.
class Game:
def __init__(self):
self.map = [LIGHT] * (COL_LEN ** 2)
self.food = point(0, 0) # food's position
self.frame = 0
self.finished = False
def generate_food(self, snake_body):
while 1:
new_food = point(random.randint(0, COL_LEN - 1), random.randint(0, COL_LEN - 1))
if not snake_body.count(new_food):
self.food = new_food
break
Snake object that describe the snake's body and current moving direction.
class Snake:
def __init__(self):
self.body = [point(2, 2), point(2, 3)]
self.direction = 0
- direction - stores current snake's head moving direction.
- body - stores snake's body (list of coordinates)
Once we have our map, we can ask micro bit LED to render current map.
def generate_image(current_map):
graph = "".join(current_map)
return Image(":".join([graph[i: i + COL_LEN] for i in range(0, MAP_LEN, COL_LEN)]))
This function return Image
object which can then be called as input of display
method.
game = Game()
snake = Snake()
while 1:
if not game.finished:
# Button A make a left turn, Butten B make a right turn
if button_a.was_pressed():
snake.direction += 1
elif button_b.was_pressed():
snake.direction -= 1
# copy current game's map as current_map
current_map = game.map.copy()
# mark the current game's food position as MEDIUM in the map
current_map[game.food.y * COL_LEN + game.food.x] = MEDIUM
# mark snake's body as DENSE in the map
for body in snake.body:
current_map[body.y * COL_LEN + body.x] = DENSE
# Rendering the map
display.show(generate_image(current_map))
# Take the direction action and update the snake body (eg per 0.5 sec)
if game.frame % FRAME == 0:
# avoid overflow
game.frame = 0
# Taking out snake's head, tail and middle
head = snake.body[0]
tail = snake.body[-1]
middle = snake.body[1:-1]
new_body = snake.body[:-1]
x, y = head
if snake.direction % 4 == 0:
# avoid overflow
snake.direction = 0
new_head = point(x, y - 1) # UP
if new_head.y < 0 or snake.body.count(new_head) >= 2:
game.finished = True
elif snake.direction % 4 == 1:
new_head = point(x - 1, y) # LEFT
if new_head.x < 0 or snake.body.count(new_head) >= 2:
game.finished = True
elif snake.direction % 4 == 2:
new_head = point(x, y + 1) # DOWN
if new_head.y >= COL_LEN or snake.body.count(new_head) >= 2:
game.finished = True
elif snake.direction % 4 == 3:
new_head = point(x + 1, y) # RIGHT
if new_head.x >= COL_LEN or snake.body.count(new_head) >= 2:
game.finished = True
if game.finished:
break
# Add new head to new_body
new_body.insert(0, new_head)
# Add tail if the snake gets the food
if new_head == game.food:
new_body.append(tail)
# generate game's new food
game.generate_food(new_body)
# finally replace snake whole body with new_body
snake.body = new_body.copy()
time.sleep(CLOCK)
game.frame += 1
- The snake game logic starts with instantiate both
game
andsnake
objects. Now we have our init game status (map status and food position). - We have a huge
while
block to keep our game running endlessly. In every loop, there is a 2 ms delay as game's frame period. - Inside each loop, we have to keep tracking chip's A and B keys, determine the direction of the snake head movement. Press A for a left turn, whereas press B for a right turn.
- As the game going in the frame, we need to render the snake's body and food in the map. After that,
display
is called to actually show the LED output to the chip. - There is a
if-clause
to check if the current's game frame is 50, so the snake can move its head to next position. By doing so, it means in our game, the snake moves at a speed of 1 step/s, obtained by 2 ms 50 frames - Before we are deciding on where the snake head is moving, we roll the snake body array by 1 ahead. For example 2nd position of the body will be overwritten to 1st position's value, the 3rd will be overwritten to the 2nd postion's value and so on.
- Unlike 6. snake head has no previous body position to allocate to, so we have to determine the current direction of snake movement, and asssign the new value to snake head.
- 0: UP - (x, y) to (x, y - 1)
- 1: LEFT - (x, y) to (x - 1, y)
- 2: DOWN - (x, y) to (x, y + 1)
- 3: RIGHT - (x, y) to (x + 1, y)
- Inside every direction movement, we will have another
if-clause
to check whether the snake is hitting the wall (0 =< x < 5, 0 =< y < 5), and head is not hitting its own body (newhead
position should not already in part of its body). - Now we almost have all the logics done, but hey! What about the snake's food? Don't worry, we can say that the snake has gotten its food when
new_head
's position isgame.food
's position. After that to to increase the snake length by 1, we simply keep the last snake tail position and append it to the snake body. - Remenber to call
game.generate_food
function to let the game generate new food position, and keep in mind new food position should not be any position that snake body already pocessesed. - Boom! Now the game is done. Enjoy.
Chai-Shi, Chang