Escape-Room Prop · 2017–2019

Connect 4 Puzzle

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.

ArduinoC4× TCS3200 color sensors Rolling-average filteringRelay maglock
How it works

Color Sensing & Design

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.

TCS3200 color sensor module
One of the four TCS3200 color-sensor modules.

The firmware turns those frequency reads into yellow/not-yellow decisions. Here's how the values present across the color channels:

Color analysis table
Channel readings used to classify a disk.

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.

Firmware

The Code

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.

main.ino
  #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);
}
colordecoding.ino
  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;
}
colortestingfunction.ino
  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;
}
miscfunction.ino
  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(" ");
    }
  }
}
setup.ino
  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);
}