Tic-Tac Toe Game
Tic-Tac Toe Game
What I wanted to learn, and why it interested me: I wanted to learn how to use a pixel display. I was particularly interested in learning this since it offers a more dynamic visual display compared to the LCDs that only display ASCII text. Personally, the more dynamic screen allows for more creativity and a better user interface.
Final outcome: I created an interactive tic-tac-toe game that is displayed on a 128x64 pixel display. The game is played using 9 buttons arranged in a 3x3 matrix, where each button's position in the matrix corresponds to its respective place on the tic-tac-toe board.
Images of final creative/exploratory output
Here is a full working demo. Buttons are pressed until there are 3 in a row, then the screen flashes and reset the board.
This is the final board layout for the buttons. They are arranged with their positions reflecting their respective positions on the tic-tac-toe board.
Process images from development towards creative/exploratory output
This was my initial set up for the buttons on the breadboard. I used male-to-male pins for all connections. It was extremely messy and hard to press buttons without accidentally disconnecting wires.
Here is the through pins soldered to the pixel display. This was required to plug the display into a breadboard.
Process and reflection:
The first step I took in this project was to get an understanding of how the wiring works for the pixel display. I learned that aside from VCC and GND, the pixel display only requires inputs for the SDA and SCL pins. This was very simple to wire up and did not take long to get working. Once I got the display wired up I began testing it with some of the example sketches in the Adafruit library. These gave me a basic understanding of how to create a bitmap and send them to the display.
After learning how to wire and use the pixel display, I finally was able to implement the tic-tac-toe portion of the project. This was probably the most difficult part of the project and was very time consuming. Wiring all nine buttons close together, each with pull down resistors, was very messy. Initially, I wired them with male-to-male pins, but I ended up cutting smaller wires for the breadboard to make the buttons easier to press. The entirety of the code for the game was written by ChatGPT, so the code aspect was not time consuming at all. I simply fed ChatGPT a working sketch from the example library and explained how I wanted to modify it. Overall, I loved using the pixel display since it allows you to be much more creative with how you can interact with the user.
Technical details
Electrical schematic depicting 9 buttons wired with pull-down resistors connected to Arduino pins 2-10. The pixel display is wired to A4 and A5 for SDA and SCL inputs.
/*
60-223 Intro to Physical Computing, fall 2025
Domain-specific Skill Building exercise: Tic-Tac-Toe Game
This function displays a tic-tac-toe board on a pixel screen and
updates it based on the inputs of 9 different buttons and the
current state of the game. The buttons, starting from pin 2,
refer to positions on the board, reading left to right and top to
bottom. The board is automatically cleared in the case of a tie
or a win.
Pin mapping:
Arduino pin | role | details
------------------------------
2-10 input button
A3 output SDA to pixel display
A4 output SCL to pixel display
Citations:
The entirety of this code was written by ChatGPT 5.
The prompt used to generate this code was:
"i am writing an arduino script that uses an adafruit
pixel display as an outputs and 9 different buttons as
inputs. i am trying to implement a tic tac toe game
using the 9 buttons. write me the code for it"
Released to the public domain by the author, October 2025
Cole Franklin, davidfra@andrew.cmu.edu
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ---------- display ----------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ---------- buttons ----------
const uint8_t BUTTON_PINS[9] = {2,3,4,5,6,7,8,9,10};
const unsigned long DEBOUNCE_MS = 30;
const unsigned long HOLD_RESET_MS = 2000;
bool lastStable[9];
bool lastReading[9];
unsigned long lastChangeAt[9];
unsigned long pressStartAt[9];
bool isHeld[9];
// ---------- game ----------
uint8_t board[9] = {0}; // 0 empty, 1 X, 2 O
uint8_t currentPlayer = 1;
bool gameOver = false;
uint8_t winner = 0;
int8_t winLine[3] = {-1, -1, -1};
const uint8_t WINS[8][3] = {
{0,1,2},{3,4,5},{6,7,8},
{0,3,6},{1,4,7},{2,5,8},
{0,4,8},{2,4,6}
};
// grid geometry
const int16_t COLS[4] = {0,42,85,128};
const int16_t ROWS[4] = {0,21,42,64};
void getCellRect(uint8_t cell,int16_t &x0,int16_t &y0,int16_t &x1,int16_t &y1){
uint8_t c=cell%3, r=cell/3;
x0=COLS[c]; x1=COLS[c+1]-1;
y0=ROWS[r]; y1=ROWS[r+1]-1;
}
// ---------- drawing ----------
void drawGrid(){
display.drawFastVLine(COLS[1],0,SCREEN_HEIGHT,WHITE);
display.drawFastVLine(COLS[2],0,SCREEN_HEIGHT,WHITE);
display.drawFastHLine(0,ROWS[1],SCREEN_WIDTH,WHITE);
display.drawFastHLine(0,ROWS[2],SCREEN_WIDTH,WHITE);
}
void drawX(int16_t x0,int16_t y0,int16_t x1,int16_t y1){
int16_t p=4;
display.drawLine(x0+p,y0+p,x1-p,y1-p,WHITE);
display.drawLine(x0+p,y1-p,x1-p,y0+p,WHITE);
}
void drawO(int16_t x0,int16_t y0,int16_t x1,int16_t y1){
int16_t cx=(x0+x1)/2, cy=(y0+y1)/2;
int16_t r=min((x1-x0),(y1-y0))/2-4;
display.drawCircle(cx,cy,r,WHITE);
}
void drawCell(uint8_t i){
if(board[i]==0)return;
int16_t x0,y0,x1,y1; getCellRect(i,x0,y0,x1,y1);
if(board[i]==1)drawX(x0,y0,x1,y1); else drawO(x0,y0,x1,y1);
}
void drawBoard(){
display.clearDisplay(); drawGrid();
for(uint8_t i=0;i<9;i++)drawCell(i);
display.display();
}
// ---------- game logic ----------
uint8_t checkWinner(){
for(uint8_t w=0;w<8;w++){
uint8_t a=WINS[w][0],b=WINS[w][1],c=WINS[w][2];
if(board[a]!=0 && board[a]==board[b] && board[b]==board[c]){
winLine[0]=a;winLine[1]=b;winLine[2]=c;
return board[a];
}
}
return 0;
}
bool boardFull(){ for(uint8_t i=0;i<9;i++)if(board[i]==0)return false;return true; }
void resetGame(){
for(uint8_t i=0;i<9;i++)board[i]=0;
currentPlayer=1; gameOver=false; winner=0;
drawBoard();
}
void tryMove(uint8_t i){
if(gameOver||board[i]!=0)return;
board[i]=currentPlayer; drawBoard();
winner=checkWinner();
if(winner){ gameOver=true; delay(800); resetGame(); return; }
if(boardFull()){ gameOver=true; delay(800); resetGame(); return; }
currentPlayer=(currentPlayer==1)?2:1;
}
// ---------- setup / loop ----------
void setup(){
Wire.begin(); Wire.setClock(400000);
display.begin(SSD1306_SWITCHCAPVCC,SCREEN_ADDRESS);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(10,20);
display.println(F("Tic-Tac-Toe"));
display.display(); delay(700);
for(uint8_t i=0;i<9;i++){
pinMode(BUTTON_PINS[i],INPUT); // external pulldown, HIGH when pressed
lastReading[i]=digitalRead(BUTTON_PINS[i]);
lastStable[i]=lastReading[i];
lastChangeAt[i]=millis();
}
resetGame();
}
void loop(){
unsigned long now=millis();
for(uint8_t i=0;i<9;i++){
bool reading=digitalRead(BUTTON_PINS[i]); // HIGH = pressed
if(reading!=lastReading[i]){ lastChangeAt[i]=now; lastReading[i]=reading; }
if(now-lastChangeAt[i]>DEBOUNCE_MS){
if(reading!=lastStable[i]){
lastStable[i]=reading;
if(reading==HIGH){ // pressed
tryMove(i);
}
}
}
}
}