/**************************************************************************
   *  
 *  TicTacToe - Oled - No pulldown resistors version
 *  
 *  It requires   an Arduino Nano, Uno, Mini Pro, ...
 * 
 * Hardware connections:
 *  DISPLAY           - Arduino Nano/Uno
 *         GND       -    GND
 *         VDD        -    3.3V depending on your model
 *         SCL       -    A5
 *         SDA        -    A4
 *
 *   Arduino pin 2 <-- button MOVE <--+----- GND
 *   Arduino   pin 3 <-- button OK   <--+  
 *   
 *   Note: No need to use resistors, we   use the internal pullup resistors
 *
 * Install the Adafruit SSD1306 libraries   
 * by  Arduino IDE, menu Tools -> Manage Libraries
 * 
 * apr/2022, Giovanni   Verrua
 * https://projecthub.arduino.cc/giobbino/easiest-tictactoe-with-and-without-an-oled-display-a9dd70
**************************************************************************/

//including   the needed libraries for the OLED display
#include <SPI.h>
#include <Wire.h>
#include   <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define BUTTON_MOVE 2
#define   BUTTON_OK   3

// Declaration for an SSD1306 display connected to I2C (SDA,   SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset   pin)
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
 

int gameStatus   = 0;
int whosplaying = 0; //0 = Arduino, 1 = Human 

int winner = -1;  //-1   = Playing, 0 = Draw, 1 = Human, 2 = CPU


int board[]={0,0,0,  
             0,0,0,
              0,0,0}; //0 = blank, 1 = human (circle), 2 = computer (cross)

              


 

//--------------------------------------------------------------------------------------------------------
void   playhuman() {
     
    int humanMove = 0;  
    
    bool stayInLoop   = true;
    bool showDot = false;
    long timerPos = millis()-1000;    
     
    
    while (stayInLoop) {  //stay in loop until the player makes   his/her choice hitting the OK button.

         //If the current "?" position   isn't avaliable (the cell value is 1 or 2), this loop will
         //move the   "?" to the next free cell.
         //NOTE: there must be at least one empty   cell (or it will never exit from this loop [deadlock]).  
         //This is   granted, because if all the cells are used, there's a winner or it's a draft.
          //(the calling function [loop function] check it before to continue). 
          
         while (board[humanMove] != 0) {  //looking for an empty cell.
              humanMove ++;
             if (humanMove >8) humanMove = 0;
          }

         //--------------------------------------------------\\-
          //this makes the flashing "?" possible. Every 200 milliseconds the IF   condition becomes true and it will toogle the
         //showDot variable between   True and False (and reset the timerPos value at the current millis() value.
          if (timerPos + 200 < millis()) {  
             timerPos = millis();
              showDot = !showDot;    
             playhuman_showpos( humanMove,   showDot);  //calling the function that will draw (or delete) the "?"  
         }
          //--------------------------------------------------/-
                  
          if (digitalRead(BUTTON_MOVE)==LOW) {   //the player hit the MOVE button.   
             playhuman_showpos( humanMove, false);  //delete the marker
              humanMove ++; //move the "?" to the next cell.
             
              while (digitalRead(BUTTON_MOVE)==LOW); //debounce 
             
              bool showDot = false;  //this two lines make sure the "?" is displayed
              long timerPos =-1000;  //at the first round.
         }
         
          if (digitalRead(BUTTON_OK  )==LOW) stayInLoop = false;  //the player hit   the OK button and made his/her choice.        

         delay(100); //required   for a correct display.          
    }
        
    board[humanMove] =   1;   //let's assign the chosen cell to the player.

    
}

//------------------------------------------------------------------

void   playhuman_showpos(int humanMove, bool showDot) {   //this function draw a flashing   "?"  (white = draw, black = delete)
  
   display.setTextSize(2);
        if   (humanMove == 0) display.setCursor( 5, 5); 
   else if (humanMove == 1) display.setCursor(25,   5); 
   else if (humanMove == 2) display.setCursor(45, 5); 
   else if (humanMove   == 3) display.setCursor( 5,25); 
   else if (humanMove == 4) display.setCursor(25,25);   
   else if (humanMove == 5) display.setCursor(45,25); 
   else if (humanMove   == 6) display.setCursor( 5,45); 
   else if (humanMove == 7) display.setCursor(25,45);   
   else if (humanMove == 8) display.setCursor(45,45); 
   
   //if (showDot)   {display.setTextColor(WHITE);display.print("?");}  else {display.setTextColor(BLACK);display.print("?");}   
   if (showDot) display.setTextColor(WHITE); else display.setTextColor(BLACK);
    
   display.print("?");
   display.display(); 
}
 

//--------------------------------------------------------------------------------------------------------
void   playcpu() {
     
     //NOTE: The player has almost no chance to win, since   the cpu will check every possible move.      
     //      Actually the only   way to beat the cpu is to have two winning move at the same time.
     
     //The   CPU has no real strategy, actually; it just prevents the player to win and put an   "X" if it has
     //a possible winning move. If no winning move are possible,   it just put an "X" into a random place.
     //It could seems a stupid AI,   however you will see the CPU will play rather well and it will be
     //hard   to beat it.

     int cpumove = checkboard(2);  //2 = cpu  let's check if   there's a cpu's winner move

     if (cpumove >=0) {
        board[cpumove]   = 2;    //cpu's winner move
     }
     else {    
         cpumove = checkboard(1);   //1=player check if the player has a chance to win (2 circles and an empty cell   in a row)   
         if (cpumove >=0) {  
            board[cpumove] = 2;   //this move will break the player's winner move
         }     
    
         //there's no possible winner move neither for the cpu, nor for the human;:   the CPU will put an "X" in a random cell
        while (cpumove < 0) {   //looking   for a random, empty cell.               
           int randomMove = random(10);
            if (randomMove >=0 && randomMove <=8 && board[randomMove] == 0) {
                cpumove = randomMove;
           }        
        }        
         board[cpumove] = 2;  //let's assign the empty cell to the CPU
     }   
}


//--------------------------------------------------------------------------------------------------------
int   checkboard(int x){   //x = 1 -> player, x = 2 -> cpu

   //this function checks   if the next move can be the winning move and return the cell that will
   //win   the game. It's used by the CPU to decide if it can win or if the player is going   to win
   //(and placing an "X" to prevent this chance). 
   //if no move   wins the game, it returns -1
   //the board[] index is 0 1 2
   //                     3   4 5
   //                     6 7 8
  
  
       if (board[0]==0 &&   board[1]==x && board[2]==x)  return  0;  //  0 1 1 
                                                                   //   . . .
                                                                   //   . . .
                                                                   
   else if (board[0]==x && board[1]==0 && board[2]==x)  return  1;  //  1 0 1 
                                                                    //  . . .
                                                                    //  . . .
                                                                    
  else if   (board[0]==x && board[1]==x && board[2]==0)  return  2;  //  1 1 0
                                                                   //   . . .
                                                                   //   . . .                                                                   
  //-------------------------------------------------
   else if (board[3]==0 && board[4]==x && board[5]==x)  return  3;  //  . . .
                                                                    //  0 1 1
                                                                    //  . . .
                                                                      
  else   if (board[3]==x && board[4]==0 && board[5]==x)  return  4;  //  . . .  
                                                                   //   1 0 1
                                                                   //   . . .                                                                 

   else if (board[3]==x && board[4]==x && board[5]==0)  return  5;  //  . . .
                                                                    //  1 1 0
                                                                    //  . . .
     //-------------------------------------------------
  else if (board[6]==0   && board[7]==x && board[8]==x)  return  6;  //  . . .
                                                                   //   . . .
                                                                   //   0 1 1
                                                                     
   else if (board[6]==x && board[7]==0 && board[8]==x)  return  7;  //  . . .  
                                                                    //  . . .
                                                                    //  1 0 1

   else if (board[6]==x && board[7]==x && board[8]==0)  return  8;  //  . . .
                                                                    //  . . .
                                                                    //  1 1 0

   //-------------------------------------------------
  else if (board[0]==0   && board[3]==x && board[6]==x)  return  0;  //  0 . .
                                                                   //   1 . .
                                                                   //   1 . .
  
  else if (board[0]==x && board[3]==0 && board[6]==x)  return  3;   //  1 . .
                                                                   //   0 . .
                                                                   //   1 . .
  
  else if (board[0]==x && board[3]==x && board[6]==0)  return  6;   //  1 . .
                                                                   //   1 . .
                                                                   //   0 . .  
                                                                   
   //-------------------------------------------------
  else if (board[1]==0   && board[4]==x && board[7]==x)  return  1;  //  . 0 .
                                                                   //   . 1 .
                                                                   //   . 1 .
  
  else if (board[1]==x && board[4]==0 && board[7]==x)  return  4;   //  . 1 .
                                                                   //   . 0 .
                                                                   //   . 1 .  
  
  else if (board[1]==x && board[4]==x && board[7]==0)  return   7;  //  . 1 .
                                                                   //   . 1 .
                                                                   //   . 0 .  
                                                                    
   //-------------------------------------------------
  else if (board[2]==0   && board[5]==x && board[8]==x)  return  2;  //  . . 0 
                                                                   //   . . 1 
                                                                   //   . . 1 
  
  else if (board[2]==x && board[5]==0 && board[8]==x)  return   5;  //  . . 1 
                                                                   //   . . 0 
                                                                   //   . . 1   
  
  else if (board[2]==x && board[5]==x && board[8]==0)  return   8;  //  . . 1 
                                                                   //   . . 1
                                                                   //   . . 0
                                                                    
   //-------------------------------------------------
  else if (board[0]==0   && board[4]==x && board[8]==x)  return  0;  //  0 . . 
                                                                   //   . 1 . 
                                                                   //   . . 1 
  
  else if (board[0]==x && board[4]==0 && board[8]==x)  return   4;  //  1 . . 
                                                                   //   . 0 .
                                                                   //   . . 1   
  
  else if (board[0]==x && board[4]==x && board[8]==0)  return   8;  //  1 . . 
                                                                   //   . 1 .
                                                                   //   . . 0

  //-------------------------------------------------
  else if   (board[2]==0 && board[4]==x && board[6]==x)  return  2;  //  . . 0 
                                                                   //   . 1 . 
                                                                   //   1 . . 

  else if (board[2]==x && board[4]==0 && board[6]==x)  return  4;   //  . . 1 
                                                                   //   . 0 . 
                                                                   //   1 . . 
    
  else if (board[2]==x && board[4]==x && board[6]==0)  return   6;  //  . . 1 
                                                                   //   . 1 . 
                                                                   //   0 . . 
  
  else                                                 return   -1;
}


//--------------------------------------------------------------------------------------------
void   checkWinner() {    //check the board to see if there is a winner

  winner   = 3;  //3=draft, 1= winner->player, 2=winner->cpu
    
  // circles win?
        if (board[0]==1 && board[1]==1 && board[2]==1)     winner=1;   
  else   if (board[3]==1 && board[4]==1 && board[5]==1)     winner=1;   
  else if (board[6]==1   && board[7]==1 && board[8]==1)     winner=1;     
  else if (board[0]==1 && board[3]==1   && board[6]==1)     winner=1;   
  else if (board[1]==1 && board[4]==1 && board[7]==1)      winner=1;   
  else if (board[2]==1 && board[5]==1 && board[8]==1)     winner=1;      
  else if (board[0]==1 && board[4]==1 && board[8]==1)     winner=1;   
   else if (board[2]==1 && board[4]==1 && board[6]==1)     winner=1; 
    
   // crosses win?
  else if (board[0]==2 && board[1]==2 && board[2]==2)     winner=2;    
  else if (board[3]==2 && board[4]==2 && board[5]==2)     winner=2;   
   else if (board[6]==2 && board[7]==2 && board[8]==2)     winner=2;     
  else   if (board[0]==2 && board[3]==2 && board[6]==2)     winner=2;   
  else if (board[1]==2   && board[4]==2 && board[7]==2)     winner=2;   
  else if (board[2]==2 && board[5]==2   && board[8]==2)     winner=2;     
  else if (board[0]==2 && board[4]==2 && board[8]==2)      winner=2;   
  else if (board[2]==2 && board[4]==2 && board[6]==2)     winner=2;    

  if (winner == 3) {      
     for(int i=0;i<9;i++) if (board[i]==0)   winner=0;  //there are some empty cells yet. 
  }   
     
 
}

//--------------------------------------------------------------------------------------------------------------

void   resetGame() {
  
  for(int i=0;i<9;i++) board[i]=0;   //Resetting the board.   0 = empty cell, 1 = player circle, 2 = CPU cross
  
  winner = 0;
  gameStatus   = 0; 
  
}

//--------------------------------------------------------------------------------------------------------------

void   boardDrawing() {

  display.clearDisplay();
  display.setTextColor(WHITE);
   
  display.drawFastHLine(0, 21, 64, WHITE); //horizontal lines
  display.drawFastHLine(0,   42, 64, WHITE);

  display.drawFastVLine(21, 0, 64, WHITE); //vertical lines
   display.drawFastVLine(42, 0, 64, WHITE);

  //drawing the content of the   nine cells: " ", "o", "x"
  display.setTextSize(2);
  display.setCursor(   5, 5); display.print(charBoard(0));  display.setCursor(25, 5); display.print(charBoard(1));   display.setCursor(45, 5); display.print(charBoard(2));
  display.setCursor( 5,25);   display.print(charBoard(3));  display.setCursor(25,25); display.print(charBoard(4));   display.setCursor(45,25); display.print(charBoard(5));
  display.setCursor( 5,45);   display.print(charBoard(6));  display.setCursor(25,45); display.print(charBoard(7));   display.setCursor(45,45); display.print(charBoard(8));  
  display.display();
   
  delay(200); //DON'T REMOVE!!!! needed for correct refresh and further flashing   "?" when it's the player turn!!!
}

//--------------------------------------------------------------------------------------------------------------
String   charBoard(int x) {  
       if (board[x] == 0) return " ";       
       if   (board[x] == 1) return "o";
       if (board[x] == 2) return "x";  

        return "?";  //error trap; but it's impossible it can return an "?" because   the board[] array is all initialized = 0
}

//--------------------------------------------------------------------------------------------------------------
void   setup() {  //function executed once, at every boot or reset.
  
  randomSeed(analogRead(0));   //resetting the random function behavior.

  //using the internal pullup   resistor, so the button will be LOW until a button is pressed.
  pinMode(BUTTON_MOVE,INPUT_PULLUP);     //Declaring the pin #2 as input (connected to the button move)
  pinMode(BUTTON_OK   ,INPUT_PULLUP);    //Declaring the pin #3 as input (connected to the button ok)   

//display.begin();                              //if the display doesn't   work with the beHIGH instruction,
//display.begin(SSD1306_SWITCHCAPVCC, 0x3D);     //try one of these two ones.
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);   
  
  delay(500);                   //needed for display correct initializing
   display.clearDisplay();       //clearing the display
  display.setTextColor(WHITE);   //setting the display color
  display.display();            //executing the   above instructions. The SSD1306 dislay will not execute any command until to use   the ::display() command.

  whosplaying = 2;  //deciding who's the first player.   Set = 2 to force it entering in the folHIGHing while loop.
  while ( whosplaying   <0 || whosplaying > 1) whosplaying = random(2);  //it will stay in the loop until   whosplaying isn't = 0 or = 1. Probably there's no
                                                                       //need   for a loop, since random(2) should return 0 or 1, but I'm an old programmer,
                                                                        //I've seen   many strange things in my programmer life and I like to be sure ;-)  
}


//--------------------------------------------------------------------------------------------------------------
void   loop() {   //main loop. Endlessly executed by Arduino.
                
  if   (gameStatus == 0){     //this is where I always put a menu in the Arduino games   I wrote. We don't need a menu here, so it's just a reset step.
     resetGame();   
     boardDrawing();     //drawing an empty board    
     gameStatus =   1;     //starting the game (see beHIGH)
     winner = 0;         //no winner   for now (winner = 1: player, winner = 2: cpu).      
  }

  //---------------------------------------------
   
  if (gameStatus == 1){   //starting the game
      
      while (winner   == 0) {  //game main loop: loop until no one wins the match.
        
        display.setTextSize(2);   
        
        if (whosplaying == 0) {  //whosplaying = 0: cpu turn
           
          display.setCursor( 72,25); display.print("CPU");
          display.display();
           delay(1000);
          
          playcpu();    //in this function   the CPU play its move.
          
          whosplaying =1;    //changing   the turn.
        }
        else { 
             
          display.setCursor(   72,25); display.print("You");
          display.display();          
          
           playhuman();  //in this function the player makes his/her move.
          
           whosplaying =0;  //changing the turn.       
        }

        boardDrawing();   //refreshing the board with all the moves already done.
        delay(500);
         
        checkWinner();  //this will check if there's a winner and assign   the winner variable

         if (winner > 0) {
            
            
             if (winner == 3) {              
                display.setTextSize(2);   display.setCursor( 68, 25); 
                display.print("Draft"); 
            }
             else {                
                //showing who's the winner
                 display.setTextSize(2); display.setCursor( 72, 25); 
                if   (winner == 1) { Serial.println(F("You")); display.print("You"); display.setCursor(   72, 45); display.print("win"); }
                else             { Serial.println(F("CPU"));   display.print("CPU"); display.setCursor( 72, 45); display.print("wins");}
                 
            }   
            display.display();                
             delay(1000);

            //debounce loop. It will not proceed   until both buttons are unpressed.
            while (digitalRead(BUTTON_MOVE)==HIGH   && digitalRead(BUTTON_OK  )==HIGH);
                      
         }
          
        //display.display();
        
      }

      //swap   the first move for the next match between CPU and human (one per match)
      if   (whosplaying == 0) whosplaying =1; else whosplaying =0; 

      gameStatus   = 0;  //entering the reset step
      delay(1000);     //just wait a second
          
  }
  
}