Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion include/display.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@

#include <string.h>

enum TaskOpt {
DISPLAY_CLEAR,
DISPLAY_WRITE
};

typedef struct {
char* text;
enum TaskOpt taskType;
char text[17];
int page;
} displayQueue_t;

Expand Down
20 changes: 20 additions & 0 deletions include/main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#define sda_pin 21
#define scl_pin 20
#define frequency 400000

void setup_i2c();
void start_game();
void cycle_choice();
void confirm_choice();

enum GameState {
STATE_MENU,
STATE_PLAYING,
STATE_OVER_LOST,
STATE_OVER_WIN,
};

enum ActionType {
Action_Select,
Action_Confirm,
};
26 changes: 18 additions & 8 deletions include/sound.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

#define SAMPLE_RATE 8000

enum soundOpts {
SOUND_MAIN_MENU,
SOUND_DRAW,
SOUND_LOST,
SOUND_WIN,
SOUND_SELECT
};

void init_sound();
void sample_start();
void sample_stop();

void play_main_menu_sound();
void play_draw_sound();
void play_lost_sound();
void play_win_sound();
void play_select_sound();

// void play_main_menu_sound();
// void play_draw_sound();
// void play_lost_sound();
// void play_win_sound();
// void play_select_sound();


void play_sound(enum soundOpts soundType);

2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ board = esp32-c6-devkitc-1
framework = espidf
monitor_speed = 115200
board_upload.flash_size=8MB
monitor_filters = esp32_exception_decoder
build_type = release
6 changes: 3 additions & 3 deletions sdkconfig.esp32-c6-devkitc-1.old
Original file line number Diff line number Diff line change
Expand Up @@ -1253,9 +1253,9 @@ CONFIG_ESP_SYSTEM_PANIC_GDBSTUB=y
CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE=y
CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y
CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y
CONFIG_ESP_SYSTEM_NO_BACKTRACE=y
# CONFIG_ESP_SYSTEM_NO_BACKTRACE is not set
# CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set
# CONFIG_ESP_SYSTEM_USE_FRAME_POINTER is not set
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y

#
# Memory protection
Expand Down Expand Up @@ -1287,7 +1287,7 @@ CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
CONFIG_ESP_TASK_WDT_EN=y
CONFIG_ESP_TASK_WDT_INIT=y
# CONFIG_ESP_TASK_WDT_PANIC is not set
CONFIG_ESP_TASK_WDT_TIMEOUT_S=5
CONFIG_ESP_TASK_WDT_TIMEOUT_S=10
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
# CONFIG_ESP_PANIC_HANDLER_IRAM is not set
# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set
Expand Down
12 changes: 6 additions & 6 deletions src/btnctrl.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ void btnctrl_init() {

// Register button events for select and confirm actions
void btnctrl_register_event(button_cb_t select_cb, button_cb_t confirm_cb) {
if (select_cb != NULL) {
if (select_cb != NULL)
iot_button_register_cb(select_btn, BUTTON_SINGLE_CLICK, NULL, select_cb, NULL);
}

if (confirm_cb != NULL) {
if (confirm_cb != NULL)
iot_button_register_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL, confirm_cb, NULL);
}
}

// Unregister button events (used when transitioning between screens to prevent unintended actions)
void btnctrl_unregister_event() {
iot_button_unregister_cb(select_btn, BUTTON_SINGLE_CLICK, NULL);
iot_button_unregister_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL);
if(iot_button_count_event_cb(select_btn, BUTTON_SINGLE_CLICK) > 0)
iot_button_unregister_cb(select_btn, BUTTON_SINGLE_CLICK, NULL);
if(iot_button_count_event_cb(confirm_btn, BUTTON_SINGLE_CLICK) > 0)
iot_button_unregister_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL);
}
60 changes: 33 additions & 27 deletions src/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,22 @@ void display_write_queue(void *pvParameters);
void display_init()
{
// Init stuff
writePageQueue = xQueueCreate(32, sizeof(displayQueue_t));
writePageQueue = xQueueCreate(32, sizeof(displayQueue_t*));
// Addr Stuff
dev._address = 0x3C;
dev._flip = false;
ssd1306_init(&dev, 128, 64);
display_clear();
xTaskCreate(display_write_queue, "display_write_queue", configMINIMAL_STACK_SIZE*1.5, NULL, 5, NULL);
xTaskCreate(display_write_queue, "display_write_queue", 4096, NULL, 5, NULL);
}

// Clean the display
void display_clear() {
ssd1306_clear_screen(&dev, false);
// for(int i=0;i<5;i++) {
// displayQueue_t data;
// data.text = malloc(16);
// memset(data.text, 0x0, 16);
// data.page = i;
// xQueueSend(writePageQueue, &data, 0);
// }
//ssd1306_clear_screen(&dev, false);
displayQueue_t* data = calloc(1, sizeof(displayQueue_t));
data->taskType = DISPLAY_CLEAR;
Comment thread
zhiyan114 marked this conversation as resolved.
if(xQueueSend(writePageQueue, &data, 0) != pdPASS)
free(data);
}

// Render text on the display
Expand All @@ -50,30 +47,27 @@ void display_text(char* text) {

// Write text to a specific line on the display (isCenter is used to center the text on the line)
void display_write_page(const char* text, int page, int isCenter) {
displayQueue_t* data = calloc(1, sizeof(displayQueue_t));
data->taskType = DISPLAY_WRITE;
data->page = page;

// Get the length and allocate the space
size_t text_len = strlen(text); // Each line only supports 16 characters
char* strArr = (char*)malloc(sizeof(char)*16);
text_len = text_len > 16 ? 16 : text_len;

// Check if the text needs to be centered
if(isCenter == 1) {
int padLen = ceil((16 - text_len) / 2);
for(int i = 0; i < padLen; i++)
strArr[i] = ' ';
data->text[i] = ' ';
for(int i = padLen; i < padLen+text_len; i++)
strArr[i] = text[i - padLen];
data->text[i] = text[i - padLen];
text_len+=padLen;

// Fill in the rest of the string with byte 0
for(int i = text_len; i < 16; i++)
strArr[i] = '\0';
} else strncpy(strArr, text, 16);
} else strncpy(data->text, text, 16);

// Add it to the queue
displayQueue_t data;
data.text = strArr;
data.page = page;
xQueueSend(writePageQueue, &data, 0);
if(xQueueSend(writePageQueue, &data, 0) != pdPASS)
free(data);
}

/*
Expand All @@ -82,15 +76,27 @@ Executing ssd1306 display command simultaneously causes the display to glitch ou
*/
void display_write_queue(void *pvParameters) {
while(1) {
displayQueue_t data;
displayQueue_t* data;
if(xQueueReceive(writePageQueue, &data, portMAX_DELAY) == pdTRUE) {
/* Handler Stuff Here */
// Dedicated Clear Task
if(data->taskType == DISPLAY_CLEAR) {
ssd1306_clear_screen(&dev, false);
free(data);
continue;
}

/* Write Task */
if(data->taskType != DISPLAY_WRITE) {
free(data);
continue; // Might be bad memory data?
}

char zeros[16] = {0};
// The reason to write 0s is because the display sometime persist the text from previous queue and 0s somehow stops this issue
ssd1306_display_text(&dev, data.page, zeros, 16, false);
ssd1306_display_text(&dev, data->page, zeros, 16, false);
vTaskDelay(pdMS_TO_TICKS(10));
ssd1306_display_text(&dev, data.page, data.text, 16, false);
free(data.text);
ssd1306_display_text(&dev, data->page, data->text, 16, false);
free(data);
}
else
vTaskDelay(pdMS_TO_TICKS(5));
Expand Down
80 changes: 56 additions & 24 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "main.h"
#include <display.h>
#include <esp_log.h>
#include <driver/i2c.h>
Expand All @@ -8,19 +9,50 @@
#include <sound.h>
#include <esp_random.h>

#define sda_pin 21
#define scl_pin 20
#define frequency 400000

void setup_i2c();
void start_game();
void cycle_choice();
void confirm_choice();

rps_choice playerChoice = ROCK; // Default choice is rock, player can cycle through choices
enum GameState currentState = STATE_MENU;

// Game flow: main menu -> game screen -> result screen -> back to game screen (if not win)

QueueHandle_t actionQueue;

void queueHandle() {
while(1) {
enum ActionType actionOpt;
if(xQueueReceive(actionQueue, &actionOpt, portMAX_DELAY) == pdTRUE) {
if(actionOpt == Action_Confirm) {
switch(currentState) {
case STATE_MENU:
start_game();
break;
case STATE_PLAYING:
confirm_choice();
break;
case STATE_OVER_LOST:
start_game();
break;
case STATE_OVER_WIN:
break;
}
}
if(actionOpt == Action_Select && currentState == STATE_PLAYING)
Comment on lines +28 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Rapidly pressing the confirm button queues multiple actions. The sequential processing of these actions causes the result screen to be immediately overwritten by the new game screen.
Severity: HIGH

Suggested Fix

To prevent this, consider clearing the action queue after a significant state transition. For example, after processing an action that moves the game from STATE_PLAYING to STATE_OVER_LOST/STATE_OVER_WIN, call xQueueReset(actionQueue) to discard any stale, queued-up actions. Alternatively, add a state check before sending actions to the queue, or introduce a short delay or a "cooldown" state after showing the result screen to prevent immediate restarts.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/main.c#L28-L38

Potential issue: Due to the use of an action queue with a capacity of 8 and non-blocking
sends from button callbacks, rapid presses of the confirm button can queue multiple
`Action_Confirm` events. The `queueHandle` task processes these sequentially. The first
event triggers `confirm_choice()`, which displays the result screen and sets the state
to `STATE_OVER_LOST` or `STATE_OVER_WIN`. The loop immediately processes the next queued
`Action_Confirm`, which then calls `start_game()` based on the new state. This
overwrites the result screen with a new game screen, making the result screen flash for
only a few milliseconds or not appear at all.

cycle_choice();
}
else
vTaskDelay(pdMS_TO_TICKS(5));
}
}

void selectHandle() {
enum ActionType curAction = Action_Select;
xQueueSend(actionQueue, &curAction, 0);
}

void confirmHandle() {
enum ActionType curAction = Action_Confirm;
xQueueSend(actionQueue, &curAction, 0);
}

void app_main() {
// Initialize all components
ESP_LOGI("main", "Starting application");
Expand All @@ -29,13 +61,17 @@ void app_main() {
display_init();
btnctrl_init();
init_sound();

// Queue Managers
actionQueue = xQueueCreate(8, sizeof(enum ActionType));
xTaskCreate(queueHandle, "main_control_handle", 4096, NULL, 5, NULL);

set_led(1); // Turn on LED to indicate device is ready

// Initiate main menu
init_main_menu();
play_main_menu_sound();
btnctrl_register_event(NULL, start_game);
play_sound(SOUND_MAIN_MENU);
btnctrl_register_event(selectHandle, confirmHandle);
}

void setup_i2c() {
Expand All @@ -56,42 +92,38 @@ void setup_i2c() {

// Initialize game screen and register button events for cycling and confirming choice
void start_game() {
btnctrl_unregister_event(); // Clear main menu event

init_game_screen(playerChoice);
play_select_sound();

btnctrl_register_event(cycle_choice, confirm_choice);
play_sound(SOUND_SELECT);
currentState = STATE_PLAYING;
}

// Update game screen with current player choice when cycle button is pressed
void cycle_choice() {
playerChoice = (playerChoice + 1) % 3; // Cycle through ROCK, PAPER, SCISSORS
update_game_screen(playerChoice);
play_select_sound();
play_sound(SOUND_SELECT);
}


// Determine and display the game outcome
void confirm_choice() {
btnctrl_unregister_event(); // Clear start_game events

rps_choice cpuChoice = (rps_choice)(esp_random() % 3); // Generate random CPU choice
rps_outcome outcome = determine_rps_outcome(playerChoice, cpuChoice); // Determine game outcome
init_result_screen(outcome, playerChoice, cpuChoice); // Update screen with outcome and choices

// Play sound based on outcome and register event to start new game if not a win
switch (outcome) {
case WIN:
play_win_sound();
play_sound(SOUND_WIN);
currentState = STATE_OVER_WIN;
break;
case LOSE:
play_lost_sound();
btnctrl_register_event(NULL, start_game);
play_sound(SOUND_LOST);
currentState = STATE_OVER_LOST;
break;
case DRAW:
play_draw_sound();
btnctrl_register_event(NULL, start_game);
play_sound(SOUND_DRAW);
currentState = STATE_OVER_LOST;
break;
}

Expand Down
Loading