-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcatapult.ino
More file actions
406 lines (361 loc) · 11.1 KB
/
catapult.ino
File metadata and controls
406 lines (361 loc) · 11.1 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
#include <Adafruit_NeoPixel.h>
#define STRIPE_PIN 2 // input pin Neopixel is attached to
#define SHORT_STRIPE1_PIN 3
#define SHORT_STRIPE2_PIN 4
#define RING_PIN 5
#define BUTTON1_PIN 9
#define BUTTON2_PIN 10
#define POTI1_PIN A1
#define POTI2_PIN A0
#define STRIPPIXELS 69
#define DEMO_DELAY 30 // time in s until demo mode gets activated
#define DEMO_WINDOW 1000 // random variations of loading time in demo mode. 1000 means +-500ms variation.
// two led strips back to back for 360° "view". Comment out to have only one stripe.
#define DOUBLE_STRIPE
#ifdef DOUBLE_STRIPE
#define NUMPIXELS 300 // number of neopixels in main stripe
#define MAX_H 5.0 // maximum throwable height in meters
#else
#define NUMPIXELS 300 // number of neopixels in main stripe
#define MAX_H 10.0 // maximum throwable height in meters
#endif
enum states {
IDLE, LOAD, THROW, HIT
};
Adafruit_NeoPixel stripe = Adafruit_NeoPixel(NUMPIXELS, STRIPE_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_l = Adafruit_NeoPixel(STRIPPIXELS, SHORT_STRIPE1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_r = Adafruit_NeoPixel(STRIPPIXELS, SHORT_STRIPE2_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring = Adafruit_NeoPixel(16, RING_PIN, NEO_GRB + NEO_KHZ800);
// global settings and states
double brightness = 100;
double gravity = 9;
double max_v = 1;
int target = 0;
int bullet_size = 10;
unsigned int max_score = 10;
bool demo_mode = false;
// player related variables
states state[2] = {IDLE, IDLE};
int score[2] = {0, 0};
uint8_t color_p[2][3] = {{255, 40, 41}, {50, 60, 180}};//{106, 95, 219}};
// load
unsigned long load_t0[2] = {0, 0};
// throw
double throw_v0[2] = {-1, -1};
unsigned long throw_t0[2] = {0, 0};
int throw_last_h[2] = {0, 0};
bool throw_missed[2] = {false, false};
// hit
int hit_position[2] = {0, 0};
unsigned long hit_t0[2] = {0, 0};
// demo / idle
unsigned long demo_last_interaction = 0;
unsigned long demo_t0[2] = {0, 0};
unsigned long demo_load[2] = {0, 0};
void setup() {
stripe.begin(); // Initializes the NeoPixel library.
strip_l.begin();
strip_r.begin();
ring.begin();
pinMode(BUTTON1_PIN, INPUT_PULLUP); // initialize input pin
pinMode(BUTTON2_PIN, INPUT_PULLUP); // initialize input pin
#ifdef DOUBLE_STRIPE
target = random(20, NUMPIXELS/2);
#else
target = random(20, NUMPIXELS);
#endif
//randomSeed(analogRead(0)); // initialize random seed
Serial.begin(9600);
stripe.clear();
stripe.show();
strip_l.clear();
strip_l.show();
strip_r.clear();
strip_r.show();
}
bool init_sequence = true;
void loop() {
// read brightness poti
int poti = analogRead(POTI1_PIN);
if(abs(brightness - poti/1023.0) > 0.01) {
brightness = poti/1023.0;
}
// read gravity poti
poti = analogRead(POTI2_PIN);
double tmp_gravity = max(1.0, poti/1023.0 * 16.0);
if(abs(tmp_gravity - gravity) > 0.05) {
Serial.print("update gravity to: ");
Serial.println(gravity);
gravity = tmp_gravity;
// compute new maximum velocity
max_v = sqrt(2 * MAX_H * gravity);
}
updateRing();
// this is only executed once, but after reading poti values, therefore not in setup()
if(init_sequence) {
initSequence();
init_sequence = false;
}
// clear current stripe status
stripe.clear();
// visualize target pixel
stripe.setPixelColor(target, br_color(0, 255, 0));
#ifdef DOUBLE_STRIPE
stripe.setPixelColor(NUMPIXELS-target-1, br_color(0, 255, 0));
#endif
// visualize score
updateScore();
// update state machine
for(int player=0; player <= 1; player++) {
bool input = (player == 0) ? !digitalRead(BUTTON1_PIN) : !digitalRead(BUTTON2_PIN); // read push buttons
// interrupt demo mode and reset timer if an interaction happens
if(input) {
if(demo_mode) {
score[0] = 0;
score[1] = 0;
}
demo_last_interaction = millis();
demo_mode = false;
demo_t0[0] = 0;
demo_t0[1] = 0;
}
// demo mode?
if(millis() - demo_last_interaction > DEMO_DELAY * 1000) {
if(!demo_mode) {
score[0] = 0;
score[1] = 0;
}
demo_mode = true;
}
if(demo_mode) {
switch(state[player]) {
case states::LOAD: // don't understand why, but this needs to be earlier than states::IDLE, otherwise it will be skipped. Weird.
input = true;
if((millis() - demo_t0[player]) > demo_load[player]) {
input = false; // <- trigger throw
}
break;
case states::IDLE:
// initiate demo throw
demo_t0[player] = millis();
// figure out a loading time (+- some random) to hit the target
// h = 1/2 * a * t^2 -> t = sqrt((2*h)/a) (time of flight, unit: s)
double t = sqrt((2.0*target/30.0)/gravity);
double v0 = t*gravity;
demo_load[player] = v0/max_v * 2500; // max_v would be reached by holding the button for 2.5s. THIS IS PROBABLY NOT HOW IT ACTS IN REALITY, seems that max loading time is > 2.5s for some reason.
demo_load[player] += random(0, DEMO_WINDOW) - DEMO_WINDOW*0.5; //some random variation demo load time
input = true; // trigger state switch
break;
case states::THROW:
break;
case HIT:
break;
}
}
// local variables for switch/case
uint8_t energy = 0;
bool hit;
switch(state[player]) {
case states::IDLE:
if(input) {
state[player] = states::LOAD;
}
break;
case states::LOAD:
energy = load_pixel(player);
if(!input) {
state[player] = states::THROW;
load_t0[player] = 0;
throw_v0[player] = max_v/255.0 * energy;
}
break;
case states::THROW:
if(throw_pixel(player, hit)) {
// reset throw state
throw_t0[player] = 0; // reset to invalid
throw_missed[player] = false;
if(hit) {
state[player] = states::HIT;
score[player]++;
// generate new target
hit_position[player] = target;
#ifdef DOUBLE_STRIPE
target = random(20, NUMPIXELS/2);
#else
target = random(20, NUMPIXELS);
#endif
} else {
state[player] = states::IDLE;
}
}
break;
case HIT:
if(visualizeHit(player)) {
// won this round??
if(score[player] >= max_score) {
visualize_win(player);
for(int i=0; i<=1; i++) {
score[i] = 0;
throw_t0[i] = 0;
throw_missed[i] = false;
state[i] = states::IDLE;
}
}
state[player] = states::IDLE;
}
break;
}
}
stripe.show();
//delay(100);
}
// returns current energy as value between 0-255. 255 leads to max_v.
uint8_t load_pixel(int player) {
uint8_t energy = 0;
if(load_t0[player] == 0) load_t0[player] = millis();
energy = min((millis() - load_t0[player]) * 0.1, 255); // max loading time 2.5sec -> / 2500 * 255
// visualize loading
for(int i = 0; i <= energy/25; i++) {
stripe.setPixelColor(i, stripe.getPixelColor(i) | br_color(color_p[player][0], color_p[player][1], color_p[player][2]));
#ifdef DOUBLE_STRIPE
stripe.setPixelColor(NUMPIXELS-i-1, stripe.getPixelColor(i) | br_color(color_p[player][0], color_p[player][1], color_p[player][2]));
#endif
}
return energy;
}
// returns true if hit or returned to floor
bool throw_pixel(int player, bool& hit) {
hit = false; // default return value
if(throw_t0[player] == 0) throw_t0[player] = millis();
double t = (millis() - throw_t0[player]) / 1000.0;
int h = ((throw_v0[player] * t) - (0.5 * gravity * t*t)) * 30.0; // 30 leds per meter
for(int i = 0; i < bullet_size; i++) {
int k = min(h+i, NUMPIXELS/2-1);
uint32_t color = stripe.getPixelColor(k) | br_color(color_p[player][0], color_p[player][1], color_p[player][2]);
stripe.setPixelColor(k, color);
#ifdef DOUBLE_STRIPE
color = stripe.getPixelColor(NUMPIXELS-k-1) | br_color(color_p[player][0], color_p[player][1], color_p[player][2]);
stripe.setPixelColor(NUMPIXELS-k-1, color);
#endif
}
// target hit?
if(throw_last_h[player] > h && !throw_missed[player]) {
// highest point
if((throw_last_h[player] <= target) && (target <= (throw_last_h[player] + bullet_size))) {
Serial.print("player ");
Serial.print(player);
Serial.println(" HIT!!");
hit = true;
throw_last_h[player] = 0;
return true;
}else{
throw_missed[player] = true;
Serial.print("player ");
Serial.print(player);
Serial.println(" missed...");
}
}
if(h < 0) {
throw_last_h[player] = 0;
return true;
}
throw_last_h[player] = h;
return false;
}
bool visualizeHit(int player) {
if(hit_t0[player] == 0) hit_t0[player] = millis();
unsigned long t = millis() - hit_t0[player];
if(t % 100 >= 50) {
int viz_start = max(hit_position[player] - bullet_size/2, 0);
for(int i = 0; i < bullet_size; i++) {
int k = min(viz_start+i, NUMPIXELS/2-1);
stripe.setPixelColor(k, br_color(0, 0, 255));
#ifdef DOUBLE_STRIPE
stripe.setPixelColor(NUMPIXELS-k-1, br_color(0, 0, 255));
#endif
}
}
if(t > 500) {
hit_t0[player] = 0;
return true;
} else {
return false;
}
}
void updateScore() {
int blocksize = 5;
strip_l.clear();
strip_r.clear();
for(int i=0; i<score[0]*blocksize; i++) {
if(i % blocksize == blocksize-1) {
// skip pixel as seperator
} else {
strip_l.setPixelColor(i, br_color(color_p[0][0], color_p[0][1], color_p[0][2]));
}
}
for(int i=0; i<score[1]*blocksize; i++) {
if(i % blocksize == blocksize-1) {
// skip pixel as seperator
} else {
strip_r.setPixelColor(i, br_color(color_p[1][0], color_p[1][1], color_p[1][2]));
}
}
strip_l.show();
strip_r.show();
}
void visualize_win(int player) {
for(int k=0; k<=3; k++) {
strip_l.clear();
strip_r.clear();
strip_l.show();
strip_r.show();
for(int i=0; i < STRIPPIXELS; i++) {
if(player == 0) {
//left
strip_l.setPixelColor(i, br_color(color_p[0][0], color_p[0][1], color_p[0][2]));
strip_l.show();
} else {
//right
strip_r.setPixelColor(i, br_color(color_p[1][0], color_p[1][1], color_p[1][2]));
strip_r.show();
}
delay(10);
}
}
}
void updateRing() {
ring.clear();
for(int i=0; i < gravity; i++) {
ring.setPixelColor(15-i, br_color(50, 10, 0));
}
ring.show();
}
void initSequence() {
// init sequence
for(int i=0; i < STRIPPIXELS; i++) {
strip_l.clear();
strip_l.setPixelColor(i, br_color(0, 0, 255));
strip_l.show();
strip_r.clear();
strip_r.setPixelColor(i, br_color(0, 0, 255));
strip_r.show();
delay(10);
}
for(int i=STRIPPIXELS; i >= 0; i--) {
strip_l.clear();
strip_l.setPixelColor(i, br_color(0, 0, 255));
strip_l.show();
strip_r.clear();
strip_r.setPixelColor(i, br_color(0, 0, 255));
strip_r.show();
delay(10);
}
strip_l.clear();
strip_l.show();
strip_r.clear();
strip_r.show();
}
// returns brightness adjustes pixel color value
uint32_t br_color(uint8_t r, uint8_t g, uint8_t b) {
return Adafruit_NeoPixel::Color(r*brightness, g*brightness, b*brightness);
}