Tactical Escape 101 · co-produced with Sam Henningsen
A 4 ft × 5 ft, human-sized Connect 4 board played with yellow and blue frisbee disks. Drop the disks into the correct pattern and four color sensors verify it, releasing a magnetic lock to open the door into the next room.
The board only needs to know one thing per slot: is a yellow disk here? Four light-frequency color sensors answer that, calibrated to survive changing room lighting.


The puzzle uses four TCS3200 color-sensor modules. Each exposes VCC (5 V), GND,
the S2/S3 filter-select pins, and OUT. The sensor sits behind
an array of photodiodes covered by red, green and blue filters. Driving S2/S3
low reads the red-filtered diodes — a red surface returns a low frequency, other colors return higher
frequencies the further they sit from red. Setting both high reads green; S2 low /
S3 high reads blue. With no disk present, ambient light pushes red, green and blue all
high.
The firmware turns those frequency reads into yellow/not-yellow decisions. Here's how the values present across the color channels:
Readings drift roughly ±200 with ambient light — darker rooms shift down, brighter rooms shift up — so each of the four sensors has its own calibrated range (the constraints at the top of the code). A short rolling average smooths noise. Once every sensor reads inside its yellow range, the Arduino drops a relay to release the door's magnetic lock; after about 30 minutes it re-engages the lock to reset the puzzle.
A single sketch split across files — the main loop, color decoding, per-channel frequency reads, the averaging/range helpers, and pin setup. Expand any listing to read it.
#define A_S2 3
#define A_S3 4
#define A_sensorOut A0
#define B_S2 5
#define B_S3 6
#define B_sensorOut A1
#define C_S2 7
#define C_S3 8
#define C_sensorOut A2
#define D_S2 9
#define D_S3 10
#define D_sensorOut A3
#define relayPin 2
//--------misc vars----------------------
int frequency = 0;
bool complete = LOW;
const int arSize = 5;
int sensorTest = 1;
//---------------A constraints--------------
//white
float AwMax = 4000;
float AwMin = 200;
float AwAvg[arSize];
//int aSpread = 50;
//red
//int ArMid = 1900;
float ArMax = 1.83;
float ArMin = 1.23;
float ArAvg[arSize];
//green
//int AgMid = 3370;
float AgMax = 4.43;
float AgMin = 1.95;
float AgAvg[arSize];
//blue
//int AbMid = 5440;
float AbMax = 11;
float AbMin = 5;
float AbAvg[arSize];
//---------------B constraints--------------
//white
float BwMax = 4100;
float BwMin = 200;
float BwAvg[arSize];
//int bSpread = 50;
//red
//int BrMid = 920;
float BrMax = 2.7;
float BrMin = 1.1;
float BrAvg[arSize];
//green
//int BgMid = 1033;
float BgMax = 4.2;
float BgMin = 2.5;
float BgAvg[arSize];
//blue
//int BbMid = 856;
float BbMax = 7.9;
float BbMin = 5.6;
float BbAvg[arSize];
//---------------C constraints--------------
//white
float CwMax = 4100;
float CwMin = 100;
float CwAvg[arSize];
//int cSpread = 50;
//red
//int CrMid = 1425;
float CrMax = 2.21;
float CrMin = 1.23;
float CrAvg[arSize];
//green
//int CgMid = 2130;
float CgMax = 4.33;
float CgMin = 2.74;
float CgAvg[arSize];
//blue
//int CbMid = 2570;
float CbMax = 9;
float CbMin = 5;
float CbAvg[arSize];
//---------------D constraints--------------
//white
float DwMax = 4100;
float DwMin = 100;
float DwAvg[arSize];
//int dSpread = 100;
//red
//int DrMid = 1560;
float DrMax = 2.35;
float DrMin = 1.13;
float DrAvg[arSize];
//green
//int DgMid = 2700;
float DgMax = 4.42;
float DgMin = 2.68;
float DgAvg[arSize];
//blue
//int DbMid = 5070;
float DbMax = 9;
float DbMin = 5;
float DbAvg[arSize];
//------------------Basics--------------------
void setup() {
pinSetUp();
Serial.begin(9600);
digitalWrite(relayPin, HIGH);
zeroArray(ArAvg,arSize);
zeroArray(AgAvg,arSize);
zeroArray(AbAvg,arSize);
zeroArray(BrAvg,arSize);
zeroArray(BgAvg,arSize);
zeroArray(BbAvg,arSize);
zeroArray(CrAvg,arSize);
zeroArray(CgAvg,arSize);
zeroArray(CbAvg,arSize);
zeroArray(DrAvg,arSize);
zeroArray(DgAvg,arSize);
zeroArray(DbAvg,arSize);
}
void loop() {
while(complete == LOW){
testColorSensors();
}
Serial.print("All Yellow Disks are in Place");
//delay 2 minutes then it will reset
delay(120000);
}
void testColorSensors(){
byte aColor = testColor(A_S2,A_S3,A_sensorOut,1);
byte bColor = testColor(B_S2,B_S3,B_sensorOut,2);
byte cColor = testColor(C_S2,C_S3,C_sensorOut,3);
byte dColor = testColor(D_S2,D_S3,D_sensorOut,4);
Serial.print("A: ");
Serial.print(aColor);
Serial.print(" B: ");
Serial.print(bColor);
Serial.print(" C: ");
Serial.print(cColor);
Serial.print(" D: ");
Serial.println(dColor);
if(aColor == 1 && bColor == 1 && cColor == 1 && dColor == 1){
digitalWrite(relayPin, LOW);
complete = HIGH;
Serial.println("Done");
}
}
byte testColor(int S2, int S3, int sensorOut, int sensorNum){
/*
* 0 = Not Yellow
* 1 = Yellow
*/
Serial.println(sensorNum);
Serial.println(" ");
float White = testWhite(S2,S3,sensorOut,sensorNum);
float Red = testRed(S2,S3,sensorOut,sensorNum);
float Green = testGreen(S2,S3,sensorOut,sensorNum);
float Blue = testBlue(S2,S3,sensorOut,sensorNum);
float Re = Red/White;
float Gr = Green/White;
float Bl = Blue/White;
bool diskColor = 0;
float R;
float G;
float B;
float W;
if(sensorNum == 1){
R = average(ArAvg, arSize, Re, sensorNum);
G = average(AgAvg, arSize, Gr, sensorNum);
B = average(AbAvg, arSize, Bl, sensorNum);
W = average(AwAvg, arSize, White, sensorNum);
Serial.print("w:");
Serial.print(W);
Serial.print(" awmin:");
Serial.print(AwMin);
Serial.print(" awmax:");
Serial.print(AwMax);
Serial.print(" inrange:");
Serial.println(inRange(W,AwMin,AwMax));
Serial.println(inRange(R, ArMin, ArMax));
Serial.println(inRange(G, AgMin, AgMax));
Serial.println(inRange(B, AbMin, AbMax));
Serial.print("r:");
Serial.print(R);
Serial.print(" g:");
Serial.print(G);
Serial.print(" b:");
Serial.print(B);
Serial.print(" w:");
Serial.println(W);
if(inRange(W,AwMin,AwMax) && inRange(R, ArMin, ArMax) && inRange(G, AgMin, AgMax) && inRange(B, AbMin, AbMax)){
diskColor = 1;
}
} else if(sensorNum == 2){
R = average(BrAvg, arSize, Re, sensorNum);
G = average(BgAvg, arSize, Gr, sensorNum);
B = average(BbAvg, arSize, Bl, sensorNum);
W = average(BwAvg, arSize, White, sensorNum);
//prints out the numarical values for each of the variables that the sensor reads then you can changee the calibration.
Serial.print("r:");
Serial.print(R);
Serial.print(" g:");
Serial.print(G);
Serial.print(" b:");
Serial.print(B);
Serial.print(" w:");
Serial.println(W); Serial.print(" inrange:");
Serial.println(inRange(W,AwMin,AwMax));
if(inRange(W,BwMin,BwMax) && inRange(R, BrMin, BrMax) && inRange(G, BgMin, BgMax) && inRange(B, BbMin, BbMax)){
diskColor = 1;
}
} else if(sensorNum == 3){
R = average(CrAvg, arSize, Re, sensorNum);
G = average(CgAvg, arSize, Gr, sensorNum);
B = average(CbAvg, arSize, Bl, sensorNum);
W = average(CwAvg, arSize, White, sensorNum);
Serial.print("r:");
Serial.print(R);
Serial.print(" g:");
Serial.print(G);
Serial.print(" b:");
Serial.print(B);
Serial.print(" w:");
Serial.println(W);
Serial.print(" inrange:");
Serial.println(inRange(W,AwMin,AwMax));
if(inRange(W,CwMin,CwMax) && inRange(R, CrMin, CrMax) && inRange(G, CgMin, CgMax) && inRange(B, CbMin, CbMax)){
diskColor = 1;
}
} else if(sensorNum == 4){
//If the values are not as desired uncomment the print code to determine the error
//Serial.print("r:");
R = average(DrAvg, arSize, Re, sensorNum);
//Serial.print(R);
//Serial.print(" g:");
G = average(DgAvg, arSize, Gr, sensorNum);
//Serial.print(G);
//Serial.print(" b:");
B = average(DbAvg, arSize, Bl, sensorNum);
//Serial.println(B);
W = average(DwAvg, arSize, White, sensorNum);
Serial.print("r:");
Serial.print(R);
Serial.print(" g:");
Serial.print(G);
Serial.print(" b:");
Serial.print(B);
Serial.print(" w:");
Serial.println(W);
Serial.print(" inrange:");
Serial.println(inRange(W,AwMin,AwMax));
if(inRange(W,DwMin,DwMax) && inRange(R, DrMin, DrMax) && inRange(G, DgMin, DgMax) && inRange(B, DbMin, DbMax)){
diskColor = 1;
}
}
return diskColor;
}
float testRed(int S2, int S3, int sensorOut, int testNum){
digitalWrite(S2,LOW);
digitalWrite(S3,LOW);
//Read freq
frequency = pulseIn(sensorOut, LOW);
//Print Value
delay(10);
float freq = frequency;
return freq;
}
float testGreen(int S2, int S3, int sensorOut, int testNum){
digitalWrite(S2,HIGH);
digitalWrite(S3,HIGH);
//Read freq
frequency = pulseIn(sensorOut, LOW);
//Print Value
delay(10);
float freq = frequency;
return freq;
}
float testBlue(int S2, int S3, int sensorOut, int testNum){
digitalWrite(S2,LOW);
digitalWrite(S3,HIGH);
//Read freq
frequency = pulseIn(sensorOut, LOW);
//Print Value
delay(10);
float freq = frequency;
return freq;
}
float testWhite(int S2, int S3, int sensorOut, int testNum){
digitalWrite(S2,HIGH);
digitalWrite(S3,LOW);
//Read freq
frequency = pulseIn(sensorOut, LOW);
//Print Value
delay(10);
float freq = frequency;
return freq;
}
bool inRange(float val, float low, float high){
return ((low <= val) && (val <= high));
}
float average( float color[], int sizeArray, float newValue, int testNum){
float average = 0;
float sum = 0;
for ( int k = (sizeArray - 1) ; k > 0 ; --k ){
color[k] = color[k-1];
}
color[0] = newValue;
for ( int k = 0 ; k < sizeArray ; ++k ){
sum = sum + color[k];
}
average = sum / sizeArray;
return average;
}
void zeroArray(float zero[], int num){
for ( int k = 0 ; k < num ; ++k ){
zero[k] = 0;
}
}
void printArray(int printed[], int num){
for(int k = 0; k < num ; ++k){
Serial.print(printed[k]);
Serial.print(" ");
if(k == (num - 1)){
Serial.println(" ");
}
}
}
void pinSetUp(){
pinMode(A_S2, OUTPUT);
pinMode(A_S3, OUTPUT);
pinMode(B_S2, OUTPUT);
pinMode(B_S3, OUTPUT);
pinMode(C_S2, OUTPUT);
pinMode(C_S3, OUTPUT);
pinMode(D_S2, OUTPUT);
pinMode(D_S3, OUTPUT);
pinMode(A_sensorOut, INPUT);
pinMode(B_sensorOut, INPUT);
pinMode(C_sensorOut, INPUT);
pinMode(D_sensorOut, INPUT);
pinMode(relayPin, OUTPUT);
}

