-
Notifications
You must be signed in to change notification settings - Fork 7
/
AVRGame.ino
473 lines (435 loc) · 12.2 KB
/
AVRGame.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
////
//// A V R G A M E C O R E
////
//// The main sketch file for the AVRGAME project
////
//// FIRMWARE RELEASES
////
//// Rev 1.0 27jan13 Initial release
//// Rev 1.1 10feb13 Bug fixes
//// Rev 2.0 06mar13 Display dimming, Display invert, 8-bits per pixel mode
//// Rev 2.1 19mar13 8-bits per pixel mode bug fix
//// Rev 2.2 14feb14 minor cosmetic tweaks
//// Rev 2.3 23feb14 adding Stephen Cropp's Snake Game to main build
//// Rev 2.4 10mar14 crappy bird!
////
#define VERSION_HI 2
#define VERSION_LO 4
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
#include <Arduino.h>
#include <Tone.h>
#include <EEPROM.h>
#include "AVRGame.h"
#include "Menu.h"
///////////////////////////////////////////////////////////////////////////
//
// include games here
//
#include "InvadersGame.h"
#include "GhostMazeGame.h"
#include "BreakoutGame.h"
#include "FourInARowGame.h"
#include "MemoryGame.h"
#include "SnakeGame.h"
#include "BirdGame.h"
//
///////////////////////////////////////////////////////////////////////////
// Global variables
Disp8x8Class Disp8x8;
CGameFactory *gameFactory[MAX_GAMES] = {0};
byte numGameFactories = 0;
CGame *pGame = NULL;
Tone speaker;
byte soundOn = 0;
char thisGame = -1;
char nextGame = -1;
byte heartbeat = 0;
unsigned long gameScore;
char firstButtonPress = 1;
unsigned long menuSelectKeyPressed = 0;
unsigned long timeButtonAPress = 0;
unsigned long timeButtonBPress = 0;
unsigned long timeButtonCPress = 0;
unsigned long timeButtonDPress = 0;
unsigned long nextTimer1Event;
unsigned long nextTimer2Event;
unsigned long nextTimer3Event;
unsigned long nextTimer4Event;
unsigned long nextTimer5Event;
unsigned int Timer1Period;
unsigned int Timer2Period;
unsigned int Timer3Period;
unsigned int Timer4Period;
unsigned int Timer5Period;
///////////////////////////////////////////////////////////////////////////
// getMenuIcon
// Defines the content of the game menu. Any new games need to be hooked into
// the menu via this function
void getMenuIcon(int which, byte *dst, byte count)
{
memset(dst,0,16);
if(which>=numGameFactories)
which=0;
gameFactory[which]->getGameIcon(dst, count);
}
///////////////////////////////////////////////////////////////////////////
// startGame
// Creates an instance of a game class and starts it running
void startGame(byte which)
{
if(which>=numGameFactories)
which=0;
thisGame = which;
EEPROM.write(EEPROM_GAMESELECTED,thisGame);
pGame = gameFactory[thisGame]->createInstance();
nextTimer1Event = 0;
nextTimer2Event = 0;
nextTimer3Event = 0;
nextTimer4Event = 0;
nextTimer5Event = 0;
Timer1Period = 0;
Timer2Period = 0;
Timer3Period = 0;
Timer4Period = 0;
Timer5Period = 0;
gameScore = 0;
pGame->init();
pGame->handleEvent(EV_START);
}
///////////////////////////////////////////////////////////////////////////
// setNextGame
// Called from the Menu to select a game to start up
void setNextGame(int which)
{
nextGame = which;
}
///////////////////////////////////////////////////////////////////////////
// initSound
// Simple wrapper around the Tone library to first check if sound is
// enabled by the user
void initSound()
{
speaker.begin(P_SPK);
soundOn = EEPROM.read(EEPROM_SOUNDON);
}
///////////////////////////////////////////////////////////////////////////
// playSound
// Simple wrapper around the Tone library to first check if sound is
// enabled by the user
inline void playSound(int pitch, int dur)
{
if(soundOn)
speaker.play(pitch,dur);
}
///////////////////////////////////////////////////////////////////////////
// setSoundOn
void setSoundOn(byte on)
{
soundOn = on;
// remember the setting in EEPROM
EEPROM.write(EEPROM_SOUNDON,soundOn);
}
///////////////////////////////////////////////////////////////////////////
// isSoundOn
byte isSoundOn()
{
return soundOn;
}
///////////////////////////////////////////////////////////////////////////
// getDigit
// insert a digit 0-9 (defined in a 5x4 grid) into a byte buffer as
// either the left or right 4 columns
void getDigit(byte which, byte side, byte *data)
{
byte d0=0,d1=0,d2=0;
switch(which)
{
// set up the character design for the digit (3 bytes contain the 5 nybbles
// that define the 5x4 charater)
case 0: d0=0b01110101; d1=0b01010101; d2=0b01110000; break;
case 1: d0=0b00100110; d1=0b00100010; d2=0b01110000; break;
case 2: d0=0b01110001; d1=0b01110100; d2=0b01110000; break;
case 3: d0=0b01110001; d1=0b00110001; d2=0b01110000; break;
case 4: d0=0b01010101; d1=0b01110001; d2=0b00010000; break;
case 5: d0=0b01110100; d1=0b01110001; d2=0b01110000; break;
case 6: d0=0b01110100; d1=0b01110101; d2=0b01110000; break;
case 7: d0=0b01110001; d1=0b00100100; d2=0b01000000; break;
case 8: d0=0b01110101; d1=0b01110101; d2=0b01110000; break;
case 9: d0=0b01110101; d1=0b01110001; d2=0b00010000; break;
}
// extract and shift byte values as needed
byte o0=(d0>>4)&0x0f;
byte o1=(d0)&0x0f;
byte o2=(d1>>4)&0x0f;
byte o3=(d1)&0x0f;
byte o4=(d2>>4)&0x0f;
if(!side) {
data[0] |= (o0<<=4);
data[1] |= (o1<<=4);
data[2] |= (o2<<=4);
data[3] |= (o3<<=4);
data[4] |= (o4<<=4);
}
else
{
data[0] |= o0;
data[1] |= o1;
data[2] |= o2;
data[3] |= o3;
data[4] |= o4;
}
}
///////////////////////////////////////////////////////////////////////////
// showScore
// This function displays a number on the display, scrolling it to allow
// all digits to be seen
void showScore(unsigned long n)
{
unsigned long div = 10000000;
byte dest = 0;
byte side = 0;
byte m[4][5] = {0};
int j;
while(div>0)
{
int z = n/div;
n=n%div;
div/=10;
getDigit(z,side,m[dest]);
side=!side;
if(!side) dest++;
}
for(int ofs=0; ofs<41; ++ofs)
{
Disp8x8.cls();
for(j=0;j<5;++j)
{
byte b0=0,b1=0;
if(ofs>7 && ofs<40) b0=m[(ofs-8)/8][j];
if(ofs<32) b1=m[ofs/8][j];
byte q=ofs%8;
if(ofs&8)
Disp8x8.green[j+1] = (b0<<q)|(b1>>(8-q));
else
Disp8x8.red[j+1] = (b0<<q)|(b1>>(8-q));
}
Disp8x8.delayWithRefresh(100);
}
}
///////////////////////////////////////////////////////////////////////////
// endGame
// Standard "Game Over" routine alternately flashes the last display frame
// of the game and scrolls the content of the gameScore variable. The function
// does not return (user must reset the console)
void endGame()
{
byte red[8];
byte green[8];
int j,k;
memcpy(red,Disp8x8.red,8);
memcpy(green,Disp8x8.green,8);
for(;;)
{
for(k=0; k<5; ++k)
{
Disp8x8.delayWithRefresh(300);
delay(300);
}
byte *buffer8 = Disp8x8.setBuffer8(NULL);
showScore(gameScore);
Disp8x8.setBuffer8(buffer8);
memcpy(Disp8x8.red,red,8);
memcpy(Disp8x8.green,green,8);
}
}
///////////////////////////////////////////////////////////////////////////
// setTimeOut
// Schedule a "one off" timer event based on current time. Remember to clear
// timer period afterwards
void setTimeOut(byte whichTimer, unsigned long period)
{
switch(whichTimer)
{
case 1: Timer1Period = 3600000; nextTimer1Event = millis() + period; break;
case 2: Timer2Period = 3600000; nextTimer2Event = millis() + period; break;
case 3: Timer3Period = 3600000; nextTimer3Event = millis() + period; break;
case 4: Timer4Period = 3600000; nextTimer4Event = millis() + period; break;
case 5: Timer5Period = 3600000; nextTimer5Event = millis() + period; break;
}
}
///////////////////////////////////////////////////////////////////////////
// setup
// Arduino "setup" entry point called when the sketch starts
void setup()
{
// configure IO pins
pinMode(P_LED, OUTPUT);
pinMode(P_SPK, OUTPUT);
pinMode(P_BUTA, INPUT);
pinMode(P_BUTB, INPUT);
pinMode(P_BUTC, INPUT);
pinMode(P_BUTD, INPUT);
// set internal pull up resistors on button inputs
digitalWrite(P_BUTA, HIGH);
digitalWrite(P_BUTB, HIGH);
digitalWrite(P_BUTC, HIGH);
digitalWrite(P_BUTD, HIGH);
digitalWrite(P_LED, HIGH);
// IRLink.init();
// Disp8x8.invert = IRLink.enabled;
// initialise sound handling
initSound();
// the last selected game is started (or menu if not valid)
byte which = EEPROM.read(EEPROM_GAMESELECTED);
startGame(which);
}
///////////////////////////////////////////////////////////////////////////
// loop
// Arduino "loop" entry point called repeatedly
void loop()
{
byte event = 0;
unsigned long milliseconds = millis();
byte buttonA, buttonB, buttonC, buttonD;
if(!Disp8x8.invert)
{
buttonA = !digitalRead(P_BUTA);
buttonB = !digitalRead(P_BUTB);
buttonC = !digitalRead(P_BUTC);
buttonD = !digitalRead(P_BUTD);
}
else
{
buttonA = !digitalRead(P_BUTD);
buttonB = !digitalRead(P_BUTC);
buttonC = !digitalRead(P_BUTB);
buttonD = !digitalRead(P_BUTA);
}
// We use the milliseconds until first button press after startup to give us some random
// number entropy
if(firstButtonPress)
{
if(buttonA || buttonB || buttonC || buttonD)
{
randomSeed(milliseconds);
firstButtonPress = 0;
}
}
// Check for buttons A and C being held together for 1 second. This
// combination returns to the menu
if(buttonA && buttonC && thisGame > 0)
{
if(!menuSelectKeyPressed)
{
menuSelectKeyPressed = milliseconds + 1000;
}
else if(milliseconds > menuSelectKeyPressed)
{
setNextGame(0); // select the menu handler
}
}
else
{
menuSelectKeyPressed = 0;
}
// Poll and debounce button A
if(buttonA)
{
if(!timeButtonAPress)
{
timeButtonAPress = milliseconds + DEBOUNCE_TIME;
pGame->handleEvent(EV_PRESS_A);
}
}
else if(timeButtonAPress && timeButtonAPress < milliseconds)
{
timeButtonAPress = 0;
pGame->handleEvent(EV_RELEASE_A);
}
// Poll and debounce button B
if(buttonB)
{
if(!timeButtonBPress)
{
timeButtonBPress = milliseconds + DEBOUNCE_TIME;
pGame->handleEvent(EV_PRESS_B);
}
}
else if(timeButtonBPress && timeButtonBPress < milliseconds)
{
timeButtonBPress = 0;
pGame->handleEvent(EV_RELEASE_B);
}
// Poll and debounce button C
if(buttonC)
{
if(!timeButtonCPress)
{
timeButtonCPress = milliseconds + DEBOUNCE_TIME;
pGame->handleEvent(EV_PRESS_C);
}
}
else if(timeButtonCPress && timeButtonCPress < milliseconds)
{
timeButtonCPress = 0;
pGame->handleEvent(EV_RELEASE_C);
}
// Poll and debounce button D
if(buttonD)
{
if(!timeButtonDPress)
{
timeButtonDPress = milliseconds + DEBOUNCE_TIME;
pGame->handleEvent(EV_PRESS_D);
}
}
else if(timeButtonDPress && timeButtonDPress < milliseconds)
{
timeButtonDPress = 0;
pGame->handleEvent(EV_RELEASE_D);
}
// Check whether timer events 1..5 are due
if(Timer1Period && nextTimer1Event < milliseconds)
{
pGame->handleEvent(EV_TIMER_1);
nextTimer1Event = milliseconds + Timer1Period;
}
if(Timer2Period && nextTimer2Event < milliseconds)
{
pGame->handleEvent(EV_TIMER_2);
nextTimer2Event = milliseconds + Timer2Period;
}
if(Timer3Period && nextTimer3Event < milliseconds)
{
pGame->handleEvent(EV_TIMER_3);
nextTimer3Event = milliseconds + Timer3Period;
}
if(Timer4Period && nextTimer4Event < milliseconds)
{
pGame->handleEvent(EV_TIMER_4);
nextTimer4Event = milliseconds + Timer4Period;
}
if(Timer5Period && nextTimer5Event < milliseconds)
{
pGame->handleEvent(EV_TIMER_5);
nextTimer5Event = milliseconds + Timer5Period;
}
// refresh the display
Disp8x8.refresh();
// check if a change of game has been requested by the
// currently running game. This is used by the menu
if(nextGame >= 0)
{
Disp8x8.setBuffer8(NULL);
delete pGame;
pGame = NULL;
startGame(nextGame);
nextGame = -1;
}
// Flash the heartbeat LED
digitalWrite(P_LED,!heartbeat);
heartbeat++;
}