A Gauntlet Game

I was thinking: "What kind of game can I do on a 6x12 screen?" What I came up with was a gauntlet game. This is sort of like a running game and much like early racing games (early as in Atari 2600) in that the track continually advances and swerves, and the player has to stay on it. I added the extra feature of having randomly spawning targets that add to the players score when they are hit. Finally if the player hits the edge of the track the score and number of steps is output to the console and the game restarts.

I’ll be showing edited snippets of the code as I discuss various aspects. The full code is at the end of this page and in the examples directory of the library.

Setup

We need to import the libraries and create the global variables that will be used later.

import time
import random

import board
import busio
import dotstar_featherwing
import Adafruit_seesaw

i2c = busio.I2C(board.SCL, board.SDA)
ss = Adafruit_seesaw.Seesaw(i2c)
wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11, 0.1)

black = 0x000000
wall = 0x200800                 # must not have any blue, must have red
pellet = 0x000040               # must have blue
player = 0x00FF00

The reason the wall and pellet colors have the noted constraints is to simplify (and thus optimize for space) the tests later.

Making the track

The first thing that was needed was a way to shift a row onto the display from the top. I added a method to do that to the library. I added a slight change from the left/right shifting methods: an offset into the slice at which to start taking 12 pixels. By randomly changing the offset into a slice that is wider than the 12 column display I can make the track swerve back and forth.

row = (wall, wall, wall, wall,
       wall, wall, wall, wall,
       black, black, black, black, black,
       wall, wall, wall, wall,
       wall, wall, wall, wall)

offset = 4
# ...
offset = min(max(0, offset + random.randint(-1, 1)), 9)
wing.shift_into_top(row, offset)

The main loop simply adjusts the offset like that and shifts into the top (edited to show just the track creation):

while True:
    offset = min(max(0, offset + random.randint(-1, 1)), 9)
    wing.shift_into_top(row, offset)
    wing.show()
    time.sleep(0.1)

The player

Next we need a player sprite. We need to be able to move the player sprite back & forth to stay on the track. That's where the Joy FeatherWing comes in. Functionally it's a Seesaw and we use the Seesaw library to interact with it. In this case interaction is simple reading the horizontal axis of the thumbstick.

Adafruit Joy FeatherWing for all Feathers

PRODUCT ID: 3632
Make a game or robotic controller with this Joy-ful FeatherWing. This FeatherWing has a 2-axis joystick and 5 momentary buttons (4 large and 1 small)...
$9.95
IN STOCK
wing.set_color(3, player_x, black)

joy_x = ss.analog_read(3)
    if joy_x < 256 and player_x > 0:
        player_x -= 1
    elif joy_x > 768 and player_x < 11:
        player_x += 1

wing.set_color(3, player_x, player)

Score pellets

To add a goal I spawn a score pellet with a 5% chance each time through the loop. If the player maneuvers their sprite over a score pellet they get a point.

The score pellet only needs to be initially placed; the shifting/scrolling will take care of moving it.

if random.randint(1, 20) == 1:
    wing.set_color(0, random.randint(8, 12) - offset, pellet)

Collisions

There are two things that the player sprite can collide with: the wall and a score pellet.

Colliding with the wall causes the game to end, print the players score and number of steps they survived to the console, and start a fresh game.

Colliding with a pellet increments the score.

This is where the pellet having blue in it, and the wall having red and no blue comes into play to drastically simplify the check.

r, _, b = wing.get_color(3, player_x)
if b:
    score += 1
elif r:
    return score

I also have it increment the score every 25 iterations and increase the speed every 100.

steps += 1
if steps % 25 == 0:
    score += 1
if steps % 100 == 0:
    step_delay *= 0.9
sleep(step_delay)

Notice that in the case of a wall collision, the function returns the number of steps and score. The top level code in main.py calls the game function that I've been describing, prints the returned values, flashes the screen red, and loops.

while True:
    result = run()
    # got here because of a crash, so report score and restart
    wing.shift_in_string(numbers, '{:03d}'.format(result), 0x101010)
    sleep(5)

The entire code

# The MIT License (MIT)
#
# Copyright (c) 2018 Dave Astels
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
A gaunlet running game using the dotstar wing and the joy wing.
"""

from time import sleep
from random import randint

import board
import busio
import dotstar_featherwing
import Adafruit_seesaw

i2c = busio.I2C(board.SCL, board.SDA)
ss = Adafruit_seesaw.Seesaw(i2c)
wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11, 0.1)

black = 0x000000
wall = 0x200800           # must not have any blue, must have red
pellet = 0x000040               # must have blue
player = 0x00FF00

numbers = {
    ' ': [0,  0,  0],
    '0': [30, 33, 30],
    '1': [34, 63, 32],
    '2': [50, 41, 38],
    '3': [33, 37, 26],
    '4': [ 7,  4, 63],
    '5': [23, 37, 25],
    '6': [30, 41, 25],
    '7': [49,  9,  7],
    '8': [26, 37, 26],
    '9': [38, 41, 30],
}

row = (wall, wall, wall, wall,
       wall, wall, wall, wall,
       black, black, black, black, black,
       wall, wall, wall, wall,
       wall, wall, wall, wall)


def run():
    """Play the game."""

    player_x = 6
    score = 0
    steps = 0
    step_delay = 0.15
    offset = 4

    for _ in range(wing.rows):
        wing.shift_into_top(row, offset)
    wing.show()


    while True:
        # remove player sprite
        wing.set_color(3, player_x, black)

        # shift/advance the track
        offset = min(max(0, offset + randint(-1, 1)), 9)
        wing.shift_into_top(row, offset)

        # Maybe add a pellet
        if randint(1, 20) == 1:
            wing.set_color(0, randint(8, 12) - offset, pellet)

        # Adjust player position
        joy_x = ss.analog_read(3)
        if joy_x < 256 and player_x > 0:
            player_x -= 1
        elif joy_x > 768 and player_x < 11:
            player_x += 1

        # Check for collisions
        r, _, b = wing.get_color(3, player_x)
        if b:
            score += 1
        elif r:
            return score

        # Show player sprite
        wing.set_color(3, player_x, player)

        # Update some things and sleep a bit
        wing.show()
        steps += 1
        if steps % 25 == 0:
            score += 1
        if steps % 100 == 0:
            step_delay *= 0.9
        sleep(step_delay)

while True:
    result = run()
    # got here because of a crash, so report score and restart
    wing.shift_in_string(numbers, '{:03d}'.format(result), 0x101010)
    sleep(5)
Last updated on Jan 26, 2018 Published on Dec 21, 2017