/*
Name: Eric Wooldridge
Date: December 15th, 2022
Description: This is functions exactly like anyother Tic-Tac-Toe game should.
It has modes like AI and multiplayer chosen by the user.
*/
#include <LiquidCrystal.h>// Includes the lcd library
#include <IRremote.h> //including infrared remote library
//IR
int RECV_PIN = 12, value;
IRrecv irrecv(RECV_PIN);//Sets up the pin to use IR
decode_results results;
//LCD
LiquidCrystal lcd( 7,6,5,4,3,2);
//Remote Buttons
#define left 2040
#define right 2550
#define select 5610
#define down 6630
#define up 16575
#define reset 17850
//Setting up the board and variables related to it
// 3rd r o w -- 2nd row -- 1st row
//char board[9] ={' ',' ',' ',' ',' ',' ',' ',' ',' '};//This array is used to determine a win and for AIx
//The multi dimensional array is better for checking score
char board[3][3] = {// height,length (y,x)
{' ',' ',' '},//0 - bottom
{' ',' ',' '},//1 - middle
{' ',' ',' '}//2 - top
//0 1 2
//
};
int pos = 0; //Used to keep track of position in the array
int bpos[2] ={11,3};//x y. Used to keep track of position with lcd
bool playerturn = true;//Keeps track of who's turn it is
bool win = false;
bool draw = false;
//Multiplayer
char letter = 'X';
char mode =' ';
void setup() {
Serial.begin(9600); //Opens the Serial port 9600 for console
irrecv.enableIRIn();//Enables reciever
lcd.begin(20,4); //Initialize the LCD with the size of 20x and 4y
//Setup the board
for(int x = 1;x<5;x++){ //R and L border
lcd.setCursor(8,0+x);
lcd.print("|");
lcd.setCursor(12,0+x);
lcd.print("|");
}
lcd.setCursor(11,3);
//I have to use the console instead of the lcd to print these because the lcd is upside down
Serial.println("Welcome to Tic-Tac-Toe!");
Serial.println("There are 2 modes. If you don't select one, the game doesn't work properly");
Serial.println("1. Singleplayer");
Serial.println("2. Multiplayer");
lcd.blink();
}
void loop() {
//Waits for the user to input the mode they want.
while(Serial.available() >0 && mode == ' '){
mode = Serial.read();
Serial.println(mode);
}
if (irrecv.decode(&results))
{
/*
This section takes the value from the button pressed on the remote and does one of the following below
*/
value = results.value;
//Serial.println(value);//For adding new buttons. I use this to get the value of the button
irrecv.resume();// Allows recieving the next value from remote
if(value == right || value == left || value == up || value == down)
move(value);//Sends the value to move
//If the play button is pressed and the position I am in is equal to empty we place a 'X' or 'O'
if(value == select && win !=true && board[bpos[1]-1][pos] == ' '){
Serial.println("Select");
lcd.print(letter);
board[bpos[1]-1][pos] = letter;//y x
/*Serial.println("Printing X at: "); // Debugging purposes
Serial.print("Row:");
Serial.println(bpos[1]-1);
Serial.print("Column:");
Serial.println(pos);
Serial.print("Stored as ");//x
Serial.println(board[bpos[1]-1][pos]);
Serial.println(" ");*/
scorecheck('X'); // Checks to see if X won
blink();
if(mode == '1')
ai();
else if(mode == '2'){//If the user wants multiplayer. Switches the letter variable to containt 'O' and
//'X' Based on who just went
if(win !=true)
scorecheck('O');//Checks if O won
if(letter=='O')
letter = 'X';
else if(letter=='X')
letter = 'O';
}
}
if(value == reset){
clearBoard();
}
}
}
//clearBoard
//clears the board and empties the board array along with making win and draw to false, just incase
//you win and want to restart
void clearBoard(){
lcd.clear();
for(int y =0;y<3;y++)//Clears the saved data of the board
for(int x = 0; x<3;x++)
board[y][x] = ' ';
for(int x = 1;x<5;x++){ //R and L border
lcd.setCursor(8,0+x);
lcd.print("|");
lcd.setCursor(12,0+x);
lcd.print("|");
}
win = false;
draw = false;
blink();
}
/* move
Recieves the value from the loop function and moves the cursor based on that value
*/
void move(int val){
Serial.println("Here!");
if(val == right && bpos[0] !=9){
pos++;//Moves the psoition right for the board array
bpos[0]--;//Moves the cursor to the right for the LCD
/*Serial.print("Right: "); //Debugging
Serial.println(bpos[0]-9);*/
}
else if(val == left && bpos[0] !=11){
pos--;//Moes the position left for the board array
bpos[0]++;//moves the cursore left for the LCD
/*Serial.print("Left: "); // Debugging
Serial.println(bpos[0]-9);*/
}
else if(val == up && bpos[1] != 3){//The !=3 is so we don't go out of the boards bounds
//We keep the position in the board array because we are going to be in the same position in the next row of the array
bpos[1]++;//Sets the cursor one up for the LCD and array
/*Serial.print("Up: ");
Serial.println(bpos[1]);*/
}
else if(val == down && bpos[1] != 1){//The !=1 is so we don't go out of the boards bounds
bpos[1]--;//Sets the cursor one down for the LCD and array
/*Serial.print("Down: ");
Serial.println(bpos[1]);*/
}
blink();
}
/* Blink
Blinks a square in the position of the lcd cursor
*/
void blink(){//This is scattered around for accurate blinking square placement.
//Cursor blinks
lcd.noBlink();
lcd.setCursor(bpos[0],bpos[1]);// x[Y][X]
lcd.blink();
}
/* scorecheck
The score check works with a single for loop and 3 if statements. Each if statement checks
for a different way of winning using the value from the y loop.
It also checks if there is space on the board to determine a draw.
Finally prints out the results
*/
void scorecheck(char lettercheck){
int spacesRemaining = 9;//The amount of spaces that could be empty on the board
for(int y = 0;y<3;y++)//Checks how many empty spaces are on the board
for(int x =0;x<3;x++)
if (board[y][x] != ' ')
spacesRemaining--;
for(int y =0;y<3;y++)//a auto incrementing value used to change what square is being checked
if(board[y][0] == lettercheck && board[y][1] == lettercheck && board[y][2] == lettercheck && win!=true)
win = true;//Horizontal check
else if(board[0][y] == lettercheck && board[1][y] == lettercheck && board[2][y] == lettercheck && win!=true)
win = true;//Vertical check
else if(board[0][0+y] == lettercheck && board[1][1] == lettercheck && board[2][2-y] == lettercheck && win!=true)
win = true;//Diagonal check. Scans like this \ | /
//Win output comes first just incase we do fill up the board, but that last spot is used to win the game
if(win == true && draw == false){//Since we can't win and draw
lcd.setCursor(7,0);//Outputs who won
lcd.print("SNIM ");
lcd.setCursor(12,0);
lcd.print(lettercheck);
}
if(spacesRemaining == 0 && win!=true){//if there is no space left on the board and we have not already won
lcd.setCursor(8,0);
lcd.print("DRAW");
draw = true;
}
blink();//updates the blink position with the position of the cursor
return;
}
/* AI
The AI is done by picking 2 random numbers between 0 and 2. I chose these numbers for the board array
Next it will scan to see if that position in the board array is free, if it is than it places a O
if it isn't, it will re roll the random number until it does get a empty spot
*/
void ai(){
int rand[2] = {0,0};
randomSeed(analogRead(0));//Seeds the random by listening to random static
for(int x=0;x<2;x++)
rand[x] = random(0, 3);//0-2;
if(board[rand[1]][rand[0]] == ' '){
lcd.setCursor(11-rand[0],rand[1]+1);//x,y
board[rand[1]][rand[0]] = 'O';//y,x
//DEBUGGING
/*Serial.println("Printing O at: ");
Serial.print("Column: ");//x
Serial.println(rand[0]);
Serial.print("Row: ");//y
Serial.println(rand[1]);
Serial.print("Stored as ");//x
Serial.println(board[rand[1]][rand[0]]);*/
//
lcd.print('O');
if(win != true)
scorecheck('O');
}
else
ai();
return;
}
/* ISSUES
1. LCD is upside down because the wires would get block screen (fixed)
2. When printing X to the screen, when pressed where x already exists,
it will just print to square on the left without moving the setcursor(FIXED)
3. After adding the up and down buttons, the x and y position sometimes gets
placed in a random positon - GameBreaking (FIXED)
4.When outputting who wins, the text is upside down because the lcd is upside down
5. Not much of a issue but I had to switch my array to a multidimensional array for better score checking
6. AI is playing by its own rules, overwriting x and will win when not 3 in a row, but only for O - FIXED
7. When AI is generating a position, it seems to get stuck whenever only 4-5 spots are available -FIXED
8. User can change modes mid game from multiplayer to AI.
*/
/* Things to do
Add score checker - DONE
Add basic AI. (More A than I) Done
- RNG
- Make sure it doesn't place it on a X or a O
Add smarter AI. (AI?) - not doing - more complicated than originally thought
- descisions based on where the user places X
Add a little menu that allows the user to select between AI difficulty.
Add flashing position(If I have time) DONE
Reset button(If I have time) DONE
2nd player(If I have time) DONE
Future additions
Add Music
The smarter AI
The possibility of allowing the mode to change after resetting the board
*/
/* Sources
https://www.geeksforgeeks.org/multidimensional-arrays-c-cpp/ - multidimensional array
https://arduinogetstarted.com/reference/library/lcd-blink - positon blink
*/