The Simon memory game is one of the first electronic games I remember from my childhood, together with late 70′s Atari consoles and early 80′s Game & Watch Nintendo handheld (Donkey Kong was my favourite).
The game is very simple: there are four coloured buttons that correspond to four different tones. The game presents a sequence of colours and the player has to repeat the sequence. If the player is successful, a further colour is added, otherwise a horrible 42 Hz saw-shaped sound is emitted; check this commercial http://www.youtube.com/watch?v=yF0ZUXclW8Y
It is not difficult to implement the game in Racket using an Arduino UNO board with four LEDs and four digital buttons, connected using Firmata.
I have connected the LEDs to PINs 8, 9, 10 and 11; I have connected the buttons to PINs 2, 3, 4 and 5. The figure on the left shows the spaghetti-like circuit. If you want to see what happens when you run the code, have a look at this link of my daughter playing with it: http://youtu.be/tk69MUwQs5A (in Italian with some subtitles…)
This game is very interesting for teaching purposes: it is easy enough to be covered in a session, but it also contains some issues that are not so simple, such as distinguishing between a state in which the code displays the sequence from a state in which the player is entering the sequence. The sequence is not fixed and it needs to be reset when the player makes a mistake. One exercise I will do in class is to ask the students to write down all the possible states and the possible transitions.
Let’s now have a look at a possible Racket code for this. I assume you have Firmata installed on your Arduino UNO and you also know how to connect it to Racket using Firmata to read a button. If this is not the case, it is probably better if you have a look at this other link before proceeding: http://www.youtube.com/watch?v=7Zzh7qrQph4. I start with
The following is the description of a possible Racket implementation of the game; it is available with sound files and firmata at this link: http://www.rmnd.net/racket/simon. The implementation could be improved in a number of ways, see at the bottom of this post for a list of tasks for students. I start by including the appropriate Firmata module and by declaring the PINs to which the LEDs and the push buttons are connected to. I then define two lists that I use in other places in the code to initialise the PINs and to do boot sequences.
;; change as appropriate for your OS (require "firmata-mac.rkt") (define yellow 8) (define green 9) (define red 10) (define blue 11) (define in-yellow 2) (define in-green 3) (define in-red 4) (define in-blue 5) (define colours (list yellow green red blue)) (define in-colours (list in-yellow in-green in-red in-blue))
To keep track of the various buttons being pressed, I extend the code at http://www.youtube.com/watch?v=7Zzh7qrQph4 defining four sets of states for the buttons (all initially UP):
(define curStateYellow "UP") (define previousStateYellow "UP") (define curStateGreen "UP") (define previousStateGreen "UP") (define curStateRed "UP") (define previousStateRed "UP") (define curStateBlue "UP") (define previousStateBlue "UP")
I then declare a list that I use to store the sequence of states to be guessed with
(define seq '()). The key idea of the game is to use “states” to keep track of what the code should do. We start from a state “gameOver”, declaring it with
(define state "gameOver").
I then declare a few support functions, see comments in the code below:
;; Show a sequence of colours: switch on a PIN, play the ;; corresponding sound with play-index (see below), clear ;; the PIN, wait 0.2 seconds and then call itself recursively until ;; there is something to show. (define (showSeq s) (cond ( (> (length s) 0) (set-arduino-pin! (first s)) (play-index (first s)) (clear-arduino-pin! (first s)) (sleep 0.2) (showSeq (rest s)) ) ) ) ;; end of showSeq ;; play a note using its position in the list (yellow - green - red - blue) ;; (see below the definition of play) (define (play-index i) (cond ( (= i yellow) (play "yellow")) ( (= i green) (play "green")) ( (= i red) (play "red")) ( (= i blue) (play "blue")) ) ) ;; play a sound with afplay (works on a mac) ;; in Linux, you can use aplay. ;; It just calls a command-line using system (define (play note) (system (string-append "afplay " note ".wav &")) ) ;; This is used to set-up the mode of the various PINs. I do ;; so using the high-order function map, together with a lambda ;; function (define (setup) (map (λ (i) (set-pin-mode! i OUTPUT_MODE)) colours) (report-digital-port! 0 1) (map (λ (i) (set-pin-mode! i INPUT_MODE)) in-colours) )
The core of the code is the function
mainLoop that loops through the following sequence of states (code below, after this list):
- gameOver: this is either the initial state or the state following a wrong choice by the player. In this state we clear the sequence of colours and we generate a new random initial colour. After gameOver the code shows (and play with sounds) the sequence to the player.
- After showing the sequence, the code moves to state playing. In this state we go through a new loop, see
- The code exits playingLoop either when the player guesses all the sequence correctly or when the player makes a mistake. In the former case the code enters a state called “correctSequence”; in the latter it enters a state called “wrongChoice”.
- In state “correctSequence” the code adds a new colour to
seqand then shows the sequence. It then goes back to state “playing”.
- In state “wrongChoice” the code moves to gameOver and repeats the loop.
This is the full code for the function:
(define (mainLoop) (cond ( (equal? state "gameOver") (printf "DEBUG: Current state is gameOver\n") ;; If we are in game over, we clear the sequence, we add ;; a random value and we set state to "playing" (set! seq '()) (set! seq (append seq (list (list-ref colours (random (length colours)))))) (sleep 0.5) (set! state "showSequence") ) ( (equal? state "showSequence") (printf "DEBUG: Current state is showSequence\n") ;; We show the sequence and then we enter playing (sleep 0.3) (set! state "playing") (showSeq seq) ) ( (equal? state "playing") (printf "DEBUG: Current state is playing\n") ;; We enter the main playing loop, see below (player does things) (playingLoop seq) ) ( (equal? state "wrongChoice") ;; The player has made a wrong choice: ;; we enter game over state (sleep 0.5) (set! state "gameOver") ) ( (equal? state "correctSequence") (set! seq (append seq (list (list-ref colours (random (length colours)))))) (sleep 0.5) (showSeq seq) (set! state "playing")) ) (mainLoop) ;; call recursively ) ;; end of mainLoop
The player interacts with the push buttons in the
playingLoop function. The function takes in input a sequence (of colours) and expects that the player presses the first colour in the list. If this is the case, it calls itself recursively on the tail of the sequence of colours. Otherwise, it sets state to “wrongChoice” and plays the horrible 42 Hz sound. The code is a simple extension of the code described in FIXME to 4 buttons. The full code is the following:
;; The playing loop: the player has to guess the sequence s, we call ourselves ;; recursively on s. (define (playingLoop s) ;; to keep track of a correct choice, used to loop at the end (define correct "") (cond ( (= (length s) 0) ;; if the list is empty, we guessed all the colours correctly! (set! state "correctSequence") (printf "DEBUG: well done, the sequence is correct!\n") ) (else ;; There are more colours to be guessed, we keep looping here ;; recording what is being pressed (cond ( (is-arduino-pin-set? in-yellow) (set! curStateYellow "DOWN")) (else (set! curStateYellow "UP"))) (cond ( (is-arduino-pin-set? in-green) (set! curStateGreen "DOWN")) (else (set! curStateGreen "UP"))) (cond ( (is-arduino-pin-set? in-red) (set! curStateRed "DOWN")) (else (set! curStateRed "UP"))) (cond ( (is-arduino-pin-set? in-blue) (set! curStateBlue "DOWN")) (else (set! curStateBlue "UP"))) ;; switch on the lights and check if it is correct when we release ;; Blue (cond ( (and (equal? curStateBlue "DOWN") (equal? previousStateBlue "UP")) (printf "DEBUG: blue pressed\n") (set-arduino-pin! blue) (play "blue") )) (cond ( (and (equal? curStateBlue "UP") (equal? previousStateBlue "DOWN")) (printf "DEBUG: blue released\n") (clear-arduino-pin! blue) (cond ( (= (first s) blue) ;; the player chose the right colour (set! correct "T") ) (else (set! correct "F")) ) ) ) ;; end of blue button released ;; You need to repeat the code above for the three remaining colours, ;; see source code available on-line at the link below. ;; Setting the previous states (set! previousStateBlue curStateBlue) (set! previousStateRed curStateRed) (set! previousStateGreen curStateGreen) (set! previousStateYellow curStateYellow) (cond ( (equal? correct "T") (playingLoop (rest s))) ( (equal? correct "F") (set! state "wrongChoice") (printf "DEBUG: ops, wrong choice!\n") (play "wrong")) (else (playingLoop s)) ) ) ;; end else (sequence not empty) ) ;; end cond ) ;; end playingLoop
This completes more or less the code. There are other minor things that could be added, see the code available online at http://www.rmnd.net/racket/simon. You can launch the code by setting the appropriate port for Firmata. I have run this code both on a Mac laptop and on a Raspberry Pi and it works fine. I have not tried Windows… let me know if it works for you.
Possible tasks for students:
- A lot of code is repeated, for instance in playingLoop. Improve it by reducing the amount of duplicated code.
- Calling OS instructions using (system ) causes the code to stop while the command is being executed because it is a synchronous call. Implement system calls using (process ). You need to manage timing for this is a different way…
- In the original Simon game, the timing between different colours / sounds in the presentation state as you progress with levels. Modify the code to take this into account.
- Introduce a winning state, e.g., if the player can guess a sequence of 15 colours.
- Save best scores, or publish them on-line (Twitter, Facebook, Google+)