-
Notifications
You must be signed in to change notification settings - Fork 2
/
Firecracker.ino
233 lines (140 loc) · 7.41 KB
/
Firecracker.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
/*
Happy New Years - 2018
Firecracker
Rules:
If button pressed...
explode with firecracker like flashing of lights that spreads to neighboring tiles
if receive spark, explode like firecracker and remember who I received spark from
after an initial ignition time, pick one of my other neighbors to send the spark to.
we will not send a spark back to the same place we got it from.
if no suitable neighbors available, firecracker is extinguished.
*/
// Our local state. We also reuse these values as messages to send to neighbors
enum state_t {
READY , // We will explode if we see an INFECT message on any face.
IGNITE , // Running the explosion pattern. soruceFace is the face we got SPARK from.
BURN, // Spreading to any neighbor besides sourceFace.
};
static state_t state = READY;
#define NO_FACE FACE_COUNT // Use FACE_COUNT as a match-none special value for sourceFace
// We use this when the explosion was manually started so no
// face is the source.
static int sourceFace=NO_FACE; // The face we got the spark from.
// Only valid in EXPLODING, COOLDOWN, and INFECT
// NO_FACE if we did not get the spark from anywhere (was cause by button press)
static int targetFace=NO_FACE; // The face we are sending the spark to.
// Only matters in BURN state. NO_FACE if we are not spreading
Timer nextState; // Time we switch to next state. Valid in EXPLODING, COOLDOWN, and INFECT.
// How long between when we first get a spark and start sending a new spark
static const uint16_t igniteDurration_ms = 200;
// Time we will continue to burn after we start sparking
static const uint16_t burnDuration_ms = 400;
// Show on an occupied face in READY state
static Color readyFaceColor = dim( WHITE, 32);
// Show on the face that we are sending the spark to
static Color sparkFaceColor = ORANGE;
// Show on random faces as we ignite and burn
static Color sparkleColor = makeColorRGB( 255, 255 , 255);
void setup() {
}
// Returns a face that (1) has not yet expired, and (2) is not `exclude`
// Returns NO_FACE is no faces meet the criteria
static byte pickSparkTarget( byte exclude ) {
// First tally up all the potential target faces
byte potentialTargetList[FACE_COUNT];
byte potentialTargetCount=0;
FOREACH_FACE(f) {
if ( (!isValueReceivedOnFaceExpired(f)) && (f!=exclude) ) {
potentialTargetList[ potentialTargetCount ] = f;
potentialTargetCount++;
}
}
if ( potentialTargetCount==0) {
// No place to send
return NO_FACE;
}
// Pick which of the potential nodes is the winner...
byte targetIndex = rand( potentialTargetCount-1 );
return potentialTargetList[ targetIndex ];
}
void loop() {
// put your main code here, to run repeatedly:
bool detonateFlag = false; // Set this flag to true to cause detonation
// since the ignition can come from either button or spark
// and we don't care which
// First get some situational awareness
FOREACH_FACE(f) {
if (didValueOnFaceChange(f)) {
byte receivedMessage = getLastValueReceivedOnFace(f);
if (receivedMessage==BURN) { // We just got a spark!
detonateFlag=true;
sourceFace=f;
} else if (receivedMessage==IGNITE) {
// Looks like the target we were trying to light did catch fire!
// No need to keep sending to them
targetFace=NO_FACE;
}
}
}
// Next update our state based on new info
// if button pressed, firecracker
if (buttonSingleClicked()) {
detonateFlag=true;
sourceFace = NO_FACE; // Manually initiated, so no source
}
if (detonateFlag) {
state=IGNITE;
nextState.set( igniteDurration_ms );
}
if ( nextState.isExpired() ) { // Time for next timed state transition?
// These are the only states that can timeout
if (state==IGNITE) {
// We are ready to spread the flame!
// Try to pick one. Will return NO_FACE if none possible.
targetFace = pickSparkTarget( sourceFace );
state=BURN;
nextState.set( burnDuration_ms );
} else if (state==BURN) { // Technically don't need this `if` since this is the only possible case, but here for clarity.
state=READY;
}
}
// In all states, we show orange on any face that has a neighbor
// (may be covered by effect if we are EXPLODING)
FOREACH_FACE(f) {
if (!isValueReceivedOnFaceExpired(f)) {
setFaceColor(f, readyFaceColor );
} else {
setFaceColor(f, OFF );
}
}
// If there is an explosion in progress, blink a random face
// will draw over the orange set above.
if (state == IGNITE || state == BURN ) {
// Blink a random face white
setFaceColor( rand( FACE_COUNT -1 ) , sparkleColor );
// Blink up to two random faces white
setFaceColor( rand( FACE_COUNT -1 ) , sparkleColor );
}
// Finally we set out outputs
// We send out current state on all faces EXCEPT for the special
// case when we are INFECTing, in which case we want to only send that
// on the target face and look READY to everyone else.
if (state==BURN) {
// Burn state is special since we only send the burn to the target neighbor
// or else all our neighbors would catch on fire
// This is a bit of a hack. We can't send our current state to everyone because then
// we would infect everyone. So we send READY because it is benign.
setValueSentOnAllFaces( READY );
if (targetFace != NO_FACE ) {
// Show the spark flying away
setFaceColor( targetFace , sparkFaceColor );
// We do explicitly send BURN to the target we are aiming to infect.
setValueSentOnFace( state , targetFace );
}
} else {
// For any other state besides BURN, we can just send our real state to everyone
// Note that when we send IGNITE, the person who sent us burn will see it and
// know that we ignited and will stop sending BURN out anymore.
setValueSentOnAllFaces( state );
}
}