-
Notifications
You must be signed in to change notification settings - Fork 0
/
car1.c
629 lines (524 loc) · 15.9 KB
/
car1.c
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
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
/* Simple Car game for Olimex AVR-MT128 board
Main game loop and road rendering developed originally by @TheRealDod, Nov 25, 2010
https://gist.github.com/thedod/715269
And we reused the modified version by @alesf, Dec 1, 2012
https://gist.github.com/alesf/4186127
+ Car can jump, accelerate/deaccelerate in additional to change lanes
+ Added Sound and simplified game logic ( only two lanes )
+ High Scores are stored in EEPROM
*/
/* car1.c - main file: game loop, sound playing, road rendering .. etc */
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "lcd.h"
#include "lcd2.h"
#include "utils.h"
#include "Bxxxxx.h"
#define SAMPLE_RATE 8000
// Loop music
#include "jingle.h"
// Crash sound
#include "crash18605.h"
void init(void);
void get_buttons(void);
void setup_lcd(void);
void reset_state(void);
void show_intro(void);
void wait_car_jump(void);
void game_loop(void);
void show_game_over(void);
void enable_sound(void);
void disable_sound(void);
void play_loop(void);
void play_crash(void);
void draw_road(void);
const int ROADLEN = 15; // Maximum length of the road, used to circularly iterate over the road map
const int MAXSTEPDURATION = 600; // Start slowly (600ms between steps), each step is 1 millisec shorter.
const int MINSTEPDURATION = 150; // This is as fast as it gets
const int CARJUMPSTEPS = 4; // MOD of number of steps a car jump stays
const char RIGHTLANE = 1; // B01
const char LEFTLANE = 2; // B10
const char BOTHLANES = 3; // B11
const long MAXCARACCEL = 5000; // Max Car acceleration (in 1/10 msec)
const long MINCARACCEL = -5000; // Min Car brake (in 1/10 msec)
const unsigned int SCORE_ADDR = 0x0004; // Arbitary address in EEPROM to store High Score between power cycles
// 1: Left lane obstacle, 2: Right lane, 3: Both lanes! extra 12 to make obstacles propability 1/5
const int MAXOBSTACLES = 5 * 3;
const int NGLYPHS = 5;
const char BLANK = 32;
//const char MARKS[] = {47, 96};
const int GLYPH_CAR = 1;
const int GLYPH_CAR_JUMP = 2;
const int GLYPH_OBSTACLE = 3;
const int GLYPH_CRASH = 4;
const int GLYPH_HSCORE = 5;
char glyphs[5][8] = {
// 1: car normal
{B00011,
B00110,
B00010,
B01111,
B01111,
B00010,
B00110,
B00011}
// 2: car jump!
,{B01100,
B11100,
B11000,
B11000,
B11000,
B11000,
B11100,
B01100}
// 3: obstacle
,{B00001,
B00011,
B00111,
B00011,
B00011,
B00111,
B00011,
B00001}
// 4: crash
,{B10001,
B11011,
B01110,
B01010,
B01010,
B01110,
B11011,
B10001}
// 5: New High Score
,{B00100,
B01110,
B11111,
B01110,
B01010,
B11011,
B11011,
B10001}
};
/* State Variables*/
char car_pos; // 1: Right lane, 2: Left lane
int car_jump; // 0: On the ground, 1~3: Jumping and rotates back to 0 then stops
int crash; // Indicates an unfortunate car crash!
long score; // Accumlate the score ( time passed without crash in msec )
long car_accel; // Car accelration in micro-seconds: -ve Accelerating, +ve Braking or 0 None.
int jump_down = 0; // Indicates if Jump button is pressed but not yet released
int step_duration; // Moving speed, speed should increase with 1msec each loop
char road[15]; // Rotating array of road with random obstacles placed
int road_index; // Current index in the road
char line_buffer[17];
volatile uint16_t sample;
#define SOUND_MUSIC 1
#define SOUND_CRASH 2
int sound_loop;
int sound_type; // Only Music and Crash sounds are currently supported!
ISR(TIMER1_COMPA_vect) {
int loop_length = 0;
// Determine maximum samples based on sound_type
if(sound_type == SOUND_MUSIC)
loop_length = sound_music_length;
else if (sound_type == SOUND_CRASH)
loop_length = sound_crash_length;
// Check if at the end of the sound
if(sample>=loop_length)
{
// If loop flag set, repeat
if(sound_loop)
sample=0;
else
disable_sound(); // Otherwise, stop the timers
}
// Play different sound according to sound_type
if(sound_type == SOUND_MUSIC)
{
OCR3BL = pgm_read_byte(&sound_music_data[sample]);
OCR3CL = pgm_read_byte(&sound_music_data[sample]);
}
else if(sound_type == SOUND_CRASH)
{
OCR3BL = pgm_read_byte(&sound_crash_data[sample]);
OCR3CL = pgm_read_byte(&sound_crash_data[sample]);
}
else
{
OCR3BL = 0x00;
OCR3CL = 0xFF;
}
// Next sample
++sample;
}
/* Timer2 Interrupt Handler for more responsive Inputs */
ISR(TIMER2_COMP_vect) {
get_buttons();
}
/*
- Prepare the Timers and interrupts
- Sound is generated using two Timers 1 and 3
- Timer1 works as sampler at 8kHz
- Timer3 works as 8-bit output (using PWM)
- Both pins OC3B & OC3C are connected to the Buzzer
- OC3B/OC3C are used for non-inverting / inverting PWM output!
*/
void enable_sound(void)
{
/* Set buzzer pins as output*/
DDRE |= (1 << PE4) | (1 << PE5);
/* Timer1 used for sampling sound on 8kHz*/
// Set Timer1 to CTC mode
// WGM13:0 = b0100
TCCR1A &= ~((1 << WGM11) | (1 << WGM10));
TCCR1B |= (1 << WGM12);
TCCR1B &= ~(1 << WGM13);
// Set Timer1 no prescaler 16 000 000 Hz CS12:0 = 001
TCCR1B |= (1 << CS10);
TCCR1B &= ~((1 << CS12) | (1 << CS11));
// Set OCR1A register value to F_CPU / SAMPLE_RATE 16e6 / 8000 (corresponds to 8kHz)
OCR1A = F_CPU/SAMPLE_RATE;
// Enable per-sample interrupt for Timer1
TIMSK |= (1 << OCIE1A);
/* Timer3 is used to generate sound using Fast PWM output */
// Set Timer3 to Fast PWM 8-bit mode
// WGM33:0 = b0101
TCCR3A |= (1<<WGM30);
TCCR3A &= ~(1<<WGM31);
TCCR3B |= (1<<WGM32);
TCCR3B &= ~(1<<WGM33);
// Do non-inverting PWM on OC3B
// COM3B1:0 = b10
TCCR3A &= ~(1<<COM3B0);
TCCR3A |= (1<<COM3B1);
// And inverting on the other PIN
// COM3C1:0 = b11
TCCR3A |= (1<<COM3C1) | (1<<COM3C0);
// No prescalar for Timer3
// CS32:0 = b001
TCCR3B |= (1<<CS30);
TCCR3B &= ~((1<<CS32) |(1<<CS31));
// Set initial pulse width value
OCR3B = 0x00; // 128
OCR3C = 0x00; // 128
// No need for interrupts, the Timer3 is used merely to generate modulated PWM signal
// Set the other pin of the buzzer to 0 always!
//PORTE &= ~(1 << PE5);
//PORTE |= (1 << PE5);
sample = 0;
//current_sound = 0;
}
/*
Stop Timers and disable Timer1 interrupt and mute the buzzer!
*/
void disable_sound(void)
{
// Disable per-sample interrupt for Timer1
TIMSK &= ~(1 << OCIE1A);
// Disable the per-sample timer completely.
TCCR1B &= ~(1<<CS10);
// Disable the PWM timer.
TCCR3B &= ~(1<<CS10);
// Write low to the output pins OC3B, OC3C
PORTE &= ~(1 << PE4);
PORTE &= ~(1 << PE5);
sample = 0;
}
// Play loop music
void play_loop(void)
{
disable_sound();
sound_loop = 1;
sound_type = SOUND_MUSIC;
enable_sound();
}
// Play crash sound
void play_crash(void)
{
disable_sound();
sound_loop = 0;
sound_type = SOUND_CRASH;
enable_sound();
}
void init(void) {
/* Clear interrupts */
cli();
/* Timer2 used for reading user inputs in responsive way*/
// Set Timer2 to CTC mode, and normal port operation
// WGM21:0 = b10 and COM21:0 = 00
TCCR2 &= ~( (1 << WGM20) | (1 << COM21) | (1 << COM20));
TCCR2 |= (1 << WGM21);
// Set Timer2 prescaler (16 000 000 / 1024) = 15625 Hz
TCCR2 |= (1 << CS22) | (1 << CS20);
TCCR2 &= ~(1<< CS21);
// Set OCR2 register value to 0x003e (62 ~ 15625/250) (corresponds to ~250hz)
OCR2 = 0x3e; //62
// Enable Output Compare Match Interrupt for Timer 2
TIMSK |= (1 << OCIE2);
/* näppäin pinnit sisääntuloksi */
DDRA &= ~(1 << PA0);
DDRA &= ~(1 << PA2);
DDRA &= ~(1 << PA4);
/* rele/led pinni ulostuloksi */
DDRA |= (1 << PA6);
/* lcd-näytön alustaminen */
lcd_init();
}
int main(void)
{
init();
sei();
setup_lcd();
// Basic state machine (0) Intro -> (1) Wait to Start -> (2) Game Loop -> (3) Game Over
// ^_____________________________________|
// (0)
show_intro();
play_loop();
// Stepping through the states
while(1)
{
// (1)
wait_car_jump();
reset_state();
disable_sound();
// (2)
game_loop();
play_crash();
// (3)
show_game_over();
}
}
/* Read user inputs: Right, Left and Jump! */
void get_buttons(void)
{
// Button 1 moves the car to Right Lane
if(!(PINA & 0b00000001))
{
car_pos = RIGHTLANE;
}
// Mutually exclusive checking, so only one button takes precedency
else if(!(PINA & 0b00010000))
{
// Button 5 moves the car to Left Lane
car_pos = LEFTLANE;
}
// Accelerating/Breaking is increasing/decreasing rather than hard -ve/+ve Steps
//
// Note: this function is called 250 times per second!
// the acceleration is incremented linearly and slowly,
//
// Button 2 moves the car faster (accelerate) -ve step duration
if(!(PINA & 0b00000010))
{
if(car_accel>MINCARACCEL)
car_accel -= 10;
}
// Mutually exclusive checking, so only one button takes precedency
else if(!(PINA & 0b00001000))
{
// Button 4 moves the car slower (brake) +ve step duration
if(car_accel<MAXCARACCEL)
car_accel += 10;
}
else
{
// Ramping down/up to Normal speed
if(car_accel>0)
car_accel -= 1;
else if(car_accel<0)
car_accel += 1;
}
// Jump when button released after it was pressed, to avoid Flying Car!
if(!(PINA & 0b00000100))
{
jump_down = 1;
}
else
{
if(jump_down){
car_jump = 1;
jump_down = 0;
}
}
}
// Mainly to setup LCD characters
void setup_lcd(void)
{
for (int i = 0; i < NGLYPHS; i++) {
lcd_create_glyph(i + 1, glyphs[i]); // create glyphs
}
lcd_write_ctrl(LCD_ON);
lcd_write_ctrl(LCD_CLEAR);
}
// Reset game state and clear previous display
void reset_state(void)
{
// Initialize seed to Timer2 which roughly works as global time
int seed = TCNT2;
srand(seed);
car_pos = RIGHTLANE; // Initially in the right lane
step_duration = MAXSTEPDURATION; // Start from the slowest step speed ( maximum duration )
// Brand new obstacle free road !
for(int i=0; i<ROADLEN;i++)
{
road[i] = 0;
}
line_buffer[ROADLEN+1] = '\0';
car_jump = 0;
crash = 0;
car_accel = 0;
road_index = 0;
score = 0 ;
lcd_write_ctrl(LCD_CLEAR);
}
// Print Splash Screen!
void show_intro(void)
{
char welcome_1[] = "_--Drive Safe--_";
char welcome_2[] = "'Jump to Start!'";
lcd_print(welcome_1);
lcd_set_cursor(0,1);
lcd_print(welcome_2);
//delay_msec(2000);
//lcd_write_ctrl(LCD_CLEAR);
}
// Wait until proper Jump button press/release
void wait_car_jump(void)
{
while (1)
{
delay_msec(50);
if(car_jump) {
car_jump = 0;
return;
}
}
}
/* Main game loop generating obstacles, drawing the road and checking for crash*/
void game_loop(void)
{
/* ikuinen silmukka */
while (1) {
/*if(!crash){
}*/
// Check if car_pos is the same as an Obstacle ( or two lanes obstacles )
if(car_jump==0)
crash = (car_pos == road[road_index]) || (road[road_index] == BOTHLANES) ;
if(crash){
return;
}
else
{
// Put new random obstacle, but first make sure obstacles are avoidable
// So, check if previous position had an obstacle if so don't put new obstacles
int prev_obstacle;
if(road_index==0)
prev_obstacle = road[ROADLEN-1];
else
prev_obstacle = road[(road_index-1)%ROADLEN];
// By default there is no obstacle!
road[road_index] = 0;
if(prev_obstacle==0){
int new_obstacle = rrand(MAXOBSTACLES) + 1; // Get random pattern of obstacles (1,2,3 or otherwise)
if(new_obstacle<=3){
road[road_index] = new_obstacle; // Set the pattern of the new obstacle
}
}
road_index = (road_index + 1) % ROADLEN;
draw_road();
if(car_jump)
{
car_jump = (car_jump+1)%CARJUMPSTEPS;
}
// The speed of rendering the road (this loop) is decreasing
// Also, if accelerate/brake buttons pressed, increase/decrease the rendering speed
int dly = step_duration + (car_accel/10);
delay_msec(dly);
if (step_duration > MINSTEPDURATION) {
step_duration--;
}
score += dly;
}
}
}
// Draw crash glyph and print Scores
void show_game_over(void)
{
char game_over_1[] = " Score: nnn "; // indices: 7,8,9 to write the score digits, 11 for HScore Symbol
char game_over_2[] = " Highest: nnn"; // indices: 9,10,11 to write the high score digits
long hscore = eeprom_read_dword((uint32_t*)SCORE_ADDR);
if(hscore < score)
{
eeprom_write_dword((uint32_t*)SCORE_ADDR, score);
game_over_1[12] = GLYPH_HSCORE;
}
long score_sec = score/1000; // Score in seconds
long hscore_sec = hscore/1000; // High Score in seconds
game_over_1[10] = score_sec%10 + '0'; score_sec /= 10;
game_over_1[9] = score_sec%10 + '0'; score_sec /= 10;
game_over_1[8] = score_sec%10 + '0';
game_over_2[12] = hscore_sec%10 + '0'; hscore_sec /= 10;
game_over_2[11] = hscore_sec%10 + '0'; hscore_sec /= 10;
game_over_2[10] = hscore_sec%10 + '0';
road_index = (road_index + 1) % ROADLEN;
draw_road();
lcd_set_cursor(1, 0);
lcd_print(game_over_1);
lcd_set_cursor(1, 1);
lcd_print(game_over_2);
}
// Draw the scrolling road and render new obstacles as well as the score, car, and crash symbol!
void draw_road(void)
{
// Score is displayed on two lines
char score_lines[2];
// Get the Higher and Lower order bytes of the score
long score_sec = score/1000;
score_lines[1] = score_sec%10;
score_lines[0] = (score_sec/10)%10;
//Loop through the lanes
for (int i = 1; i <= 2; i++) {
// Print the score byte;
line_buffer[0] = score_lines[i-1] + '0';
// Print the car, car jump or the crash symbol, or BLANK if nothing in this lane;
if(car_pos == i)
{
if (crash)
{
line_buffer[ROADLEN] = GLYPH_CRASH;
}
else
{
if(car_jump)
{
line_buffer[ROADLEN] = GLYPH_CAR_JUMP;
}
else
{
line_buffer[ROADLEN] = GLYPH_CAR;
}
}
}
else
{
line_buffer[ROADLEN] = BLANK;
}
// Print the obstacles
for (int j = 0; j < ROADLEN - 1; j++) {
int idx = (j + road_index)%ROADLEN;
int obs = road[idx];
// Here we do bitwise AND between obstacle pattern and lane
// Use the fact that obstacle patterns are: 1 (B01) right lane, 2 (B10) left lane and 3 (B11) both lanes
line_buffer[ROADLEN - j - 1] = (obs & i) ? GLYPH_OBSTACLE : BLANK;
}
// Null-terminate in order for lcd_print() to function properly
line_buffer[ROADLEN+1] = 0;
// Position the cursor in place
lcd_set_cursor(0, i-1);
// Actually print the line
lcd_print(line_buffer);
}
}