Adafruit Circuit Playground Cyclone Game Tutorial

By Nath Tumlin. Feel free to send comments, questions, concerns, corrections, or cash to ngtumlin@crimson.ua.edu

Video demo

In this tutorial we'll look at how to recreate the Cyclone game on an Adafruit Circuit Playground, walking through the code section by section. At the end is the complete source code for this game.

Initialization

Adafruit has created a library to make programming the Circuit Playground easier. We start by including it.

#include "Adafruit_CircuitPlayground.h"

Next we'll declare the variables that we'll need, as well as declare a function for playing a sad and happy tune when the player loses or scores.

static uint8_t pos = 0;
static uint8_t ticks = 0;
static uint8_t score = 10;
static bool lastButtonPos = false;
static bool buttonPos = false;

void playSad();
void playHappy();

pos is used to keep track of where the light is, and score keeps track of the player's score. lastButtonPos and buttonPos keep track of the left button so we know when the player pressed it. We'll talk about ticks in a little bit.

The setup() function runs one time when the board is powered on. First it sets up our random number generator, then it sets up the CircuitPlayground library.

void setup() {
  randomSeed(analogRead(0));
  CircuitPlayground.begin();
}

Running the game

After setup() finishes, loop() runs over and over as long as the board is turned on. This is where we put the code for running the game.

There are two modes, one for actually playing the game, and one for checking our score. The first thing we do in loop() is check which way the switch on the board is turned. If it's to the right we play the game, and if it's to the left we show the score.

void loop() {
  //if the switch is to the right
  if (!CircuitPlayground.slideSwitch()) {

If the switch is to the right

If the switch is to the right then we're in the mode for playing the game. The first thing we do is turn off all the lights, say we want to turn on the one light at pos with a color based on pos, and then use show() to actually turn on the light.

CircuitPlayground.clearPixels();
CircuitPlayground.strip.setPixelColor(pos, CircuitPlayground.colorWheel((pos * 256 / 10) & 255));
CircuitPlayground.strip.show();

Next we save the old position and get the current position of the left button so that we'll be able to tell if the player pressed it.

lastButtonPos = buttonPos;
buttonPos = CircuitPlayground.leftButton();

The way we check if the player pressed the button is to see if the button is held down now and that the last time we checked it wasn't held down. If we only checked that it's being held down now, the player could cheat by pressing the button and never letting go, scoring whenever the light came to the right position.

if (buttonPos && !lastButtonPos) {

The light by the button is at position 2, so once we know the player has pressed the button, we'll check to see if the light is in the right place.

if (pos == 2) {

If it is, we'll play the happy tune, add 5 to the player's score, put the light in a random position, reset ticks, and return to restart loop().

playHappy();
score += 5;
pos = random(10);
ticks = 0;
return;

If the light wasn't at 2, that means the player missed when they pressed the button. We'll start by taking one away from thier score.

} else {
    score--;

Then we check if the player has run out of points and lost the game. If they have then we play the sad tune and reset everything so they can try again.

if (score == 0) {
    playSad();
    score = 10;
    pos = random(10);
    ticks = 0;
    return;
}

Now let's talk about ticks. ticks is used so that we can check if the player pushed the button, not just when we move the light, but while the light is still too. The following code increases ticks by one, then checks if it's reached 10 yet. If it has, we'll reset ticks, and move the position of the light by one. We also have to check if pos is 10, and if so move it back around to 0,s ince there is no light #10.

ticks++;

if (ticks == 10) {
      ticks = 0;
      pos++;
      if (pos == 10) { 
        pos = 0;
      }
}

Finally, we add a delay based on the player's score. The longer this is the slower the light moves. As the player's score gets higher, we make the delay shorter to make the game harder. At a score of 1, the light takes more than half a second to move. As the score gets higher, the time the light stays in place goes towards 1/20th of a second, less time than it takes to blink!

delay(5 + ((10.0 / score) * 5));

If the switch is to the left

If the switch is to the left, we show the player's score. We don't have a screen to show digits, and if we just turned on a light for every point the player had, we'd run out as soon as we started since the player starts with a score of 10, and there's only 10 lights on the board. The solution is to show the score in binary so that we can show a score as high as 1023 using all 10 lights. The following code puts score on the board in binary. It's a little confusing and not that important, so we won't talk about exactly how it works.

} else {
    CircuitPlayground.clearPixels();
    for (int i = 0; i < 10; i++) {
      CircuitPlayground.strip.setPixelColor(i, ((score >> i) & 1) * 255, 0, 0);
    }
    CircuitPlayground.strip.show();
    delay(100);
}

This finishes the loop() function.

Playing the music

The Circuit Playground has a built in speaker that we use to play the sounds when the player scores or loses. The method accepts a number in hertz to play, as well as a duration. Certain musical notes correspond to certain values of hertz, and the actual notes played are written int comment at the top of the functions. playSad() is a bit slower and descends, while playHappy() is a bit faster and the notes ascend. We also need calls to delay() to wait for each note to finish before starting the next one.

void playSad() {
  //D Db C B
  CircuitPlayground.playTone(294, 500);
  delay(550);
  CircuitPlayground.playTone(277, 500);
  delay(550);
  CircuitPlayground.playTone(262, 500);
  delay(550);
  CircuitPlayground.playTone(247, 1000);
  delay(1100);
}

void playHappy() {
  //A Bb B C
  CircuitPlayground.playTone(440, 250);
  delay(260);
  CircuitPlayground.playTone(466, 250);
  delay(260);
  CircuitPlayground.playTone(494, 250);
  delay(260);
  CircuitPlayground.playTone(523, 1000);
  delay(1100);
}

The complete code

That's all there is to the Cyclone game. Below is the complete code that you can copy and paste into the Arduino IDE and run on your Circuit Playground.

#include "Adafruit_CircuitPlayground.h"

static uint8_t pos = 0;
static uint8_t ticks = 0;
static uint8_t score = 10;
static bool lastButtonPos = false;
static bool buttonPos = false;

void playSad();
void playHappy();

void setup() {
  randomSeed(analogRead(0));
  CircuitPlayground.begin();
}

void loop() {
  //if the switch is to the right
  if (!CircuitPlayground.slideSwitch()) {
    CircuitPlayground.clearPixels();
    CircuitPlayground.strip.setPixelColor(pos, CircuitPlayground.colorWheel((pos * 256 / 10) & 255));
    CircuitPlayground.strip.show();
    
    lastButtonPos = buttonPos;
    buttonPos = CircuitPlayground.leftButton();

    if (buttonPos && !lastButtonPos) {
      if (pos == 2) {
        playHappy();
        score += 5;
        pos = random(10);
        ticks = 0;
        return;
      } else {
        score--;
        if (score == 0) {
          playSad();
          score = 10;
          pos = random(10);
          ticks = 0;
          return;
        }
      }
    }

    ticks++;

    if (ticks == 10) {
      ticks = 0;
      pos++;
      if (pos == 10) { 
        pos = 0;
      }
    }

    delay(5 + ((10.0 / score) * 5));
  } else {
    CircuitPlayground.clearPixels();
    for (int i = 0; i < 10; i++) {
      CircuitPlayground.strip.setPixelColor(i, ((score >> i) & 1) * 255, 0, 0);
    }
    CircuitPlayground.strip.show();
    delay(100);
  }
}

void playSad() {
  //D Db C B
  CircuitPlayground.playTone(294, 500);
  delay(550);
  CircuitPlayground.playTone(277, 500);
  delay(550);
  CircuitPlayground.playTone(262, 500);
  delay(550);
  CircuitPlayground.playTone(247, 1000);
  delay(1100);
}

void playHappy() {
  //A Bb B C
  CircuitPlayground.playTone(440, 250);
  delay(260);
  CircuitPlayground.playTone(466, 250);
  delay(260);
  CircuitPlayground.playTone(494, 250);
  delay(260);
  CircuitPlayground.playTone(523, 1000);
  delay(1100);
}