Friday, December 7, 2018

Simon

For an index off all my stories click this text

Simon is an electronic memory game invented by Ralph H. Baer and Howard J. Morrison and was develloped and sold by Studio 54 in 1978. It took till 1980 before it was available in Europe. The original Simon is as far as I know not sold anymore but there are many derivates available.

Simon as a game is still very popular.

How does it work.




The layout (mostly in a circle) shows 4 colours. A random color lights up and you press the button with the same color. Next step is that the same color lights up followed by another random color. You press the buttons in the same sequence of the colors shown etc etc etc. The first steps are easy to remember but it gets more and more difficult as the sequence grows. It is very addictive.

You can find a version for windows here: http://www.memozor.com/other-free-memory-games-online/simon-games/simon-game
And you can find many variations for Android here: https://play.google.com/store/apps/collection/search_results_cluster_apps?clp=ggEHCgVzaW1vbg%3D%3D:S:ANO1ljIRByI

I have seen several versions of Simon on the internet so it is not like this has never done before. But I wanted to make my own version: hardware and software. And most of all it is a fun project for a rainy afternoon. Considering the total cost it could be a nice present for under the Christmas tree. And indeed you still have time to build one for Christmas.

Let's buid a simon game.

Most versions I saw on the internet are build with an Arduino Uno and use 4 pushbuttons and 4 leds. Therefore they need 8 I/O pins.
I am building my version with 4 pushbuttons and a string of neopixels. Therefore I only need 5 I/O pins.

Using my multiple buttons on I/O pins trick I could even reduce the I/O pins to 3 for the pushbuttons and 1 for the Neopixels. I will publish that trick in an upcoming story on this weblog. So keep on coming back.

For a detailed introduction on Neopixels read this story:
http://lucstechblog.blogspot.com/2015/10/neopixels-ws2812-intro.html

Hardware




The first version I build used the ESP8266 as a microcontroller programmed in Arduino.




The breadboard layout is simple. Just 4 pushbuttons connected to 4 I/O pins (D3,D5,D6 and D7) and a string of 4 neopixels connected to D8.
As you can see I used a separate power supply (USB wall socket) for the neopixels and for the ESP. In the end it proved that this was not necessary as the Wemos D1 supplies enough current through the 5Volt output (pin 5V) to drive the 4 neopixels.




Therefore this complete setup can be powered from a powerbank as the photo shows.

Software.

The software is in Arduino code and therefore can easily be ported between Arduino, Attiny85 and ESP8266 or ESP32 models. Choose whatever suits you best. The version presented here is the ESP8266 (Wemos D1 Mini) version. So adjust the pin numbers for your preferred microcontroller.


/* Simon Says by Luc Volders
 *  ESP8266 version
 *  1 = green  0,255,0
 *  2 = red 255,0,0
 *  3 = blue 0,0,255
 *  4 = yellow 255,255,0
*/


#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN    D8
#define PIXEL_COUNT 7

int i = 0;
int j = 0;
int k = 0;
int found = 0;
int mustbe = 0;
int num = 0;
int playnum = 0;
int startprog = 1;
int turns = 0;
int turnarray[100];

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup()
{
  randomSeed(analogRead(0));
  delay(10);
  pinMode(D3, INPUT_PULLUP); // geen 
  pinMode(D5, INPUT_PULLUP); // red
  pinMode(D6, INPUT_PULLUP); // blue
  pinMode(D7, INPUT_PULLUP); // yellow
  strip.begin();
  strip.show();
}

void loop()
{
  start();
}

void start()
{
  if (digitalRead(D3) == LOW or digitalRead(D5) == LOW or digitalRead(D6) == LOW or digitalRead(D7) == LOW)
  {
    strip.setPixelColor(0, 0, 255, 0);
    strip.setPixelColor(1, 0, 255, 0);
    strip.setPixelColor(2, 0, 255, 0);
    strip.setPixelColor(3, 0, 255, 0);
    strip.show();
    delay (500);
    startprog = 0;
  }
  if (startprog == 1)
  {
    initprog();
  }
  if (startprog == 0)
  {
    startsequence();
  }
}

void initprog()
{
  for (i = 0; i < 4; i++)
  {
    strip.setPixelColor(0, 0, 0, 0);
    strip.setPixelColor(1, 0, 0, 0);
    strip.setPixelColor(2, 0, 0, 0);
    strip.setPixelColor(3, 0, 0, 0);
    strip.setPixelColor(4, 0, 0, 0);
    strip.setPixelColor(5, 0, 0, 0);
    strip.setPixelColor(6, 0, 0, 0);
    strip.setPixelColor(i, 0, 255, 0);
    strip.show();
    delay(100);
  }
}

void keydetect()
{
  if (startprog == 1)
  {
    strip.setPixelColor(0, 0, 0, 0);
    strip.setPixelColor(1, 0, 0, 0);
    strip.setPixelColor(2, 0, 0, 0);
    strip.setPixelColor(3, 0, 0, 0);
    strip.setPixelColor(4, 0, 0, 0);
    strip.setPixelColor(5, 0, 0, 0);
    strip.setPixelColor(6, 0, 0, 0);
    strip.show();
    delay (100);
    strip.setPixelColor(0, 0, 0, 255);
    strip.setPixelColor(1, 0, 0, 255);
    strip.setPixelColor(2, 0, 0, 255);
    strip.setPixelColor(3, 0, 0, 255);
    strip.show();
    delay (100);
    strip.setPixelColor(0, 0, 0, 0);
    strip.setPixelColor(1, 0, 0, 0);
    strip.setPixelColor(2, 0, 0, 0);
    strip.setPixelColor(3, 0, 0, 0);
  }
  startprog = 0;
  startsequence();
}


void testsequence ()
{
  for (k = 1; k <= turns; k++)
  {
    found = 0;
    do
    {
      delay(10);
    }
    while (digitalRead(D3) == HIGH && digitalRead(D5) == HIGH && digitalRead(D6) == HIGH && digitalRead(D7) == HIGH);
    mustbe = turnarray[k];

    // =========================================================================================
    // test for right button
    // =========================================================================================

    if (mustbe == 1)
    {
      if (digitalRead(D3) == LOW)
      {
        strip.setPixelColor(0, 0, 255, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.setPixelColor(2, 0, 0, 0);
        strip.setPixelColor(3, 0, 0, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(500);
        found = 1;
      }
    }

    if (mustbe == 2)
    {
      // button 2 ==> 101
      if (digitalRead(D5) == LOW)
      {
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.setPixelColor(2, 0, 0, 0);
        strip.setPixelColor(3, 0, 0, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(500);
        found = 1;
      }
    }

    if (mustbe == 3)
    {
      // button 3 ==> 110
      if (digitalRead(D6) == LOW)
      {
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.setPixelColor(2, 0, 0, 255);
        strip.setPixelColor(3, 0, 0, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(500);
        found = 1;
      }
    }

    if (mustbe == 4)
    {
      // button 4 ==> 100
      if (digitalRead(D7) == LOW) //
      {
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.setPixelColor(2, 0, 0, 0);
        strip.setPixelColor(3, 255, 255, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(500);
        found = 1;
      }
    }

    if (found == 0) // FAULT FAULT FAULT

    {
      for (j = 1; j < 10; j++)
      {
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.setPixelColor(2, 255, 0, 0);
        strip.setPixelColor(3, 255, 0, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(120);
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.setPixelColor(2, 0, 0, 0);
        strip.setPixelColor(3, 0, 0, 0);
        strip.setPixelColor(4, 0, 0, 0);
        strip.setPixelColor(5, 0, 0, 0);
        strip.setPixelColor(6, 0, 0, 0);
        strip.show();
        delay(120);

      }
      i = 0;
      j = 0;
      k = 0;
      found = 0;
      mustbe = 0;
      num = 0;
      playnum = 0;
      startprog = 1;
      turns = 0;
      delay(1000);
      start();
    }



    // ============================================================================
    // end test right button
    // ============================================================================

    strip.setPixelColor(0, 0, 0, 0);
    strip.setPixelColor(1, 0, 0, 0);
    strip.setPixelColor(2, 0, 0, 0);
    strip.setPixelColor(3, 0, 0, 0);
    strip.setPixelColor(4, 0, 0, 0);
    strip.setPixelColor(5, 0, 0, 0);
    strip.setPixelColor(6, 0, 0, 0);
    strip.show();
    delay(500);
  }// end for i to turns
} // end void testsequence

void playsequence()
{
  for (i = 1; i <= turns; i++)
  {
    playnum = turnarray[i];
    if (playnum == 1)
    {
      strip.setPixelColor(0, 0, 255, 0);
      strip.setPixelColor(1, 0, 0, 0);
      strip.setPixelColor(2, 0, 0, 0);
      strip.setPixelColor(3, 0, 0, 0);
      strip.show();
    }
    if (playnum == 2)
    {
      strip.setPixelColor(0, 0, 0, 0);
      strip.setPixelColor(1, 255, 0, 0);
      strip.setPixelColor(2, 0, 0, 0);
      strip.setPixelColor(3, 0, 0, 0);
      strip.show();
    }
    if (playnum == 3)
    {
      strip.setPixelColor(0, 0, 0, 0);
      strip.setPixelColor(1, 0, 0, 0);
      strip.setPixelColor(2, 0, 0, 255);
      strip.setPixelColor(3, 0, 0, 0);
      strip.show();
    }
    if (playnum == 4)
    {
      strip.setPixelColor(0, 0, 0, 0);
      strip.setPixelColor(1, 0, 0, 0);
      strip.setPixelColor(2, 0, 0, 0);
      strip.setPixelColor(3, 255, 255, 0);
      strip.show();
    }
    delay(1000);
    strip.setPixelColor(0, 0, 0, 0);
    strip.setPixelColor(1, 0, 0, 0);
    strip.setPixelColor(2, 0, 0, 0);
    strip.setPixelColor(3, 0, 0, 0);
    strip.show();
    delay(500);
  }
  do
  {
    delay(10);
  }
  while (digitalRead(D3) == HIGH && digitalRead(D5) == HIGH && digitalRead(D6) == HIGH && digitalRead(D7) == HIGH);
  testsequence();
}


void startsequence()
{
  /*
   *  1 = green  0,255,0
   *  2 = red 255,0,0
   *  3 = blue 0,0,255
   *  4 = yellow 255,255,0
  */
  turns = turns + 1;
  num = random (1, 5);
  turnarray[turns] = num;
  startprog = 0;
  strip.setPixelColor(0, 0, 0, 0);
  strip.setPixelColor(1, 0, 0, 0);
  strip.setPixelColor(2, 0, 0, 0);
  strip.setPixelColor(3, 0, 0, 0);
  strip.show();
  delay(500);
  playsequence();
}


Now before you start nagging: yes I know this software can very much be optimised. However in this form everyone might be able to read and understand what is happening so anyone can alter it to its own needs. Besides that, neither speed or memory space are a problem here.


Lets look at some parts of the software.

#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN    D8
#define PIXEL_COUNT 4

As you can see you need the Adafruit Neopixel library. With the new Arduino IDE's it is a piece of cake to install new libraries so that should not give you any problems.

The setup starts with:
  randomSeed(analogRead(0));

Please make sure that this is indeed the first line in your program. The next lines will set the IO pins as input with a pull-up resistor. If you do not define the randomseed first you will not get a random figure.

In the start() part we wait till a key is pressed. Until that happens the Neopixels wil give a seqeunce of green pixels.
When a button is pressed all pixels will briefly be green and then one pixel will get the first random color choosen in the startsequence() routine.

When the first color has been choosen by startsequence() the program moves to playsequence() where the colors will be shown. The first time there will be just 1 color. The second time the array turnarray[] will have 2 values etc etc etc.

When the color sequence has been shown the program jumps to the testsequence() routine. There your keypresses will be chequed against the values in the array. If you pressed the correct buttons the variable turns is incremented and the program jumps to startsequence() again to pick the next random color.

This keeps repeating till you make an error.

If you make an error the testsequence() routine will notice. The routine will clear all variables and the program will start again.

Next step.

Attiny85

The ESP is a bit of overkill. No it is a lot of overkill !!!

Nowadays everything seems to be reeling around the ESP8266 and ESP32 but for small projects like this we seem to forget the Arduino and it's little broter the Attiny85. And the Attiny85 is well equipped for this project. So let's exchange the ESP8266 for an Attiny85. This microcontroller has 5 digital I/O pins and works at 8Mhz which is sufficient. 




As you can see the breadboard setup is not much different. However there is one extra feature which gives this setup an extra feature: it works on batteries !! 



So for the end result we can choose wether we want to power it using a power bank or plain AA or even AAA batteries. I was able to play Simon in this setup for several hours on just AAA batteries.
 

The software alterations are also minimal. In the program just replace the IO pin references to the Attiny references:

D3  ==> 0
D5  ==> 1
D6  ==> 2
D7  ==> 3
D8  ==> 4

Thats all !!!

How to play Simon.

A) the program starts by sequencing all neopixels in green till you press a button
B) If a button is pressed the Neopixels will all briefly light up green then dim
C) A random color is choosen
D) The program waits till you press the right button
E) When the right color button is chosen the program picks another random
color and will display the first and second after eachtother
F) The program then waits till you press in sequence the right color buttons
G) If the right sequence has been pushed the program loops back to step E and another color is added etc etc etc
H) if you pressed the wrong color button all Neopixels will flash red for several times and the program restarts.

Next stop: Stripboard layout and casing

Till then
Have fun

Luc Volders