-
Notifications
You must be signed in to change notification settings - Fork 1
/
terrarium.ino
2263 lines (1991 loc) · 62.3 KB
/
terrarium.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
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
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// DONE
// * Change plant leaves to branches as it grows
// * Timed main loop - 48 ms at start - 31 ms typical
// * Merge gravity tile into dripper
// * Learned removing repeated calls helps code size 5594 vs 5728 for five Timer.set calls
// TODO
// * Change sense of neighbor water level to be how much it can accept
// * Require click before plant will grow
// * Bring back the bug!
// * Fix comm errors when waking from sleep
// WHEN HAVE MORE TILES
// * Ensure sunlight is absorbed/blocked by things correctly
#define null 0
#define INCLUDE_BASE_TILES 0
#define INCLUDE_SPECIAL_TILES 1
#define DEBUG_COMMS 0
#define DEBUG_COLORS 0
#define USE_DATA_SPONGE 0
#define NEW_RENDER 0
#define NEW_DRIPPER_RENDER 1
#define HUE_DRIPPER 106
#define HUE_DIRT 32
#define HUE_SUN 42
#define HUE_BUG 55
#define HUE_WATER 171
#define HUE_LEAF 85
#define HUE_BRANCH 28
#define HUE_FLOWER 233
//#define COLOR_BUG makeColorHSB(HUE_BUG, 255, 128)
#define COLOR_LEAF makeColorHSB(HUE_LEAF, 255, 128)
#define COLOR_BRANCH makeColorHSB(HUE_BRANCH, 255, 128)
#define COLOR_WATER makeColorHSB(HUE_WATER, 255, 128)
#if DEBUG_COLORS
#define COLOR_WATER_FULL makeColorRGB( 0, 128, 128)
#define COLOR_WATER_FULL1 makeColorRGB( 64, 0, 0)
#define COLOR_WATER_FULL2 makeColorRGB(128, 0, 0)
#define COLOR_WATER_FULL3 makeColorRGB(255, 0, 0)
#define COLOR_WATER_FULL4 makeColorRGB( 0, 64, 0)
#define COLOR_WATER_FULL5 makeColorRGB( 0, 128, 0)
#define COLOR_WATER_FULL6 makeColorRGB( 0, 255, 0)
#define COLOR_WATER_FULL7 makeColorRGB( 0, 0, 64)
#define COLOR_WATER_FULL8 makeColorRGB( 0, 0, 128)
#define COLOR_WATER_FULL9 makeColorRGB( 0, 0, 255)
#endif
#if DEBUG_COLORS
#define COLOR_PLANT_GROWTH1 makeColorRGB( 128, 0, 64)
#define COLOR_PLANT_GROWTH2 makeColorRGB( 64, 0, 128)
#define COLOR_PLANT_GROWTH3 makeColorRGB( 64, 64, 64)
#endif
#define COLOR_DEBUG1 makeColorRGB(255, 255, 0) // yellow
#define COLOR_DEBUG2 makeColorRGB(255, 0, 255) // purple
#define COLOR_DEBUG3 makeColorRGB(0, 255, 255) // cyan
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define OPPOSITE_FACE(f) (((f) < 3) ? ((f) + 3) : ((f) - 3))
#define CW_FROM_FACE(f, amt) ((f + amt) % FACE_COUNT)
#define CCW_FROM_FACE(f, amt) (((f) - (amt)) + (((f) >= (amt)) ? 0 : 6))
// Gravity is a constant velocity for now (not accelerative)
#define GRAVITY 200 // ms to fall from pixel to pixel
Timer gravityTimer;
enum eTileFlags
{
kTileFlag_PulseSun = 1<<0,
kTileFlag_HasBug = 1<<1,
};
byte tileFlags;
unsigned long currentTime = 0;
byte frameTime;
byte worstFrameTime = 0;
enum eRenderBuffer
{
kRenderBuffer_LerpFrom,
kRenderBuffer_LerpTo,
kRenderBuffer_LerpNext
};
byte renderHueOnFace[FACE_COUNT][3];
#define RENDER_TIMER_RATE 200
Timer renderTimer;
#if USE_DATA_SPONGE
byte sponge[143];
#endif
// =================================================================================================
//
// SYSTEMS
//
// =================================================================================================
// Roles the player can select for a tile
enum eTileRole
{
kTileRole_Base,
kTileRole_Dripper,
kTileRole_Dirt,
kTileRole_Sun,
kTileRole_MAX
};
// Current tile state - starts empty
#if INCLUDE_BASE_TILES
char tileRole = kTileRole_Base;
#else
char tileRole = kTileRole_Dirt;
#endif
#define GRAVITY_UP_FACE_UNKNOWN -1
char gravityUpFace = 0;
#define SUN_SEEN_RATE 500
char sunSeen;
char sunBrightness;
Timer sunSeenTimer;
// -------------------------------------------------------------------------------------------------
// DRIPPER/WATER
//
#define MAX_WATER_LEVEL 15 // water level can actually go above this, but will stop accepting new water
#if INCLUDE_SPECIAL_TILES
#define DRIPPER_AMOUNT 4
#define DRIPPER_RATE 1000 // ms between drips
Timer dripperTimer;
byte dripperSpeedScale = 0;
#endif
#if INCLUDE_SPECIAL_TILES
#define DIRT_EVAPORATION 4
#endif
// Normal tiles and dirt tiles both evaporate water
#define EVAPORATION_RATE 5000
Timer evaporationTimer;
// -------------------------------------------------------------------------------------------------
// DIRT/PLANT
//
#if INCLUDE_SPECIAL_TILES
#define MAX_DIRT_RESERVOIR 100
#endif
// Most gathered sunlight a tile can hold
// The practical max will be a lot lower than this because leaves only collect 1 unit at a time
// Used by both normal tiles (plants) and dirt tiles
#define MAX_GATHERED_SUN 200
#if INCLUDE_SPECIAL_TILES
byte dirtReservoir = 0;
#endif
#if INCLUDE_BASE_TILES
byte gatheredSun = 0; // used by plants (dirt tiles use the per-face 'gatheredSun')
#endif
// Amount of water that seeps out of a saturated dirt tile
#if INCLUDE_SPECIAL_TILES
#define DIRT_WATER_SEEP 4
#endif
// Used with plants. This is the rate that...
// ...collected sun is transmitted down the plant to the dirt
// ...dirt tiles distributes energy up to its plants
// ...plants distribute excess energy upwards to continue growth
#define PLANT_ENERGY_RATE 5000
Timer plantEnergyTimer;
#define MAX_ENERGY_PER_TILE 24
enum eBranchState
{
kBranchState_Empty,
kBranchState_Leaf,
kBranchState_Branch,
kBranchState_Flower
};
struct BranchState
{
eBranchState state : 2;
byte growthEnergy : 2;
byte didGatherSun : 1;
};
struct PlantState
{
byte rootFace : 3;
byte energyTotal : 5;
BranchState branches[3];
};
PlantState plantState;
#if INCLUDE_BASE_TILES
#define PLANT_FLOWER_RATE 60000 // blossom a flower after a minute in the right conditions
Timer plantFlowerTimer;
#endif
// -------------------------------------------------------------------------------------------------
// SUN
//
#if INCLUDE_SPECIAL_TILES
// Controls the rate at which sun tiles generate sunlight
#define SUN_STRENGTH 3
#define GENERATE_SUN_RATE 500
Timer generateSunTimer;
byte sunStrengthScale = 1;
#endif
//#if DEBUG_COLORS
#define SUN_PULSE_RATE 250
Timer sunPulseTimer;
//#endif
// -------------------------------------------------------------------------------------------------
// GRAVITY
//
// This timer is used by gravity tiles to broadcast the gravity direction
// It is also used by normal tiles to know not to accept any new gravity for a while
#define SEND_GRAVITY_RATE 5000
#define CHECK_GRAVITY_RATE (SEND_GRAVITY_RATE-1000)
Timer sendGravityTimer;
// -------------------------------------------------------------------------------------------------
// BUG
//
#define BUG_FLAP_RATE 100
Timer bugFlapTimer;
#define BUG_MOVE_RATE 7
byte bugTargetCorner = 0;
char bugDistance = 0;
char bugDirection = 1;
byte bugFlapOpen = 0;
// =================================================================================================
//
// COMMUNICATIONS
//
// =================================================================================================
#define TOGGLE_COMMAND 1
#define TOGGLE_DATA 0
struct FaceValue
{
byte value : 4;
byte toggle : 1;
byte ack : 1;
};
enum FaceFlag
{
FaceFlag_Present = 1<<0,
FaceFlag_GotWater = 1<<1,
FaceFlag_SentWater = 1<<2,
FaceFlag_Debug = 1<<7
};
struct FaceState
{
FaceValue faceValueIn;
FaceValue faceValueOut;
byte lastCommandIn;
byte flags;
byte waterLevel;
byte waterLevelNew;
byte waterLevelNeighbor;
byte waterLevelNeighborNew;
#if INCLUDE_SPECIAL_TILES
byte gatheredSun; // so dirt tiles send energy to the right face
#endif
// byte faceColor; // DEBUG
#if DEBUG_COMMS
byte ourState;
byte neighborState;
#endif
};
FaceState faceStates[FACE_COUNT];
#define NeighborSynced(f) ((faceStates[f].flags & \
(FaceFlag_Present | FaceFlag_GotWater)) == \
(FaceFlag_Present | FaceFlag_GotWater))
enum eTransferType
{
kTransferType_Bug
};
enum CommandType
{
CommandType_None, // no data
CommandType_WaterLevel, // Our current water level
CommandType_WaterAdd, // Add Water To Neighbor : value=water level to add to the neighbor face
CommandType_DistEnergy, // Distribute Energy : value=energy
CommandType_SendSun, // Send sunlight (sent from Sun tiles): value=sun amount
CommandType_SendSunCW, // Send sunlight (sent from Sun tiles): value=sun amount (turns CW after one tile)
CommandType_SendSunCCW, // Send sunlight (sent from Sun tiles): value=sun amount (turns CCW after one tile)
CommandType_GatherSun, // Gather sun from leaves to root : value=sun amount
CommandType_GravityDir, // Tell neighbor which way gravity points - propagates out from gravity tile
CommandType_TryTransfer, // Attempt to transfer something from one tile to another (must be confirmed)
CommandType_TryTransferCW, // Same as above, but transfer to the face CW from the receiver
CommandType_TransferAccept, // Confirmation of the transfer - sender must tolerate this never arriving, or arriving late
CommandType_BlossomFlower, // Plant is trying to blossom a flower at the end of a branch
#if DEBUG_COMMS
CommandType_UpdateState,
#endif
CommandType_MAX
};
struct CommandAndData
{
CommandType command : 4;
byte data : 4;
};
#define COMM_QUEUE_SIZE 4
CommandAndData commQueues[FACE_COUNT][COMM_QUEUE_SIZE];
#define COMM_INDEX_ERROR_OVERRUN 0xFF
#define COMM_INDEX_OUT_OF_SYNC 0xFE
#define COMM_DATA_OVERRUN 0xFD
byte commInsertionIndexes[FACE_COUNT];
#define ErrorOnFace(f) (commInsertionIndexes[f] > COMM_QUEUE_SIZE)
#if DEBUG_COMMS
// Timer used to toggle between green & blue
Timer sendNewStateTimer;
#endif
// =================================================================================================
//
// SETUP
//
// =================================================================================================
void setup()
{
#if USE_DATA_SPONGE
// Use our data sponge so that it isn't compiled away
if (sponge[0])
{
sponge[1] = 3;
}
#endif
currentTime = millis();
resetOurState();
FOREACH_FACE(f)
{
resetCommOnFace(f);
}
gravityTimer.set(GRAVITY);
}
// =================================================================================================
//
// COMMUNICATIONS
// Cut-and-paste from sample project
//
// =================================================================================================
void resetCommOnFace(byte f)
{
// Clear the queue
commInsertionIndexes[f] = 0;
FaceState *faceState = &faceStates[f];
// Put the current output into its reset state.
// In this case, all zeroes works for us.
// Assuming the neighbor is also reset, it means our ACK == their TOGGLE.
// This allows the next pair to be sent immediately.
// Also, since the toggle bit is set to TOGGLE_DATA, it will toggle into TOGGLE_COMMAND,
// which is what we need to start sending a new pair.
faceState->faceValueOut.value = 0;
faceState->faceValueOut.toggle = TOGGLE_DATA;
faceState->faceValueOut.ack = TOGGLE_DATA;
sendValueOnFace(f, faceState->faceValueOut);
// Clear sync flags dealing with our state
faceState->flags &= ~FaceFlag_SentWater;
}
void sendValueOnFace(byte f, FaceValue faceValue)
{
byte outVal = *((byte*)&faceValue);
setValueSentOnFace(outVal, f);
}
// Called by the main program when this tile needs to tell something to
// a neighbor tile.
void enqueueCommOnFace(byte f, CommandType commandType, byte data, bool clampToMax)
{
if (commInsertionIndexes[f] >= COMM_QUEUE_SIZE)
{
// Buffer overrun - might need to increase queue size to accommodate
commInsertionIndexes[f] = COMM_INDEX_ERROR_OVERRUN;
return;
}
if (data & 0xF0)
{
if (clampToMax)
{
data = 0xF;
}
else
{
commInsertionIndexes[f] = COMM_DATA_OVERRUN;
}
}
byte index = commInsertionIndexes[f];
commQueues[f][index].command = commandType;
commQueues[f][index].data = data;
commInsertionIndexes[f]++;
}
// Replace the data for the given command in the queue, if it exists.
// Otherwise add it to the queue.
void replaceOrEnqueueCommOnFace(byte f, CommandType commandType, byte data, bool clampToMax)
{
for (byte index = 0; index < commInsertionIndexes[f]; index++)
{
if (commQueues[f][index].command == commandType)
{
if (data & 0xF0)
{
if (clampToMax)
{
data = 0xF;
}
else
{
commInsertionIndexes[f] = COMM_DATA_OVERRUN;
}
}
commQueues[f][index].data = data;
return;
}
}
enqueueCommOnFace(f, commandType, data, clampToMax);
}
// Called every iteration of loop(), preferably before any main processing
// so that we can act on any new data being sent.
void updateCommOnFaces()
{
FOREACH_FACE(f)
{
// Is the neighbor still there?
if (isValueReceivedOnFaceExpired(f))
{
// Lost the neighbor - no longer in sync
resetCommOnFace(f);
faceStates[f].flags = 0;
continue;
}
// If there is any kind of error on the face then do nothing
// The error can be reset by removing the neighbor
if (ErrorOnFace(f))
{
continue;
}
FaceState *faceState = &faceStates[f];
faceState->flags |= FaceFlag_Present;
// Read the neighbor's face value it is sending to us
byte val = getLastValueReceivedOnFace(f);
faceState->faceValueIn = *((FaceValue*)&val);
//
// RECEIVE
//
// Did the neighbor send a new comm?
// Recognize this when their TOGGLE bit changed from the last value we got.
if (faceState->faceValueOut.ack != faceState->faceValueIn.toggle)
{
// Got a new comm - process it
byte value = faceState->faceValueIn.value;
if (faceState->faceValueIn.toggle == TOGGLE_COMMAND)
{
// This is the first part of a comm (COMMAND)
// Save the command value until we get the data
faceState->lastCommandIn = value;
}
else
{
// This is the second part of a comm (DATA)
// Do application-specific stuff with the comm
processCommForFace(faceState->lastCommandIn, value, f);
}
// Acknowledge that we processed this value so the neighbor can send the next one
faceState->faceValueOut.ack = faceState->faceValueIn.toggle;
}
//
// SEND
//
// Did the neighbor acknowledge our last comm?
// Recognize this when their ACK bit equals our current TOGGLE bit.
if (faceState->faceValueIn.ack == faceState->faceValueOut.toggle)
{
// If we just sent the DATA half of the previous comm, check if there
// are any more commands to send.
if (faceState->faceValueOut.toggle == TOGGLE_DATA)
{
if (commInsertionIndexes[f] == 0)
{
// Nope, no more comms to send - bail and wait
continue;
}
}
// Send the next value, either COMMAND or DATA depending on the toggle bit
// Toggle between command and data
faceState->faceValueOut.toggle = ~faceState->faceValueOut.toggle;
// Grab the first element in the queue - we'll need it either way
CommandAndData commandAndData = commQueues[f][0];
// Send either the command or data depending on the toggle bit
if (faceState->faceValueOut.toggle == TOGGLE_COMMAND)
{
faceState->faceValueOut.value = commandAndData.command;
}
else
{
faceState->faceValueOut.value = commandAndData.data;
// No longer need this comm - shift everything towards the front of the queue
for (byte commIndex = 1; commIndex < COMM_QUEUE_SIZE; commIndex++)
{
commQueues[f][commIndex-1] = commQueues[f][commIndex];
}
// Adjust the insertion index since we just shifted the queue
if (commInsertionIndexes[f] == 0)
{
// Shouldn't get here - if so something is funky
commInsertionIndexes[f] = COMM_INDEX_OUT_OF_SYNC;
continue;
}
else
{
commInsertionIndexes[f]--;
}
}
}
}
FOREACH_FACE(f)
{
// Update the value sent in case anything changed
sendValueOnFace(f, faceStates[f].faceValueOut);
}
}
// =================================================================================================
//
// LOOP
//
// =================================================================================================
void loop()
{
// Keep track of time
unsigned long previousTime = currentTime;
currentTime = millis();
// Clamp frame time at 255 ms to fit within a byte
// Hopefully processing doesn't ever take longer than that for one frame
unsigned long timeSinceLastLoop = currentTime - previousTime;
frameTime = (timeSinceLastLoop > 255) ? 255 : (timeSinceLastLoop & 0xFF);
if (frameTime > worstFrameTime)
{
worstFrameTime = frameTime;
}
// User input
updateUserSelection();
// Update neighbor presence and comms
updateCommOnFaces();
#if DEBUG_COMMS
if (sendNewStateTimer.isExpired())
{
FOREACH_FACE(f)
{
byte nextVal = faceStates[f].neighborState == 2 ? 3 : 2;
faceStates[f].neighborState = nextVal;
enqueueCommOnFace(f, CommandType_UpdateState, nextVal, true);
}
sendNewStateTimer.set(500);
}
#else // DEBUG_COMMS
// Systems updates
switch (tileRole)
{
#if INCLUDE_BASE_TILES
case kTileRole_Base:
loopWater();
loopPlant();
loopBug();
break;
#endif
#if INCLUDE_SPECIAL_TILES
case kTileRole_Dripper: loopDripper(); loopGravity(); break;
case kTileRole_Dirt: loopDirt(); break;
case kTileRole_Sun: loopSun(); break;
#endif
}
// Update water levels and such
postProcessState();
#endif // DEBUG_COMMS
// Set the colors on all faces according to what's happening in the tile
render();
}
// =================================================================================================
//
// GENERAL
// Non-system-specific functions.
//
// =================================================================================================
void updateUserSelection()
{
// Dirt tiles get reset too
if (buttonMultiClicked())
{
// Reset our state and tell our neighbors to reset their perception of us
resetOurState();
// Give ourselves a bug!
if (tileRole == kTileRole_Base)
{
tileFlags |= kTileFlag_HasBug;
}
}
#if INCLUDE_SPECIAL_TILES
if (buttonDoubleClicked())
{
// User state changed
char prevTileState = tileRole;
tileRole++;
if (tileRole >= kTileRole_MAX)
{
#if INCLUDE_BASE_TILES
tileRole = kTileRole_Base;
#else
tileRole = kTileRole_Dripper;
#endif
}
// Reset our state and tell our neighbors to reset their perception of us
resetOurState();
if (prevTileState != tileRole)
{
switch (prevTileState)
{
case kTileRole_Dripper:
break;
}
switch (tileRole)
{
case kTileRole_Dripper:
dripperTimer.set(DRIPPER_RATE >> dripperSpeedScale); // start dripping
break;
case kTileRole_Dirt:
plantEnergyTimer.set(PLANT_ENERGY_RATE);
break;
}
}
} // buttonDoubleClicked
#endif
if (buttonSingleClicked() && !hasWoken())
{
#if INCLUDE_SPECIAL_TILES
switch (tileRole)
{
case kTileRole_Dripper:
dripperSpeedScale++;
if (dripperSpeedScale > 2)
{
dripperSpeedScale = 0;
}
break;
case kTileRole_Sun:
sunStrengthScale++;
if (sunStrengthScale > 3)
{
sunStrengthScale = 1;
}
break;
}
#endif
}
}
void memclr(byte *ptr, byte len)
{
byte *ptrEnd = ptr + len;
while (ptr != ptrEnd)
{
*ptr = 0;
ptr++;
}
}
void resetOurState()
{
FOREACH_FACE(f)
{
FaceState *faceState = &faceStates[f];
// Clear sync flags dealing with our state
faceState->flags &= ~FaceFlag_SentWater;
switch (tileRole)
{
case kTileRole_Base:
faceState->waterLevel = 0;
faceState->waterLevelNew = 0;
memclr((byte*) &plantState, sizeof(plantState));
break;
#if INCLUDE_SPECIAL_TILES
case kTileRole_Dripper:
dripperTimer.set(1);
break;
case kTileRole_Dirt:
faceState->waterLevel = MAX_WATER_LEVEL;
faceState->gatheredSun = 0;
dirtReservoir = 0;
break;
case kTileRole_Sun:
generateSunTimer.set(1);
break;
#endif
}
#if INCLUDE_BASE_TILES
gatheredSun = 0;
#endif
}
#if INCLUDE_BASE_TILES
// Remove all plant energy - causes all plants to die immediately
plantState.energyTotal = 0;
#endif
tileFlags = 0;
worstFrameTime = 0;
}
void processCommForFace(CommandType command, byte value, byte f)
{
FaceState *faceState = &faceStates[f];
// Use the saved command value to determine what to do with the data
switch (command)
{
case CommandType_WaterLevel:
faceState->waterLevelNeighbor = value;
faceState->flags |= FaceFlag_GotWater;
break;
case CommandType_WaterAdd:
switch (tileRole)
{
#if INCLUDE_BASE_TILES
case kTileRole_Base:
{
// Normal tiles accumulate water in each face
faceState->waterLevel += value;
replaceOrEnqueueCommOnFace(f, CommandType_WaterLevel, faceState->waterLevel, true);
}
break;
#endif
#if INCLUDE_SPECIAL_TILES
case kTileRole_Dirt:
// Dirt tiles have one large reservoir absorbed in the tile
dirtReservoir += value;
replaceOrEnqueueCommOnFace(f, CommandType_WaterLevel, getDirtWaterLevelToSend(), true);
break;
#endif
}
break;
case CommandType_DistEnergy:
#if INCLUDE_BASE_TILES
if (tileRole == kTileRole_Base)
{
// Can only accept the energy if there is no plant growing here, or if
// the energy is coming from the same root.
if (plantState.branches[0].state == kBranchState_Empty || plantState.rootFace == f)
{
// Clamp to max amount we can store
if ((MAX_ENERGY_PER_TILE - plantState.energyTotal) < value)
{
plantState.energyTotal = MAX_ENERGY_PER_TILE;
}
else
{
plantState.energyTotal += value;
}
plantState.rootFace = f;
}
}
#endif
break;
case CommandType_SendSun:
case CommandType_SendSunCW:
case CommandType_SendSunCCW:
if (sunSeen < (255 - value))
{
sunSeen += value;
}
#if INCLUDE_BASE_TILES
if (tileRole == kTileRole_Base)
{
tileFlags |= kTileFlag_PulseSun;
// Plant leaves absorb sunlight
// Send on the rest in the same direction
for (int branchIndex = 0; branchIndex < 3; branchIndex++)
{
if (value == 0)
{
break; // break out once there's no more sun
}
if (plantState.branches[branchIndex].state == kBranchState_Leaf)
{
// Leaves absorb the sun
plantState.branches[branchIndex].didGatherSun = 1;
value--;
}
else if (plantState.branches[branchIndex].state == kBranchState_Branch)
{
// Branches just block the sun
value--;
}
}
}
#endif
#if INCLUDE_SPECIAL_TILES
if (tileRole == kTileRole_Dirt)
{
// Dirt blocks all sunlight
value = 0;
}
// Note: Drippers and sun tiles do not block sunlight
#endif
// Any remaining sun gets sent on
if (value > 0)
{
byte faceOffset = 3; // typically we want the opposite face
if (faceState->lastCommandIn == CommandType_SendSunCW)
{
faceOffset = 2; // special case when sent out of a sun tile to make sunshine 3 hexes wide
}
else if (faceState->lastCommandIn == CommandType_SendSunCCW)
{
faceOffset = 4; // special case when sent out of a sun tile to make sunshine 3 hexes wide
}
byte exitFace = CW_FROM_FACE(f, faceOffset);
enqueueCommOnFace(exitFace, CommandType_SendSun, value, true);
}
break;
case CommandType_GatherSun:
#if INCLUDE_BASE_TILES
if (tileRole == kTileRole_Base)
{
// Plants propagate the sun until it gets to the root in the dirt
if (plantState.branches[0].state != kBranchState_Empty)
{
if (gatheredSun < MAX_GATHERED_SUN)
{
gatheredSun += value;
}
// When receiving sun, convert our leaves to branches
char faceOffsetFromRoot = plantState.rootFace - f;
if (faceOffsetFromRoot == 2 || faceOffsetFromRoot == -4)
{
// Still growing - don't spawn a flower yet
if (plantState.branches[2].state != kBranchState_Branch)
{
resetFlowerTimer();
}
plantState.branches[2].state = kBranchState_Branch;
}
else if (faceOffsetFromRoot == 4 || faceOffsetFromRoot == -2)
{
// Still growing - don't spawn a flower yet
if (plantState.branches[1].state != kBranchState_Branch)
{
resetFlowerTimer();
}
plantState.branches[1].state = kBranchState_Branch;
}
}
}
#endif
#if INCLUDE_SPECIAL_TILES
if (tileRole == kTileRole_Dirt)
{
// Dirt tiles convert sun to energy!
if (faceState->gatheredSun < MAX_GATHERED_SUN)
{
faceState->gatheredSun += value;
}
}
#endif
break;
case CommandType_GravityDir:
if (tileRole != kTileRole_Dripper) // dripper now sends out the gravity
{
if (sendGravityTimer.isExpired())
{
// Value is the CCW offset from this face
gravityUpFace = CCW_FROM_FACE(f, value);
// Propagate this on to our neighbors, except the one that just sent it
propagateGravityDir(f);
sendGravityTimer.set(CHECK_GRAVITY_RATE);
}
}
break;
#if INCLUDE_BASE_TILES
case CommandType_TryTransfer:
case CommandType_TryTransferCW:
{
eTransferType transferType = value;
switch (transferType)
{
case kTransferType_Bug:
if (!(tileFlags & kTileFlag_HasBug))
{
// No bug in this tile - transfer accepted
tileFlags |= kTileFlag_HasBug;
bugTargetCorner = (command == CommandType_TryTransfer) ? f : CW_FROM_FACE(f, 1);