Sam Kristoff
Published © MIT

Tic-Tac-Toe with Teensy

Play tic-tac-toe against the Teensy with a physical board and playing pieces.

IntermediateShowcase (no instructions)20 hours2,590
Tic-Tac-Toe with Teensy

Things used in this project

Hardware components

NeoPixel strip
NeoPixel strip
×1
Teensy 3.1
Teensy 3.1
×1
Tic-Tac-Toe Game
×1
Uxcell U18 Hall Effect Sensor
×1
Magnets
×1
5v 60A Power Supply
×1
Copper Conductive Tape
×1
OctoWS2811 Adapter Board
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hand Drill
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Code

tic-tac-toe-1.0.0

C/C++
/****************************************
 * Written by:   Sam Kristoff
 * Version:      1.0 
****************************************/

#include <OctoWS2811.h>

//LED Variables
const int ledsPerStrip = 108;
DMAMEM int displayMemory[ledsPerStrip*6];
int drawingMemory[ledsPerStrip*6];
const int config = WS2811_GRB | WS2811_800kHz;

OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);

#define NUM_COLORS 7


#define BLACK  0x000000
#define RED    0xFF0000
#define GREEN  0x00FF00
#define BLUE   0x0000FF
#define YELLOW 0xFFFF00
#define PINK   0xFF1088
#define ORANGE 0xE05800
#define WHITE  0xFFFFFF

/*
// Less intense...
#define BLACK  0x000000
#define RED    0x160000
#define GREEN  0x001600
#define BLUE   0x000016
#define YELLOW 0x101400
#define PINK   0x120009
#define ORANGE 0x100400
#define WHITE  0x101010
*/

#define PLAYERWIN 0
#define COMPUTERWIN 1
#define TIE 2


int colors[NUM_COLORS] = {RED, GREEN, BLUE, YELLOW, PINK, ORANGE, WHITE};
int colorIndex = 0;
int playerColor = RED;
int computerColor = GREEN;
int drawColor = BLUE;

//Define game states
enum gameState_t {
  PREGAME,
  PLAYERTURN,
  COMPUTERTURN,
  GAMEOVER,
};

gameState_t gameState = PREGAME;


void setup() {
  Serial.begin(9600);
  Serial.println("Tic-Tac-Toe");
  leds.begin();
  setStripColor(BLACK);
  leds.show();  
  //delay(1000);

  //Set hall effect sensor pins as inputs.
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, INPUT);
}


unsigned int boardState = 0;

unsigned int playerCell = 0;
unsigned int computerCell = 0;

unsigned int playerCells = 0;
unsigned int computerCells = 0;

unsigned int previousBoardState = 0;

int winner = -1;
void loop() {
   
  //Game State Machine
  switch(gameState) {
    case PREGAME:   
      //Initialize values
      previousBoardState = 0;
      playerCell = 0;
      computerCell = 0;

      playerCells = 0;
      computerCells = 0;
      
      winner = -1;
      
      boardState = readBoardValue();
      cycleCellColors(50);     
      Serial.print("boardState = "); Serial.println(boardState, BIN);
      if(boardState != 0){        
        //Clear the board, then highlight the cell the player chose
        setStripColor(BLACK);  
        playerCell = findPlayerMoveCell(boardState, previousBoardState);
        playerCells |= 0x0001 << playerCell;
        setCellColor(playerCell, playerColor);                
        previousBoardState = boardState;
        gameState = COMPUTERTURN;       
        Serial.println("Computer Turn");
      }
      break;
    case COMPUTERTURN:   
      delay(100);
      computerCell = computeMove(boardState);
      computerCells |= 0x0001 << computerCell;   
      setCellColor(computerCell, computerColor);      
      boardState = boardState | 0x0001 << computerCell;   
      previousBoardState = boardState;
      //Check for winner
      winner = checkForGameOver(playerCells, computerCells);
      if(winner >= 0){
          gameState = GAMEOVER;       
          Serial.println("Game Over");
      } 
      else 
      {
        gameState = PLAYERTURN;       
        Serial.println("Player Turn");
      }      
      break;
    case PLAYERTURN:
      boardState = boardState | readBoardValue();
      if(boardState != previousBoardState)
      {
        playerCell = findPlayerMoveCell(boardState, previousBoardState);
        playerCells |= 0x0001 << playerCell;
        setCellColor(playerCell, playerColor);      
        previousBoardState = boardState;

        //Check for winner
        winner = checkForGameOver(playerCells, computerCells);
        if(winner >= 0){
          gameState = GAMEOVER;       
          Serial.println("Game Over");
        } 
        else 
        {                  
          gameState = COMPUTERTURN;       
          Serial.println("Computer Turn");
          delay(1000);
        }        
      }
      break;
    case GAMEOVER:
      if(winner == TIE)
      {      
        setStripColor(drawColor);
        delay(1000);
      }
      if(winner == PLAYERWIN)
      {      
        setStripColor(playerColor);
        flashWinningLine(playerCells, computerCells, 2);
      }
      if(winner == COMPUTERWIN)
      {      
        setStripColor(computerColor);
        flashWinningLine(playerCells, computerCells, 2);
      }
      break;
    default:
      break;
  }  

  //If the board is ever empty reutrn to pre-game
  if(readBoardValue() == 0)
  {
    gameState = PREGAME;
  }
}

//Set the entire strip to the specified color
void setStripColor(int color)
{
  for (int i=0; i < leds.numPixels(); i++) {
    leds.setPixel(i, color);       
  }
  leds.show(); 
}

//Set the color of a single cell
void setCellColor(int cell, int color)
{  
  for (int i=0; i < 12; i++) {
    leds.setPixel((cell*12) + i -1, color);       
  }
  leds.show();
}

//Read board value
unsigned int readBoardValue(){
  unsigned int boardState = 0;
  for(int i=0; i<9; i++){
      int set = !digitalRead(i+4);
      boardState = boardState | (set << i); 
  }  
  return boardState;
}

//Find the cell number that the player just claimed
int findPlayerMoveCell(unsigned int boardState, unsigned int previousBoardState){
  unsigned int diff = boardState - previousBoardState;
  Serial.print("dif = ");  
  Serial.println(diff, BIN);  
  int index = 0;
  //Scan through bits until we find the one that is set
  while(((diff >> index) & 0x0001) == 0)
  {
    index++;
  }
  Serial.print("Player Chose ");  
  Serial.println(index);  
  return index;
}

//Calculate the comptuer's move and return the selected cell
unsigned int computeMove(unsigned int boardState){  
  Serial.println("Computing Move... ");  
  Serial.print("boardState = ");  
  Serial.println(boardState, BIN);  
  while(1) {    
    int cell = random(0, 9);
    Serial.print("Checking Cell ");  
    Serial.println(cell, DEC); 

    /*
    Serial.print("Shifted Value =  ");  
     Serial.println((boardState >> cell), BIN); 
      Serial.print("ANDED Value =  "); 
     Serial.println(((boardState >> cell) & 0x0001) == 0); 
    */ 
    if(((boardState >> cell) & 0x0001) == 0)
    {
      Serial.print("Computer Chose ");  
      Serial.println(cell); 
      return cell;
    }     
  }  
}

int checkForGameOver(unsigned int playerCells, unsigned int computerCells) 
{ 
  Serial.print("Player Cells = ");
  Serial.println(playerCells, BIN);
  Serial.print("Computer Cells = ");
  Serial.println(computerCells, BIN);
  if((playerCells & 0b000000111) == 0b000000111 ||
      (playerCells & 0b000111000) == 0b000111000 ||
      (playerCells & 0b111000000) == 0b111000000 ||
      (playerCells & 0b001100001) == 0b001100001 ||
      (playerCells & 0b010010010) == 0b010010010 ||
      (playerCells & 0b100001100) == 0b100001100 ||
      (playerCells & 0b100010001) == 0b100010001 ||
      (playerCells & 0b001010100) == 0b001010100 )
   {
    return PLAYERWIN;
   }
   
   if((computerCells & 0b000000111) == 0b000000111 ||
      (computerCells & 0b000111000) == 0b000111000 ||
      (computerCells & 0b111000000) == 0b111000000 ||
      (computerCells & 0b001100001) == 0b001100001 ||
      (computerCells & 0b010010010) == 0b010010010 ||
      (computerCells & 0b100001100) == 0b100001100 ||
      (computerCells & 0b100010001) == 0b100010001 ||
      (computerCells & 0b001010100) == 0b001010100 )
   {
    return COMPUTERWIN;
   }

   if((playerCells | computerCells) == 0b111111111)
   {
    return TIE;
   }
   
   return -1;
}

int flashWinningLine(unsigned int playerCells, unsigned int computerCells , int numFlashes) {
  int winner =  checkForGameOver(playerCells, computerCells);
  int winningColor = BLACK;
  unsigned int winningBoard = 0;
  if(winner == PLAYERWIN){
     winningColor = playerColor;
     winningBoard = playerCells;
  } else if(winner == COMPUTERWIN){
     winningColor = computerColor;
     winningBoard = computerCells;
  }

  int cell0 = 0;
  int cell1 = 0;
  int cell2 = 0;

  if((winningBoard & 0b000000111) == 0b000000111)  {
    cell0 = 0;
    cell1 = 1;
    cell2 = 2;
  } else if ((winningBoard & 0b000111000) == 0b000111000)  {
    cell0 = 3;
    cell1 = 4;
    cell2 = 5;
  } else if ((winningBoard & 0b111000000) == 0b111000000)  {
    cell0 = 6;
    cell1 = 7;
    cell2 = 8;
  } else if ((winningBoard & 0b001100001) == 0b001100001)  {
    cell0 = 0;
    cell1 = 5;
    cell2 = 6;
  } else if ((winningBoard & 0b010010010) == 0b010010010)  {
    cell0 = 1;
    cell1 = 4;
    cell2 = 7;
  } else if ((winningBoard & 0b100001100) == 0b100001100)  {
    cell0 = 2;
    cell1 = 3;
    cell2 = 8;
  } else if ((winningBoard & 0b100010001) == 0b100010001)  {
    cell0 = 0;
    cell1 = 4;
    cell2 = 8;
  } else if ((winningBoard & 0b001010100) == 0b001010100)  {
    cell0 = 2;
    cell1 = 4;
    cell2 = 6;
  }  

  //Flash Colors
  for(int i=0; i<numFlashes; i++){
    setCellColor(cell0, winningColor);
    setCellColor(cell1, winningColor);
    setCellColor(cell2, winningColor);
    delay(250);
  
    setCellColor(cell0, BLACK);
    setCellColor(cell1, BLACK);
    setCellColor(cell2, BLACK);
    delay(250);
  }  
}


//---------- Cycle cell colors ---------
//Cycle through all cells and all colors in order.
#define NUM_CYCLE_COLORS 7

int cycleCellIndex = 0;
int cycleColors[NUM_COLORS] = {RED, GREEN, BLUE, YELLOW, PINK, ORANGE, WHITE};
int cycleColorIndex = 0;

void cycleCellColors(int cellDelay) {
  for (int i=0; i < 12; i++) {
   setCellColor(cycleCellIndex, cycleColors[cycleColorIndex]);
   cycleCellIndex = (cycleCellIndex + 1)  % 9;
   cycleColorIndex = (cycleColorIndex + 1) % NUM_CYCLE_COLORS;
   delay(cellDelay);
   leds.show();
  }
}

Credits

Sam Kristoff

Sam Kristoff

35 projects • 54 followers
R&D Director at NI

Comments