For The Cloud Game, the cluster of Blinks needs to determine if all Blinks are illuminated or in the cloud state. Conversely, a single Blink that is off immediately returns the fact that this is not a win condition. Fracture could use a similar technique but be specific to to color.
Below is an algorithm I wrote that on each compute cycle (or more accurately, I’ve slowed down the compute cycles to 50ms, feel free to play with this as it is nice to see how the Blinks think), continues to find neighbors that have not yet been searched, and if the neighbor confirms a no-win, it will halt and return that news up the chain to then be spread to the entire population, or if it covers all connected Blinks, and returns, it will spread the news of the win to the entire population.
single click = turn a Blink on or off
double click = start the search for a win
The problem with this current method is that it is not safe from another search being started before a single search has ended. What do you think is the best way to handle this gracefully. The current search algorithm uses 7 distinct messages (5 for the search and 2 for win or lose condition). It would be great to not use more than 8 distinct messages because then it only uses 3 bits in parallel communication.
Here is a video of the algorithm in action and with two examples of failures from two searches being started around the same time.
Note: Win is represented by MAGENTA and No-win found is represented by ORANGE
The following also runs nicely in the simulator to quickly try it out.
/*
Search for any "OFF" Blinks, if none found, signal win condition
How to use:
Single Click = turn a Blink on or off (toggle)
Double Click = start search from this Blink
*/
enum winSearchValues {CHILL, SEARCHING, WAITING, FOUND_OFF, NO_FOUND_OFF, VICTORY, DEFEAT};
bool isSearchingForWin;
bool isWaitingOnNeighbor;
bool foundWin = false;
byte neighborSearchingForWin; //changing index
byte indexOfNeighborToReportTo = 6; // use this for where we started our search
bool isOn = true;
bool flashOn = false;
byte faceValues[6] = {CHILL, CHILL, CHILL, CHILL, CHILL, CHILL};
Timer slowTimer;
#define FRAME_DELAY 50
Timer flashTimer;
#define FLASH_DELAY 100
void setup() {
}
void loop() {
if (slowTimer.isExpired()) {
slowTimer.set(FRAME_DELAY);
if (buttonSingleClicked()) {
// I am one that was clicked
isOn = !isOn;
}
if (buttonDoubleClicked()) {
//Start Search for any off blinks
//If I am an off blink, no need to search
if (!isOn) {
isSearchingForWin = false;
}
else {
//Else Ask each neighbor to search for off blinks
isSearchingForWin = true;
setAllTo(SEARCHING);
neighborSearchingForWin = 0;
indexOfNeighborToReportTo = 6; //special for master blink
}
}
if (isSearchingForWin) {
//check if done
if (isDoneSearching()) {
isSearchingForWin = false;
if (indexOfNeighborToReportTo == 6) { // if I am the origin
foundWin = true;
setAllTo(VICTORY);
}
}
if (isValueReceivedOnFaceExpired(neighborSearchingForWin)) { // no neighbor!
faceValues[neighborSearchingForWin] = NO_FOUND_OFF;
neighborSearchingForWin = (neighborSearchingForWin + 1) % 6;
}
else { //found neighbor!
if (neighborSearchingForWin == indexOfNeighborToReportTo) {
faceValues[neighborSearchingForWin] = NO_FOUND_OFF;
faceValues[indexOfNeighborToReportTo] = NO_FOUND_OFF;
isSearchingForWin = false;
}
else {
byte neighborValue = getLastValueReceivedOnFace(neighborSearchingForWin);
if (!isWaitingOnNeighbor) { // if neighbor has not yet been asked to search
if (neighborValue == CHILL) {
faceValues[neighborSearchingForWin] = WAITING;
isWaitingOnNeighbor = true;
}
else if (neighborValue == SEARCHING ) {
//ignore and move on to the next one
faceValues[neighborSearchingForWin] = NO_FOUND_OFF;
neighborSearchingForWin = (neighborSearchingForWin + 1) % 6; // continue looking to the next one
}
else if (neighborValue == FOUND_OFF) {
isSearchingForWin = false;
neighborSearchingForWin = 0;
faceValues[indexOfNeighborToReportTo] = FOUND_OFF;
}
else if (neighborValue == NO_FOUND_OFF) {
//Ask neighbor to search for off blinks
faceValues[neighborSearchingForWin] = NO_FOUND_OFF;
neighborSearchingForWin = (neighborSearchingForWin + 1) % 6; // continue looking to the next one
}
} // end not waiting on neighbor
else { // am waiting on neighor
// let the one I am reporting to know I am waiting on a neighbor
if (indexOfNeighborToReportTo != 6) { // if I have someone to report to
faceValues[indexOfNeighborToReportTo] = WAITING;
}
if (neighborValue == FOUND_OFF) { // just heard back from neighbor that they FOUND AN OFF
faceValues[neighborSearchingForWin] = FOUND_OFF;
isSearchingForWin = false;
isWaitingOnNeighbor = false;
neighborSearchingForWin = 0;
faceValues[indexOfNeighborToReportTo] = FOUND_OFF;
// if I am the starting point..
// signal no victory, stop the search, cuz we found an off
if (indexOfNeighborToReportTo == 6) {
setAllTo(DEFEAT);
}
}
else if (neighborValue == NO_FOUND_OFF) { // just heard back from neighbor that they haven't found and OFF
// Acknowledge and move on to the next one
faceValues[neighborSearchingForWin] = NO_FOUND_OFF;
isWaitingOnNeighbor = false;
// if I am the starting point..
// signal victory, cuz we searched all and didn't find an OFF
if (indexOfNeighborToReportTo != 6) { // I'm not the starting point
neighborSearchingForWin = (neighborSearchingForWin + 1) % 6;
}
else { // I'm the starting point
if (neighborSearchingForWin == 5) { // last side I will search
// signal victory, cuz we searched all and didn't find an OFF
isSearchingForWin = false;
foundWin = true;
setAllTo(VICTORY);
}
else {
// advance to the next side, if we have search all sides, stop searching
neighborSearchingForWin = (neighborSearchingForWin + 1) % 6;
}
}
}
} //end waiting on neighbor
}
}
}
else // not yet searching, listening for the opportunity to participate
{
// look to present neighbors
FOREACH_FACE(f) {
if (!isValueReceivedOnFaceExpired(f)) {
byte neighborValue = getLastValueReceivedOnFace(f);
if (neighborValue == VICTORY) {
setAllTo(VICTORY); // spread the victory
}
else if (neighborValue == DEFEAT) {
setAllTo(DEFEAT); // spread the victory
}
if (neighborValue == WAITING && f != indexOfNeighborToReportTo) { //if neighbor face value is searching
// set all to searching
setAllTo(SEARCHING);
if (!isOn) { //if off
faceValues[f] = FOUND_OFF;
setAllTo(FOUND_OFF);
}
else { //if on
// I've been asked to search
indexOfNeighborToReportTo = f;
faceValues[f] = WAITING;
neighborSearchingForWin = (f + 1) % 6; //move clockwise on faces
isSearchingForWin = true;
}
} // end if neighbor is searching
}// end if neighbor is present
} // end for loop
} // end not yet searching
} // end slow timer
FOREACH_FACE(f) {
setValueSentOnFace(faceValues[f], f);
}
// Listen for message from neighbor to search for any off blinks
// if I am off,
// return message to neighbor asking me to search; message should say "i am an off blink"
// else if any of my neighbors are not yet searched blinks
// then ask them to search for any off blinks
if (isOn) {
setColor(WHITE);
}
else {
setColor(dim(BLUE, 64));
}
// DEBUG VISUALIZATION
FOREACH_FACE(f) {
switch (faceValues[f]) {
case CHILL:
{
if (isOn) {
setColorOnFace(WHITE, f);
}
else {
setColorOnFace(dim(BLUE, 64), f);
}
}
break;
case SEARCHING:
setColorOnFace(BLUE, f);
break;
case WAITING:
setColorOnFace(YELLOW, f);
break;
case FOUND_OFF:
setColorOnFace(RED, f);
break;
case NO_FOUND_OFF:
setColorOnFace(GREEN, f);
break;
case DEFEAT:
setColorOnFace(ORANGE, f);
break;
case VICTORY:
setColorOnFace(MAGENTA, f);
break;
}
} // end face loop
if (flashTimer.isExpired()) {
flashOn = !flashOn;
flashTimer.set(FLASH_DELAY);
}
if (flashOn && isSearchingForWin) {
setColorOnFace(OFF, neighborSearchingForWin);
}
//END DEBUG VISUALIZATION
}
void setAllTo(byte state) {
FOREACH_FACE(f) {
faceValues[f] = state;
}
}
bool isDoneSearching() {
FOREACH_FACE(f) {
if (faceValues[f] == CHILL || faceValues[f] == WAITING || faceValues[f] == SEARCHING) {
return false;
}
}
return true;
}