Nick BartlettAnthony Cortina
Published

IoT Tic-Tac-Toe

Play tic-tac-toe using lights and two hand-crafted boards over the internet.

IntermediateWork in progress6 hours855
IoT Tic-Tac-Toe

Things used in this project

Hardware components

Photon
Particle Photon
×2
LED (generic)
LED (generic)
×26
3 Pin ON/ON SPDT Switch
×18
Resistor 1k ohm
Resistor 1k ohm
×12
Resistor 2.21k ohm
Resistor 2.21k ohm
×6
Resistor 475 ohm
Resistor 475 ohm
×1
1/4" MDF Sheet (24" x 48")
×1

Software apps and online services

ThingSpeak API
ThingSpeak API
Particle IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
7/32" Drill Bit
Drill
Wiring Pliers
2 Part Epoxy

Story

Read more

Schematics

Wiring Schematic

Code

IoT Tic-Tac-Toe Code

C/C++
 // Define the Output Vars
    int ErrLED = D2;
    int WinLED = D6;
    int LoseLED = D1;
    int MyTurnLED = D7;
    int OpponentA1 = D3;
    int OpponentA2 = D4;
    int OpponentA3 = D5;
    int OpponentB1 = D0;
    int OpponentB2 = A3;
    int OpponentB3 = TX;
    int OpponentC1 = RX;
    int OpponentC2 = A5;
    int OpponentC3 = A4;
    
    // Define the Input Vars
    int PositionDetectA = A0; // Position Detection Pin for Row A
    int PositionDetectB = A1; // Position Detection Pin for Row B
    int PositionDetectC = A2; // Position Detection Pin for Row C
    int Avalue = 0;
    int Bvalue = 0;
    int Cvalue = 0;
    
    // Local constants
    int value1min = 2500;
    int value1max = 3000;
    int value2min = 1200;
    int value2max = 1400;
    int value3min = 1850;
    int value3max = 2400;
    int value12min = 1060;
    int value12max = 1190;
    int value23min = 945;
    int value23max = 1050;
    int value13min = 1450;
    int value13max = 1800;
    int value123min = 700;
    int value123max = 935;
    
    // Define the local game Vars
    int MyPositionA1; // Sets a high bit when this spot is activated by the user
    int MyPositionA2;
    int MyPositionA3;
    int MyPositionB1;
    int MyPositionB2;
    int MyPositionB3;
    int MyPositionC1;
    int MyPositionC2;
    int MyPositionC3;
    int TurnCount = 1; // Iterative counter for the number of turns. Used in error checking.
    int GameStop; // Sets a high bit when the game needs to be halted.
    int value;

    // Webhook stuff
    #define publish_delay 6000
    unsigned int lastPublish = 0;

void setup() 
{
   // Set Output pins
    pinMode(ErrLED, OUTPUT);
    pinMode(WinLED, OUTPUT);
    pinMode(LoseLED, OUTPUT);
    pinMode(MyTurnLED, OUTPUT);
    pinMode(OpponentA1, OUTPUT);
    pinMode(OpponentA2, OUTPUT);
    pinMode(OpponentA3, OUTPUT);
    pinMode(OpponentB1, OUTPUT);
    pinMode(OpponentB2, OUTPUT);
    pinMode(OpponentB3, OUTPUT);
    pinMode(OpponentC1, OUTPUT);
    pinMode(OpponentC2, OUTPUT);
    pinMode(OpponentC3, OUTPUT);
   
   // Set Input Pins
    pinMode(PositionDetectA, INPUT);
    pinMode(PositionDetectB, INPUT);
    pinMode(PositionDetectC, INPUT);
    

   //Subscribe to global variables
    Particle.subscribe("nbv70rTurnChangeB", TurnChangeHandler);
    Particle.subscribe("nbv70rwinB", winHandler);
    Particle.subscribe("nbv70rResetB", resetHandler);
    

   // Blink 4 LEDs to check to see if we made it this far correctly
    analogWrite(ErrLED, 175);
    digitalWrite(WinLED, HIGH);
    analogWrite(LoseLED, 175);
    digitalWrite(MyTurnLED, HIGH);
    delay(3000);
    analogWrite(ErrLED, 0);
    digitalWrite(WinLED, LOW);
    analogWrite(LoseLED, 0);
    digitalWrite(MyTurnLED, LOW);
    delay(750);
    
    resetHandler(NULL, NULL);
}

void resetHandler (const char *event, const char *data)
{
   // Test the rows to see if everything is reset. Do nothing, but set the LED until the board is reset
   while (Avalue < value1max)
   {
       analogWrite(analogRead(A0), 175);
       delay(500);
   }
   if (analogRead(A0) > value1max)
   {
     analogWrite(ErrLED, 0);
   }
   
   while (analogRead(A1) < value1max)
   {
       analogWrite(ErrLED, 175);
       delay(500);
   }
   
   if (analogRead(A1) > value1max)
   {
      analogWrite(ErrLED, 0);
   }
   
   while (analogRead(A2) < value1max)
   {
       analogWrite(ErrLED, 175);
       delay(500);
   }
   if (analogRead(A2) > value1max)
   {
     analogWrite(ErrLED, 0);
   }
   delay(100);
   
   if (analogRead(ErrLED) < 50) // Once the board has no errors, reset the variables and the win/lose LEDs 
   {
        MyPositionA1 = 0; // Set all positions to 0
        MyPositionA2 = 0;
        MyPositionA3 = 0;
        MyPositionB1 = 0;
        MyPositionB2 = 0;
        MyPositionB3 = 0;
        MyPositionC1 = 0;
        MyPositionC2 = 0;
        MyPositionC3 = 0;
   }
    
    TurnCount = 1;
    // Set the A player's (my) LED HIGH because I get to start the game
    digitalWrite(MyTurnLED, HIGH);
    delay(100);
    digitalWrite(OpponentA1, LOW);
    delay(100);
    digitalWrite(OpponentA2, LOW);
    delay(100);
    digitalWrite(OpponentA3, LOW);
    delay(100);
    digitalWrite(OpponentB1, LOW);
    delay(100);
    digitalWrite(OpponentB2, LOW);
    delay(100);
    digitalWrite(OpponentB3, LOW);
    delay(100);
    digitalWrite(OpponentC1, LOW);
    delay(100);
    digitalWrite(OpponentC2, LOW);
    delay(100);
    digitalWrite(OpponentC3, LOW);
    delay(500);
    loop();
}

void loop()
{   // Didn't add code for errors if the player activates the same place as the opponent
    // Didn't add code to run a winCheck func at the end of the turn to check versus other 
      
    if (digitalRead(MyTurnLED) == HIGH)
    {	
        
        // Check the TurnCount vs the total number of active positions and don't continue if they aren't equal
        while (MyPositionA1 + MyPositionA2 + MyPositionA3 + MyPositionB1 + MyPositionB2 + MyPositionB3 + MyPositionC1 + MyPositionC2 + MyPositionC3 != TurnCount)
        {
            //Checking and calculating where each switch is activated in Row A
            while(analogRead(A0) < value123min);
            {
                analogWrite(ErrLED, 175);
            }
        
            analogWrite(ErrLED, 0);
        
            PositionDetectA = analogRead(A0);
            delay(1000);
            if(PositionDetectA>=value1min && PositionDetectA<=value1max && MyPositionA1 != 1)
            {
                MyPositionA1 = 1;
                delay(1100);
                Particle.publish("nbv70rTurnChangeA", "1");
            }
            if(PositionDetectA>=value2min && PositionDetectA<=value2max  && MyPositionA2 != 1)
            {
                MyPositionA2 = 1;
                delay(1100);
                Particle.publish("nbv70rTurnChangeA", "2");
            }
            if(PositionDetectA>=value3min && PositionDetectA<=value3max  && MyPositionA3 != 1)
            {
                MyPositionA3 = 1;
                delay(1100);
                Particle.publish("nbv70rTurnChangeA", "3");
            }
            if(PositionDetectA>=value12min && PositionDetectA<=value12max)
            {
                if(MyPositionA1 != 1 || MyPositionA2 != 1)
		        {
			        MyPositionA1 = 1;
                	MyPositionA2 = 1;
                	delay(1100);
                	Particle.publish("nbv70rTurnChangeA", "12");
		        }
            }
            if(PositionDetectA>=value23min && PositionDetectA<=value23max)
            {
                if(MyPositionA2 != 1 || MyPositionA3 != 1)
		        {
			        MyPositionA2 = 1;
                	MyPositionA3 = 1;
                	delay(1100);
                	Particle.publish("nbv70rTurnChangeA", "23");
		        }
            }
            if(PositionDetectA>=value13min && PositionDetectA<=value13max)
            {
                if(MyPositionA1 != 1 && MyPositionA3 != 1)
		        {
			        MyPositionA1 = 1;
                	MyPositionA3 = 1;
                	delay(1100);
                	Particle.publish("nbv70rTurnChangeA", "13");
		        }
            }
            if(PositionDetectA>=value123min && PositionDetectA<=value123max)
            {
		        if(MyPositionA1 != 1 || MyPositionA2 != 1 || MyPositionA3 != 1)
                {
			        MyPositionA1 = 1;
                	MyPositionA2 = 1;
                	MyPositionA3 = 1;
                	delay(1100);
                	Particle.publish("nbv70rTurnChangeA", "123");
                	myWin();
		        }
            }
      
            //Checking and calculating where each switch is activated in Row B
             while(analogRead(A1) < value123min);
            {
                delay(100);
                analogWrite(ErrLED, 175);
		        delay(100);
            }
            
            analogWrite(ErrLED, 0);
            
            PositionDetectB = analogRead(A1);
            delay(1000);
            if(PositionDetectB>=value1min && PositionDetectB<=value1max && MyPositionB1 != 1)
            {
                MyPositionB1 = 1;
                Particle.publish("nbv70rTurnChangeA", "4");
            }
            if(PositionDetectB>=value2min && PositionDetectB<=value2max && MyPositionB2 != 1)
            {
                MyPositionB2 = 1;
                Particle.publish("nbv70rTurnChangeA", "5");
            }
            if(PositionDetectB>=value3min && PositionDetectB<=value3max && MyPositionB3 != 1)
            {
                MyPositionB3 = 1;
                Particle.publish("nbv70rTurnChangeA", "6");
            }
            if(PositionDetectB>=value12min && PositionDetectB<=value12max)
            {
		        if(MyPositionB1 != 1 || MyPositionB2 != 1)
		        {
                	MyPositionB1 = 1;
                	MyPositionB2 = 1;
                	Particle.publish("nbv70rTurnChangeA", "45");
		        }
            }
            if(PositionDetectB>=value23min && PositionDetectB<=value23max)
            {
		        if(MyPositionB2 != 1 || MyPositionB3 != 1)
		        {
                	MyPositionB2 = 1;
                	MyPositionB3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "56");
		        }
            }
            if(PositionDetectB>=value13min && PositionDetectB<=value13max)
            {
		        if(MyPositionB1 != 1 || MyPositionB3 != 1)
		        {
                	MyPositionB1 = 1;
                	MyPositionB3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "46");
		        }
            }
            if(PositionDetectB>=value123min && PositionDetectB<=value123max) // AutoWin
            {
		        if(MyPositionB1 != 1 || MyPositionB2 != 1 || MyPositionB3 != 1)
		        {
                	MyPositionB1 = 1;
                	MyPositionB2 = 1;
                	MyPositionB3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "456");
                	myWin();
		        }
            }
      
            //Checking and calculating where each switch is activated in Row C
            while(analogRead(A2) < value123min);
            {
                analogWrite(ErrLED, 175);
            }
        
            analogWrite(ErrLED, 0);
            
            PositionDetectC = analogRead(A2);
            delay(1000);
            if(PositionDetectC>=value1min && PositionDetectC<=value1max && MyPositionC1 != 1)
            {
                MyPositionC1 = 1;
                Particle.publish("nbv70rTurnChangeA","7");
            }
            if(PositionDetectC>=value2min && PositionDetectC<=value2max && MyPositionC2 != 1)
            {
                MyPositionC2 = 1;
                Particle.publish("nbv70rTurnChangeA", "8");
            }
            if(PositionDetectC>=value3min && PositionDetectC<=value3max && MyPositionC3 != 1)
            {
                MyPositionC3 = 1;
                Particle.publish("nbv70rTurnChangeA", "9");
            }
            if(PositionDetectC>=value12min && PositionDetectC<=value12max)
            {
		        if(MyPositionC1 != 1 || MyPositionC2 != 1)
		        {
                	MyPositionC1 = 1;
                	MyPositionC2 = 1;
                	Particle.publish("nbv70rTurnChangeA", "78");
		        }
            }
            if(PositionDetectC>=value23min && PositionDetectC<=value23max)
            {
		        if(MyPositionC2 != 1 || MyPositionC3 != 1)
		        {
                	MyPositionC2 = 1;
                	MyPositionC3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "89");
		        }
            }
            if(PositionDetectC>=value13min && PositionDetectC <=value13max)
            {
		        if(MyPositionC1 != 1 || MyPositionC3 != 1)
		        {
                	MyPositionC1 = 1;
                	MyPositionC3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "79");
            	}
	        }
            if(PositionDetectC>=value123min && PositionDetectC<=value123max) // AutoWin
            {
		        if(MyPositionC1 != 1 || MyPositionC2 != 1 || MyPositionC3 != 1)
		        {
                	MyPositionC1 = 1;
                	MyPositionC2 = 1;
                	MyPositionC3 = 1;
                	Particle.publish("nbv70rTurnChangeA", "789");
                	myWin();
		        }
            }
        }
      
        // Using the Turn Counter, determine if a single move has been made and switch turns
        if(MyPositionA1 + MyPositionA2 + MyPositionA3 + MyPositionB1 + MyPositionB2 + MyPositionB3 + MyPositionC1 + MyPositionC2 + MyPositionC3 == TurnCount)
        {
            //Webhook stuff
            unsigned long now = millis();
            if ((now - lastPublish) < publish_delay) 
            {
                return;
            }
            value = TurnCount;
            delay(1100);
            Particle.publish("thingSpeakWrite_A0", "{ \"1\": \"" + String(value) + "\", \"k\": \"CBAPTBVL2Y4KQI3X\" }", 60, PRIVATE);
            lastPublish = now;
            
            digitalWrite(MyTurnLED, LOW);
            TurnCount = TurnCount + 1;
        }
    }
}

void TurnChangeHandler (const char *event, const char *data)
{
    if(strcmp(data, "1") == 0)
    {
       digitalWrite(OpponentA1, HIGH);
    }
    if(strcmp(data, "2") == 0)
    {
       digitalWrite(OpponentA2, HIGH);
    }
    if(strcmp(data, "3") == 0)
    {
       digitalWrite(OpponentA3, HIGH);
    }
    if(strcmp(data, "12") == 0)
    {
       digitalWrite(OpponentA1, HIGH);
       digitalWrite(OpponentA2, HIGH);
    }
    if(strcmp(data, "23") == 0)
    {
       digitalWrite(OpponentA2, HIGH);
       digitalWrite(OpponentA3, HIGH);
    }
    if(strcmp(data, "13") == 0)
    {
        digitalWrite(OpponentA1, HIGH);
        digitalWrite(OpponentA3, HIGH);
    }
    if(strcmp(data, "123") == 0)
    {
       digitalWrite(OpponentA1, HIGH);
       digitalWrite(OpponentA2, HIGH);
       digitalWrite(OpponentA3, HIGH);
    }
    
    if(strcmp(data, "4") == 0)
    {
	    digitalWrite(OpponentB1, HIGH);
	}
    if(strcmp(data, "5") == 0)
    {
       digitalWrite(OpponentB2, HIGH);
    }
    if(strcmp(data, "6") == 0)
    {
       digitalWrite(OpponentB3, HIGH);
    }
    if(strcmp(data, "45") == 0)
    {
	    digitalWrite(OpponentB1, HIGH);
	    digitalWrite(OpponentB2, HIGH);
	}
    if(strcmp(data, "56") == 0)
    {
       digitalWrite(OpponentB2, HIGH);
       digitalWrite(OpponentB3, HIGH);
    }
    if(strcmp(data, "46") == 0)
    {
        digitalWrite(OpponentB1, HIGH);
        digitalWrite(OpponentB3, HIGH);
    }
    if(strcmp(data, "456") == 0)
    {
        digitalWrite(OpponentB1, HIGH);
        digitalWrite(OpponentB2, HIGH);
	    digitalWrite(OpponentB3, HIGH);
	}
    
    if(strcmp(data, "7") == 0)
    {
       digitalWrite(OpponentC1, HIGH);
    }
    if(strcmp(data, "8") == 0)
    {
       digitalWrite(OpponentC2, HIGH);
    }
    if(strcmp(data, "9") == 0)
    {
       digitalWrite(OpponentC3, HIGH);
    }
    if(strcmp(data, "78") == 0)
    {
       digitalWrite(OpponentC1, HIGH);
       digitalWrite(OpponentC2, HIGH);
    }
    if(strcmp(data, "89") == 0)
    {
       digitalWrite(OpponentC2, HIGH);
       digitalWrite(OpponentC3, HIGH);
    }
    if(strcmp(data, "79") == 0)
    {
       digitalWrite(OpponentC3, HIGH);
       digitalWrite(OpponentC1, HIGH);
    }
    if(strcmp(data, "789") == 0)
    {
       digitalWrite(OpponentC1, HIGH);
       digitalWrite(OpponentC2, HIGH);
       digitalWrite(OpponentC3, HIGH);
    }
    
    digitalWrite(MyTurnLED, HIGH);
    delay(100);
    loop();
}

void myWin()
{	// Didn't write code to analyze and reset if errors are detected in a potential win scenario
	// This could be the winCheck function as well
	
     while(digitalRead(ErrLED) == HIGH)
        {
            delay(500);
            //reset error if no problem persists
        }
     if(digitalRead(ErrLED) == LOW)
     {
        digitalWrite(WinLED, HIGH);
        delay(750);
        digitalWrite(MyTurnLED, LOW);
        delay(250);
        Particle.publish("nbv70rwinA");
        delay(9000);
        digitalWrite(WinLED, LOW);
        delay(2000);
        digitalWrite(WinLED, HIGH);
        delay(2000);
        digitalWrite(WinLED, LOW);
        delay(2000);
        digitalWrite(WinLED, HIGH);
        delay(2000);
        digitalWrite(WinLED, LOW);
        delay(2000);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
        delay(200);
        digitalWrite(WinLED, HIGH);
        delay(200);
        digitalWrite(WinLED, LOW);
          
        resetHandler(NULL,NULL);
    } 
}

void winHandler (const char *event, const char *data) // If the opponent wins
{
    while(digitalRead(ErrLED) == HIGH)
        {
            delay(500);
            //reset error if no problem persists
        }
     if(digitalRead(ErrLED) == LOW)
     {
        digitalWrite(LoseLED, HIGH);
        delay(500);
        digitalWrite(MyTurnLED, LOW);
        delay(9500);
        digitalWrite(LoseLED, LOW);
        delay(2000);
        digitalWrite(LoseLED, HIGH);
        delay(2000);
        digitalWrite(LoseLED, LOW);
        delay(2000);
        digitalWrite(LoseLED, HIGH);
        delay(2000);
        digitalWrite(LoseLED, LOW);
        delay(2000);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
        delay(200);
        digitalWrite(LoseLED, HIGH);
        delay(200);
        digitalWrite(LoseLED, LOW);
          
        resetHandler(NULL,NULL);
     }
}

Credits

Nick Bartlett

Nick Bartlett

1 project • 0 followers
Mechanical Engineering student at UNCC.
Anthony Cortina

Anthony Cortina

1 project • 0 followers

Comments