/**
*
* HARDWARE CONNECTIONS
* - GPIO 16 ---> VGA Hsync
* - GPIO 17 ---> VGA Vsync
* - GPIO 18 ---> 330 ohm resistor ---> VGA Red
* - GPIO 19 ---> 330 ohm resistor ---> VGA Green
* - GPIO 20 ---> 330 ohm resistor ---> VGA Blue
* - RP2040 GND ---> VGA GND
*
* RESOURCES USED
* - PIO state machines 0, 1, and 2 on PIO instance 0
* - DMA channels 0, 1
*
*/
// Include the VGA grahics library
//#include "tic_tac_toe.h"
#include "vga_graphics.h"
//Include menu library
#include "menu.h"
//Include bluetooth library
#include "bluetooth.h"
// Include standard libraries
#include
#include
#include
#include
// Include Pico libraries
#include "pico/stdlib.h"
#include "pico/divider.h"
#include "pico/multicore.h"
// Include hardware libraries
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/clocks.h"
#include "hardware/pll.h"
// Include protothreads
#include "pt_cornell_rp2040_v1.h"
// === the fixed point macros ========================================
typedef signed int fix15 ;
#define multfix15(a,b) ((fix15)((((signed long long)(a))*((signed long long)(b)))>>15))
#define float2fix15(a) ((fix15)((a)*32768.0)) // 2^15
#define fix2float15(a) ((float)(a)/32768.0)
#define absfix15(a) abs(a)
#define int2fix15(a) ((fix15)(a << 15))
#define fix2int15(a) ((int)(a >> 15))
#define char2fix15(a) (fix15)(((fix15)(a)) << 15)
#define divfix(a,b) (fix15)(div_s64s64( (((signed long long)(a)) << 15), ((signed long long)(b))))
#define LED 25
#define BUTTON_PIN 10 // Replace with our actual GPIO
#define SCOPE_PIN 14
//Declaring game functions
int placeholder();
void initializeBoard();
void displayBoard();
bool isMoveLegal(int x, int y);
void playerMove(int player, int cursorX, int cursorY);
bool isBoardFull();
int checkWinner();
int startGame();
//Variables used to keep track of user requests like the user clicked or the calibration
volatile bool userClicked = false;
volatile bool is_calibrated = false;
//Volatile variables for updating the cursor position
int screen_x = 0;
int screen_y = 0;
volatile int x = 0;
volatile int y = 0;
// ==================================================
// === users serial input thread
// ==================================================
//Simple placeholder function for other buttons in menu system that do nothing
int placeholder()
{
return 0;
}
//Global variable for the main menu displayed on start up.
//Kept as global so it can be used in multiple places to clear or draw menu
menu_t menu;
//Serial thread not used in final implementation
static PT_THREAD (protothread_serial(struct pt *pt))
{
PT_BEGIN(pt);
// stores user input
static int parameter_input;
static int user_value;
// wait for 0.1 sec
PT_YIELD_usec(1000000) ;
// announce the threader version
sprintf(pt_serial_out_buffer, "Protothreads RP2040 v1.0\n\r");
// non-blocking write
serial_write ;
// int x,y, result;
while(1) {
// print prompt
// sprintf(pt_serial_out_buffer, "ENTER VALID BT MESSAGE: ");
// // non-blocking write
// serial_write ;
// serial_read;
//result = parseResponse("POS:001-001|CALIBRATE:F|USERCLICKED:T", &data);
//sprintf(pt_serial_out_buffer, "%d ,%d, %d, %d", data.x, data.y, data.calibrate, data.userClicked);
// // spawn a thread to do the non-blocking serial read
// serial_read ;
// sscanf(pt_serial_in_buffer, "%d", ¶meter_input);
// if(parameter_input == 0)
// {
// sprintf(pt_serial_out_buffer, "X-Coord for Cursor: \n\r ");
// serial_write;
// serial_read;
// sscanf(pt_serial_in_buffer, "%d", &user_value);
// cursorX = user_value;
// sprintf(pt_serial_out_buffer, "%d", cursorX);
// serial_write;
// }
// if(parameter_input == 1)
// {
// sprintf(pt_serial_out_buffer, "Y-Coord for Cursor: ");
// serial_write;
// serial_read;
// sscanf(pt_serial_in_buffer, "%d", &user_value);
// cursorY = user_value;
// }
PT_YIELD_usec(1000000);
} // END WHILE(1)
PT_END(pt);
} // timer thread
//Menu Animation & State handled by Core 0
static PT_THREAD (protothread_menu(struct pt *pt))
{
// Mark beginning of thread
PT_BEGIN(pt);
//Initializing menu items and adding them to the global menu struct declared previously
menuItem_t startButton;
menuItem_t button2;
menuItem_t button3;
menuItem_t button4;
initMenu(&menu, RED, WHITE, 320, 240, 400, 400);
initMenuItem(&startButton, "Start Game", &startGame);
initMenuItem(&button2, "Single Player", &placeholder);
initMenuItem(&button3, "Multiplayer", &placeholder);
initMenuItem(&button4, "Settings", &placeholder);
addMenuItem(&menu, &startButton);
addMenuItem(&menu, &button2);
addMenuItem(&menu, &button3);
addMenuItem(&menu, &button4);
setCursor(180, 100);
setTextColor(WHITE);
setTextSize(2);
writeString("WELCOME TO THE PICO WII");
while(1)
{
gpio_put(SCOPE_PIN, 1);
//Draw menu on every thread call, leads to a bit of flickering but nothing too crazy
drawMenu(&menu);
setCursor(180, 100);
setTextColor(WHITE);
setTextSize(2);
writeString("WELCOME TO THE PICO WII");
//Handles input based on location of cursor
handleInput(&menu, x, y, userClicked);
gpio_put(SCOPE_PIN, 0);
PT_YIELD_usec(100000);
}
PT_END(pt);
} // animation thread
//Bluetooth message buffers
char bluetooth_recieve_buffer[50];
char bluetooth_parse_buffer[50];
// Master bluetooth communication handles by Core 1
static PT_THREAD (protothread_bluetooth(struct pt *pt))
{
// Mark beginning of thread
PT_BEGIN(pt);
//Initialize bluetooth
initBluetooth(9600, 4, 5, 1);
//Response variable used to react to parsed messages
static int response;
//Temp x and y cursor variables so not every parsed message updates the cursor. Allows us more control as to when the cursor should be updated
static int temp_x, temp_y;
const int cursor_size = 10;
const int cursor_color = WHITE;
const int bg_color = BLACK; // whatever colors we want to use
while(1)
{
gpio_put(LED, !gpio_get(LED));
PT_YIELD_usec(30000) ;
// sendBluetoothData("sending data?");
//get x and y coordinate from bluetooth then use thiose to draw cursor here
//Reset userclicked on each pass through
if(userClicked == true)
{
userClicked = false;
}
//Read recieved bluetoth message
readBluetoothResponse(bluetooth_recieve_buffer);
//Transfer message into buffer for parsing
snprintf(bluetooth_parse_buffer, 50, bluetooth_recieve_buffer);
//Parse message and save response for later use
response = parseBluetoothResponse(bluetooth_parse_buffer, &temp_x, &temp_y, &is_calibrated, &userClicked);
//Print message to serial monitor
sprintf(pt_serial_out_buffer, bluetooth_parse_buffer);
serial_write;
//If response is 2, successful positional parse so update curor positions
if(response == 2)
{
x = temp_x;
y = temp_y;
}
//If the device has never been calibrated or is requesting calibration, center cursor on screen
if(is_calibrated == false)
{
x = 320;
y = 240;
}
// Erase previous cursor
fillRect(screen_x, screen_y, cursor_size, cursor_size, bg_color);
// Draw the cursor at new position
fillRect(x, y, cursor_size, cursor_size, cursor_color);
// Update cursor position variables
screen_x = x;
screen_y = y;
sleep_ms(500);
}
PT_END(pt);
} // animation thread
// ========================================
// === core 1 main -- started in main below
// ========================================
void core1_main(){
// Add animation thread
pt_add_thread(protothread_menu);
// Start the scheduler
pt_schedule_start ;
}
// ========================================
// === main
// ========================================
// USE ONLY C-sdk library
int main(){
// initialize stio
stdio_init_all() ;
// initialize VGA
initVGA() ;
// //init gpio for button
// gpio_init(BUTTON_PIN);
// gpio_set_dir(BUTTON_PIN, GPIO_);
// gpio_pull_up(BUTTON_PIN); // Use pull-up or pull-down as appropriate
//init gpio for scoping
gpio_init(SCOPE_PIN);
gpio_set_dir(SCOPE_PIN, GPIO_OUT);
gpio_put(SCOPE_PIN, 0);
//init gpio for led
gpio_init(LED) ;
gpio_set_dir(LED, GPIO_OUT) ;
gpio_put(LED, 0) ;
// start core 1
multicore_reset_core1();
multicore_launch_core1(&core1_main);
// add threads
//pt_add_thread(protothread_serial);
pt_add_thread(protothread_bluetooth);
// start scheduler
pt_schedule_start ;
}
//can map these to controllers later on
#define EMPTY 0
#define PLAYER_X 1
#define PLAYER_O 2
#define SIZE 3
#define VGA_WIDTH 640
#define VGA_HEIGHT 480
int moveCount = 0;
int board[SIZE][SIZE]; //thinking me might be able to change have a flexible size if we have time
const int cellWidth = VGA_WIDTH / SIZE;
const int cellHeight = VGA_HEIGHT / SIZE;
//initializes global board variable
void initializeBoard() {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
board[i][j] = EMPTY;
}
}
}
//draws game board to screen
void drawField() {
// Draw vertical lines
for (int i = 1; i < SIZE; i++) {
int x = i * cellWidth;
drawLine(x, 0, x, VGA_HEIGHT, WHITE); // Change color if needed
}
// Draw horizontal lines
for (int j = 1; j < SIZE; j++) {
int y = j * cellHeight;
drawLine(0, y, VGA_WIDTH, y, WHITE); // Change color if needed
}
}
//Draws the board with any required x's and o's
void displayBoard() {
//draw characters
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
int topLeftX = i * cellWidth;
int topLeftY = j * cellHeight;
if(board[i][j] == PLAYER_X)
{
// Draw X
drawLine(topLeftX, topLeftY, topLeftX + cellWidth, topLeftY + cellHeight, RED); // Diagonal line
drawLine(topLeftX, topLeftY + cellHeight, topLeftX + cellWidth, topLeftY, RED); // Diagonal line
} else if (board[i][j] == PLAYER_O)
{
int centerX = topLeftX + cellWidth / 2;
int centerY = topLeftY + cellHeight / 2;
int radius = (cellWidth < cellHeight ? cellWidth : cellHeight) / 4; // Adjust size here
drawCircle(centerX, centerY, radius, RED); //circle
}
}
}
}
//Ensures the inputted move is legal
bool isMoveLegal(int x, int y) {
if (x >= 0 && x < SIZE && y >= 0 && y < SIZE && board[x][y] == EMPTY) {
return 1; // The move is legal
}
return 0;
}
//Initiates a player moved
void playerMove(int player, int cursorX, int cursorY) {
// Convert cursor position to board coordinates
int boardX = cursorX / (VGA_WIDTH / SIZE);
int boardY = cursorY / (VGA_HEIGHT / SIZE);
if (isMoveLegal(boardX, boardY)) {
board[boardX][boardY] = player;
moveCount++;
}
}
//Checks whether board is full
bool isBoardFull() {
return moveCount >= (SIZE * SIZE);
}
//Checks possible win conditions and ouputs whether someone won and who
int checkWinner() {
// Check rows and columns for a win
for (int i = 0; i < SIZE; i++) {
if ((board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != EMPTY) ||
(board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != EMPTY)) {
return board[i][i]; // Return the winner (PLAYER_X or PLAYER_O)
}
}
// Check diagonals for a win
if ((board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != EMPTY) ||
(board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != EMPTY)) {
return board[1][1];
}
// No winner yet
return 0;
}
//Starts game, does all required setup for game
int startGame() {
//setup(); // Initialize the sensor
int currentPlayer = PLAYER_X;
int winner = EMPTY;
clearMenu(&menu, BLACK);
initializeBoard();
drawField();
int count = 0;
while (!isBoardFull() && winner == EMPTY) {
displayBoard(); // Display the current state of the game board
// Check for button press
if (userClicked) {
playerMove(currentPlayer, x, y);
currentPlayer = (currentPlayer == PLAYER_X) ? PLAYER_O : PLAYER_X;
winner = checkWinner(); // Check if there is a winner
}
}
displayBoard(); // Final board display
//If a player wins, update screen to show it
if (winner == PLAYER_X) {
setCursor(180, 100);
setTextColor(WHITE);
setTextSize(2);
writeString("PLAYER X WINS");
printf("Player X wins!\n");
} else if (winner == PLAYER_O) {
setCursor(180, 100);
setTextColor(WHITE);
setTextSize(2);
writeString("PLAYER O WINS");
printf("Player O wins!\n");
} else {
setCursor(180, 100);
setTextColor(WHITE);
setTextSize(2);
writeString("ITS A DRAW!");
printf("It's a draw!\n");
}
sleep_ms(10000); // shows result for 5 seconds
fillRect(0,0,640,480, BLACK);
return 0;
}