Battleship Game with Rpi PICO-W Microcontrollers using UDP

ECE 5730 Final Project

presented on

May 9th 2025

by

Dikshanya Lashmi Ramaswamy(dr655)
Akansha Shrestha(as4329)
Chengle Zheng (cz556)

Introduction

A networked two-player Battleship game implemented on Raspberry Pi Pico W boards, featuring VGA graphics, joystick control, and synchronized turn-based play over UDP.

Summary: We built a real-time, two-player Battleship game using Raspberry Pi Pico W microcontrollers that communicate via UDP. Each device uses a 320×240 VGA display, joystick for ship placement and attacks, and tactile buttons for game interaction. The project features a custom FSM-based turn protocol and integrates both DMA- and interrupt-based audio feedback.

High Level Design

The idea stemmed from the desire to replicate classic board games with microcontrollers while gaining hands-on experience in real-time control, networking, and multimedia feedback. Battleship was selected due to its turn-based, grid-centric gameplay, which incorporates many core elements covered in the ECE5730 curriculum. We felt this game would effectively leverage the full range of course material while offering enough complexity to keep the project engaging.

The game proceeds through the following states (or “pages”):
  • Welcome Page: Displays title and waits for yellow button press.
  • Level Selection: Lets the player choose difficulty and displays the rules
  • Ship Placement: Cursor used to place 5 ships with feedback and validation.
  • Ongoing Gameplay: FSM ensures alternate turns via UDP coordination.
  • End Game (Win/Lose): Triggered when all ships of one player are sunk.

  • A turn-based FSM (PlayerState) manages the flow between YOUR_TURN (YT), RECEIVE_RESPONSE (RR), and RECEIVE_ATTACK (RA) states.

    PlayerStateFSM
    Hardware:
    We utilized the dual-core RP2040 microcontroller, assigning Core 0 to manage communication and Core 1 to handle display updates and game logic. One early consideration was whether a single microcontroller could host both players' games simultaneously. However, we quickly found this approach to be impractical due to resource limitations and the increased complexity of managing two instances. By deploying two full, independent setups, we enabled players to participate from physically separate locations as long as both devices were connected to the same Wi-Fi network, preserving the integrity of the gameplay experience. This created a more reliable game setup even though the total cost of the system had increased.

    Software:
    In exploring communication methods, we considered standard protocols like UART and I2C, but these required physical proximity and limited gameplay to a closed area. Since Battleship is a strategy game that relies on secrecy and distance between players, we needed a communication method that supported remote interaction. This led us to network-based protocols. Between TCP and UDP, we chose UDP for its speed and simplicity. The game follows a strict turn-based model, and each turn only proceeds once the correct data is received. This made an acknowledgment-based protocol like TCP unnecessary and overly complex. UDP allowed us to avoid overhead while still ensuring reliable synchronization through a mode-based FSM controlling game flow.

    Our project did not involve the use or creation of any patents. All core functionality was implemented independently, without reverse-engineering any proprietary algorithms or designs. VGA display drivers and sound libraries were adapted from resources provided through the course or sourced from openly available, permissively licensed codebases. As such, no copyrighted or trademarked materials were used in violation of their terms, and all third-party components were either public domain or compatible with academic and open-source use.


    Demonstration Video

    A youtube video showcasing the functionality and demonstration of the Battleship Game with Rpi PICO-W Microcontrollers using UDP. The video shows a full run of the game.

    Project Design


    The Battleship game was implemented as a two-player turn-based system on Raspberry Pi Pico W microcontrollers, utilizing VGA output for visuals and UDP networking for player synchronization. The core game logic runs on a dedicated animation thread (protothread_anim) which handles state transitions through a finite state machine (FSM). This FSM guides the player through various stages: the welcome screen, difficulty selection, ship placement, active gameplay, and win/lose conditions. User interaction is driven by a joystick for directional input and physical buttons for selections, with button presses detected via GPIO interrupts that incorporate debouncing for reliable edge detection.

    Visual elements such as grids, cursors, ships, hits, and status messages are rendered to a 320x240 VGA display using graphics routines built on the vga256_graphics library. These functions allowed for pixel-level control and were essential for dynamic elements like drawing and updating pegs during gameplay. Display rendering and cursor motion are synchronized with game logic to ensure fluid user interaction. Input from the joystick translates into smooth cursor movement with boundary wrapping to maintain usability across the grid edges. The cursor's previous state is preserved and restored during movement to prevent screen flickering.

    Communication between the two players' devices is handled through UDP over WiFi using the Pico W. This communication layer is managed within two protothreads: protothread_receive and protothread_send. These threads handle parsing and formatting of messages that relay game status, grid state changes, and attack coordinates. UDP packets are structured with semaphores to coordinate when messages are sent or received, ensuring synchronization of game state across devices. The networking and WiFi setup were based on the picow_udp_beacon.h template code by Dr. Bruce Land and Dr. Hunter Adams.

    Communication between the two players' devices is handled through UDP over WiFi using the Pico W. This communication layer is managed within two protothreads: protothread_receive and protothread_send. These threads handle parsing and formatting of messages that relay game status, grid state changes, and attack coordinates. UDP packets are structured with semaphores to coordinate when messages are sent or received, ensuring synchronization of game state across devices. The networking and WiFi setup were based on the picow_udp_beacon.h template code by Dr. Bruce Land and Dr. Hunter Adams.

    We intended our game to run VGA only and game logic code with fully CPU support. Thus, we used DMA channels to control DAC outputs. The design is the same as in Lab 2. For each sound effect, we had one data channel to read the sample array from the beginning to the end and write samples to the SPI register. We have one control channel to reset the data channel to restart from the first data in the sample array. Once the control channel finishes, it will trigger the data channel to start DAC output. Therefore, we call the control channel to play the sound effect.

    Initially, we divided our workload and developed the DMA-based audio playback in a separate PICO project. At that stage, the DMA channels for both the boom and splash sounds functioned correctly. However, upon integrating the audio component into the main project, we encountered an issue where the splash DMA no longer worked, despite explicitly requesting an unused DMA channel and assigning it a different timer. Due to limited time we decided to use an interrupt service routine to play the splash sound. To control the sound out and silence, we had a global flag which was defined as false. If the main program wants to play splash, it will set this flag to be true and the interrupt will send out samples. Once it finishes all samples, the flag will be reset to false and the interrupt will be ready to play the sound again.

    For user interaction, we also included a joystick and two buttons for each player. The joystick has five wire connections, one is GND and the remaining four for each directional switch of left, right, up and down. These internally switches act like the cursor movement in four directions. The cursor helps to move and control the selection and placement of ships in the game. We checked the individual switch and decided the position of x and y in the VGA screen. Both buttons are configured as pull-up inputs. When unpressed, the GPIO input reads 1, when pressed, it reads 0. The interrupt routine simply reads the GPIO inputs and updates two global variables: the current state and previous state of each button. These states are used throughout the game program to determine user input.

    For the welcome and difficulty level selection pages, we use edge-triggered logic. Actions are triggered only when the button transitions from unpressed to pressed. This method works well because the main loop logic on those pages is relatively short. However, for the rest of the game, we rely solely on the current button state. If the button is currently pressed, the associated action is executed immediately. This is because the game logic becomes more complex and time-consuming, making it harder to reliably detect short press transitions within a single loop iteration. By checking the current state directly, we ensure that button presses are still recognized.

    The code flow for difficulty level hard is shown in the diagram below:
    Code_Flow HARD

    Hardware Setup

    Our hardware design is built around two Raspberry Pi Pico W boards that serve as independent game units for each player. Each unit integrates multiple peripherals to support display output, user interaction, and audio feedback. The Pico W features dual-core architectures that allowed us to dedicate Core 1 to display updates, game logic, and user input processing and Core 0 to handle UDP connection.

    We generated VGA graphics through 8-bit colors connected by GPIOs 8-15. The pins were connected with 330 ohms, 660 ohms, and 1 K Ohms resistors for each Red, Blue and Green signal. The HSync and VSync lines are connected to GPIO 16 and 17 pins with GND connection from Pico. Frame buffering and drawing were handled with a custom display loop using DMA to offload processing from the CPU.

    The four directions of joystick movement: left, right, top and down are connected to GPIO pins 22, 26-28. We connected these pins with 330 ohms resistors and then connected to the internal switches inside the joystick. Additionally, we included two push buttons in GPIO 18 and 19 as a pull-up circuit. Here, the other pin connects to 3.3V from Pico. We also included a resistor from these button GPIOs to the GND so that input stays low when not pressed.

    We also used a 12-bit DAC (MCP49) connected via SPI to generate audio. The SPI connections are mapped to GPIO pins 5 (chip select), 6 (clock), 7 (MOSI). We pre-processed the WAV files for the boom and splash sound effect for hit and miss cases in the game. The DMA channels are again configured to handle seamless audio playback by transferring sample data directly to the SPI register when needed. We updated the previous lab templates for audio streaming through DAC. We also connected a RPi-based debugger to monitor the system’s real-time performance and troubleshoot issues during development.


    Estimated Cost

    Part Description Quantity Estimated Cost (USD)
    RP2040 Microcontroller (Raspberry Pi Pico) 2 $8
    VGA Display Module 2 $15
    Push Buttons (for user input) 4 $5
    Resistors (for button interfaces) 10 $1
    JoyStick 2
    Jumper Wires & Breadboard 20 $5
    Micro-USB Cable (for power) 2 $2
    We might use LED for display as a future option 2 $12
    Total Estimated Cost $50

    Challenges in Implementation

    One of the more challenging aspects of the project was maintaining synchronized gameplay between the two devices over an unreliable UDP connection. This required careful management of game states and explicit handling of message ordering and content in the receive thread. Another tricky part was implementing the cursor movement system. Translating joystick input into usable grid navigation while preserving responsiveness, screen alignment, and wrap-around behavior took several iterations to fine-tune.

    Debouncing physical buttons using GPIO interrupts also posed a challenge. The system had to correctly detect rising edges to avoid false positives, and also required managing button state transitions carefully across frames. The button pin was connected as a pull-up circuit, hence we added a resistor from GPIO to ground in parallel with the button connection. This resolved the issue of input being always triggered high when not pressed as floating input pins and noise issues.

    Initially, we planned to generate the sounds, boom and splash using Direct Digital Synthesis (DDS) as what we did before in Lab 1 for birdsong. When checked in the frequency spectrum the splash and boom sounds waveforms were very complicated with rapid and unstable patterns in the frequency waves. So, we used DAC to work with direct audio samples.

    In addition, implementing the ship placement logic was non-trivial. Ensuring that ships fit within bounds and do not overlap required conditional checks and interactive feedback using the display. Also, managing the VGA screen refresh and updates without flickering required a thoughtful design. Instead of redrawing the entire screen every frame, only modified regions were updated, and previous pixel data was cached during cursor movement. Integrating audio playback via DMA and SPI added another layer of complexity, especially with coordinating timers and ensuring audio samples were correctly formatted and streamed.

    Moreover, fix15 and int/float conversion created few issues. For instance, when defining the cursor position, we used fix15 as the data type, even though the cursor’s position was effectively handled as an integer throughout the code. This mismatch caused subtle issues, such as incorrect coordinate comparisons, drawing errors, and inconsistent behavior during updates. The overuse of fix15, especially in places where fixed-point precision was unnecessary, complicated the logic and reduced code clarity.

    Lastly, several approaches were explored during development that ultimately proved unfeasible. We initially attempted to trigger both sound effects using DMA channels for performance, but this approach was limited by the available DMA resources and had to be scaled back. A serial input fallback mode was also implemented to simulate test attacks during early stages of development, though it was disabled in the final version. Additionally, our first attempt at multiplayer logic relied on polling-based UDP communication, which led to timing mismatches and desynchronization between players, this was later resolved using interrupt-driven communication. We also experimented with displaying custom images for the ship grids; however, DMA limitations prevented this from functioning as intended. This remains an area we plan to revisit as part of future enhancements.

    Code Attribution

    The project made extensive use of foundational code written by Dr. Bruce Land and Dr. Hunter Adams. Their vga256_graphics library served as the basis for all display rendering routines, including drawing shapes, text, and VGA synchronization. Additionally, the UDP communication setup and message-handling framework were based on their picow_udp_beacon and lwIP integration examples. These references were critical in enabling real-time, two-player interaction over a WiFi connection, and in providing a stable graphics framework to support dynamic game elements.

    Results

    The system demonstrated solid overall performance with respect to execution speed, interactivity, and synchronization. The VGA display maintained a steady frame rate with minimal flickering, and cursor updates remained responsive with under 100ms of lag, supporting smooth gameplay. Real-time UDP communication between devices ensured accurate and timely turn synchronization, essential for the turn-based structure of Battleship. However, we observed occasional hesitation in text rendering, particularly when overwriting strings of varying widths. A more significant issue arose from the VGA VSync and UDP communication sharing the same DMA channel, a constraint we were unable to resolve due to limited DMA flexibility on the RP2040. This led to incomplete rendering of every alternate VGA line, creating a distinct “liney” visual artifact. Despite these challenges, the system maintained accurate behavior in key areas: coordinates were correctly converted to and from screen pixel values, sound frequencies for effects matched expectations, splash at 11kHz and boom at 16kHz and VGA signal timing remained properly synced with correct color output. These results reflect a well-functioning real-time embedded system, with room for optimization in visual rendering and DMA channel allocation.

    To ensure reliable and error-free operation, we implemented several safety mechanisms in the design. Button inputs were handled using edge-triggered detection for the welcome and difficulty level pages, effectively eliminating issues caused by bouncing and false triggers. This ensured that only intentional presses were registered during gameplay. Additionally, boundary checks were enforced throughout the interface logic, preventing the cursor from moving outside the visible display area and avoiding invalid ship placements or attack coordinates. These safeguards contributed to a more stable and predictable user experience.

    The system was designed with usability in mind, closely mimicking the familiar layout and flow of the original Battleship board game. The joystick and buttons provided an intuitive input method, making ship placement and attacks simple and responsive. Clear text prompts were displayed at each stage of the game, guiding users through setup, gameplay, and turn transitions. This ensured that both the developers and new users could interact with the system effectively, with minimal instruction or prior exposure.

    Conclusion

    The final product met our expectations by delivering a fully functional, multiplayer Battleship game on microcontrollers. The implementation of a finite state machine (FSM) for managing turns, real-time graphical feedback on a VGA display, and two distinct sound paths for hits and misses significantly enhanced the interactivity and user experience. In retrospect, there are several areas we could improve in future iterations. These include adding smoother ship rotation and placement controls for better usability, switching from UDP to TCP to ensure guaranteed message delivery, and incorporating a more robust wireless pairing and retry mechanism to handle network disruptions. From a standards perspective, the design adhered to VGA timing specifications and made use of the UDP/IP stack over WiFi, as supported by the PicoW SDK. Additionally, throughout the project we learned the importance of organizing our time effectively and planning our work in a more systematic manner, which proved critical to staying on track and managing the complexity of a multi-threaded, real-time embedded system.
    Team Photo

    Appendix A

    Project on the course page:
    The group approves this report for inclusion on the course website.

    Project on the course YouTube channel:
    The group approves the video for inclusion on the course YouTube channel.

    Appendix - Code

    Battleship Game Main Code
    
    
    //==================================================
    // BATTLESHIP MAIN
    //==================================================
    
    #include "Battleship_main.h"
    
    using namespace std;
    GameBoard playerBoard;
    
    // the color of the boid
    char color = WHITE;
    
    // Boid on core 0
    fix15 cursorpos_x;
    fix15 cursorpos_y;
    fix15 cursorpos_cx;
    fix15 cursorpos_cy;
    
    // Boid on core 1
    fix15 cursorpos_prev_x = 0;
    fix15 cursorpos_prev_y = 0;
    
    // =================== declare DMA chains =========
    int data_chan_boom;
    int ctrl_chan_boom;
    int data_chan_splash;
    int ctrl_chan_splash;
    
    // =========================== JOYSTICK - CURSOR =================
    
    void spawnCursor(fix15 *x, fix15 *y, fix15 *vx, fix15 *vy)
    {
    	*x = int2fix15(77);
    	*y = int2fix15(79);
    	*vx = int2fix15(0);
    	*vy = int2fix15(0);
    }
    
    void checkPOS(fix15 *x, fix15 *y, fix15 *vx, fix15 *vy)
    {
    	if (gpio_get(JOY_RIGHT) == 0)
    	{
    	*vx = int2fix15(-STRIDE_LENGTH);
    	*vy = int2fix15(0);
    	}
    	else if (gpio_get(JOY_LEFT) == 0)
    	{
    	*vx = int2fix15(STRIDE_LENGTH);
    	*vy = int2fix15(0);
    	}
    	else if (gpio_get(JOY_UP) == 0)
    	{
    	*vx = int2fix15(0);
    	*vy = int2fix15(STRIDE_LENGTH);
    	}
    	else if (gpio_get(JOY_DOWN) == 0)
    	{
    	*vx = int2fix15(0);
    	*vy = int2fix15(-STRIDE_LENGTH);
    	}
    	else
    	{
    	*vx = int2fix15(0);
    	*vy = int2fix15(0);
    	}
    	*x = *x + *vx;
    	*y = *y + *vy;
    
    	sleep_ms(200);
    }
    
    void wrapCursor(fix15 *x_pos, fix15 *y_pos)
    {
    	// Wrap around horizontally
    	if (*x_pos > int2fix15(SCREEN_WIDTH))
    	{
    	*x_pos = int2fix15(0);
    	}
    	else if (*x_pos < int2fix15(0))
    	{
    	*x_pos = int2fix15(SCREEN_WIDTH);
    	}
    
    	// Wrap around vertically
    	if (*y_pos > int2fix15(SCREEN_HEIGHT))
    	{
    	*y_pos = int2fix15(0);
    	}
    	else if (*y_pos < int2fix15(0))
    	{
    	*y_pos = int2fix15(SCREEN_HEIGHT);
    	}
    }
    
    
    void drawTextforGameState(PlayerState my_state_1)
    {
    
    	setTextColor(BLUE);
    	setTextSize(2);
    	static char status[50];
    
    	// Display my state
    	fillRect(START_GAME_X - 1, START_GAME_Y - 3, 120, 20, BLACK);
    	setCursor(START_GAME_X, START_GAME_Y);
    	switch (my_state_1)
    	{
    	case YT:
    		sprintf(status, "YOUR TURN");
    		break;
    	case RR:
    		sprintf(status, "WAITING RESPONSE");
    		break;
    	case RA:
    		sprintf(status, "WAITING FOR ATTACK");
    		break;
    	}
    	writeString(status);
    }
    
    Coordinate8 isInMYGRID(int x_pos, int y_pos)
    {
    	Coordinate8 coord;
    
    	int grid_end_x = LEFT_GRID_X + 10 * GRID_SQUARE_SIZE;
    	int grid_end_y = LEFT_GRID_Y + 10 * GRID_SQUARE_SIZE;
    
    	if (x_pos >= LEFT_GRID_X && x_pos < grid_end_x &&
    		y_pos >= LEFT_GRID_Y && y_pos < grid_end_y)
    	{
    	coord.x = (x_pos - LEFT_GRID_X) / GRID_SQUARE_SIZE;
    	coord.y = (y_pos - LEFT_GRID_Y) / GRID_SQUARE_SIZE;
    	}
    	else
    	{
    	coord.x = 30;
    	coord.y = 30;
    	}
    
    	return coord;
    }
    
    Coordinate8 isInOtherGRID(int x_pos, int y_pos)
    {
    	Coordinate8 coord;
    
    	int grid_end_x = RIGHT_GRID_X + 10 * GRID_SQUARE_SIZE;
    	int grid_end_y = RIGHT_GRID_Y + 10 * GRID_SQUARE_SIZE;
    
    	if (x_pos >= RIGHT_GRID_X && x_pos < grid_end_x &&
    		y_pos >= RIGHT_GRID_Y && y_pos < grid_end_y)
    	{
    	// coord.x = (x_pos - RIGHT_GRID_X) / GRID_SQUARE_SIZE; //Chengle's version
    	coord.x = (x_pos - RIGHT_GRID_X) / GRID_SQUARE_SIZE - 26; // diksh's version
    	coord.y = (y_pos - RIGHT_GRID_Y) / GRID_SQUARE_SIZE;
    	}
    	else
    	{
    	coord.x = 30;
    	coord.y = 30;
    	}
    
    	return coord;
    }
    
    //=========================== IRQ - Button ===================
    volatile bool prev_yellow_button_state = false;
    volatile bool prev_red_button_state = false;
    volatile bool yellow_button_state = false; // new variable with similar name!!!!
    volatile bool red_button_state = false;
    volatile bool yellow_pressed = false; // button change from 0 to 1 --> pressed = 1
    volatile bool red_pressed = false;
    
    uint16_t DAC_data_0;
    int sound_count = 0; 
    bool sound_flag = true;
    
    static void sound_irq(void)
    {
    	gpio_put(SOUND_ISR_GPIO, 1);
    
    	// Clear the alarm irq
    	hw_clear_bits(&timer_hw->intr, 1u << SOUND_ALARM_NUM);
    
    	// Reset the alarm register
    	timer_hw->alarm[SOUND_ALARM_NUM] = timer_hw->timerawl + SOUND_DELAY;
    
    	if(sound_flag){
    	DAC_data_0 = DAC_config_chan_A | (splash_audio[sound_count] & 0x0FFF);
    	spi_write16_blocking(SPI_PORT, &DAC_data_0, 1) ;
    	sound_count ++;
    
    	if(sound_count > splash_audio_len){
    		sound_count = 0;
    		sound_flag = false;
    	}
    	}
    
    	gpio_put(SOUND_ISR_GPIO, 0);
    
    
    }
    
    static void button_irq(void)
    {
    	// Assert a GPIO when we enter the interrupt
    	gpio_put(ISR_GPIO, 1);
    
    	// Clear the alarm irq
    	hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);
    
    	// Reset the alarm register
    	timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + DELAY;
    
    	// ------ MAIN FUNCTION HERE ----------
    	// update previous state
    	prev_yellow_button_state = yellow_button_state;
    	prev_red_button_state = red_button_state;
    
    	// set current state
    	yellow_button_state = gpio_get(BUT_PIN_Y);
    	red_button_state = gpio_get(BUT_PIN_R);
    
    	// by default, we assume no button change from 0 to 1
    	yellow_pressed = false;
    	red_pressed = false;
    
    	// onlt 0->1 is press
    	if (prev_yellow_button_state != yellow_button_state)
    	{
    	// printf("Yellow: from %b to %b", yellow_button_state, y_state);
    	if (yellow_button_state == 1)
    	{
    		yellow_pressed = true;
    		printf("\nyellow pressed");
    	}
    	}
    
    	if (prev_red_button_state != red_button_state)
    	{
    	// printf("Red: from %b to %b", red_button_state, r_state);
    	if (red_button_state == 1)
    	{
    		red_pressed = true;
    		printf("\nred pressed");
    	}
    	}
    
    	// ------------------------------------
    	// De-assert the GPIO when we leave the interrupt
    	gpio_put(ISR_GPIO, 0);
    }
    						
    // ==================================================
    // === Animation Thread
    // ==================================================
    int intcursor_x;
    int intcursor_y;
    
    static PT_THREAD(protothread_anim(struct pt *pt))
    {
    	// Mark beginning of thread
    	PT_BEGIN(pt);
    
    	static int begin_time;
    	static int spare_time;
    
    	static char buffer1[50];
    
    	// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy); // Spawn a boid at (20,20,0,0)
    	// moveCursor(&cursorpos_prev_x, &cursorpos_prev_y, cursorpos_x, cursorpos_y, color);
    
    	uint8_t prev_val = 0;
    	uint8_t val_ship = 0;
    	bool select_flag = false;
    	uint8_t ctr_ship = 0;
    	int ctr_button = 0;
    	// static bool prev_left_button_state = false;
    	// bool curr_left_button_state = false;
    	// static bool prev_right_button_state = false;
    	// bool curr_right_button_state = false;
    
    	// PT_SEM_SAFE_WAIT(pt, &new_message);
    
    	while (1)
    	{
    	// Measure time at start of thread
    	begin_time = time_us_32();
    
    	// Each Page Working
    	if (playerBoard.game_status == GAME_STATUS::INITIAL)
    	{
    		if (yellow_pressed)
    		{
    		welcomeText(BLACK);
    
    		playerBoard.game_status = GAME_STATUS::LEVEL;
    
    		raw_send(GAME_STATUS::LEVEL, GRID_STATE::WATER, {(uint8_t)intcursor_x, (uint8_t)intcursor_y}, 1);
    
    		difficultyChoose(YELLOW, RED, 0, 1);
    		printf("\nGAME_STATUS: %d", playerBoard.game_status_check());
    
    		yellow_pressed = false; // avoid skip difficulty level
    		}
    	}
    	else if (playerBoard.game_status == GAME_STATUS::LEVEL)
    	{
    
    		// "Easy" == Yellow button
    		if (yellow_pressed)
    		{
    		difficultyChoose(YELLOW, BLUE, 0, 0);
    		playerBoard.game_status = GAME_STATUS::PLACE;
    		fillRect(0,0,320,240,BLACK);
    		drawGRID(BOARD_SIZE, LEFT_GRID_X, LEFT_GRID_Y, GRID_OUTLINE, BLUE);
    		drawGridDim(LEFT_GRID_X, LEFT_GRID_Y, WHITE);
    		drawTextforShip(YELLOW, BLUE, 1);
    		printf("\nGAME_STATUS: %d", playerBoard.game_status_check());
    
    		spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy); // Spawn a boid
    		// drawCursor(fix2int15(cursorpos_x), fix2int15(cursorpos_y), color);
    		moveCursor(&cursorpos_prev_x, &cursorpos_prev_y, cursorpos_x, cursorpos_y, color);
    
    		yellow_pressed = false;
    		}
    
    		//"Hard" == Red button
    		if (red_pressed)
    		{
    		difficultyChoose(YELLOW, BLUE, 0, 0);
    		playerBoard.game_status = GAME_STATUS::PLACE;
    		fillRect(0,0,320,240,BLACK);
    		drawGRID(BOARD_SIZE, LEFT_GRID_X, LEFT_GRID_Y, GRID_OUTLINE, BLUE);
    		drawGridDim(LEFT_GRID_X, LEFT_GRID_Y, WHITE);
    		drawTextforShip(YELLOW, BLUE, 1);
    		printf("\nGAME_STATUS: %d", playerBoard.game_status_check());
    
    		spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy); // Spawn a boid
    		// drawCursor(fix2int15(cursorpos_x), fix2int15(cursorpos_y), color);
    		moveCursor(&cursorpos_prev_x, &cursorpos_prev_y, cursorpos_x, cursorpos_y, color);
    
    		red_pressed = false;
    		}
    	}
    	else if (playerBoard.game_status == GAME_STATUS::PLACE)
    	{
    		checkPOS(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    		wrapCursor(&cursorpos_x, &cursorpos_y);
    
    		if (cursorpos_prev_x != cursorpos_x | cursorpos_prev_y != cursorpos_y)
    		{
    		void drawBoundary();
    		printf("\nOpponentGame Status %d", opponent_player);
    
    		fillRect(10, SCREEN_HEIGHT - 10, 100, 10, BLACK);
    		sprintf(buffer1, "x: %d, y: %d", fix2int15(cursorpos_x), fix2int15(cursorpos_y));
    		setCursor(10, SCREEN_HEIGHT - 10);
    		setTextColor(WHITE);
    		setTextSize(1);
    		writeString(buffer1);
    
    		// drawCursor(fix2int15(cursorpos_prev_x), fix2int15(cursorpos_prev_y), BLACK);
    		// cursorpos_prev_x = cursorpos_x;
    		// cursorpos_prev_y = cursorpos_y;
    		moveCursor(&cursorpos_prev_x, &cursorpos_prev_y, cursorpos_x, cursorpos_y, color);
    		}
    
    		intcursor_x = fix2int15(cursorpos_x);
    		intcursor_y = fix2int15(cursorpos_y);
    
    		const char *shipname;
    		if (ctr_ship != 5)
    		{
    
    		if (select_flag == false)
    		{
    			val_ship = checkCursorOverShip(intcursor_x, intcursor_y);
    			// printf("val_ship = %d\n", val_ship);
    			// printf("prev_val = %d\n", prev_val);
    			if (val_ship != 0 && prev_val != val_ship && yellow_button_state && prev_yellow_button_state)
    			{
    			printf("pass through\n");
    			if (val_ship == 1)
    			{
    				shipname = "Carrier    (5)";
    				drawBoxforShip(RED, RIGHT_GRID_X, SHIPLIST_SPACE_Carrier, shipname);
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				prev_val = val_ship;
    				select_flag = true;
    				// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    			}
    			else if (val_ship == 2)
    			{
    				printf("select batteship");
    				shipname = "Battleship (4)";
    				drawBoxforShip(RED, RIGHT_GRID_X, SHIPLIST_SPACE_Battleship, shipname);
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				prev_val = val_ship;
    				select_flag = true;
    				// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    			}
    			else if (val_ship == 3)
    			{
    				shipname = "Cruiser    (3)";
    				drawBoxforShip(RED, RIGHT_GRID_X, SHIPLIST_SPACE_Cruiser, shipname);
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				prev_val = val_ship;
    				select_flag = true;
    				// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    			}
    			else if (val_ship == 4)
    			{
    				shipname = "Submarine  (3)";
    				drawBoxforShip(RED, RIGHT_GRID_X, SHIPLIST_SPACE_Submarine, shipname);
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				prev_val = val_ship;
    				select_flag = true;
    				// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    			}
    			else if (val_ship == 5)
    			{
    				shipname = "Destroyer  (2)";
    				drawBoxforShip(RED, RIGHT_GRID_X, SHIPLIST_SPACE_Destroyer, shipname);
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				prev_val = val_ship;
    				select_flag = true;
    				// spawnCursor(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy);
    			}
    			}
    		}
    		else if (select_flag)
    		{
    			if (val_ship != 0)
    			{
    			Coordinate8 grid_pos = isInMYGRID(intcursor_x, intcursor_y);
    			// printf("prev:%x, curr:%x", prev_right_button_state, curr_right_button_state);
    
    			// Check if we're within the grid (i.e., not the sentinel value 30,30)
    			if (yellow_button_state && prev_yellow_button_state && !(grid_pos.x == 30 && grid_pos.y == 30))
    			{
    				
    				char encoded[4]; // Enough space for something like "A10" + null terminator
    				encodeCoord(grid_pos, encoded);
    				printf("\nEncoded:%c %c, VAL_SHIP:%d", encoded[0], encoded[1], val_ship - 1);
    				bool success = playerBoard.place_ship(SHIP_TYPE(val_ship - 1), SHIP_ORIENTATION::HORIZONTAL, grid_pos);
    
    				if (success)
    				{
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				ctr_ship++;          // Increase placed ship counter
    				select_flag = false; // Reset flag to allow next ship to be selected
    				printf("Ship placed successfully at %d,%d\n", grid_pos.x, grid_pos.y);
    				}
    				else
    				printf("Invalid position. Try again.\n");
    			}
    			// Check if we're within the grid (i.e., not the sentinel value 30,30)
    			else if (red_button_state && prev_red_button_state && !(grid_pos.x == 30 && grid_pos.y == 30))
    			{
    				char encoded[4]; // Enough space for something like "A10" + null terminator
    				encodeCoord(grid_pos, encoded);
    				printf("\nEncoded:%c %c, VAL_SHIP:%d", encoded[0], encoded[1], val_ship - 1);
    				bool success = playerBoard.place_ship(SHIP_TYPE(val_ship - 1), SHIP_ORIENTATION::VERTICAL, grid_pos);
    
    				if (success)
    				{
    
    				moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    				ctr_ship++;          // Increase placed ship counter
    				select_flag = false; // Reset flag to allow next ship to be selected
    				printf("Ship placed successfully at %d,%d\n", grid_pos.x, grid_pos.y);
    				}
    				else
    				printf("Invalid position. Try again.\n");
    			}
    			}
    		}
    		}
    		else
    		{
    		// printf("\nReached here finish placement");
    		if (checkCursorOverStartGame(intcursor_x, intcursor_y) && yellow_button_state && prev_yellow_button_state)
    		{
    			playerBoard.game_status = GAME_STATUS::ONGOING;
    			raw_send(GAME_STATUS::ONGOING, GRID_STATE::WATER, {(uint8_t)intcursor_x, (uint8_t)intcursor_y}, 1);
    			printf("\nGAME_STATUS: %d", playerBoard.game_status_check());
    			drawTextforShip(BLACK, BLACK, 1);
    			drawBoxforStartGame(BLACK);
    
    			moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    
    			drawBlackBoxforShip();
    			drawGRID(BOARD_SIZE, RIGHT_GRID_X, RIGHT_GRID_Y, GRID_OUTLINE, BLUE);
    			drawGridDim(RIGHT_GRID_X, RIGHT_GRID_Y, WHITE);
    		}
    		}
    	}
    	else if (playerBoard.game_status == GAME_STATUS::ONGOING)
    	{
    		// ------------- Check Cursor Movement --------------
    		checkPOS(&cursorpos_x, &cursorpos_y, &cursorpos_cx, &cursorpos_cy); // update cursor loctaion
    		wrapCursor(&cursorpos_x, &cursorpos_y);                             // avoid go out of boundary
    
    		if (cursorpos_prev_x != cursorpos_x | cursorpos_prev_y != cursorpos_y)
    		{
    		drawTextforGameState(my_state);
    		// void drawBoundary();
    		printf("\nOpponentGame Status %d", opponent_player);
    
    		fillRect(10, SCREEN_HEIGHT - 10, 100, 10, BLACK);
    		sprintf(buffer1, "x: %d, y: %d", fix2int15(cursorpos_x), fix2int15(cursorpos_y));
    		setCursor(10, SCREEN_HEIGHT - 10);
    		setTextColor(WHITE);
    		setTextSize(1);
    		writeString(buffer1);
    
    		moveCursor(&cursorpos_prev_x, &cursorpos_prev_y, cursorpos_x, cursorpos_y, color);
    
    		} // End if cursor moved
    
    		intcursor_x = fix2int15(cursorpos_x);
    		intcursor_y = fix2int15(cursorpos_y);
    
    		// printf("\nOpponentGame Status %d", opponent_player);
    		// printf("\nMY_STATE%x", my_state);
    		Coordinate8 posn;
    		posn.x = uint8_t(intcursor_x);
    		posn.y = uint8_t(intcursor_y);
    
    		if (opponent_player == GAME_STATUS::ONGOING)
    		{
    		Coordinate8 grid_pos = isInOtherGRID(intcursor_x, intcursor_y);
    		// PRINT HERE TURN RESULT ALSO
    		if (my_state == YT)
    		{
    			if (!(grid_pos.x == 30 && grid_pos.y == 30) && prev_yellow_button_state && yellow_button_state)
    			{
    			drawPegPotentialShip(grid_pos.x, grid_pos.y);
    			moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    			printf("\nEntered send attack segment");
    			raw_send(GAME_STATUS::ONGOING, GRID_STATE::REPEAT, grid_pos, 3);
    			my_state = RR;
    			opponent_state = RA;
    			}
    		}
    		else if (my_state == RR && strstr(received_data, "GRID") != NULL)
    		{
    			if (opponent_gridstate == GRID_STATE::HIT)
    			{
    			dma_start_channel_mask(1u << ctrl_chan_boom);
    			drawPegHitRight((int)grid_pos.x, (int)grid_pos.y);
    			moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    			}
    
    			else if (opponent_gridstate == GRID_STATE::MISS)
    			{
    			// dma_start_channel_mask(1u << ctrl_chan_splash);
    			sound_flag = true;
    			drawPegMissRight((int)grid_pos.x, (int)grid_pos.y);
    			moveCursor(&cursorpos_x, &cursorpos_y, cursorpos_x, cursorpos_y, color);
    			}
    
    			my_state = RA;
    			opponent_state = YT;
    		}
    		else if (my_state == RA)
    		{
    			int length_string = strlen(received_data);
    			if (length_string <= 18 && length_string > 16)
    			{
    			GRID_STATE my_GRID = playerBoard.attack(our_shippos);
    			if (playerBoard.all_ships_sunk())
    			{
    				raw_send(GAME_STATUS::WIN, GRID_STATE::HIT, posn, 1);
    				playerBoard.game_status = GAME_STATUS::LOSE;
    			}
    			else
    			{
    				raw_send(GAME_STATUS::ONGOING, my_GRID, posn, 2);
    				my_state = YT;
    				opponent_state = RA;
    			}
    			}
    		}
    		}
    		else if (opponent_player == GAME_STATUS::LOSE)
    		{
    		playerBoard.game_status = GAME_STATUS::WIN;
    		fillRect(0, 0, 320, 240, BLACK);
    		winnerDeclare(YELLOW);
    		break;
    		}
    		else
    		{
    		raw_send(GAME_STATUS::ONGOING, GRID_STATE::WATER, posn, 1);
    		}
    	}
    
    	else if (playerBoard.game_status == GAME_STATUS::LOSE)
    	{
    		raw_send(GAME_STATUS::LOSE, GRID_STATE::HIT, {30, 30}, 1);
    		fillRect(0, 0, 320, 240, BLACK);
    		loserDeclare(WHITE);
    		break;
    	}
    
    	spare_time = FRAME_RATE - (time_us_32() - begin_time);
    	} // END WHILE(1)
    	PT_END(pt);
    
    } // animation thread
    
    // ==================================================
    // === CORE 1 MAIN
    // ==================================================
    void core1_main()
    {
    	initVGA();
    	welcomeText(YELLOW);
    	// raw_send_test();
    	pt_add_thread(protothread_anim);
    	// pt_add_thread(protothread_serial);
    	pt_schedule_start;
    }
    
    // ========================================
    // === main
    // ========================================
    
    int main()
    {
    	stdio_init_all();
    	printf("Start main\n");
    
    	gpio_init(LED_PIN);
    	gpio_set_dir(LED_PIN, GPIO_OUT);
    
    	// initialize button
    	gpio_init(BUT_PIN_Y);
    	gpio_set_dir(BUT_PIN_Y, GPIO_IN);
    	gpio_pull_up(BUT_PIN_Y);
    
    	gpio_init(BUT_PIN_R);
    	gpio_set_dir(BUT_PIN_R, GPIO_IN);
    	gpio_pull_up(BUT_PIN_R);
    
    	// initialize Joystick
    	//  set up gpio 4 for joystick button
    	gpio_init(JOY_RIGHT); // right
    	gpio_init(JOY_LEFT);  // left
    	gpio_init(JOY_UP);    // up
    	gpio_init(JOY_DOWN);  // down
    
    	gpio_set_dir(JOY_RIGHT, GPIO_IN);
    	gpio_set_dir(JOY_LEFT, GPIO_IN);
    	gpio_set_dir(JOY_UP, GPIO_IN);
    	gpio_set_dir(JOY_DOWN, GPIO_IN);
    
    	// pullup ON, pulldown OFF
    	gpio_pull_up(JOY_RIGHT);
    	gpio_pull_up(JOY_LEFT);
    	gpio_pull_up(JOY_UP);
    	gpio_pull_up(JOY_DOWN);
    
    	// Setup the ISR-timing GPIO
    	gpio_init(ISR_GPIO);
    	gpio_set_dir(ISR_GPIO, GPIO_OUT);
    	gpio_put(ISR_GPIO, 0);
    
    	// Enable the interrupt for the alarm (we're using Alarm 0)
    	hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM);
    	// Associate an interrupt handler with the ALARM_IRQ
    	irq_set_exclusive_handler(ALARM_IRQ, button_irq);
    	// Enable the alarm interrupt
    	irq_set_enabled(ALARM_IRQ, true);
    	// Write the lower 32 bits of the target time to the alarm register, arming it.
    	timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + DELAY;
    
    	// playerBoard.game_status = GAME_STATUS::PLACE;
    	printf("\nGAME_STATUS: %d", playerBoard.game_status_check());
    
    	memset(received_data, 0, BEACON_MSG_LEN_MAX); // clean received_data content (remove garbge)
    	// Connect to WiFi
    	if (connectWifi(country, WIFI_SSID, WIFI_PASSWORD, auth))
    	{
    	printf("Failed connection.\n");
    	}
    	else
    	{
    
    	printf("My IP is: %s\n", ip4addr_ntoa(netif_ip_addr4(netif_default)));
    	}
    
    	// Initialize semaphore
    	PT_SEM_INIT(&new_message, 0);
    	PT_SEM_INIT(&ready_to_send, 0);
    
    	//============================
    	// UDP recenve ISR routines
    	udpecho_raw_init();
    
    	// Initialize SPI channel (channel, baud rate set to 20MHz)
    	spi_init(SPI_PORT, 20000000);
    
    	// Format SPI channel (channel, data bits per transfer, polarity, phase, order)
    	spi_set_format(SPI_PORT, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_LSB_FIRST);
    
    	// Map SPI signals to GPIO ports, acts like framed SPI with this CS mapping
    	gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    	gpio_set_function(PIN_CS, GPIO_FUNC_SPI);
    	gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    	gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    
    	// Build sine table and DAC data table
    	int i;
    	for (i = 0; i < (sine_table_size); i++)
    	{
    	raw_sin[i] = (int)(2047 * sin((float)i * 6.283 / (float)sine_table_size) + 2047); // 12 bit
    	DAC_data[i] = DAC_config_chan_A | (raw_sin[i] & 0x0fff);
    	}
    
    	int k;
    	for (k = 0; k < splash_audio_len; k++)
    	{
    	splash_data[k] = DAC_config_chan_A | (splash_audio[k] & 0x0FFF); // first convert to 12 bit then add config
    	}
    
    	int j;
    	for (j = 0; j < boom_audio_len; j++)
    	{
    	boom_data[j] = DAC_config_chan_A | (boom_audio[j] & 0x0FFF); // first convert to 12 bit then add config
    	}
    
    	// ============================== IRQ SOUND     ===========================
    	// // SOUND----------------------Setup the ISR-timing GPIO
    	gpio_init(SOUND_ISR_GPIO);
    	gpio_set_dir(SOUND_ISR_GPIO, GPIO_OUT);
    	gpio_put(SOUND_ISR_GPIO, 0);
    
    	// Enable the interrupt for the alarm (we're using Alarm 0)
    	hw_set_bits(&timer_hw->inte, 1u << SOUND_ALARM_NUM);
    	// Associate an interrupt handler with the ALARM_IRQ
    	irq_set_exclusive_handler(SOUND_ALARM_IRQ, sound_irq);
    	// Enable the alarm interrupt
    	irq_set_enabled(SOUND_ALARM_IRQ, true);
    	// Write the lower 32 bits of the target time to the alarm register, arming it.
    	timer_hw->alarm[SOUND_ALARM_NUM] = timer_hw->timerawl + SOUND_DELAY;
    	// ==================================================
    
    	// SPLASH
    	//  Select DMA channels
    	data_chan_splash = dma_claim_unused_channel(true);
    	;
    	ctrl_chan_splash = dma_claim_unused_channel(true);
    	;
    
    	// Setup the control channel
    	dma_channel_config c8 = dma_channel_get_default_config(ctrl_chan_splash); // default configs
    	channel_config_set_transfer_data_size(&c8, DMA_SIZE_32);                  // 32-bit txfers
    	channel_config_set_read_increment(&c8, false);                            // no read incrementing
    	channel_config_set_write_increment(&c8, false);                           // no write incrementing
    	channel_config_set_chain_to(&c8, data_chan_splash);                       // chain to data channel
    
    	dma_channel_configure(
    		ctrl_chan_splash,                        // Channel to be configured
    		&c8,                                     // The configuration we just created
    		&dma_hw->ch[data_chan_splash].read_addr, // Write address (data channel read address)
    		&splash_pointer,                         // Read address (POINTER TO AN ADDRESS)
    		1,                                       // Number of transfers
    		false                                    // Don't start immediately
    	);
    
    	// Setup the data channel
    	dma_channel_config c9 = dma_channel_get_default_config(data_chan_splash); // Default configs
    	channel_config_set_transfer_data_size(&c9, DMA_SIZE_16);                  // 16-bit txfers
    	channel_config_set_read_increment(&c9, true);                             // yes read incrementing
    	channel_config_set_write_increment(&c9, false);                           // no write incrementing
    	// (X/Y)*sys_clk, where X is the first 16 bytes and Y is the second
    	// sys_clk is 125 MHz unless changed in code. Configured to ~44 kHz
    	// dma_timer_set_fraction(0, 0x0017, 0xffff);
    	dma_timer_set_fraction(1, 0x005, 0XDf00); // ~11025Hz
    	// dma_timer_set_fraction(1, 0x005, 0XDf00); // 16kHz
    
    	// 0x3b means timer0 (see SDK manual)
    	channel_config_set_dreq(&c9,0x3d);// 0x3d); // DREQ paced by timer 1
    	// chain to the controller DMA channel
    	// channel_config_set_chain_to(&c9, ctrl_chan_splash); // Chain to control channel
    
    	dma_channel_configure(
    		data_chan_splash,          // Channel to be configured
    		&c9,                       // The configuration we just created
    		&spi_get_hw(SPI_PORT)->dr, // write address (SPI data register)
    		splash_data,               // The initial read address
    		splash_audio_len,          // Number of transfers
    		false                      // Don't start immediately.
    	);
    
    	// BOOM
    	//  Select DMA channels
    	data_chan_boom = dma_claim_unused_channel(true);
    	;
    	ctrl_chan_boom = dma_claim_unused_channel(true);
    	;
    
    	// Setup the control channel
    	dma_channel_config c6 = dma_channel_get_default_config(ctrl_chan_boom); // default configs
    	channel_config_set_transfer_data_size(&c6, DMA_SIZE_32);                // 32-bit txfers
    	channel_config_set_read_increment(&c6, false);                          // no read incrementing
    	channel_config_set_write_increment(&c6, false);                         // no write incrementing
    	channel_config_set_chain_to(&c6, data_chan_boom);                       // chain to data channel
    
    	dma_channel_configure(
    		ctrl_chan_boom,                        // Channel to be configured
    		&c6,                                   // The configuration we just created
    		&dma_hw->ch[data_chan_boom].read_addr, // Write address (data channel read address)
    		&boom_pointer,                         // Read address (POINTER TO AN ADDRESS)
    		1,                                     // Number of transfers
    		false                                  // Don't start immediately
    	);
    
    	// Setup the data channel
    	dma_channel_config c7 = dma_channel_get_default_config(data_chan_boom); // Default configs
    	channel_config_set_transfer_data_size(&c7, DMA_SIZE_16);                // 16-bit txfers
    	channel_config_set_read_increment(&c7, true);                           // yes read incrementing
    	channel_config_set_write_increment(&c7, false);                         // no write incrementing
    	// (X/Y)*sys_clk, where X is the first 16 bytes and Y is the second
    	// sys_clk is 125 MHz unless changed in code. Configured to ~44 kHz
    	// dma_timer_set_fraction(0, 0x0017, 0xffff);
    	//  dma_timer_set_fraction(0, 0x005, 0XDf00); // ~11025Hz
    	dma_timer_set_fraction(0, 0x0008, 0xf500); // 16kHz
    
    	// 0x3b means timer0 (see SDK manual)
    	channel_config_set_dreq(&c7, 0x3b); // DREQ paced by timer 0
    	// chain to the controller DMA channel
    	// channel_config_set_chain_to(&c2, ctrl_chan); // Chain to control channel
    
    	dma_channel_configure(
    		data_chan_boom,            // Channel to be configured
    		&c7,                       // The configuration we just created
    		&spi_get_hw(SPI_PORT)->dr, // write address (SPI data register)
    		boom_data,                 // The initial read address
    		boom_audio_len,            // Number of transfers
    		false                      // Don't start immediately.
    	);
    
    	// dma_start_channel_mask(1u << ctrl_chan_splash);
    
    	multicore_reset_core1();
    	multicore_launch_core1(&core1_main);
    
    	// sleep_ms(100);
    	pt_add_thread(protothread_send);
    	pt_add_thread(protothread_receive);
    
    	// start scheduler
    	pt_schedule_start;
    }
    									

    References

    1. Battle Ship Game Play rules and definitions
    2. Setting up two player arcade joystick
    3. Rpi Pico W - UDP Protocol references
    4. Dr. Hunter Adams - Course Website
    5. Dr. Bruce Land Website - RP2040 development
    Acknowledgement: We would like to thank Dr. Hunter Adams, Dr. Bruce Land and all the TAs of the lab. They were extremely helpful and insightful with their help and guidance. Some parts of this project involved the use of AI , namely for clarification and content enhancement.