-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathLEDpong.ino
More file actions
422 lines (376 loc) · 14 KB
/
LEDpong.ino
File metadata and controls
422 lines (376 loc) · 14 KB
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
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#define BLYNK_PRINT Serial
#include <Ticker.h>
#include <PxMatrix.h>
#include <SimpleTimer.h>
// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "BLYNK_AUTH_TOKEN";
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "SSID";
char pass[] = "PASSWORD";
Ticker display_ticker;
SimpleTimer timer;
// Pins for LED MATRIX
#define P_LAT 16
#define P_A 5
#define P_B 4
#define P_C 15
#define P_OE 2
#define P_D 12
#define P_E 0
// PxMATRIX display(32,16,P_LAT, P_OE,P_A,P_B,P_C);
// PxMATRIX display(64,32,P_LAT, P_OE,P_A,P_B,P_C,P_D);
PxMATRIX display(64, 32, P_LAT, P_OE, P_A, P_B, P_C, P_D, P_E);
// Some standard colors
uint16_t myRED = display.color565(255, 0, 0);
uint16_t myGREEN = display.color565(0, 255, 0);
uint16_t myBLUE = display.color565(0, 0, 255);
uint16_t myWHITE = display.color565(255, 255, 255);
uint16_t myYELLOW = display.color565(255, 255, 0);
uint16_t myCYAN = display.color565(0, 255, 255);
uint16_t myMAGENTA = display.color565(255, 0, 255);
uint16_t myBLACK = display.color565(0, 0, 0);
uint16 myCOLORS[8] = {myRED, myGREEN, myBLUE, myWHITE, myYELLOW, myCYAN, myMAGENTA, myBLACK};
// ISR for display refresh
void display_updater()
{
display.display(70);
}
int imageHeight = 32;
int imageWidth = 64;
int x = 0;
int y = 1;
int OriginalPingPongRacketValue = 1;
int PingPongRacketValue = 1;
// Length of the rackets. The code is written with
// length 6 in mind, so it would be a hassle to
// change it (e.g. the ball bounces with different
// angles, depending on which of the
// 6 possible positions it hits the racket. If you
// change the racket length, you need to come up with
// the corresponding angles as well.
int PingPongRacketLength = 6;
int PingPongRacketLength2 = 6;
int OriginalPingPongRacketValue2 = 1;
int PingPongRacketValue2 = 1;
int BallX = 32; // Initial x-coordinate of the ball, mid-screen
int BallY = random(2, 30); // Initial y-coordinate of the ball, a random pixel.
int IncrementX = 1;
int IncrementY = 1;
int BlueScore = 0;
int GreenScore = 0;
int BallXreplacement;
int BallYreplacement;
int counterReplacement = 0;
int initiator = 0;
int initiator2 = 0;
int counterReplacementX = 0;
int counterReplacementY = 0;
// y-coordinate of the top of the right rocket.
// The way I wrote the code, the range of V13 and V14
// needs to be determined within the app. For instance,
// Ranges for both V13 and V14 must be 25 to 1. "Send values on
// release only" option must be off.
BLYNK_WRITE(V13) {
PingPongRacketValue = param.asInt();
}
// y-coordinate of the top of the left rocket.
BLYNK_WRITE(V14) {
PingPongRacketValue2 = param.asInt();
}
void setup() {
Serial.begin(115200);
Blynk.begin(auth, ssid, pass);
display.begin(16);
display.clearDisplay();
display_ticker.attach(0.002, display_updater);
yield();
display.clearDisplay();
Blynk.syncVirtual(V13); // Gets the position of the left slider from the app.
Blynk.syncVirtual(V14); // Gets the position of the right slider from the app.
Blynk.virtualWrite(V15, 0); // Displays, in the app, zero as the score of the left player.
Blynk.virtualWrite(V16, 0); // Displays, in the app, zero as the score of the right player.
// Displays introduction.
displayName("LED", "PONG!");
delay(3000);
display.clearDisplay();
// Paints borders.
PaintEdges();
// Counts down from 5 to start the game.
for (int b = 5; b > 0; b--) {
display.setTextSize(2);
display.setTextColor(myCYAN);
display.setCursor(27, 9);
display.print(b);
delay(1000);
clearArea(27, 9, 36, 22);
}
// Draws the racket of left player.
for (int i = PingPongRacketValue; i < PingPongRacketValue + PingPongRacketLength; i++)
display.drawPixel(2, i, myBLUE);
// Draws the rocket of right player.
for (int i = PingPongRacketValue2; i < PingPongRacketValue2 + PingPongRacketLength2; i++)
display.drawPixel(61, i, myGREEN);
// Displays the current score (0-0).
displayScore();
// Running three functions: two for controlling the rackets, one for the ball. Changing the "moveBall" frequency changes the speed of the ball.
timer.setInterval(1, movePingPongRacket);
timer.setInterval(1, movePingPongRacket2);
timer.setInterval(35, moveBall);
}
void loop() {
Blynk.run();
timer.run();
}
// Displays the racket of left player, runs every 1 millisecond.
void movePingPongRacket () {
if (PingPongRacketValue != OriginalPingPongRacketValue) {
for (int i = 1; i < 31; i++)
display.drawPixel(2, i, myBLACK);
for (int i = 0; i < PingPongRacketLength; i++)
display.drawPixel(2, PingPongRacketValue + i, myBLUE);
OriginalPingPongRacketValue = PingPongRacketValue;
}
}
// Displays the racket of right player, runs every 1 millisecond.
void movePingPongRacket2 () {
if (PingPongRacketValue2 != OriginalPingPongRacketValue2) {
for (int i = 1; i < 31; i++)
display.drawPixel(61, i, myBLACK);
for (int i = 0; i < PingPongRacketLength2; i++)
display.drawPixel(61, PingPongRacketValue2 + i, myGREEN);
OriginalPingPongRacketValue2 = PingPongRacketValue2;
}
}
// Moves ball around, runs every 35 milliseconds.
void moveBall () {
// Clears the previous position of the ball.
display.drawPixel (BallX, BallY, myBLACK);
if (counterReplacementY == 1) {
display.drawPixel(BallX, BallYreplacement, myBLACK);
}
if (counterReplacementX == 1) {
display.drawPixel(BallXreplacement, BallY , myBLACK);
}
counterReplacementY = 0;
counterReplacementX = 0;
// Displays the current position of the ball.
BallX = BallX + IncrementX;
BallY = BallY + IncrementY;
display.drawPixel(BallX, BallY, myCYAN);
// Clears the area where the score is displayed, regardless of the score has just been displayed or not. This can be optimized to run only after the score is displayed.
if (((BallX == 24 || BallX == 25) && IncrementX < 0) || ((BallX == 40 || BallX == 39) && IncrementX > 0)) {
clearArea(10, 9, 19, 22);
clearArea(43, 9, 52, 22);
}
// What to do when the ball hits, or fails to hit, the rackets.
if (BallX % (imageWidth - 7) == 3) {
int k = 0;
if (BallX < imageWidth / 2) { //If player on the left is supposed to hit the ball...
for (int i = 0; i < PingPongRacketLength; i++) {
if (BallY == PingPongRacketValue + i) { //...see if the y-coordinate of the ball matches the y-coordinate of the racket.
k++; // if, after the "for" loop, k returns as zero, a goal has been scored.
if (i <= PingPongRacketLength / 2 - 1) { // If the ball hit the racket on the upper half of the racket, adjust increment (i.e. angle of the ball).
if (i == 0) {
IncrementX = 1;
IncrementY = -2;
}
if (i == 1) {
IncrementX = 1;
IncrementY = -1;
}
if (i == 2) {
IncrementX = 2;
IncrementY = -1;
}
} else { // If the ball hit the racket on the lower half of the racket, adjust increment (i.e. angle of the ball).
if (i == 3) {
IncrementX = 2;
IncrementY = 1;
}
if (i == 4) {
IncrementX = 1;
IncrementY = 1;
}
if (i == 5) {
IncrementX = 1;
IncrementY = 2;
}
}
}
}
} else { // If player on the right is supposed to hit the ball... (similar process as above)
for (int i = 0; i < PingPongRacketLength; i++) {
if (BallY == PingPongRacketValue2 + i) {
k++;
if (i <= PingPongRacketLength2 / 2 - 1) {
if (i == 0) {
IncrementX = -1;
IncrementY = -2;
}
if (i == 1) {
IncrementX = -1;
IncrementY = -1;
}
if (i == 2) {
IncrementX = -2;
IncrementY = -1;
}
} else {
if (i == 3) {
IncrementX = -2;
IncrementY = 1;
}
if (i == 4) {
IncrementX = -1;
IncrementY = 1;
}
if (i == 5) {
IncrementX = -1;
IncrementY = 2;
}
}
}
}
}
if (k == 0) { // If the racket didn't hit the ball (i.e. a goal has been scored)...
display.drawPixel (BallX, BallY, myBLACK); // Remove the ball from display.
// Adjust the increments to 1 or -1, facing towards whoever just scored the goal.
// This step ensures (1) ease of play for goal receiver (i.e. they don't keep
// receiving the ball after they just allowed a goal) and (2) angles other than
// 45 degrees (that is, othar than (1,1), (-1,-1), (1,-1), (-1,1)) are difficult
// to catch when they originate from mid-screen, which they will, now that a
// goal has been scored.
if (IncrementX % 2 == 0)
IncrementX = IncrementX / 2;
if (IncrementY % 2 == 0)
IncrementY = IncrementY / 2;
IncrementX = IncrementX * -1;
IncrementY = IncrementY * -1;
if (BallX > imageWidth / 2) { // If the goal has been scored on the right side...
BlueScore++; // Increase left player's score by one.
Blynk.virtualWrite(V15, BlueScore); // Display the new score for the left player on the app.
if (BlueScore == 10) { // If 10 is reached, display the winner, update the score on the app, and end the game.
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(myBLUE);
display.setCursor(1, 1);
display.print("BLUE");
display.setCursor(1, 17);
display.print("WINS!");
delay(5000);
Blynk.virtualWrite(V15, 0);
Blynk.virtualWrite(V16, 0);
delay(1000000); // In effect, stops the game. There is almost certainly a better way to do this.
}
} else {
GreenScore++;
Blynk.virtualWrite(V16, GreenScore);
if (GreenScore == 10) {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(myGREEN);
display.setCursor(1, 1);
display.print("GREEN");
display.setCursor(1, 17);
display.print("WINS!");
delay(5000);
Blynk.virtualWrite(V15, 0);
Blynk.virtualWrite(V16, 0);
delay(1000000);
}
}
displayScore(); // Display the score in the mid-screen.
BallX = 32; // Re-adjust ball coordinates for the next point.
BallY = random(2, 30);
}
}
if (BallY % (imageHeight - 3) == 1) { //When the ball hits the up and down borders, change the direction.
IncrementY = IncrementY * -1;
}
// The code was originally written for increments of only 1 or - 1.
// After seeing that that makes the game too predictable, I introduced
// increments of 2 and - 2, which did solve the predictability problem,
// but introduced the problem of ball running outside the boundaries of
// the field. When the increments were 1 and - 1, the ball would definitely
// have to hit the line right before the boundaries (or the line
// right before the rackets), and I could change the
// direction according to when that happened. But with the introduction of
// increments of 2 an - 2, the ball sometimes skipped the line right before
// the boundary, and consequently didn't get caught by the direction changing
// conditionals. The following two "if"s are solving this problem.
if ((IncrementY == 2 && BallY == 29) || (IncrementY == -2 && BallY == 2)) { //When IncrementY is 2 or -2, and the ball will go outside of the borders in the next iteration, adjust the ball so that it doesn't go out of bounds via up or down.
BallYreplacement = BallY;
BallY = BallY - IncrementY / 2;
counterReplacementY = 1;
if (initiator == 0) {
BallXreplacement = BallX;
initiator = 1;
}
}
if ((IncrementX == 2 && BallX == 59) || (IncrementX == -2 && BallX == 4)) { //When IncrementX is 2 or -2, and the ball will go outside of the rackets in the next iteration, adjust the ball so that it doesn't go out of bounds via left or right.
BallXreplacement = BallX;
BallX = BallX - IncrementX / 2;
counterReplacementX = 1;
if (initiator2 == 0) {
BallYreplacement = BallY;
initiator2 = 1;
}
}
}
// Paints borders.
void PaintEdges () {
int WL = 1;
for (int y = 0; y < 32; y++) {
for (int x = 0; x < 64; x++) {
if (x % (64 - WL) < WL || y % (32 - WL) < WL)
display.drawPixel(x , y, myCYAN);
}
}
}
// Displays score.
void displayScore() {
display.setTextSize(2);
display.setCursor(10, 9);
display.print(BlueScore);
display.setCursor(43, 9);
display.print(GreenScore);
}
// Paints a specified area black. Format: clearArea (x-coordinate of top left, y-coordinate of top left, x-coordinate of bottom right, y-coordinate of bottom right)
void clearArea (int CleanXstart, int CleanYstart, int CleanXend, int CleanYend) {
for (int k = CleanYstart; k <= CleanYend; k++) {
for (int i = CleanXstart; i <= CleanXend; i++) {
display.drawPixel(i, k, myBLACK);
}
}
}
// Displays a text. Original coder: Brian Lough, aka. witnessmenow on Github, Reddit, Twitter and YouTube.
void displayText(String text, int yPos) {
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(2);
char charBuf[text.length() + 1];
text.toCharArray(charBuf, text.length() + 1);
display.getTextBounds(charBuf, 0, yPos, &x1, &y1, &w, &h);
int startingX = 33 - (w / 2);
if (startingX < 0) {
display.setTextSize(1);
display.getTextBounds(charBuf, 0, yPos, &x1, &y1, &w, &h);
startingX = 33 - (w / 2);
}
display.setCursor(startingX, yPos);
Serial.println(startingX);
Serial.println(yPos);
display.print(text);
}
// Displays a text. Original coder: Brian Lough, aka. witnessmenow on Github, Reddit, Twitter and YouTube.
void displayName(String first, String last)
{
display.setTextColor(myCYAN);
displayText(first, 1);
display.setTextColor(myCYAN);
displayText(last, 17);
}