Simon Game

Simon Game

This mbed application will utilize multiple SPI port expanders in order to create a Simon game. The game will have increasing levels of difficulty, which will be selectable in menu navigation. It will use the mbed's random number generator in it's highest difficulty. Similar to the game Simon, the board will light up the LEDs in a specific order. It will be the user's task to push the corresponding pushbuttons in the correct order within a certain time interval. If the user succeeds, another LED is added to the sequence. If the user fails, it's game over. The board will also generate sound cues for correct sequences and incorrect sequences, if the optional speaker is attached.

Hardware

9 LEDs (different colors if available)
9 Pushbuttons
3 SPI port expanders
1 Text LCD
1 Rotary Pulse Generator for menu navigation
1 Speaker
1 Transistor
SD card with breakout board
9 10k Ohm Resistors for pushbuttons
9 100 Ohm Resistors for LEDs
1 1 kOhm resistor for speaker data line

Picture of Setup

/media/uploads/jtillma3/imag0079.jpg

Schematic

/media/uploads/jtillma3/hyperion-vii_-2-.png

Software

Cookbook Pages (for reference)


The MCP23S17 library is used for the SPI port expanders

Repository: MCP23S17


The Text LCD protocol is used for menu navigation and program-user communication

Import library

Public Types

enum LCDType { LCD16x2 , LCD16x2B , LCD20x2 , LCD20x4 }

LCD panel format.

More...

Public Member Functions

TextLCD (PinName rs, PinName e, PinName d4, PinName d5, PinName d6, PinName d7, LCDType type=LCD16x2)
Create a TextLCD interface.
int putc (int c)
Write a character to the LCD.
int printf (const char *format,...)
Write a formated string to the LCD.
void locate (int column, int row)
Locate to a screen column and row.
void cls ()
Clear the screen and locate to 0,0.


The rotary pulse generator library is used to navigate the LCD screen

Import libraryRPG

Simple RPG Library


The Wave Player library is used to play the .wav files for the SD card

Import librarywave_player

Wave playing code, based on Big Mouth Billy Bass, but cleaned up and capable of playing more bitrates and sample sizes.


The SD Card FileSystem is used to store the wav files for keypresses and the winning and losing sound cues as well

Import librarySDFileSystem

SDFileSystem

Program

Download

Import programSimonGame

Simple Simon game implemented on the mbed using SPI port expanders, an SD card file system, a Text LCD, LEDs, Push buttons and a speaker.

Video Demonstrations

Board Overview

Menu Navigation

Hard Mode and Defeat Sound Cue

Easy Mode and Victory Sound Cue

Code

main.cpp

// See http://mbed.org/users/romilly/notebook/mcp23s17-addressable-16-bit-io-expander-with-spi/
// MCP23S17 datasheet http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
// uses MCP23S17 library version 0.4
 
#include "mbed.h"
#include "MCP23S17.h"
#include "TextLCD.h"
#include "RPG.h"
#include "wave_player.h" 
#include "SDFileSystem.h"
 
#define S_BEGIN 0
#define S_PLAY 1
#define S_VICTORY 2
#define S_DEFEAT 3
 
// Creates an initializes all necessary objects
void init();
// Obstains menu input and updates LCD text accordingly
int handleMenu();
// Plays the current sequence so far using timing appropriate to the chosen difficulty
void playSequence();
// Listens for the player move and returns a value indicating success or failure
int listenSequence();
// Re-initializes the game
void restart();
 
 
// Create SPI bus
SPI spi(p5, p6, p7);
// Create Text LCD Controller
TextLCD lcd(p21, p22, p23, p24, p25, p26);
// Create RPG interface
RPG rpg(p16,p17,p20);
// Create SD interface
SDFileSystem sd(p11, p12, p13, p14, "sd");
// Sound output
AnalogOut DACout(p18);
// Wave file player
wave_player waver(&DACout);
// Sound files
FILE *Sounds[11];
 
// Wiring Connections:
// mbed p5,p6,p7 are tied to MCP23S17 SI, SO, SCK pins
// mbed p8 to MCP23S17 CS
// MCP23S17 reset pin pulled high
 
// Chip addresses (opcodes)
char opcodes[3];
// Chip objects
MCP23S17* chips[3];
 
 
// Menu variables
char* menuOptions[] = {
    "      Easy     >",
    "<    Medium    >",
    "<     Hard      "
};
short menuState = 0;
short oldMenuState = -1;
short menuBuffer = 0;
 
// Difficulty settings
double timeoutVals[3] = { 2,  1.5, 1 };
double delayVals[3] = { 0.5, 0.3, 0.1};
short  victoryVals[3] = {4 , 6, 8};
 
// Game variables
short sequence[15];
short current = 0;
short gameState = 0;
short victory;
double timeout;
double delay;
char input = 0; // Push button input
int main()
{
    
    init();
    while (1) {
        int menuSelection;
        switch(gameState) 
        {
            case S_BEGIN:
                menuSelection = handleMenu();
                if(menuSelection > -1) 
                {
                    gameState = S_PLAY;
                    timeout = timeoutVals[menuSelection];
                    delay = delayVals[menuSelection];
                    victory = victoryVals[menuSelection];
                }
                break;
 
            case S_PLAY:
                playSequence();
                if(!listenSequence())
                    gameState = S_DEFEAT;
                else if(current == victory)
                    gameState = S_VICTORY;
                break;
 
            case S_VICTORY:
                lcd.cls();
                lcd.locate(0,0);
                lcd.printf("   YOU WON :D   ");
                lcd.printf("   <( . _ . )>  ");
                waver.play(Sounds[10]);
                rewind(Sounds[10]);
                wait(5);
                gameState = S_BEGIN;
                restart();
                break;
 
            case S_DEFEAT:
                lcd.cls();
                lcd.locate(0,0);
                lcd.printf("     DEFEAT     ");
                lcd.printf("   TRY HARDER   ");
                waver.play(Sounds[9]);
                rewind(Sounds[9]);
                wait(5);
                gameState = S_BEGIN;
                restart();
                break;
            default:
                lcd.printf("Invalid State");
        }
        handleMenu();
    }
}
 
void init()
{
 
    // Set chips opcodes (Dependent on hardware connections)
    opcodes[0] = 0x40;
    opcodes[1] = 0x42;
    opcodes[2] = 0x44;
 
    /* Create IO Expander objects with their respective opcodes
    * and set port B of each chip for input and port A of each chip
    * for output.
    */
 
    for(int i = 0; i <3 ; i++) 
    {
        chips[i] = new MCP23S17(spi, p8, opcodes[i]);
        //  Set all 8 Port A bits to output direction
        chips[i]->direction(PORT_A, 0x00);
        //  Set all 8 Port B bits to input direction
        chips[i]->direction(PORT_B, 0xFF);
        // Turn off all LEDs
        chips[i]->write(PORT_A, 0);
    }
 
    // Clear sequence
    for(int i = 0; i < 15; i++)
        sequence[i] = 0 ;
 
    set_time(1256729737);  // Set RTC time to Wed, 28 Oct 2009 11:35:37
    
    // Initialize sounds and file system
    char ftotal[] = "/sd/sounds/x.wav";
    for(int i = 0;i<9;i++)
    {
      ftotal[11] = i + '0';
      Sounds[i] = fopen(ftotal, "r");
      
      if(Sounds[i] == NULL)
        lcd.printf("SOUND ERROR");
    }
    
    Sounds[9]  = fopen("/sd/sounds/lose.wav", "r");
    Sounds[10] = fopen("/sd/sounds/win.wav" , "r");
}
 
int handleMenu()
{
    menuBuffer +=rpg.dir();
 
    if(menuBuffer == -15) 
    {
        menuState++;
        menuBuffer = 0;
    } else if (menuBuffer == 15) 
    {
        menuState--;
        menuBuffer = 0;
    }
 
    if(menuState < 0)
        menuState = 0;
    else if(menuState > 2)
        menuState = 2;
 
    if( oldMenuState != menuState) 
    {
        lcd.cls();
        lcd.printf("Difficulty:");
        lcd.locate(0,1);
        lcd.printf("%s", menuOptions[menuState]);
    }
    oldMenuState = menuState;
 
    if(rpg.pb())
        return menuState;
    else
        return -1;
}
 
void playSequence()
{
    lcd.cls();
    lcd.locate(0,0);
    lcd.printf("Sequence Playing");
    lcd.printf("...");
    wait(delay + 0.5);
    sequence[current] =  time(NULL)%9 ;
    current++;
    for(int i = 0; i < current; i++)
    {
        int chipNum = sequence[i]/3;
        int ledNum  =  sequence[i]%3;
        chips[chipNum]->write(PORT_A, (1 << ledNum) );
        waver.play(Sounds[sequence[i]]);
        rewind(Sounds[sequence[i]]);
        wait(delay);
        chips[chipNum]->write(PORT_A, 0);
        wait(delay);
    }
    lcd.cls();
    lcd.locate(0,0);
    lcd.printf("Get ready...");
    wait(timeout + 0.5 );
    lcd.cls();
    lcd.locate(0,0);
    lcd.printf("GO!!!");
    wait(delay + 0.3);
}
 
int listenSequence()
{
    time_t startTime = time(NULL);
    time_t currentTime = startTime;
    time_t timeDiff = 0;
    int totalTime = timeout * current;
    int currentCheck = 0;
    bool correct = false;
    int input;
    int oldInput[3] = {0, 0, 0};
    lcd.cls();
    while(timeDiff <= totalTime)
    {
        lcd.locate(0,0);
        lcd.printf("Time:         %d", totalTime - timeDiff);
        if(totalTime - timeDiff < 10)
        lcd.printf(" ");
        lcd.locate(0,1);
        lcd.printf("Round:        %d",current);
        currentTime = time(NULL);
        timeDiff = currentTime - startTime;
        for(int i = 0; i < 3; i++) 
        {
            // Read lower 3 bits
            input = chips[i]->read(PORT_B) & 7;
            
            if(input != 0 && oldInput[i] == 0)
            {
                chips[i]->write(PORT_A, input);
                wait(0.3);
                chips[i]->write(PORT_A, 0);
                if(correct)
                    return 0;
                
                int rest;
                if(input == 1)
                    rest = 0;
                else if(input == 2)
                    rest = 1;
                else if(input == 4)
                    rest = 2;
                else
                    rest = 3;
                    
                if( i * 3 + rest == sequence[currentCheck])
                {
                     waver.play(Sounds[sequence[currentCheck]]);
                     rewind(Sounds[sequence[currentCheck]]);
                    currentCheck++;
                    correct = true;
                   
                }
                else
                {   lcd.cls();
                    lcd.locate(0,0);
                    lcd.printf("You pressed: %d", i * 3 + rest);
                    lcd.locate(0,1);
                    lcd.printf("Next Was: %d",sequence[currentCheck]);
                    wait(6);
                    return 0; 
                }
            }
            oldInput[i] = input;
            
        }
        correct = false;
        
     if(currentCheck == current)
        break;
    }
    wait(delay + 0.5);
    
    if(currentCheck == current)
       return 1;
    else
       return 0;
}
 
void restart()
{
    current = 0;
    menuState = 0 ;
    oldMenuState = -1;
}
 


Please log in to post comments.