#include <Wire.h>
#include <BluetoothSerial.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneButton.h>
//pins:
#define IN_UP 16
#define IN_LEFT 17
#define IN_DOWN 18
#define IN_RIGHT 19
#define IN_PRESS 23
#define OUT_SDA 21
#define OUT_SCL 22
#define OLED_RESET -1
//technical:
#define DEBOUNCE_TIME 30
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C
#define BUFFER_SIZE 7
//enums:
typedef enum : int8_t
{
NONE,
UP,
LEFT,
DOWN,
RIGHT,
PRESS,
} direction_e;
typedef enum : int8_t
{
MENU,
GAME_LOCAL,
LOBBY_BLUETOOTH,
GAME_BLUETOOTH,
WIN_SCREEN,
} scene_e;
//global variables:
String device_name = "ESP32-BT-OXO-BOX";
BluetoothSerial SerialBT;
int8_t buffer[BUFFER_SIZE] = {0};
int8_t buffer_next = 0;
OneButton btn_up(IN_UP, false , false);
OneButton btn_left(IN_LEFT, false , false);
OneButton btn_down(IN_DOWN, false , false);
OneButton btn_right(IN_RIGHT, false , false);
OneButton btn_press(IN_PRESS, false , false);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
direction_e input = NONE;
scene_e scene = MENU;
int8_t menu_select = 0;
const int8_t goal = 4;
const int8_t grid_x = 8;
const int8_t grid_y = 8;
const int8_t cell_x = (SCREEN_WIDTH + grid_x - 1) / grid_x;
const int8_t cell_y = (SCREEN_HEIGHT + grid_y - 1) / grid_y;
int8_t grid[grid_y][grid_x];
int8_t select_x = 0;
int8_t select_y = 0;
int8_t player_symbol;
int8_t turn;
int8_t opponent_x;
int8_t opponent_y;
bool opponent_move = false;
//bluetooth functions:
void bluetoothDisconnect()
{
SerialBT.println("DC");
SerialBT.flush();
SerialBT.disconnect();
SerialBT.end();
}
void bufferInterpret()
{
if (buffer[0] == 'D' && buffer[1] == 'C')
{
scene = WIN_SCREEN;
display.clearDisplay();
drawGrid();
display.display();
delay(1000);
bluetoothDisconnect();
}
if (buffer[0] == 'M')
{
opponent_x = 0;
int i;
for (i = 2; i < buffer_next; i++)
{
if (buffer[i] >= '0' && buffer[i] <= '9')
{
opponent_x = opponent_x * 10 + buffer[i] - 48;
}
else
{
break;
}
}
opponent_y = 0;
int j;
for (j = i + 1; j < buffer_next; j++)
{
if (buffer[j] >= '0' && buffer[j] <= '9')
{
opponent_y = opponent_y * 10 + buffer[j] - 48;
}
else
{
break;
}
}
if (i > 2 && j > i + 1)
{
opponent_move = true;
}
}
}
//button functions:
void pressUp()
{
input = UP;
}
void pressLeft()
{
input = LEFT;
}
void pressDown()
{
input = DOWN;
}
void pressRight()
{
input = RIGHT;
}
void pressPress()
{
input = PRESS;
}
void longPress()
{
scene = MENU;
bluetoothDisconnect();
}
//game functions:
void gameReset()
{
for (int8_t y = 0; y < grid_y; y++)
{
for (int8_t x = 0; x < grid_x; x++)
{
grid[y][x] = 0;
}
}
select_x = grid_x / 2;
select_y = grid_y / 2;
turn = 0;
}
void gameSelect()
{
if (input == UP)
{
select_y--;
if (select_y < 0)
{
select_y = grid_y - 1;
}
}
if (input == LEFT)
{
select_x--;
if (select_x < 0)
{
select_x = grid_x - 1;
}
}
if (input == DOWN)
{
select_y++;
if (select_y >= grid_y)
{
select_y = 0;
}
}
if (input == RIGHT)
{
select_x++;
if (select_x >= grid_x)
{
select_x = 0;
}
}
}
int8_t gameScore()
{
for (int8_t y = 0; y < grid_y; y++) //check horizontal
{
for (int8_t x = 0; x < grid_x - goal + 1; x++)
{
if (grid[y][x] != 0)
{
bool win = true;
for (int8_t i = 1; i < goal; i++)
{
if (grid[y][x+i] != grid[y][x])
{
win = false;
}
}
if (win)
{
return grid[y][x];
}
}
}
}
for (int8_t y = 0; y < grid_y - goal + 1; y++) //check vertical
{
for (int8_t x = 0; x < grid_x; x++)
{
if (grid[y][x] != 0)
{
bool win = true;
for (int8_t i = 1; i < goal; i++)
{
if (grid[y+i][x] != grid[y][x])
{
win = false;
}
}
if (win)
{
return grid[y][x];
}
}
}
}
for (int8_t y = 0; y < grid_y - goal + 1; y++) //check left-right diagonal
{
for (int8_t x = 0; x < grid_x - goal + 1; x++)
{
if (grid[y][x] != 0)
{
bool win = true;
for (int8_t i = 1; i < goal; i++)
{
if (grid[y+i][x+i] != grid[y][x])
{
win = false;
}
}
if (win)
{
return grid[y][x];
}
}
}
}
for (int8_t y = 0; y < grid_y - goal + 1; y++) //check right-left diagonal
{
for (int8_t x = goal - 1; x < grid_x; x++)
{
if (grid[y][x] != 0)
{
bool win = true;
for (int8_t i = 1; i < goal; i++)
{
if (grid[y+i][x-i] != grid[y][x])
{
win = false;
}
}
if (win)
{
return grid[y][x];
}
}
}
}
return 0;
}
//draw functions:
void drawMenu()
{
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.write("PRESS TO PLAY!");
switch (menu_select)
{
case 0:
display.setCursor(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 2);
display.write("LOCAL");
break;
case 1:
display.setCursor(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 2);
display.write("BLUETOOTH");
break;
}
}
void drawGrid()
{
for (int8_t y = 0; y < grid_y; y++)
{
for (int8_t x = 0; x < grid_x; x++)
{
display.drawRect(x * cell_x - x, y * cell_y - y, cell_x, cell_y, SSD1306_WHITE);
if (grid[y][x] == 1) //draw X
{
display.drawLine(x * cell_x - x, y * cell_y - y, (x + 1) * cell_x - x - 1, (y + 1) * cell_y - y - 1, SSD1306_WHITE);
display.drawLine((x + 1) * cell_x - x - 1, y * cell_y - y, x * cell_x - x, (y + 1) * cell_y - y - 1, SSD1306_WHITE);
}
else if (grid[y][x] == 2) //draw O
{
display.drawCircle(x * cell_x - x + cell_x / 2, y * cell_y - y + cell_y / 2, min(cell_x, cell_y) / 2 - 1, SSD1306_WHITE);
}
}
}
}
void drawSelect()
{
display.drawCircle(select_x * cell_x - select_x + cell_x / 2, select_y * cell_y - select_y + cell_y / 2, 1, SSD1306_WHITE);
}
void drawWinner()
{
display.setTextSize(5);
display.setTextColor(SSD1306_WHITE);
display.setCursor(SCREEN_WIDTH / 2 - 3 * 5, SCREEN_HEIGHT / 2 - 4 * 5);
switch (player_symbol)
{
case 1:
display.write("X");
break;
case 2:
display.write("O");
break;
}
}
//main code:
void setup()
{
SerialBT.deleteAllBondedDevices();
btn_up.setDebounceMs(DEBOUNCE_TIME);
btn_left.setDebounceMs(DEBOUNCE_TIME);
btn_down.setDebounceMs(DEBOUNCE_TIME);
btn_right.setDebounceMs(DEBOUNCE_TIME);
btn_press.setDebounceMs(DEBOUNCE_TIME);
btn_up.attachPress(pressUp);
btn_left.attachPress(pressLeft);
btn_down.attachPress(pressDown);
btn_right.attachPress(pressRight);
btn_press.attachPress(pressPress);
btn_press.attachLongPressStart(longPress);
pinMode(OUT_SDA, OUTPUT);
pinMode(OUT_SCL, OUTPUT);
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.display();
delay(1000);
}
void loop()
{
delay(10); //Wokwi specific thing
input = NONE;
btn_up.tick();
btn_left.tick();
btn_down.tick();
btn_right.tick();
btn_press.tick();
switch (scene)
{
case MENU:
drawMenu();
if (input == PRESS)
{
switch (menu_select)
{
case 0:
scene = GAME_LOCAL;
gameReset();
player_symbol = 1;
break;
case 1:
scene = LOBBY_BLUETOOTH;
gameReset();
player_symbol = random(1, 3);
break;
}
}
else if (input != NONE)
{
if (menu_select)
{
menu_select = 0;
} else
{
menu_select = 1;
}
}
break;
case GAME_LOCAL:
drawGrid();
drawSelect();
gameSelect();
if (input == PRESS && grid[select_y][select_x] == 0)
{
grid[select_y][select_x] = player_symbol;
if (player_symbol == 1)
{
player_symbol = 2;
}
else
{
player_symbol = 1;
}
int8_t winner = gameScore(); //win sequence
if (winner)
{
player_symbol = winner;
scene = WIN_SCREEN;
display.clearDisplay();
drawGrid();
display.display();
delay(1000);
input = NONE;
}
turn++;
}
break;
case LOBBY_BLUETOOTH:
SerialBT.begin(device_name);
scene = GAME_BLUETOOTH;
break;
case GAME_BLUETOOTH:
drawGrid();
drawSelect();
gameSelect();
if ((turn + player_symbol) % 2) //player turn
{
if (input == PRESS && grid[select_y][select_x] == 0)
{
grid[select_y][select_x] = player_symbol;
SerialBT.print("M ");
SerialBT.print(select_x);
SerialBT.print(' ');
SerialBT.println(select_y);
int8_t winner = gameScore(); //win sequence
if (winner)
{
bluetoothDisconnect();
player_symbol = winner;
scene = WIN_SCREEN;
display.clearDisplay();
drawGrid();
display.display();
delay(1000);
input = NONE;
}
turn++;
}
}
else //opponent turn
{
int8_t read = SerialBT.read();
switch (read)
{
case -1:
case '\r':
//nothing
break;
case '\n':
bufferInterpret();
buffer_next = 0;
break;
default:
if (buffer_next < BUFFER_SIZE)
{
buffer[buffer_next] = read;
}
buffer_next++;
break;
}
if (grid[opponent_y][opponent_x] == 0 && opponent_x >= 0 && opponent_x < grid_x && opponent_y >= 0 && opponent_y < grid_y && opponent_move)
{
if (player_symbol == 1)
{
grid[opponent_y][opponent_x] = 2;
}
else
{
grid[opponent_y][opponent_x] = 1;
}
opponent_move = false;
int8_t winner = gameScore(); //win sequence
if (winner)
{
bluetoothDisconnect();
player_symbol = winner;
scene = WIN_SCREEN;
display.clearDisplay();
drawGrid();
display.display();
delay(1000);
input = NONE;
}
turn++;
}
}
break;
case WIN_SCREEN:
drawWinner();
if (input == PRESS)
{
scene = MENU;
}
break;
}
display.display();
display.clearDisplay();
}