Arduino Platform - SIMON Game Implementation
Note: I originally published this a long time ago (October 2009) on my old website (accidentally killed by my ISP) and also on CodeProject.
I started playing with the Arduino hardware platform about two months ago, after looking for something suitable for a couple of projects I had in mind for use in the house. In order to get to the stage of being able to make use of the device, I had to first get to know its capabilities.
This article will be looking at how to overcome the problem of running a SIMON game clone using the Arduino platform, as this demonstrates methods of dealing with the main application Loop constraint and code reuse on this hardware platform.
For those of you not familiar with SIMON, this was an early electronics game from the 70's, where the user basically had to repeat back a sequence to the console to progress to the next level. In my implementation of the game, the user can play the Progressive Mode, where the game re-uses the previous level as the start of the next level before adding a random step at the end, or Random Mode, where every level of the game produces a new random sequence, each step longer than the previous.
jeffb42's Introduction to Arduino and Interfacing with LCD articles provide more details on the Arduino platform specifics, and here on my website, I have various examples of some of the IO basics, so there is no need to repeat this here.
The source can also be grabbed from GitHub Gist at: https://gist.github.com/DaveAuld/b32ee816e2aaef6a4ca5305ac4a5c99d
If you own the necessary components, you can build the game and upload the code to the Arduino to make it work. Alternatively, if you are just starting out with the platform or something similar, the code may provide you with ideas on how to achieve similar goals in your own projects.
There is also a video of the 1st version of this code/hardware running at the bottom of the post before the full source dump.
The next section of the code is the setup() method. This is where the hardware pins are initialised.
From here, the next thing that is executed is the loop() method, which is run endlessly, hence the requirement to keep track of the game status. Also, to keep the code shorter and more efficient, a number of helper methods are used to meet the reuse requirements mentioned at the start of the article.
The main loop() structure looks like the following (all the details have been stripped to leave the structure):
The easiest way of coming up with a structure is to play the game in your head and write down in blocks each step that is required; from this, you can build a flow diagram and then the code structure.
Overall, as a first project on the Arduino for myself, this gave me a good challenge into coming up with a suitably structured code that met the game requirements.
Hardware Schematic |
Introduction
Having been a CodeProject member for several years, and not having got round to ever publishing an article, left me feeling a bit disappointed in myself. I have thought many a time on what kind of article I can produce. Then after coming across some great articles by jeffb42 here on CodeProject, my problem was solved.I started playing with the Arduino hardware platform about two months ago, after looking for something suitable for a couple of projects I had in mind for use in the house. In order to get to the stage of being able to make use of the device, I had to first get to know its capabilities.
This article will be looking at how to overcome the problem of running a SIMON game clone using the Arduino platform, as this demonstrates methods of dealing with the main application Loop constraint and code reuse on this hardware platform.
For those of you not familiar with SIMON, this was an early electronics game from the 70's, where the user basically had to repeat back a sequence to the console to progress to the next level. In my implementation of the game, the user can play the Progressive Mode, where the game re-uses the previous level as the start of the next level before adding a random step at the end, or Random Mode, where every level of the game produces a new random sequence, each step longer than the previous.
jeffb42's Introduction to Arduino and Interfacing with LCD articles provide more details on the Arduino platform specifics, and here on my website, I have various examples of some of the IO basics, so there is no need to repeat this here.
Background
The challenge to building this game on the Arduino is the way in which the platform works. Within the source code, there are three areas in which to place code: the initialisation area, the setup method, and the loop method. These are executed in that order.//initialisation section
// Library references and variable/constant declarations contained here
void setup()
{
//This method runs once
//Hardware allocation done here e.g. input/output pin directions
}
void loop()
{
//This method will run until power is removed or a new program is
//uploaded to the platform
}
The challenge is being able to make the code operate in such a way that a game can be played over and over again, in either of the two modes, without ever leaving a never ending loop.Using the Code
The schematic of the hardware requirements is as you can see at the top of this post, and the full source code can be found at the end of the post.The source can also be grabbed from GitHub Gist at: https://gist.github.com/DaveAuld/b32ee816e2aaef6a4ca5305ac4a5c99d
If you own the necessary components, you can build the game and upload the code to the Arduino to make it work. Alternatively, if you are just starting out with the platform or something similar, the code may provide you with ideas on how to achieve similar goals in your own projects.
There is also a video of the 1st version of this code/hardware running at the bottom of the post before the full source dump.
The Game Structure
We need to be able to keep the game code running within an endless loop, and one method to achieve this is by using flags. Boolean variables which are either True or False is one of the easiest ways to achieve this. Looking at how the game works, there are a few basic flags that are required. They hold the state of the game, i.e., has it started, has it been played, and if the game is now over. A couple of other variables are also required to keep track of what the sequence of steps generated is and what level the user is currently on. These requirements as well as the hardware pin allocations are identified in the initialisation section of the code:#include <LiquidCrystal.h>
//initialise the library with the numbers of the interface pins
LiquidCrystal lcd(13,12,11,10,9,8);
int userInput = 0; //Analog pin for user input from buttons
int led1 = 2; //LED 1
int led2 = 3; //LED 2
int led3 = 4; //LED 3
int led4 = 5; //LED 4
int speaker = 6; //Speaker
//Game Stats
boolean started = false; //Has the game started yet
boolean gameover = false; //Has the game ended
int level = 0; //What level is the user on (Score at end = level -1)
int gameMode = 0; //Which game mode is being used
// 1 = Progressive
// 2 = Random
boolean soundEnabled = true; //Is the sound enabled or not
int lastLevelSeq[50]; //The sequence for the steps used
//in progressive mode previous level
//Also used by game over to replay correct sequence back
//Nobody can get passed 50, surely!
The next section of the code is the setup() method. This is where the hardware pins are initialised.
void setup()
{
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);
pinMode(led4, OUTPUT);
pinMode(speaker, OUTPUT);
//Set up the LCD Col and Row count
lcd.begin(16,2);
delay(100);
lcd.clear();
lcd.print("Welcome to");
lcd.setCursor(0,1);
lcd.print("Arduino SIMON");
//...........rest of setup code snipped. see the full code at bottom.
From here, the next thing that is executed is the loop() method, which is run endlessly, hence the requirement to keep track of the game status. Also, to keep the code shorter and more efficient, a number of helper methods are used to meet the reuse requirements mentioned at the start of the article.
//used to determine which button the user is pressing
getButtonPressed()
//Play a given sound
playTone(tone)
//This is the actual game level, it returns boolean true
//is user success or false if user failed
doLevel(level)
//Turn on a given led, or 0 to turn them all off
lightLed(led)
//This will play a sound and light the appropriate
//led using the other helper methods
playStep(step)
The main loop() structure looks like the following (all the details have been stripped to leave the structure):
void loop()
{
while (started == false)
{
if (getButtonPressed() > 0)
{
//This part waits for the user to press a button to start the game
}
}
while (gameMode == 0)
{
switch (getButtonPressed())
{
case 1:
//The user pressed button 1 for Progressive Mode
break;
case 2:
//The user pressed button 2 for Random Mode
break;
case 4:
soundEnabled = (!soundEnabled);
//The user user is pressing Button 4 toggling the sound on and off
break;
}
}
while (gameover == false)
{
if (doLevel(level)==true)
{
//The user played a level and got it correct
}
else
{
//Level Wrong, set game over
}
}
if (gameover == true)
{
//The game is now over, playback the correct sequence
}
//Wait for user
while (gameover == true)
{
if (getButtonPressed() > 0)
{
//Wait for the user to press a button to go back to the start menu
}
}
}
The easiest way of coming up with a structure is to play the game in your head and write down in blocks each step that is required; from this, you can build a flow diagram and then the code structure.
Points of Interest
Given the limited number of digital IO pins available, the four buttons were actually linked through a resistor network onto an analog input pin. Each button pressed will change the value of the analog input; from this value, it is then possible to determine which button is pressed, using the getButtonPressed() method. Also, some variance may occur through fluctuations of the analog signal, and suitable resistor sizes must be used to give sufficient spacing of the input values:/*
Read the analog input and determine which button is pressed
*/
int getButtonPressed()
{
// What is the pushbutton resistor matrix value
int userValue = 0;
userValue = analogRead(userInput);
int buttonPressed = 0;
if (userValue > 850)
{
buttonPressed = 0;
//No Button Pressed
}
if (userValue < 850)
{
buttonPressed = 4;
// Maybe Button 4 still to check others
}
if (userValue < 800)
{
buttonPressed = 3;
// Maybe Button 3 still to check others
}
if (userValue < 700)
{
buttonPressed = 2;
// Maybe Button 2 still to check last one
}
if (userValue < 600)
{
buttonPressed = 1;
// Done Checking buttons
}
return buttonPressed;
}
Overall, as a first project on the Arduino for myself, this gave me a good challenge into coming up with a suitably structured code that met the game requirements.
Video of 1st Version Running
Full Code Dump:
1 /*
2 Arduino Simon Game
3
4 Author: David M. Auld
5 Date: 19th October 2009
6 Version: 2.0
7
8 This is the simple game of Simon built on the Arduino Platform.
9
10 It uses the following Hardware;
11 4 x LEDs for Visual Feedback of step
12 4 x Pushbuttons for Input of step
13 1 x Speaker for Audio Feedback of Step
14 1 x LCD for Game Info and Scoring.
15 1 x Arduino
16 +various Resistors/pots etc.
17
18 Built and tested on Arduino Duemilanove.
19
20 Change Log
21 **********
22 V2: 19th October 2009
23 ---------------------
24 NEW: Added progressive game mode, V1 was random only
25 NEW: Correct sequence playback on Game Over
26 NEW: Sound On/Off Mode
27 FIX: Removed surplus variables, and code tidy
28 IMP: Random Seed Generator now reads and adds all analog inputs to generate seed value
29
30 V1: 17th October 2009
31 ---------------------
32 Initial Release
33
34 */
35
36 #include <LiquidCrystal.h>
37
38 //initialise the library with the numbers of the interface pins
39 LiquidCrystal lcd(13,12,11,10,9,8);
40
41 int userInput = 0; //Analog pin for user input from buttons
42 int led1 = 2; //LED 1
43 int led2 = 3; //LED 2
44 int led3 = 4; //LED 3
45 int led4 = 5; //LED 4
46 int speaker = 6; //Speaker
47
48 //Game Stats
49 boolean started = false; //Has the game started yet
50 boolean gameover = false; //Has the game ended
51 int level = 0; //What level is the user on (Score at end = level -1)
52
53 int gameMode = 0; //Which game mode is being used
54 // 1 = Progressive
55 // 2 = Random
56
57 boolean soundEnabled = true; //Is the sound enabled or not
58
59 int lastLevelSeq[50]; //The sequence for the steps used in progressive mode previous level
60 //Also used by game over to replay correct sequence back
61 //Nobody can get passed 50, surely!
62
63 //Setup Routine
64 void setup()
65 {
66 pinMode(led1, OUTPUT);
67 pinMode(led2, OUTPUT);
68 pinMode(led3, OUTPUT);
69 pinMode(led4, OUTPUT);
70 pinMode(speaker, OUTPUT);
71
72 //Set up the LCD Col and Row count
73 lcd.begin(16,2);
74 delay(100);
75 lcd.clear();
76 lcd.print("Welcome to");
77 lcd.setCursor(0,1);
78 lcd.print("Arduino SIMON");
79 delay(3000);
80 for (int i = 0; i < 14; i++)
81 {
82 lcd.scrollDisplayLeft();
83 delay(300);
84 }
85 lcd.clear();
86 lcd.print("initialising...");
87 for (int i = 1; i<7; i++)
88 {
89 if (i<5)
90 {
91 lightLed(i); //Light each LED
92 }
93 playTone(i); //Play each tone used
94 lightLed(0); //Clear the LED's
95 }
96 }
97
98 void loop()
99 {
100 lcd.clear();
101 lcd.print("To Start");
102 lcd.setCursor(0,1);
103 lcd.print("PRESS ANY BUTTON");
104 while (started == false)
105 {
106 if (getButtonPressed() > 0)
107 {
108 playTone(5);
109 started=true;
110 }
111 }
112
113 //Clear the previous game Last Level Steps
114 for (int s=0; s < 50; s++)
115 {
116 lastLevelSeq[s] = 0;
117 }
118
119 delay(500);
120
121 // Select Game Mode Here
122 lcd.clear();
123 lcd.print("Select Game Mode");
124
125 while (gameMode == 0)
126 {
127 lcd.setCursor(0,1);
128 lcd.print("1=P 2=R 4=TGLSND");
129
130 switch (getButtonPressed())
131 {
132 case 1:
133 if (soundEnabled)
134 {
135 playTone(5);
136 }
137 gameMode = 1;
138 lcd.clear();
139 lcd.print("Progressive");
140 lcd.setCursor(0,1);
141 lcd.print("Mode Selected");
142 break;
143
144 case 2:
145 if (soundEnabled)
146 {
147 playTone(5);
148 }
149
150 gameMode = 2;
151 lcd.clear();
152 lcd.print("Random");
153 lcd.setCursor(0,1);
154 lcd.print("Mode Selected");
155 break;
156
157 case 4:
158 soundEnabled = (!soundEnabled);
159 lcd.setCursor(0,1);
160
161 if (soundEnabled)
162 {
163 lcd.print("Sound Mode: ON ");
164 playTone(5);
165 }
166 else
167 {
168 lcd.print("Sound Mode: OFF ");
169 }
170 delay(1000);
171 break;
172 }
173 }
174
175 delay(1000);
176
177 //Set start level
178 level =1;
179
180 while (gameover == false)
181 {
182 if (doLevel(level)==true)
183 {
184 //Level Correct, increment Level and repeat
185 lcd.clear();
186 lcd.print("LEVEL: "); lcd.print(level);
187 lcd.setCursor(0,1);
188 lcd.print("Completed......");
189
190 if (soundEnabled)
191 {
192 //play level correct sound
193 for (int i =0; i < 3; i++)
194 {
195 playTone(5);
196 delay(50);
197 }
198 }
199 else
200 {
201 delay(125);
202 }
203 delay(1000); //Delay so user can see level completed message
204 level++; //Set next level
205 }
206 else
207 {
208 //Level Wrong, set game over
209 gameover = true;
210 }
211 }
212
213 if (gameover == true)
214 {
215 lcd.clear();
216 lcd.print("WRONG! GAME OVER");
217 lcd.setCursor(0,1);
218 lcd.print("SCORE: "); lcd.print(level-1);
219
220 if (soundEnabled)
221 {
222 for (int i = 0; i < 5; i++)
223 {
224 playTone(6);
225 delay(50);
226 }
227 }
228 else
229 {
230 delay(125);
231 }
232 }
233
234 //Playback the correct sequence
235 lcd.clear();
236 lcd.print("Sequence");
237 lcd.setCursor(0,1);
238 lcd.print("should have been");
239 delay(500);
240 for(int s=0; s < level; s++)
241 {
242 playStep(lastLevelSeq[s]);
243 //lightLed(lastLevelSeq[s]);
244 //playTone(lastLevelSeq[s]);
245 //lightLed(0);
246 //delay(250);
247 }
248
249 lcd.clear();
250 lcd.print("Your Score: "); lcd.print(level-1);
251 lcd.setCursor(0,1);
252 lcd.print("PRESS ANY BUTTON");
253
254 //Wait for user
255 while (gameover == true)
256 {
257 if (getButtonPressed() > 0)
258 {
259 level = 0;
260
261 if (soundEnabled)
262 {
263 playTone(5);
264 }
265 gameover = false;
266 started = false;
267 gameMode = 0;
268 }
269 }
270 }
271
272 /*
273 Read the analog input and determine which button is pressed
274 */
275 int getButtonPressed()
276 {
277 int userValue = 0; // What is the pushbutton resistor matrix value
278 userValue = analogRead(userInput);
279
280 int buttonPressed = 0;
281
282 if (userValue > 850)
283 {
284 buttonPressed = 0; //No Button Pressed
285 }
286 if (userValue < 850)
287 {
288 buttonPressed = 4; // Maybe Button 4 still to check others
289 }
290 if (userValue < 800)
291 {
292 buttonPressed = 3; // Maybe Button 3 still to check others
293 }
294 if (userValue < 700)
295 {
296 buttonPressed = 2; // Maybe Button 2 still to check last one
297 }
298 if (userValue < 600)
299 {
300 buttonPressed = 1; // Done Checking buttons
301 }
302 return buttonPressed;
303 }
304
305 /*
306 This will light the led, play the tone, switch off the led
307 */
308 void playStep(int number)
309 {
310 lightLed(number);
311 if (soundEnabled)
312 {
313 playTone(number);
314 }
315 else
316 {
317 delay(100);
318 }
319 delay(250);
320 lightLed(0);
321 }
322
323
324 /*
325 Light the Relevant LED
326 param led = which LED to light
327 */
328 void lightLed(int led)
329 {
330 switch (led)
331 {
332 case 0:
333 digitalWrite(led1,LOW); //Set all the LEDs off
334 digitalWrite(led2,LOW);
335 digitalWrite(led3,LOW);
336 digitalWrite(led4,LOW);
337 delay(50); //Give time for the LED to go
338 break;
339 case 1:
340 digitalWrite(led1,HIGH); //LED 1 on
341 break;
342 case 2:
343 digitalWrite(led2,HIGH); //LED 2 on
344 break;
345 case 3:
346 digitalWrite(led3,HIGH); //LED 3 on
347 break;
348 case 4:
349 digitalWrite(led4,HIGH); //LED 4 on
350 break;
351 }
352 }
353
354 /*
355 Play the tones for the game
356 param tone = which tone to play
357 tone 1 to 4 are the button tones
358 tone 5 is level correct tone / start tone (any button pressed)
359 tone 6 is level wrong and game over tone
360 */
361 void playTone(int tone)
362 {
363 int tones[6] = { 1000, 1250, 1500, 1750, 500, 3000};
364 for (long i = 0; i < 125; i++)
365 {
366 //generate a square wave
367 digitalWrite(speaker, HIGH);
368 delayMicroseconds(tones[tone-1]);
369 digitalWrite(speaker, LOW);
370 delayMicroseconds(tones[tone-1]);
371 }
372 }
373
374 /*
375 This is the actual game level.
376 Return TRUE if success or FALSE if fail
377 */
378 boolean doLevel(int level)
379 {
380 int steps[level]; //Which steps the user must match
381 int userStep = 0; //Which button has the user pressed
382 int userLength =0; //How many steps has the user entered
383 boolean levelPass = false; //Level was won or lost
384 boolean inProgress = true; //Level currently running
385
386 //Display a message to the user that the Arduino is doing its bit
387 lcd.clear();
388 lcd.print("Arduino Turn");
389 lcd.setCursor(0,1);
390 lcd.print("Pay Attention");
391 delay(1000); //Add a delay
392
393 //Seed the random number generator
394 randomSeed(analogRead(0) + analogRead(1) + analogRead(2)+ analogRead(3)+analogRead(4)+analogRead(5));
395
396 //The steps to be played are dependent on gameMode
397 //Setup the steps based on which mode we are playing
398
399 if (gameMode == 1)
400 {
401 //Progressive
402 //Get last level
403 for (int s=0; s < level; s++)
404 {
405 if (level > 1)
406 {
407 // Can only copy if higher than level 1
408 steps[s] = lastLevelSeq[s];
409 }
410 //Add new step
411 steps[level-1] = (int)random(1,5);
412 }
413 }
414 if (gameMode == 2)
415 {
416 //Random
417 for (int s=0; s < level; s++)
418 {
419 steps[s] = (int)random(1,5);
420 }
421 }
422
423 //Copy back to last level for next time and gameOver
424 for (int s=0; s < level; s++)
425 {
426 lastLevelSeq[s] = steps[s];
427 }
428
429 //Play the steps
430 for (int s=0; s < level; s++)
431 {
432 playStep(steps[s]);
433 //lightLed(steps[s]); //Light the led for the step from the sequence
434 //playTone(steps[s]); //Play the step tone from the sequence
435 //lightLed(0); //Turn off the LEDs
436 }
437
438 //Display user turn message
439 lcd.clear();
440 lcd.print("LEVEL: "); lcd.print(level);
441 lcd.setCursor(0,1);
442 lcd.print("Your Turn.....");
443
444 while (inProgress)
445 {
446 //Get the user input
447 userStep = getButtonPressed();
448 if (userStep >0)
449 {
450 userLength++;
451 playStep(userStep);
452
453 //lightLed(userStep);
454 //playTone(userStep);
455 //lightLed(0);
456
457 //Check the user input against the sequence
458 if (steps[userLength-1] == userStep)
459 {
460 //Correct button pressed, check if sequence still to complete
461 if (userLength ==level)
462 {
463 //finished the sequence
464 inProgress = false;
465 levelPass = true;
466 }
467 }
468 else
469 {
470 //wrong button pressed
471 inProgress = false;
472 levelPass = false;
473 }
474 }
475 }
476 return levelPass;
477 }
Bro your code and schematics is awesome but one of your interfacing pins is not matching the pin in your schematic diagram. In schematic diagram you used pin 7 but in the code you used pin 13.
ReplyDeleteHi, thanks for your comments. It's been a long time since I made this, but the difference might be how the various libraries reference things, digital pin 7 is on physical pin 13 and the LCD library may use the physical reference. I might be wrong, but only reason I can think of. Could of course be a mistake 🤣!
Delete