-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.c
358 lines (284 loc) · 10.5 KB
/
main.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
/*
* Breakout
*
* By Thomas Stoeckert
*
* One-day-project of 2021 #1 - Appx. 8Hrs of work
*
* It's a simple clone of the Atari "Breakout" classic. Use the joystick on the
* educational booster pack to move left and right, bouncing the ball up into
* the field of bricks. Break all of them to move to the next level.
*
* If the ball falls off the south edge of the playing field, press the button (J4.32)
* to send it back out. You have three lives, but they are replenished every five levels.
*
* Future Goals:
* - More than a single level
* - Requires a level editor, because there's no way I'm doing that by hand
* - Power-ups
* - "Punching": ball doesn't bounce off blocks, but goes through them
* - "Multi-ball": Multiple balls. Could be too much for the MSP430.
* - "Longer Paddle": Paddle becomes longer
* - Sound effects maybe? Make use of the buzzer for simple "bzz" on hits
*/
#include <msp430fr6989.h>
#include "Grlib/grlib/grlib.h" // Graphics Library Import
#include "LcdDriver/lcd_driver.h"
#include "graphics.h"
#include "math_utils.h"
#include "levels.h"
#include <stdio.h>
Graphics_Context g_sContext; // Declare our graphics context for the library
Level* level;
Graphics_Rectangle blocks[30];
int32_t* palette;
char* bindings;
unsigned char numBlocks = 30;
const Graphics_Rectangle nullBlock = { 128, 128, 128, 128 };
unsigned int paddle_x = 1;
int lives = 3;
int levelNum = 0;
unsigned int points = 0;
unsigned char activeBlocks = 30;
#define redLED BIT0
#define JOY_X BIT2
#define BUTTON BIT1
#define circleRadius 2
void initializeADC(void);
int readADC(unsigned int*data);
int main(void)
{
// Standard MSP430 Behavior
WDTCTL = WDTPW | WDTHOLD; // Stop Watchdog Timer
PM5CTL0 &= ~LOCKLPM5; // Enable GPI
// Configure SMCLK to 8MHz for SPI
CSCTL0 = CSKEY; // Unlock CS registers
CSCTL3 &= ~(BIT4|BIT5|BIT6); // Divider = 0
CSCTL0_H = 0; // Lock CS registers
// Configure debug LED
P1DIR |= redLED;
P1OUT &= ~redLED;
// Configure Button Input
P3DIR &= ~BUTTON;
P3REN |= BUTTON;
P3OUT |= BUTTON;
P3IES |= BUTTON;
initializeADC();
/////////////////////////////////////////////////////
// Initialize graphics library //
/////////////////////////////////////////////////////
Crystalfontz128x128_Init(); // Initialize our display communication
Initialize_Graphics(&g_sContext); // Prepare the display with initial commands
// Load our level
level = &LEVELS[levelNum];
memcpy(blocks, level->blocks, sizeof(Graphics_Rectangle) * 30);
palette = level->colors;
bindings = level->bindings;
activeBlocks = level->numBlocks;
numBlocks = level->numBlocks;
Draw_Playspace(&g_sContext, blocks, palette, bindings, numBlocks, lives, levelNum);
/////////////////////////////////////////////////////
// Initialize update timer //
/////////////////////////////////////////////////////
// Use SMCLK @8KHz, Divide by 8, in up mode. Clear it, enable the interrupt flag
TA0CTL = TASSEL_2 | ID_3 | MC_1 | TACLR | TAIE;
TA0CCR0 = 33333 - 1; // Appx. 30.003Hz, or, 33.33ms
TA0CTL &= ~TAIFG; // Clear interrupt flag
// Go to sleep
_low_power_mode_1();
return 0;
}
#pragma vector = TIMER0_A1_VECTOR
__interrupt void FIXED_UPDATE(void) {
static int velocity_x = 1, velocity_y = 2;
static int pos_x = 64, pos_y = 64;
static char isFree = 0;
unsigned char i;
// Clear interrupt flag
TA0CTL &= ~TAIFG;
// Set light
P1OUT ^= redLED;
// Paddle Logic
Graphics_Rectangle paddle_rect = {
paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT
};
Draw_Paddle(&g_sContext, paddle_x, GRAPHICS_COLOR_BLACK);
readADC(&paddle_x);
paddle_x = (paddle_x / 4095.0) * 128 - PADDLE_WIDTH;
paddle_rect.xMin = paddle_x;
paddle_rect.xMax = paddle_x + PADDLE_WIDTH;
if(isFree) {
// Calculate our new position
int temp_pos_x = pos_x, temp_pos_y = pos_y;
temp_pos_x += velocity_x;
temp_pos_y += velocity_y;
// Create our bounds for our circle
Graphics_Rectangle circleBounds = {
temp_pos_x - circleRadius,
temp_pos_y - circleRadius,
temp_pos_x + circleRadius,
temp_pos_y + circleRadius
};
// Check if circle collides with walls
char isCollidingWalls = IsCollidingWalls(&circleBounds);
if((isCollidingWalls & COLLIDING_SOUTH) != 0) {
// We've hit the bottom of the map! Set our next frame to be locked to the paddle,
// take a life
isFree = 0;
lives--;
Graphics_setForegroundColor(&g_sContext, GRAPHICS_COLOR_BLACK);
Graphics_fillCircle(&g_sContext, (lives*6)+4, 123, circleRadius);
// Check for Loss Condition
if(lives < 0) {
// We're dead. Stop the game, and establish the reset button's interrupt
Draw_ModalBox(&g_sContext, "Game Over!", "Play Again?", 1, points);
// Turn off the timer
TA0CTL &= ~MC_3;
// Enable Interrupt
P3IE |= BUTTON;
P3IFG &= ~BUTTON;
// Break out of this
return;
}
}
// Check if circle is colliding with our blocks
for(i = 0; i < numBlocks; i++) {
if(IsNullBlock(&blocks[i])) continue;
char isCollidingBlock = IsCollidingAABB(&blocks[i], &circleBounds);
isCollidingWalls |= isCollidingBlock;
if(isCollidingBlock) {
Graphics_setForegroundColor(&g_sContext, GRAPHICS_COLOR_BLACK);
Graphics_fillRectangle(&g_sContext, &blocks[i]);
blocks[i] = nullBlock;
activeBlocks--;
points++;
// Check for Win Condition
if(activeBlocks == 0) {
// We won! Stop the game, and establish the reset button's interrupt
Draw_ModalBox(&g_sContext, "You Win!", "Continue?", 1, points);
// Capture the ball
isFree = 0;
// Turn off the timer
TA0CTL &= ~MC_3;
// Enable Interrupt
P3IE |= BUTTON;
P3IFG &= ~BUTTON;
// Break out of this
return;
}
}
}
char isCollidingPaddle = IsCollidingAABB(&circleBounds, &paddle_rect) & ~(COLLIDING_EAST | COLLIDING_WEST);
isCollidingWalls |= isCollidingPaddle;
// Clear our old position
Draw_Ball(&g_sContext, pos_x, pos_y, GRAPHICS_COLOR_BLACK);
// Our circle is colliding
if(isCollidingWalls != 0) {
// Find the colliding direction, flipping velocity
if((isCollidingWalls & (COLLIDING_EAST | COLLIDING_WEST)) != 0)
velocity_x = -velocity_x;
if((isCollidingWalls & (COLLIDING_NORTH | COLLIDING_SOUTH)) != 0)
velocity_y = -velocity_y;
// Apply the new vectors
pos_x += velocity_x;
pos_y += velocity_y;
} else {
// These old calcs are good, use them.
pos_x = temp_pos_x;
pos_y = temp_pos_y;
}
} else {
// We're not free - we're to be locked to the top of the paddle
// Clear our old position
Draw_Ball(&g_sContext, pos_x, pos_y, GRAPHICS_COLOR_BLACK);
// Place us above the paddle
pos_x = paddle_x + PADDLE_WIDTH / 2;
pos_y = PADDLE_Y - 4;
// If the user has the release button down
if((P3IN & BUTTON) == 0) {
// Set the ball free
isFree = 1;
}
}
Draw_Paddle(&g_sContext, paddle_x, GRAPHICS_COLOR_RED);
// Redrawing of Circle
if(lives >= 0) {
Draw_Ball(&g_sContext, pos_x, pos_y, GRAPHICS_COLOR_WHITE);
}
}
#pragma vector = PORT3_VECTOR
__interrupt void RESET_ISR() {
// Clear flag, disable interrupt
P3IFG &= ~BUTTON;
P3IE &= ~BUTTON;
if(lives < 0) {
points = 0; // Reset points if loss
lives = 3; // Reset lives if loss
levelNum = 0;
} else {
levelNum++; // Level-up
// Every five levels, we reset the user to three lives
if(levelNum % 5 == 0) {
lives = 3;
}
}
// Set lives to max
lives = 3;
// Rest our active block count
activeBlocks = 30;
// Clear our screen, reset everything to zero.
level = &LEVELS[levelNum % NUM_LEVELS];
memcpy(blocks, level->blocks, sizeof(Graphics_Rectangle) * 30);
palette = level->colors;
bindings = level->bindings;
numBlocks = level->numBlocks;
activeBlocks = level->numBlocks;
// Draw the original screen
Draw_Playspace(&g_sContext, blocks, palette, bindings, numBlocks, lives, levelNum);
// Begin play loop
TA0CTL |= MC_1;
}
void initializeADC(void) {
// Divert pins for analong functionality
P9SEL1 |= JOY_X;
P9SEL0 |= JOY_X;
// Turn on the ADC module
ADC12CTL0 |= ADC12ON;
// Turn off the ENC (Enable Conversion) bit while modifying the config
ADC12CTL0 &= ~ADC12ENC;
// Set ADC12SHT0 (Determined number of cycles)
ADC12CTL0 |= BIT8 | BIT9;
// *********** ADC12CTL1 ******** //
// Set ADC12SHS (select ADC12SC bit as the trigger)
// Set ADC12SHP bit
// Set ADC12DIV (select the divider you determined)
// Set ADC12SSEL (select MODOSC)
// Looks like only bit 9 needs to be set high.
ADC12CTL1 = BIT9;
// *********** ADC12CTL2 ******** //
// Set ADC12RES (select 12-bit resolution)
// [5:4] 10b
// Set ADC12DF (select unsigned binary format)
// [3] 0b
ADC12CTL2 = BIT5;
// *********** ADC12CTL3 ******** //
// Leave all fields default
// *********** ADC12MCTL0 ******** //
// Set ADC12VRSEL (select VR+=AVCC, VR-=AVSS)
// [11:8] 0000b
// Set ADC12INCH (select channel A10)
// [4:0] 01010b;
ADC12MCTL0 = BIT3 | BIT1;
// Turn on ENC to signal the end of configuration
ADC12CTL0 |= ADC12ENC;
return;
}
int readADC(unsigned int* data) {
// Set ADC12SC bit
ADC12CTL0 |= BIT0;
// Wait for ADC12Busy bit to clear
while((ADC12CTL1 & BIT0) != 0) {}
// Read result from register ADC12MEM0
(*data) = ADC12MEM0;
return 1;
}