From b936d57abf2f1e2fb675648ed8205336b1aa2fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Tue, 15 Jul 2025 17:06:22 +0200 Subject: [PATCH 1/4] refactoring state machine --- Project/main/src/helperFunctions.c | 3 - Project/main/src/statemachine.c | 204 ++++++++++++++++++++--------- Project/prj.conf | 2 +- Project/states/src/idle.c | 6 +- 4 files changed, 149 insertions(+), 66 deletions(-) diff --git a/Project/main/src/helperFunctions.c b/Project/main/src/helperFunctions.c index 74d9100..9b39b61 100644 --- a/Project/main/src/helperFunctions.c +++ b/Project/main/src/helperFunctions.c @@ -127,7 +127,6 @@ void wait_till_abc_depressed(){ #endif uint8_t input_count = 1; uint8_t *abcbtns; - LOG_WRN("Waiting\n"); while(input_count != 0){ abcbtns = abcbtnGetMutexValue(); @@ -140,8 +139,6 @@ void wait_till_abc_depressed(){ } native_loop(); } - - LOG_WRN("Done waiting\n"); } //Function to wait untill every 4x4 button has been released diff --git a/Project/main/src/statemachine.c b/Project/main/src/statemachine.c index d60a63d..27a4e2c 100644 --- a/Project/main/src/statemachine.c +++ b/Project/main/src/statemachine.c @@ -27,31 +27,20 @@ #include LOG_MODULE_REGISTER(statemachine); - +#define MINIGAME_OFFSET 4 // Setup state machine -struct state; -typedef void state_fn(struct state *); uint8_t trivia_ID; -struct state { - state_fn *next; - int i; -}; - -state_fn init_state, idle_state, mg1_state, mg2_state, mg3_state, mg4_state, mg5_state, mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, ctp_state, end_game_state, exit_state; - -// Array of state functions -state_fn* minigame_states[] = { - mg1_state, mg2_state, mg3_state, mg4_state, mg5_state, - mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, - ctp_state -}; +enum statemachineStates {init_state, idle_state, end_game_state, exit_state, mg1_state, mg2_state, mg3_state, mg4_state, + mg5_state, mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, ctp_state}; +enum statemachineStates current_state = init_state; static int16_t end_time; // State functions -void init_state(struct state *state) { +enum statemachineStates init_stateFunction() { LOG_INF("Initialization\n"); + enum statemachineStates next_state; disableAllThreads(); uint8_t ret = 0; ret = configure(); @@ -75,8 +64,8 @@ void init_state(struct state *state) { start_time = sd_get_start_time(); if (start_time < 0) { LOG_ERR("Start time not set, exiting state machine"); - state->next = exit_state; - return; + next_state = exit_state; + return next_state; } int16_t hour = getHour(); @@ -90,10 +79,14 @@ void init_state(struct state *state) { end_time = sd_get_end_time(); #endif - state->next = idle_state; + LOG_INF("Going to idle\n"); + next_state = idle_state; + return next_state; } -void idle_state(struct state *state) { +enum statemachineStates idle_stateFunction() { + LOG_INF("Idle\n"); + enum statemachineStates next_state; char **names; unsigned amount; getIdleThreads(&names, &amount); @@ -103,21 +96,23 @@ void idle_state(struct state *state) { if (ret < -1) { LOG_ERR("Error in idle state\n"); - state->next = 0; + next_state = 0; } else if (ret == -1) { LOG_INF("Going to exit state\n"); - state->next = exit_state; + next_state = exit_state; }else if(ret >= 100){ // if a minigame ID above 100 is assigned, it is a triva question, (change this when more than 100 games are made) trivia_ID = ret - 100; // internaly to the trivia game questions are labeled 0 to [however many are on the SD] - state->next = mg4_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack + next_state = mg4_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack } else { - state->next = minigame_states[ret]; + next_state = ret + MINIGAME_OFFSET; } + return next_state; } -void mg1_state(struct state *state) { // Makes use of button and led +enum statemachineStates mg1_stateFunction() { // Makes use of button and led // Initialise state, enable and disable corresponding threads LOG_INF("Minigame 1\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -131,11 +126,13 @@ void mg1_state(struct state *state) { // Makes use of button and led sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg2_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg2_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 2\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -149,11 +146,13 @@ void mg2_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg3_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg3_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 3\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -167,11 +166,13 @@ void mg3_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg4_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg4_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 4\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -185,11 +186,13 @@ void mg4_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg5_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg5_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 5\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -203,11 +206,13 @@ void mg5_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg6_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg6_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 6\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -221,11 +226,13 @@ void mg6_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg7_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg7_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 7\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -239,11 +246,13 @@ void mg7_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg8_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg8_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 8\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -257,11 +266,13 @@ void mg8_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg9_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg9_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 9\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -275,11 +286,13 @@ void mg9_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void mg10_state(struct state *state) { // Makes use of gyro and buzzer +enum statemachineStates mg10_stateFunction() { // Makes use of gyro and buzzer LOG_INF("Minigame 10\n"); + enum statemachineStates next_state; int score = 0; char **names; @@ -293,35 +306,39 @@ void mg10_state(struct state *state) { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void ctp_state(struct state *state) // Catch the Pokemon minigame +enum statemachineStates ctp_stateFunction() // Catch the Pokemon minigame { char **names; unsigned amount; getCatchThePokemonThreads(&names, &amount); enableThreads(names, amount); + enum statemachineStates next_state; int ret = playCatchThePokemon(); if ( ret < 0 ) { LOG_ERR("Error in catch the pokemon state\n"); - state->next = exit_state; - return; + next_state = exit_state; + return next_state; } disableThreads(names, amount); sd_set_score(ret); show_mg_score(ret); - state->next = idle_state; + next_state = idle_state; + return next_state; } -void end_game_state(struct state *state) +enum statemachineStates end_game_stateFunction() { LOG_INF("End game state"); + enum statemachineStates next_state; char **names; unsigned amount; @@ -332,13 +349,16 @@ void end_game_state(struct state *state) disableThreads(names, amount); - // state->next = exit_state; + next_state = exit_state; + return next_state; } -void exit_state(struct state *state) { +enum statemachineStates exit_stateFunction() { LOG_INF("Exit state"); + enum statemachineStates next_state; disableAllThreads(); // Shouldn't be required, but just to be sure - state->next = 0; + next_state = exit_state; + return next_state; } bool check_end_time_reached() { @@ -356,13 +376,79 @@ bool check_end_time_reached() { } void startStatemachine() { - struct state state = {init_state, 0}; - while (state.next) - { - state.next(&state); + bool statemachine_ongoing = 1; + bool test = 0; + + while(statemachine_ongoing){ #ifndef CONFIG_TESTMODE - if (check_end_time_reached()) - state.next = end_game_state; + statemachine_ongoing = check_end_time_reached(); #endif +#if defined(CONFIG_TESTMODE) + if(test){ + LOG_INF("Time reached"); + statemachine_ongoing = 0; + } + else{ + LOG_INF("Time will be reached"); + statemachine_ongoing = 0; + } +#endif + switch (current_state) { + case init_state: + current_state = init_stateFunction(); + LOG_INF("Score after init: %d", sd_get_score()); + break; + case idle_state: + current_state = idle_stateFunction(); + break; + case mg1_state: + current_state = mg1_stateFunction(); + break; + case mg2_state: + current_state = mg2_stateFunction(); + break; + case mg3_state: + current_state = mg3_stateFunction(); + break; + case mg4_state: + current_state = mg4_stateFunction(); + break; + case mg5_state: + current_state = mg5_stateFunction(); + break; + case mg6_state: + current_state = mg6_stateFunction(); + break; + case mg7_state: + current_state = mg7_stateFunction(); + break; + case mg8_state: + current_state = mg8_stateFunction(); + break; + case mg9_state: + current_state = mg9_stateFunction(); + break; + case mg10_state: + current_state = mg10_stateFunction(); + break; + case ctp_state: + current_state = ctp_stateFunction(); + break; + case end_game_state: + current_state = end_game_stateFunction(); + break; + case exit_state: + current_state = exit_stateFunction(); + break; + default: + break; + } + } + + LOG_INF("Score after 0 games: %d", sd_get_score()); + + current_state = end_game_stateFunction(); + while(1){ + current_state = exit_stateFunction(); } } \ No newline at end of file diff --git a/Project/prj.conf b/Project/prj.conf index eaa37f0..d12f294 100644 --- a/Project/prj.conf +++ b/Project/prj.conf @@ -9,4 +9,4 @@ CONFIG_SHELL=y CONFIG_SHELL_THREAD_PRIORITY_OVERRIDE=y #CONFIG_SHELL_VT100_COLORS=n -CONFIG_TESTMODE=n \ No newline at end of file +CONFIG_TESTMODE=y \ No newline at end of file diff --git a/Project/states/src/idle.c b/Project/states/src/idle.c index 012ab54..90c3e4d 100644 --- a/Project/states/src/idle.c +++ b/Project/states/src/idle.c @@ -163,7 +163,7 @@ int playIdle() { #if defined(CONFIG_BOARD_NUCLEO_H743ZI) int distMeters = 100; // Initialize to a value outside the expected range int dir = 0; // Direction the user must head in - char lcd_msg[32]; + char lcd_distance_msg[32]; lcdEnable(); bool lcdSet = false; while(distMeters > REQUIRED_DIST_METERS) { // Device is too far away from next target @@ -178,8 +178,8 @@ int playIdle() { } else { - sprintf(lcd_msg, "Volg de LEDs! %d meter", distMeters); - lcdStringWrite(lcd_msg); + sprintf(lcd_distance_msg, "Volg de LEDs! %d meter", distMeters); + lcdStringWrite(lcd_distance_msg); k_msleep(50); lcdSet = true; From d73f0ddf45d5774e1d3de616ff0eccf1e44b89ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Wed, 16 Jul 2025 13:27:55 +0200 Subject: [PATCH 2/4] switched to void functions --- Project/main/src/statemachine.c | 174 +++++++++++++------------------- 1 file changed, 72 insertions(+), 102 deletions(-) diff --git a/Project/main/src/statemachine.c b/Project/main/src/statemachine.c index 27a4e2c..d7035d5 100644 --- a/Project/main/src/statemachine.c +++ b/Project/main/src/statemachine.c @@ -31,16 +31,13 @@ LOG_MODULE_REGISTER(statemachine); // Setup state machine uint8_t trivia_ID; -enum statemachineStates {init_state, idle_state, end_game_state, exit_state, mg1_state, mg2_state, mg3_state, mg4_state, - mg5_state, mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, ctp_state}; - -enum statemachineStates current_state = init_state; +typedef enum {init_state, idle_state, end_game_state, exit_state, mg1_state, mg2_state, mg3_state, mg4_state, + mg5_state, mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, ctp_state} statemachineStates; static int16_t end_time; // State functions -enum statemachineStates init_stateFunction() { +void init_stateFunction(statemachineStates* next_state) { LOG_INF("Initialization\n"); - enum statemachineStates next_state; disableAllThreads(); uint8_t ret = 0; ret = configure(); @@ -64,8 +61,7 @@ enum statemachineStates init_stateFunction() { start_time = sd_get_start_time(); if (start_time < 0) { LOG_ERR("Start time not set, exiting state machine"); - next_state = exit_state; - return next_state; + *next_state = exit_state; } int16_t hour = getHour(); @@ -80,13 +76,11 @@ enum statemachineStates init_stateFunction() { end_time = sd_get_end_time(); #endif LOG_INF("Going to idle\n"); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates idle_stateFunction() { +void idle_stateFunction(statemachineStates* next_state) { LOG_INF("Idle\n"); - enum statemachineStates next_state; char **names; unsigned amount; getIdleThreads(&names, &amount); @@ -96,23 +90,21 @@ enum statemachineStates idle_stateFunction() { if (ret < -1) { LOG_ERR("Error in idle state\n"); - next_state = 0; + *next_state = 0; } else if (ret == -1) { LOG_INF("Going to exit state\n"); - next_state = exit_state; + *next_state = exit_state; }else if(ret >= 100){ // if a minigame ID above 100 is assigned, it is a triva question, (change this when more than 100 games are made) trivia_ID = ret - 100; // internaly to the trivia game questions are labeled 0 to [however many are on the SD] - next_state = mg4_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack + *next_state = mg4_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack } else { - next_state = ret + MINIGAME_OFFSET; + *next_state = ret + MINIGAME_OFFSET; } - return next_state; } -enum statemachineStates mg1_stateFunction() { // Makes use of button and led +void mg1_stateFunction(statemachineStates* next_state) { // Makes use of button and led // Initialise state, enable and disable corresponding threads LOG_INF("Minigame 1\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -126,13 +118,11 @@ enum statemachineStates mg1_stateFunction() { // Makes use of button and led sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg2_stateFunction() { // Makes use of gyro and buzzer +void mg2_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 2\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -146,13 +136,11 @@ enum statemachineStates mg2_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg3_stateFunction() { // Makes use of gyro and buzzer +void mg3_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 3\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -166,13 +154,11 @@ enum statemachineStates mg3_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg4_stateFunction() { // Makes use of gyro and buzzer +void mg4_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 4\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -186,13 +172,11 @@ enum statemachineStates mg4_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg5_stateFunction() { // Makes use of gyro and buzzer +void mg5_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 5\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -206,13 +190,11 @@ enum statemachineStates mg5_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg6_stateFunction() { // Makes use of gyro and buzzer +void mg6_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 6\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -226,13 +208,11 @@ enum statemachineStates mg6_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg7_stateFunction() { // Makes use of gyro and buzzer +void mg7_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 7\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -246,13 +226,11 @@ enum statemachineStates mg7_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg8_stateFunction() { // Makes use of gyro and buzzer +void mg8_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 8\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -266,13 +244,11 @@ enum statemachineStates mg8_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg9_stateFunction() { // Makes use of gyro and buzzer +void mg9_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 9\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -286,13 +262,11 @@ enum statemachineStates mg9_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates mg10_stateFunction() { // Makes use of gyro and buzzer +void mg10_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer LOG_INF("Minigame 10\n"); - enum statemachineStates next_state; int score = 0; char **names; @@ -306,39 +280,34 @@ enum statemachineStates mg10_stateFunction() { // Makes use of gyro and buzzer sd_set_score(score); show_mg_score(score); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates ctp_stateFunction() // Catch the Pokemon minigame +void ctp_stateFunction(statemachineStates* next_state) // Catch the Pokemon minigame { char **names; unsigned amount; getCatchThePokemonThreads(&names, &amount); enableThreads(names, amount); - enum statemachineStates next_state; int ret = playCatchThePokemon(); if ( ret < 0 ) { LOG_ERR("Error in catch the pokemon state\n"); - next_state = exit_state; - return next_state; + *next_state = exit_state; } disableThreads(names, amount); sd_set_score(ret); show_mg_score(ret); - next_state = idle_state; - return next_state; + *next_state = idle_state; } -enum statemachineStates end_game_stateFunction() +void end_game_stateFunction(statemachineStates* next_state) { LOG_INF("End game state"); - enum statemachineStates next_state; char **names; unsigned amount; @@ -349,16 +318,13 @@ enum statemachineStates end_game_stateFunction() disableThreads(names, amount); - next_state = exit_state; - return next_state; + *next_state = exit_state; } -enum statemachineStates exit_stateFunction() { +void exit_stateFunction(statemachineStates* next_state) { LOG_INF("Exit state"); - enum statemachineStates next_state; disableAllThreads(); // Shouldn't be required, but just to be sure - next_state = exit_state; - return next_state; + *next_state = exit_state; } bool check_end_time_reached() { @@ -377,68 +343,72 @@ bool check_end_time_reached() { void startStatemachine() { bool statemachine_ongoing = 1; - bool test = 0; + //bool test = 0; + statemachineStates current_state = init_state; while(statemachine_ongoing){ -#ifndef CONFIG_TESTMODE - statemachine_ongoing = check_end_time_reached(); -#endif -#if defined(CONFIG_TESTMODE) - if(test){ - LOG_INF("Time reached"); - statemachine_ongoing = 0; - } - else{ - LOG_INF("Time will be reached"); - statemachine_ongoing = 0; - } -#endif +// #ifndef CONFIG_TESTMODE +// statemachine_ongoing = check_end_time_reached(); + // if(check_end_time_reached()){ + // current_state = end_game_state; + // } +// #endif +// #if defined(CONFIG_TESTMODE) +// if(test){ +// LOG_INF("Time reached"); +// statemachine_ongoing = 0; +// } +// else{ +// LOG_INF("Time will be reached"); +// statemachine_ongoing = 0; +// } +// #endif switch (current_state) { case init_state: - current_state = init_stateFunction(); + init_stateFunction(¤t_state); LOG_INF("Score after init: %d", sd_get_score()); break; case idle_state: - current_state = idle_stateFunction(); + idle_stateFunction(¤t_state); break; case mg1_state: - current_state = mg1_stateFunction(); + mg1_stateFunction(¤t_state); break; case mg2_state: - current_state = mg2_stateFunction(); + mg2_stateFunction(¤t_state); break; case mg3_state: - current_state = mg3_stateFunction(); + mg3_stateFunction(¤t_state); break; case mg4_state: - current_state = mg4_stateFunction(); + mg4_stateFunction(¤t_state); break; case mg5_state: - current_state = mg5_stateFunction(); + mg5_stateFunction(¤t_state); break; case mg6_state: - current_state = mg6_stateFunction(); + mg6_stateFunction(¤t_state); break; case mg7_state: - current_state = mg7_stateFunction(); + mg7_stateFunction(¤t_state); break; case mg8_state: - current_state = mg8_stateFunction(); + mg8_stateFunction(¤t_state); break; case mg9_state: - current_state = mg9_stateFunction(); + mg9_stateFunction(¤t_state); break; case mg10_state: - current_state = mg10_stateFunction(); + mg10_stateFunction(¤t_state); break; case ctp_state: - current_state = ctp_stateFunction(); + ctp_stateFunction(¤t_state); break; case end_game_state: - current_state = end_game_stateFunction(); + end_game_stateFunction(¤t_state); break; case exit_state: - current_state = exit_stateFunction(); + exit_stateFunction(¤t_state); break; default: break; @@ -447,8 +417,8 @@ void startStatemachine() { LOG_INF("Score after 0 games: %d", sd_get_score()); - current_state = end_game_stateFunction(); + end_game_stateFunction(¤t_state); while(1){ - current_state = exit_stateFunction(); + exit_stateFunction(¤t_state); } } \ No newline at end of file From 3f31dac96e80b12cbe597e7be3693255028a2e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Wed, 16 Jul 2025 16:00:23 +0200 Subject: [PATCH 3/4] Removed duplicate code and start of trivia state --- Project/main/src/statemachine.c | 311 +++------ Project/states/inc/catchThePokemon.h | 24 - Project/states/inc/minigame4.h | 28 +- Project/states/inc/trivia.h | 38 + Project/states/src/catchThePokemon.c | 837 ---------------------- Project/states/src/idle.c | 2 +- Project/states/src/minigame4.c | 996 ++++++++++++++++++++++----- Project/states/src/trivia.c | 201 ++++++ 8 files changed, 1156 insertions(+), 1281 deletions(-) delete mode 100644 Project/states/inc/catchThePokemon.h create mode 100644 Project/states/inc/trivia.h delete mode 100644 Project/states/src/catchThePokemon.c create mode 100644 Project/states/src/trivia.c diff --git a/Project/main/src/statemachine.c b/Project/main/src/statemachine.c index d7035d5..07dc4ab 100644 --- a/Project/main/src/statemachine.c +++ b/Project/main/src/statemachine.c @@ -16,7 +16,7 @@ #include "minigame8.h" #include "minigame9.h" #include "minigame10.h" -#include "catchThePokemon.h" +#include "trivia.h" #include #include @@ -27,14 +27,47 @@ #include LOG_MODULE_REGISTER(statemachine); -#define MINIGAME_OFFSET 4 -// Setup state machine -uint8_t trivia_ID; -typedef enum {init_state, idle_state, end_game_state, exit_state, mg1_state, mg2_state, mg3_state, mg4_state, - mg5_state, mg6_state, mg7_state, mg8_state, mg9_state, mg10_state, ctp_state} statemachineStates; +typedef enum {init_state, idle_state, end_game_state, exit_state, mg_state, trivia_state} statemachineStates; static int16_t end_time; +void getMgThreads(char*** names, unsigned* amount, int mgID){ + switch(mgID){ + case 1: + getMg1Threads(names, amount); + break; + case 2: + getMg2Threads(names, amount); + break; + case 3: + getMg3Threads(names, amount); + break; + case 4: + getMg4Threads(names, amount); + break; + case 5: + getMg5Threads(names, amount); + break; + case 6: + getMg6Threads(names, amount); + break; + case 7: + getMg7Threads(names, amount); + break; + case 8: + getMg8Threads(names, amount); + break; + case 9: + getMg9Threads(names, amount); + break; + case 10: + getMg10Threads(names, amount); + break; + default: + break; + } +} + // State functions void init_stateFunction(statemachineStates* next_state) { LOG_INF("Initialization\n"); @@ -79,7 +112,7 @@ void init_stateFunction(statemachineStates* next_state) { *next_state = idle_state; } -void idle_stateFunction(statemachineStates* next_state) { +void idle_stateFunction(statemachineStates* next_state, int* mgID, uint8_t* trivia_ID) { LOG_INF("Idle\n"); char **names; unsigned amount; @@ -95,60 +128,57 @@ void idle_stateFunction(statemachineStates* next_state) { LOG_INF("Going to exit state\n"); *next_state = exit_state; }else if(ret >= 100){ // if a minigame ID above 100 is assigned, it is a triva question, (change this when more than 100 games are made) - trivia_ID = ret - 100; // internaly to the trivia game questions are labeled 0 to [however many are on the SD] - *next_state = mg4_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack + *trivia_ID = ret - 100; // internaly to the trivia game questions are labeled 0 to [however many are on the SD] + *next_state = trivia_state; // keep in mind that increasing the amount of questions will influence required buffersizes, as well as the main stack } else { - *next_state = ret + MINIGAME_OFFSET; + *next_state = mg_state; + *mgID = ret; } } -void mg1_stateFunction(statemachineStates* next_state) { // Makes use of button and led - // Initialise state, enable and disable corresponding threads - LOG_INF("Minigame 1\n"); - int score = 0; - - char **names; - unsigned amount; - getMg1Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg1(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg2_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 2\n"); - int score = 0; - - char **names; - unsigned amount; - getMg2Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg2(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg3_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 3\n"); +void mg_stateFunction(statemachineStates* next_state, int mgID) { + LOG_INF("Minigame state\n"); int score = 0; - char **names; - unsigned amount; - getMg3Threads(&names, &amount); + char **names = NULL; + unsigned amount = 0; + getMgThreads(&names, &amount, mgID); enableThreads(names, amount); - score = playMg3(); + switch(mgID){ + case 1: + score = playMg1(); + break; + case 2: + score = playMg2(); + break; + case 3: + score = playMg3(); + break; + case 4: + score = playMg4(); + break; + case 5: + score = playMg5(); + break; + case 6: + score = playMg6(); + break; + case 7: + score = playMg7(); + break; + case 8: + score = playMg8(); + break; + case 9: + score = playMg9(); + break; + case 10: + score = playMg10(); + break; + default: + break; + } disableThreads(names, amount); sd_set_score(score); @@ -157,13 +187,13 @@ void mg3_stateFunction(statemachineStates* next_state) { // Makes use of gyro an *next_state = idle_state; } -void mg4_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 4\n"); +void trivia_stateFunction(statemachineStates* next_state, uint8_t trivia_ID) { + LOG_INF("Trivia\n"); int score = 0; char **names; unsigned amount; - getMg4Threads(&names, &amount); + getTriviaThreads(&names, &amount); enableThreads(names, amount); score = playMg4(trivia_ID); @@ -175,136 +205,6 @@ void mg4_stateFunction(statemachineStates* next_state) { // Makes use of gyro an *next_state = idle_state; } -void mg5_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 5\n"); - int score = 0; - - char **names; - unsigned amount; - getMg5Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg5(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg6_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 6\n"); - int score = 0; - - char **names; - unsigned amount; - getMg6Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg6(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg7_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 7\n"); - int score = 0; - - char **names; - unsigned amount; - getMg7Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg7(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg8_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 8\n"); - int score = 0; - - char **names; - unsigned amount; - getMg8Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg8(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg9_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 9\n"); - int score = 0; - - char **names; - unsigned amount; - getMg9Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg9(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void mg10_stateFunction(statemachineStates* next_state) { // Makes use of gyro and buzzer - LOG_INF("Minigame 10\n"); - int score = 0; - - char **names; - unsigned amount; - getMg10Threads(&names, &amount); - enableThreads(names, amount); - - score = playMg10(); - - disableThreads(names, amount); - sd_set_score(score); - show_mg_score(score); - - *next_state = idle_state; -} - -void ctp_stateFunction(statemachineStates* next_state) // Catch the Pokemon minigame -{ - char **names; - unsigned amount; - getCatchThePokemonThreads(&names, &amount); - enableThreads(names, amount); - - int ret = playCatchThePokemon(); - - if ( ret < 0 ) - { - LOG_ERR("Error in catch the pokemon state\n"); - *next_state = exit_state; - } - - disableThreads(names, amount); - sd_set_score(ret); - show_mg_score(ret); - - *next_state = idle_state; -} - void end_game_stateFunction(statemachineStates* next_state) { LOG_INF("End game state"); @@ -345,6 +245,8 @@ void startStatemachine() { bool statemachine_ongoing = 1; //bool test = 0; statemachineStates current_state = init_state; + int mgID = 0; + uint8_t trivia_ID; while(statemachine_ongoing){ // #ifndef CONFIG_TESTMODE @@ -369,40 +271,13 @@ void startStatemachine() { LOG_INF("Score after init: %d", sd_get_score()); break; case idle_state: - idle_stateFunction(¤t_state); - break; - case mg1_state: - mg1_stateFunction(¤t_state); - break; - case mg2_state: - mg2_stateFunction(¤t_state); - break; - case mg3_state: - mg3_stateFunction(¤t_state); - break; - case mg4_state: - mg4_stateFunction(¤t_state); - break; - case mg5_state: - mg5_stateFunction(¤t_state); - break; - case mg6_state: - mg6_stateFunction(¤t_state); - break; - case mg7_state: - mg7_stateFunction(¤t_state); - break; - case mg8_state: - mg8_stateFunction(¤t_state); - break; - case mg9_state: - mg9_stateFunction(¤t_state); + idle_stateFunction(¤t_state, &mgID, &trivia_ID); break; - case mg10_state: - mg10_stateFunction(¤t_state); + case mg_state: + mg_stateFunction(¤t_state, mgID); break; - case ctp_state: - ctp_stateFunction(¤t_state); + case trivia_state: + trivia_stateFunction(¤t_state, trivia_ID); break; case end_game_state: end_game_stateFunction(¤t_state); diff --git a/Project/states/inc/catchThePokemon.h b/Project/states/inc/catchThePokemon.h deleted file mode 100644 index ffdac75..0000000 --- a/Project/states/inc/catchThePokemon.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef CATCHTHEPOKEMON_H -#define CATCHTHEPOKEMON_H - -#include -#include -#include -#include -#include "threads.h" -#include "lcd.h" -#include "genericGpio.h" -#include "gps.h" -#include "buzzers.h" -#include "helperFunctions.h" -#include "sdCard.h" -#include "locations.h" -#include - -#define catchThePokemonThreadCount 6 - -void getCatchThePokemonThreads(char ***, unsigned *); - -int playCatchThePokemon(); - -#endif // CATCHTHEPOKEMON_H \ No newline at end of file diff --git a/Project/states/inc/minigame4.h b/Project/states/inc/minigame4.h index 58a245e..20e6abe 100644 --- a/Project/states/inc/minigame4.h +++ b/Project/states/inc/minigame4.h @@ -2,37 +2,23 @@ #define MINIGAME4_H #include +#include #include #include #include "threads.h" #include "lcd.h" #include "genericGpio.h" +#include "gps.h" +#include "buzzers.h" #include "helperFunctions.h" #include "sdCard.h" -#include +#include "locations.h" +#include -#define mg4ThreadCount 3 +#define mg4ThreadCount 6 void getMg4Threads(char ***, unsigned *); -int playMg4(uint8_t question_nr); -#define AMOUNT_QUESTIONS 4 -#define AMOUNT_ANSWERS 3 -// This buffer is related to the size of the trivia folder so this might need to be increased -// asume 5-10 questions per 1000 bytes -// beware that this memory is used double, so increasing it eats the main stack quickly -#define BUFFER_SIZE 2048 - - struct Quiz { //32 bytes per string max or it wont fit the display - char* question; - char* answerA; - char* answerB; - char* answerC; - int correct; - }; - - -int trivia_load(uint16_t type, struct Quiz **questions, size_t *count, size_t maxQuestions); - +int playMg4(); #endif // MINIGAME4_H \ No newline at end of file diff --git a/Project/states/inc/trivia.h b/Project/states/inc/trivia.h new file mode 100644 index 0000000..c234c90 --- /dev/null +++ b/Project/states/inc/trivia.h @@ -0,0 +1,38 @@ +#ifndef TRIVIA_H +#define TRIVIA_H + +#include +#include +#include +#include "threads.h" +#include "lcd.h" +#include "genericGpio.h" +#include "helperFunctions.h" +#include "sdCard.h" +#include + +#define triviaThreadCount 3 + +void getTriviaThreads(char ***, unsigned *); + +int playTrivia(uint8_t question_nr); +#define AMOUNT_QUESTIONS 4 +#define AMOUNT_ANSWERS 3 +// This buffer is related to the size of the trivia folder so this might need to be increased +// asume 5-10 questions per 1000 bytes +// beware that this memory is used double, so increasing it eats the main stack quickly +#define BUFFER_SIZE 2048 + + struct Quiz { //32 bytes per string max or it wont fit the display + char* question; + char* answerA; + char* answerB; + char* answerC; + int correct; + }; + + +int trivia_load(uint16_t type, struct Quiz **questions, size_t *count, size_t maxQuestions); + + +#endif // TRIVIA_H \ No newline at end of file diff --git a/Project/states/src/catchThePokemon.c b/Project/states/src/catchThePokemon.c deleted file mode 100644 index beaeec9..0000000 --- a/Project/states/src/catchThePokemon.c +++ /dev/null @@ -1,837 +0,0 @@ -#include "catchThePokemon.h" - -LOG_MODULE_REGISTER(catchThePokemon); - -#define BALLS 16 -#define POKEMON 5 // number of pokemon to catch -#define POKEMONLOCATIONS 32 // number of locations where pokemon can be found -#define MAX_ATTEMPTS 8 -#define POKEMON_DISTANCE 10 // meters - -#define HINT_COOLDOWN 1 // in minutes - -#define TIME_MAX 10 -#define TIME_MIN 0 -#define TIME_STEP 1 -#define TIME_INTERVAL_MS 100 // Adjust for smoother or faster changes - -// sounds -#define START_GAME_SOUND 5 -#define END_GAME_SOUND 6 - -#define MAX_SCORE 1000 // Maximum score for the game - -// Curve parameters for aim minigame -#define CURVE_DAMPEN 0.5 // affects how quickly the curve increases -#define AIM_DIRECTION_OFFSET 8 - -#define BALLTHROWNARRAYSIZE 4 - -char *catchThePokemonThreads[catchThePokemonThreadCount] = {"startbtn", "btnmatrix_in", "ledcircle", "buzzers", "ledmatrix", "btnmatrix_out"}; // missing lcd threads -bool game_ongoing_pokemon, catchEvent; -uint8_t balls, pokemonCaught, pokemonFled; -uint16_t displayMatrix[16] = {0}; -uint64_t startTime; -uint8_t pokemonToCatch[POKEMON] = {0}; -static bool increasing = true; - -struct pokemonLocation -{ - uint8_t id; - int64_t lat; - int64_t longi; - bool caught; - bool fled; -} pokemonLocation[POKEMONLOCATIONS] = {0}; - -uint8_t pokemonMatrixLocation[] = { - 0b00001110, - 0b00010001, - 0b00010101, - 0b00010001, - 0b00001110 -}; - -///////////// -/// sounds: -int soundCatch[2][3] = { - {587, 740, 880}, // D chord - {784, 988, 1175} // G chord -}; - -int soundFlee[2][3] = { - {587, 740, 880}, // D chord - {554, 698, 830} // C# chord -}; - -int soundCatchFail[2][3] = { - {880, 523, 660}, // Cm chord - {415, 262, 349} // Fm chord -}; - -int soundAppear[2][3] = { - {262, 330, 302}, // C chord - {784, 1175, 1568} // G D G -}; - -int soundNoBalls[2][3] = { - {554, 698, 830}, - {523, 659, 784} // C chord // C# chord -}; - -int soundStartGame[3][3] = { - {349, 349, 349}, // F4 - {698, 698, 698}, // F5 - {1397, 1397, 1397} // F6 -}; - -int soundEndGame[3][3] = { - - {1760, 1760, 1760}, // A6 - {880, 880, 880}, // A5 - {440, 440, 440}, // A4 -}; -///////////// - -/** - * @brief returns the threads used in the catch the pokemon minigame - * @param names pointer to a char array to store the thread names - * @param amount pointer to an unsigned int to store the amount of threads - */ -void getCatchThePokemonThreads(char ***names, unsigned *amount) -{ - *names = catchThePokemonThreads; - *amount = catchThePokemonThreadCount; -} - -// TODO: This should be done inside the button matrix or helperFunctions not in the game -/** - * @brief checks if any button is pressed - * @param buttonsInput pointer to an array of button states - * @param size size of the buttonsInput array - * @returns true if any button is pressed, false otherwise - * @note handles the inversion of the button states too - */ -bool isAnyButtonPressed() { - - uint8_t *buttonsInput = btnmatrix_inGetMutexValue(); - for (int i = 0; i < 16; i++){ - if (buttonsInput[i] == 0){ - return true; - } - } - return false; // No button is pressed -} - -/** - * @brief updates the time value based on the increasing or decreasing state - * @param timing_of_throw pointer to the time value to be updated - */ -void updateThrowTiming(uint8_t *timing_of_throw) -{ - // Update the timing_of_throwtiming value based on the increasing or decreasing state - if (increasing) { - *timing_of_throw += TIME_STEP; - } else { - *timing_of_throw -= TIME_STEP; - } - - // Cap the timing_of_throw value at the defined limits - if (*timing_of_throw >= TIME_MAX) { - *timing_of_throw = TIME_MAX; // Cap the timing_of_throw at max - increasing = false; // Switch to decreasing when max is reached - } - else if (*timing_of_throw <= TIME_MIN) { - *timing_of_throw = TIME_MIN; // Cap the timing_of_throw at min - increasing = true; // Switch to increasing when min is reached - } -} - -/** - * @brief handles the non-blocking time update - * @param timing_of_throw pointer to the time value to be updated - */ -void nonBlockingTimeHandler(uint8_t *timing_of_throw) -{ - static int64_t lastUpdate = 0; - int64_t currentTime = k_uptime_get(); // Get the current time in milliseconds - - if (currentTime - lastUpdate > TIME_INTERVAL_MS) - { - updateThrowTiming(timing_of_throw); - lastUpdate = currentTime; // Update the last update time - } -} - -/** - * @brief plays a sound based on the sound number - * @param soundNr the number of the sound to play - */ -int playSound(int soundNr, int *noteDelayMs) -{ - int (*soundArray)[3]; - int soundLength; - int delay = (noteDelayMs != NULL) ? *noteDelayMs : 200; // Default delay of 200 ms if not provided - - // Select the appropriate sound array based on soundNr - switch (soundNr) - { - case 0: - soundArray = soundCatch; - soundLength = sizeof(soundCatch) / sizeof(soundCatch[0]); - break; - case 1: - soundArray = soundFlee; - soundLength = sizeof(soundFlee) / sizeof(soundFlee[0]); - break; - case 2: - soundArray = soundCatchFail; - soundLength = sizeof(soundCatchFail) / sizeof(soundCatchFail[0]); - break; - case 3: - soundArray = soundAppear; - soundLength = sizeof(soundAppear) / sizeof(soundAppear[0]); - break; - case 4: - soundArray = soundNoBalls; - soundLength = sizeof(soundNoBalls) / sizeof(soundNoBalls[0]); - break; - case 5: - soundArray = soundStartGame; // Assuming soundStartGame is defined elsewhere - soundLength = sizeof(soundStartGame) / sizeof(soundStartGame[0]); - break; - case 6: - soundArray = soundEndGame; - soundLength = sizeof(soundEndGame) / sizeof(soundEndGame[0]); - break; - default: - return -1; // Invalid sound number - } - - // Play each chord in the sound array - for (int i = 0; i < soundLength; i++) - { - for (int j = 0; j < 3; j++) // Play all 3 frequencies in the chord - { - buzzerSetPwm(j, soundArray[i][j]); // Set PWM for each buzzer - } - k_msleep(delay); // Delay between chords - } - - // Turn off all buzzers after playing the sound - for (int j = 0; j < 3; j++) - { - buzzerTurnOff(j); - } - return 0; -} - -/** - * @brief checks if you have caught all pokemon and triggers the end game if not. - * @returns false if you have caught all pokemon or they have fleed - */ -bool checkPokemonLeft() -{ - bool pokemonLeft = true; - if(pokemonCaught >= POKEMON) - { - LOG_INF("All pokemon caught!"); - lcdStringWrite("Je hebt alle pokemon"); - k_msleep(1000); - lcdStringWrite("gevangen!"); - - pokemonLeft = false; - } - else if ((pokemonCaught + pokemonFled) >= POKEMONLOCATIONS) - { - LOG_INF("No pokemon left to catch! Caught: %d, Fled: %d", pokemonCaught, pokemonFled); - lcdStringWrite("Geen pokemon over!"); - - pokemonLeft = false; - } - - return pokemonLeft; -} - -/** - * @brief checks if you have balls left and triggers the end game if not. - * @returns false if you don't have balls left - * true if you have balls left - */ -bool checkBallsLeft() -{ - if (balls <= 0) - { - LOG_DBG("Out of pokeballs!"); - lcdStringWrite("Geen ballen over!"); - playSound(4, NULL); // Play out of balls sound - game_ongoing_pokemon = false; - return false; - } - - return true; -} - -/** - * @brief checks if the locations are valid - * @returns 0 if everything went as expected - */ -int selectLocations() -{ - struct Location *locArray = NULL; - size_t count = 0; - int ret = locations_load(0, &locArray, &count, POKEMONLOCATIONS); - if (ret != 0 || count < POKEMONLOCATIONS) { - // handle error - LOG_ERR("Failed to load locations or not enough locations. Error: %d, location count: %zu", ret, count); - return ret; - } - - for (int i = 0; i < POKEMONLOCATIONS; i++) - { - pokemonLocation[i].lat = locArray[i].x; - pokemonLocation[i].longi = locArray[i].y; - pokemonLocation[i].caught = false; - pokemonLocation[i].fled = false; - pokemonLocation[i].id = (uint8_t)i; - } - - for (int i = 0; i < POKEMON; i++) - { - pokemonToCatch[i] = rand() % POKEMONLOCATIONS; // Randomly select pokemon locations - } - - free(locArray); - return 0; -} - -/** - * @brief initialises variables, locations and hints used - * @returns 0 if everything went as expected - */ -int initMg() -{ - int err; - - // init variables - balls = BALLS; - pokemonCaught = 0; - pokemonFled = 0; - game_ongoing_pokemon = true; - catchEvent = false; - startTime = k_uptime_get(); // Get the current time in milliseconds - - lcdEnable(); - lcdStringWrite("Vind en vang de pokemon!"); - int soundSpeed = 100; - playSound(START_GAME_SOUND, &soundSpeed); - k_msleep(3000); - - err = selectLocations(); - if (err != 0) { - LOG_ERR("Failed to select locations: %d", err); - return err; // Return error if location selection fails - } - return 0; -} - -/** - * @brief pushes matrix to ledmatrix and sets the displayMatrix to 0 - * @returns 0 if everything went as expected - */ -void setMatrix() -{ - ledmatrixSetMutexValue(displayMatrix); - - // clear displayMatrix - memset(displayMatrix, 0, sizeof(displayMatrix)); -} - -/** - * @brief displays the pokemon location on the 16x16 LED - * @param x center x coordinate of the pokemon on the 16x16 LED - * @param y center y coordinate of the pokemon on the 16x16 LED - * @returns 0 if everything went as expected - * @note Advisory position is to not go below Y 8 - */ -int displayPokemon(uint8_t x, uint8_t y) -{ - int8_t offsetX = -3; // The offset is used so that the pokemon doesn't appear half over the edge of the display. This way the pokemon is always fully visible. - int8_t offsetY = -3; - for (int i = 0; i < 5; i++) - { - displayMatrix[(y+offsetY+i)] += pokemonMatrixLocation[i] << (16-x+offsetX); - } - return 0; -} - -/** - * @brief displays the direction aimed on the 16x16 LED - * @param angle angle in 15 degree increments 0 is 45 degrees to the left and 15 is 45 degrees to the right - * @returns 0 if everything went as expected - */ -int displayAimDirection(char angle) -{ - // Map the angle to a direction (-7 to +7) - int direction = (angle * 14) / 15 - 7; // Map angle (0-15) to range (-7 to +7) - - // Draw the line on the bottom 8 rows - for (int y = 15; y >= AIM_DIRECTION_OFFSET; y--) { - int curveFactor = (15 - y) * (15 - y) / 16; // Add a curve factor - if (angle > 7) { - curveFactor *= -1; - } - int x = AIM_DIRECTION_OFFSET + direction * (15 - y) / 7 + CURVE_DAMPEN * curveFactor; // Adjust x with curve - if (x >= 0 && x < 16) { - displayMatrix[y] |= 1 << (15 - x); // Set the bit at position x - } - } - - // cleanup curve - for (int y = AIM_DIRECTION_OFFSET + 1; y < 2*AIM_DIRECTION_OFFSET-1; y++) - { - if (( (angle <= 7) && (displayMatrix[y] < displayMatrix[y+1])) || ((angle > 7) && (displayMatrix[y] > displayMatrix[y+1]))) - displayMatrix[y] = displayMatrix[y+1]; - } - - return 0; -} - -/* - * @brief displays the timing to throw the ball on the 16x16 LED - * @param[in] time time to display on a scale of 0-10 - * @returns 0 if everything went as expected - */ -int displayTiming(char time) -{ - // Initialize the LED circle array and temporary variable - uint8_t ledCircle[8] = {0}; - uint32_t temp = 0; - - // Generate the bit pattern based on the input time - for (int i = 0; i <= time; i++) - { - temp = (temp << 3) | 0b111; // Append 3 bits of 1 for each step - } - - // Process the first 4 bytes of the temp variable - for (int i = 0; i < 4; i++) { - // Extract and reverse the bits of the current byte - uint8_t currentByte = (temp >> (i * 8)) & 0xFF; // Extract the current byte - uint8_t reversedByte = ((currentByte & 0xF0) >> 4) | ((currentByte & 0x0F) << 4); - reversedByte = ((reversedByte & 0xCC) >> 2) | ((reversedByte & 0x33) << 2); - reversedByte = ((reversedByte & 0xAA) >> 1) | ((reversedByte & 0x55) << 1); - - // Assign the reversed byte to the second half of the LED circle - ledCircle[i + 4] = reversedByte; - - // Assign the original byte to the first half of the LED circle - ledCircle[i] = (temp >> (24 - (i * 8))) & 0xFF; - } - - // Update the LED circle with the generated values - ledcircleSetMutexValue(ledCircle); - - return 0; -} - -/** - * @brief displays the ball throw animation on the 16x16 LED - * @returns 0 if everything went as expected - * @note overrided previous display on the ledmatrix - */ -int displayBallThrow(void) -{ - // technically could've only defined 1/4th of the array and mirrored it(twice) - uint16_t ballThrown[BALLTHROWNARRAYSIZE][16] = { - { - 0, 0, 0, 0, 0, 0, - 0b0000000110000000, - 0b0000001111000000, - 0b0000001111000000, - 0b0000000110000000, - 0, 0, 0, 0, 0, 0 - }, - { - 0, 0, 0, 0, 0, - 0b0000000110000000, - 0b0000001001000000, - 0b0000010110100000, - 0b0000010110100000, - 0b0000001001000000, - 0b0000000110000000, - 0, 0, 0, 0, 0 - }, - { - 0, 0, 0, - 0b0000001111000000, - 0b0000010000100000, - 0b0000100000010000, - 0b0001000110001000, - 0b0001001111001000, - 0b0001001111001000, - 0b0001000110001000, - 0b0000100000010000, - 0b0000010000100000, - 0b0000001111000000, - 0, 0, 0 - }, - { - 0b0000000000000000, - 0b0000001111000000, - 0b0000110000110000, - 0b0001000000001000, - 0b0010001111000100, - 0b0010010000100100, - 0b0100100110010010, - 0b0100101111010010, - 0b0100101111010010, - 0b0100100110010010, - 0b0010010000100100, - 0b0010001111000100, - 0b0001000000001000, - 0b0000110000110000, - 0b0000001111000000, - 0b00000000000000000 - }, - }; - // TODO: Make non-blocking - for ( int i = 0; i < 4; i++) - { - ledmatrixSetMutexValue(ballThrown[i]); - k_msleep(250); - } - return 0; -} - -/** - * @brief Calculates the score based on how well the angle aims at the target x. - * @param x The target x-coordinate. - * @param angle The angle in 15-degree increments (0-15). - * @return A score where lower values are better - * @note if the score is -1, the ball missed the pokemon - */ -int calculateAimScore(int x, int angle) -{ - // Map the angle to a direction (-7 to +7) - int direction = (angle * 14) / 15 - 7; // Map angle (0-15) to range (-7 to +7) - int projectedX = 8 + direction; // Centered around 8 (middle of 16x16 matrix) - - // Calculate the distance from the target x - int distance = abs(projectedX - x); - - return distance < 5 ? distance : -1; // Return a score -} - -/** - * @brief checks if the ball hit the pokemon based of the aim and timing - * @param x horizontal position of the pokemon - * @param time the time at which the ball was thrown 10 is the best time 0 is the worst - * @param angle angle of the ball throw - * @return true if the ball hit the pokemon, false if it missed - */ -bool checkHit(char x, char time, char angle) -{ - int aimScore = calculateAimScore(x, angle); - if (aimScore == -1) // check if the ball missed the pokemon - return false; - - displayBallThrow(); // display the ball throw animation - memset(displayMatrix, 0, sizeof(displayMatrix)); // clear the display matrix - setMatrix(); - - int catchChance = aimScore + time; // calculate the catch chance max 15 - if (catchChance >= 15) // check if the catch chance is greater or equal to 15 then the pokemon is caught - return true; - - float randomValue = (float)rand() / RAND_MAX; // convert to float - float probability = 1.0 - exp(-0.25 * catchChance + 1); // calculate the probability of catching the pokemon - return randomValue < probability; // if the random value is less than the probability then the pokemon is caught -} - -/** - * @brief converts the roll value to an angle between 0 and 15 - * @param roll the roll value from the gyroscope - * @return the angle between 0 and 15 - */ -char rollToAngle(int roll) -{ - // Map the roll value to an angle between 0 and 15 - if (roll < -45) - return 0; - else if (roll > 45) - return 15; - else - return (roll + 45) / 6; // Map to range 0-15 -} - -/** - * @brief checks if the catching minigame is ongoing - * @returns 0 if everything went as expected - */ -int catchingMg() -{ - uint8_t matrixLedsOn[] = {0xFF, 0xFF, 0xFF, 0xFF}; - uint8_t matrixLedsOff[] = {0x00, 0x00, 0x00, 0x00}; - bool catchingMgOngoing = true; - // int err; - uint8_t attemptCounter = 0; - int roll = 0; - char angle = 0; // angle in which the box is tilted (0 to 15) - char time = 0; // timing to throw the ball (0 to 10) - - uint8_t attemptsPermitted = 1 + (rand() / MAX_ATTEMPTS); // random number - bool caughtPokemon = false; - // generate pokemon locations on matrix - uint8_t pokemonLocationX = 3 + rand()%11; - uint8_t pokemonLocationY = 3 + rand()%3; - - while (catchingMgOngoing == true) - { - // update time - nonBlockingTimeHandler(&time); - - if (time == TIME_MAX) - lcdStringWrite("Kantel om te richten"); - if (time == TIME_MIN) - lcdStringWrite("Gooi met de knoppen"); - - btnmatrix_outSetMutexValue(matrixLedsOn); // set all LEDs to 1 - - // check if the pokemon fled - if (attemptCounter > attemptsPermitted) - { - lcdStringWrite("Pokemon is weg gerend!"); - playSound(1, NULL); // play sound flee - pokemonFled++; - k_msleep(1000); - catchingMgOngoing = false; - } - - if (checkBallsLeft() == false) { - catchingMgOngoing = false; - } - - // get the angle of the box - gyroscope_get_roll(&roll); - angle = rollToAngle(roll); // convert roll to angle - - displayPokemon(pokemonLocationX, pokemonLocationY); - displayAimDirection(angle); - displayTiming(time); - - setMatrix(); - - - if (isAnyButtonPressed()) - { - balls--; - attemptCounter++; - btnmatrix_outSetMutexValue(matrixLedsOff); // set all LEDs to 0 - - caughtPokemon = checkHit(pokemonLocationX, time, angle); - - if (caughtPokemon == true) - { - LOG_INF("Caught the pokemon! Attempt: %d", attemptCounter); - lcdStringWrite("Je hebt een pokemon gevangen!"); - playSound(0, NULL); // play sound catch - pokemonCaught++; - k_msleep(1000); - catchingMgOngoing = false; - } - else - { - LOG_INF("Missed the pokemon! Attempt: %d", attemptCounter); - lcdStringWrite("Mis! Probeer opnieuw!"); - playSound(2, NULL); // catch fail sound - k_msleep(1000); - } - } - } - - // reset the display - memset(displayMatrix, 0, sizeof(displayMatrix)); - setMatrix(); - // reset the ledcircle - uint8_t ledCircle[8] = {0}; - ledcircleSetMutexValue(ledCircle); // set all LEDs to 0 - - return attemptCounter; -} - -// !NOTE this is basically just the function idle also uses thus it should be moved to a common place instead. -int8_t set_Pokemon_Hint(int pokemonIndex) -{ - const int64_t currLat = getLatitude(); // Get the current latitude - const int64_t currLon = getLongitude(); // Get the current longitude - if ( currLat == 0 && currLon == 0) { // GPS doesn't have lock - LOG_ERR("GPS does not have a lock!\n"); - return -1; // Could not set hint if GPS is not locked - } - int dir = 0; - int distMeters = 0; - lcdStringWrite("Volg het licht!"); - distMeters = getDistanceMeters(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonIndex].lat), nanoDegToLdDeg(pokemonLocation[pokemonIndex].longi)); - dir = getAngle(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonIndex].lat), nanoDegToLdDeg(pokemonLocation[pokemonIndex].longi)); - - set_led_circle_dir_dist(get_relative_dir(dir), distMeters); // Set the led circle direction and distance - return 0; // Successfully set the hint -} - -/** - * @brief checks if a pokemon is nearby - * @returns true if there a pokemon close enough to trigger catchPokemonEvent false if there are no pokemon nearby - */ -bool pokemonNearby() -{ - // Normally this should be done in one-liners but that is also blocking. For now we do it here for clarity. - lcdStringWrite("Vind en vang de Pokemon!"); - k_msleep(1000); // Wait for a second to show the message - lcdStringWrite("Door in het park"); - k_msleep(1000); - lcdStringWrite("te lopen.."); - k_msleep(1000); - - for (int i = 0; i < (POKEMON); i++) - { - // check if the pokemon is within the distance - int64_t currLat = getLatitude(); - int64_t currLon = getLongitude(); - if (currLat == 0 && currLon == 0) // GPS doesn't have lock - { - LOG_ERR("GPS does not have a lock!\n"); - // lcdStringWrite("GPS heeft geen fix.."); - // k_msleep(1000); - // lcdStringWrite("GPS heeft geen fix.."); - // k_msleep(1000); - return false; // No pokemon nearby if GPS is not locked - } - - int distance = getDistanceMeters(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonToCatch[i]].lat), nanoDegToLdDeg(pokemonLocation[pokemonToCatch[i]].longi)); - if (distance < POKEMON_DISTANCE) - { - LOG_INF("Pokemon with ID %d is nearby at a distance of %d meters", pokemonLocation[i].id, distance); - return true; - } - } - return false; -} - -/** - * @brief checks if a pokemon is nearby and triggers the catchPokemonEvent - * @returns 0 if everything went as expected - */ -int pokemonGame() -{ - static int lastEventTime = 0; - lastEventTime = getHour() * 60 + getMinute(); // store the last event time in minutes - const int currentTime = getHour() * 60 + getMinute(); // get the current time in minutes - - // check if game finish conditions have been met - if(checkBallsLeft() == false || checkPokemonLeft() == false) - { - game_ongoing_pokemon = false; - return 0; - } - - if ((currentTime - lastEventTime) > HINT_COOLDOWN / 2) - ledcircleSetMutexValue(NULL); // clear the ledcircle if the cooldown is over - - // check if there is a pokemon nearby - if (pokemonNearby()) - { - LOG_DBG("Pokemon found!"); - playSound(3, NULL); // play pokemon appeared sound - catchEvent = true; - } - else if ((currentTime - lastEventTime) > HINT_COOLDOWN) // if no pokemon is found in the last HINT COOLDOWN give a hint - { - // sound to alert the player that a hint is given - int noteDelayMs = 50; // delay in milliseconds for the sound - playSound(3, ¬eDelayMs); // play pokemon appeared sound really quickly to alert the player - - lastEventTime = currentTime; // update the last event time - bool existingPokemon = false; - // hint to the player where a pokemon could be - while (existingPokemon == false) // This is possibly very slow if a player is unlucky - { - int index = pokemonToCatch[rand() % POKEMON]; // random index for the pokemon location - if (!pokemonLocation[index].caught && !pokemonLocation[index].fled && pokemonCaught < POKEMON) // check if the pokemon was not caught and didn't flee. Also double check that the player hasn't caught all pokemon yet - { - set_Pokemon_Hint(index); - - existingPokemon = true; // pokemon exists - } - } - } - - return 0; -} - -int calculateScore(int totalAttempts) -{ - uint64_t totalTime = k_uptime_get() - startTime; // calculate the total time in milliseconds - int score = 0; - - // formula used: - // Score = ((Caught / totalPokemon) * 400) / (sqrt((0.3*attempts)/(caught/totalpokemon) * ((timeTaken+ 3 * minute)/minute) * 10 - 150 * scoreScaler - score = ((pokemonCaught / (double)POKEMON) * 400) / (sqrt(0.3 * (totalAttempts / ((double)pokemonCaught / (double)POKEMON))) * ((totalTime + 180000) / 60000) * 10 - 150) * (MAX_SCORE / 1000); - - if (score < 0) - score = 0; - if (score > MAX_SCORE) - score = MAX_SCORE; - return score; -} - -/** - * @brief starts the catch the pokemon game - * @returns 0 if everything went as expected - * @note this function is called from the main thread - */ -int playCatchThePokemon() -{ - int totalAttepts = 0; -#if defined(CONFIG_BOARD_NUCLEO_H743ZI) - int err; - err = initMg(); - if (err) - { - LOG_ERR("Init error: %d", err); - return err; - } - - int score[POKEMON] = {0}; - while (game_ongoing_pokemon) - { - // add timer check if players are playing for more than 15 minutes. - if(!catchEvent) - { - err = pokemonGame(); - if (err) - { - LOG_ERR("Pokemon game error: %d", err); - // lcdStringWrite("Pokemon Error"); - // k_msleep(1000); - } - } - else - { - catchEvent = false; - int minigameNr = pokemonCaught + pokemonFled; // the minigame number is the amount of pokemon caught + fled since one of these always happens - score[minigameNr] = catchingMg(); - } - - // delay - } - - for (int i = 0; i < POKEMON; i++) - { - totalAttepts += score[i]; - } - - lcdClear(); -#endif - return calculateScore(totalAttepts); -} \ No newline at end of file diff --git a/Project/states/src/idle.c b/Project/states/src/idle.c index 90c3e4d..9ccdc67 100644 --- a/Project/states/src/idle.c +++ b/Project/states/src/idle.c @@ -118,7 +118,7 @@ int playIdle() { lcdDisable(); k_msleep(100); //convert the testIndex from human 1-10 back to code's 0-9 - testIndex--; + //testIndex--; return testIndex; #endif diff --git a/Project/states/src/minigame4.c b/Project/states/src/minigame4.c index 40ee903..d3bf692 100644 --- a/Project/states/src/minigame4.c +++ b/Project/states/src/minigame4.c @@ -1,201 +1,837 @@ #include "minigame4.h" - -#ifdef CONFIG_ARCH_POSIX -#define native_loop() k_sleep(K_MSEC(1)) -#else -#define native_loop() -#endif -K_TIMER_DEFINE(secTimerMg4, NULL, NULL); LOG_MODULE_REGISTER(mg_4); -char *mg4Threads[mg4ThreadCount] = {"startbtn", "buzzers", "abcbtn"}; +#define BALLS 16 +#define POKEMON 5 // number of pokemon to catch +#define POKEMONLOCATIONS 32 // number of locations where pokemon can be found +#define MAX_ATTEMPTS 8 +#define POKEMON_DISTANCE 10 // meters -void getMg4Threads(char ***names, unsigned *amount) { - *names = mg4Threads; - *amount = mg4ThreadCount; -} +#define HINT_COOLDOWN 1 // in minutes + +#define TIME_MAX 10 +#define TIME_MIN 0 +#define TIME_STEP 1 +#define TIME_INTERVAL_MS 100 // Adjust for smoother or faster changes + +// sounds +#define START_GAME_SOUND 5 +#define END_GAME_SOUND 6 + +#define MAX_SCORE 1000 // Maximum score for the game + +// Curve parameters for aim minigame +#define CURVE_DAMPEN 0.5 // affects how quickly the curve increases +#define AIM_DIRECTION_OFFSET 8 + +#define BALLTHROWNARRAYSIZE 4 + +char *catchThePokemonThreads[mg4ThreadCount] = {"startbtn", "btnmatrix_in", "ledcircle", "buzzers", "ledmatrix", "btnmatrix_out"}; // missing lcd threads +bool game_ongoing_pokemon, catchEvent; +uint8_t balls, pokemonCaught, pokemonFled; +uint16_t displayMatrix[16] = {0}; +uint64_t startTime; +uint8_t pokemonToCatch[POKEMON] = {0}; +static bool increasing = true; + +struct pokemonLocation +{ + uint8_t id; + int64_t lat; + int64_t longi; + bool caught; + bool fled; +} pokemonLocation[POKEMONLOCATIONS] = {0}; + +uint8_t pokemonMatrixLocation[] = { + 0b00001110, + 0b00010001, + 0b00010101, + 0b00010001, + 0b00001110 +}; -#define MG4_ONELINERS 3 -char oneLinersMG4[MG4_ONELINERS][32] = { - "Trivia!", - "Vragen over Den Bosch", - "& Vragen over Avans" +///////////// +/// sounds: +int soundCatch[2][3] = { + {587, 740, 880}, // D chord + {784, 988, 1175} // G chord }; -int trivia_load(uint16_t type, struct Quiz **questions, size_t *count, size_t maxQuestions) +int soundFlee[2][3] = { + {587, 740, 880}, // D chord + {554, 698, 830} // C# chord +}; + +int soundCatchFail[2][3] = { + {880, 523, 660}, // Cm chord + {415, 262, 349} // Fm chord +}; + +int soundAppear[2][3] = { + {262, 330, 302}, // C chord + {784, 1175, 1568} // G D G +}; + +int soundNoBalls[2][3] = { + {554, 698, 830}, + {523, 659, 784} // C chord // C# chord +}; + +int soundStartGame[3][3] = { + {349, 349, 349}, // F4 + {698, 698, 698}, // F5 + {1397, 1397, 1397} // F6 +}; + +int soundEndGame[3][3] = { + + {1760, 1760, 1760}, // A6 + {880, 880, 880}, // A5 + {440, 440, 440}, // A4 +}; +///////////// + +/** + * @brief returns the threads used in the catch the pokemon minigame + * @param names pointer to a char array to store the thread names + * @param amount pointer to an unsigned int to store the amount of threads + */ +void getMg4Threads(char ***names, unsigned *amount) { -#if defined(CONFIG_BOARD_NUCLEO_H743ZI) - char json_buf[BUFFER_SIZE]; - size_t len = 0; - size_t i = 0; - int ret = sd_get_buffer(type, json_buf, &len, BUFFER_SIZE); - if (ret != 0){ - return ret; - } - - if (len >= BUFFER_SIZE) { - LOG_ERR("JSON too large for buffer"); - return -3; - } - json_buf[len] = '\0'; // Ensure null-termination - - LOG_WRN("%s",json_buf); - struct Quiz *quizArray = malloc(maxQuestions * sizeof(struct Quiz)); - if (!quizArray){ - free(quizArray); - return -2; - } - - static const struct json_obj_descr quiz_descr[] = { - JSON_OBJ_DESCR_PRIM(struct Quiz, question, JSON_TOK_STRING), - JSON_OBJ_DESCR_PRIM(struct Quiz, answerA, JSON_TOK_STRING), - JSON_OBJ_DESCR_PRIM(struct Quiz, answerB, JSON_TOK_STRING), - JSON_OBJ_DESCR_PRIM(struct Quiz, answerC, JSON_TOK_STRING), - JSON_OBJ_DESCR_PRIM(struct Quiz, correct, JSON_TOK_NUMBER), - }; - - - struct json_obj json_arr; - ret = json_arr_separate_object_parse_init(&json_arr, json_buf, len); - if (ret < 0) { - LOG_ERR("Parse init error: %d", ret); - free(quizArray); + *names = catchThePokemonThreads; + *amount = mg4ThreadCount; +} + +// TODO: This should be done inside the button matrix or helperFunctions not in the game +/** + * @brief checks if any button is pressed + * @param buttonsInput pointer to an array of button states + * @param size size of the buttonsInput array + * @returns true if any button is pressed, false otherwise + * @note handles the inversion of the button states too + */ +bool isAnyButtonPressed() { + + uint8_t *buttonsInput = btnmatrix_inGetMutexValue(); + for (int i = 0; i < 16; i++){ + if (buttonsInput[i] == 0){ + return true; + } + } + return false; // No button is pressed +} + +/** + * @brief updates the time value based on the increasing or decreasing state + * @param timing_of_throw pointer to the time value to be updated + */ +void updateThrowTiming(uint8_t *timing_of_throw) +{ + // Update the timing_of_throwtiming value based on the increasing or decreasing state + if (increasing) { + *timing_of_throw += TIME_STEP; + } else { + *timing_of_throw -= TIME_STEP; + } + + // Cap the timing_of_throw value at the defined limits + if (*timing_of_throw >= TIME_MAX) { + *timing_of_throw = TIME_MAX; // Cap the timing_of_throw at max + increasing = false; // Switch to decreasing when max is reached + } + else if (*timing_of_throw <= TIME_MIN) { + *timing_of_throw = TIME_MIN; // Cap the timing_of_throw at min + increasing = true; // Switch to increasing when min is reached + } +} + +/** + * @brief handles the non-blocking time update + * @param timing_of_throw pointer to the time value to be updated + */ +void nonBlockingTimeHandler(uint8_t *timing_of_throw) +{ + static int64_t lastUpdate = 0; + int64_t currentTime = k_uptime_get(); // Get the current time in milliseconds + + if (currentTime - lastUpdate > TIME_INTERVAL_MS) + { + updateThrowTiming(timing_of_throw); + lastUpdate = currentTime; // Update the last update time + } +} + +/** + * @brief plays a sound based on the sound number + * @param soundNr the number of the sound to play + */ +int playSound(int soundNr, int *noteDelayMs) +{ + int (*soundArray)[3]; + int soundLength; + int delay = (noteDelayMs != NULL) ? *noteDelayMs : 200; // Default delay of 200 ms if not provided + + // Select the appropriate sound array based on soundNr + switch (soundNr) + { + case 0: + soundArray = soundCatch; + soundLength = sizeof(soundCatch) / sizeof(soundCatch[0]); + break; + case 1: + soundArray = soundFlee; + soundLength = sizeof(soundFlee) / sizeof(soundFlee[0]); + break; + case 2: + soundArray = soundCatchFail; + soundLength = sizeof(soundCatchFail) / sizeof(soundCatchFail[0]); + break; + case 3: + soundArray = soundAppear; + soundLength = sizeof(soundAppear) / sizeof(soundAppear[0]); + break; + case 4: + soundArray = soundNoBalls; + soundLength = sizeof(soundNoBalls) / sizeof(soundNoBalls[0]); + break; + case 5: + soundArray = soundStartGame; // Assuming soundStartGame is defined elsewhere + soundLength = sizeof(soundStartGame) / sizeof(soundStartGame[0]); + break; + case 6: + soundArray = soundEndGame; + soundLength = sizeof(soundEndGame) / sizeof(soundEndGame[0]); + break; + default: + return -1; // Invalid sound number + } + + // Play each chord in the sound array + for (int i = 0; i < soundLength; i++) + { + for (int j = 0; j < 3; j++) // Play all 3 frequencies in the chord + { + buzzerSetPwm(j, soundArray[i][j]); // Set PWM for each buzzer + } + k_msleep(delay); // Delay between chords + } + + // Turn off all buzzers after playing the sound + for (int j = 0; j < 3; j++) + { + buzzerTurnOff(j); + } + return 0; +} + +/** + * @brief checks if you have caught all pokemon and triggers the end game if not. + * @returns false if you have caught all pokemon or they have fleed + */ +bool checkPokemonLeft() +{ + bool pokemonLeft = true; + if(pokemonCaught >= POKEMON) + { + LOG_INF("All pokemon caught!"); + lcdStringWrite("Je hebt alle pokemon"); + k_msleep(1000); + lcdStringWrite("gevangen!"); + + pokemonLeft = false; + } + else if ((pokemonCaught + pokemonFled) >= POKEMONLOCATIONS) + { + LOG_INF("No pokemon left to catch! Caught: %d, Fled: %d", pokemonCaught, pokemonFled); + lcdStringWrite("Geen pokemon over!"); + + pokemonLeft = false; + } + + return pokemonLeft; +} + +/** + * @brief checks if you have balls left and triggers the end game if not. + * @returns false if you don't have balls left + * true if you have balls left + */ +bool checkBallsLeft() +{ + if (balls <= 0) + { + LOG_DBG("Out of pokeballs!"); + lcdStringWrite("Geen ballen over!"); + playSound(4, NULL); // Play out of balls sound + game_ongoing_pokemon = false; + return false; + } + + return true; +} + +/** + * @brief checks if the locations are valid + * @returns 0 if everything went as expected + */ +int selectLocations() +{ + struct Location *locArray = NULL; + size_t count = 0; + int ret = locations_load(0, &locArray, &count, POKEMONLOCATIONS); + if (ret != 0 || count < POKEMONLOCATIONS) { + // handle error + LOG_ERR("Failed to load locations or not enough locations. Error: %d, location count: %zu", ret, count); return ret; } - - for (; i < maxQuestions; ++i) { - memset(&quizArray[i], 0, sizeof(struct Quiz)); - ret = json_arr_separate_parse_object(&json_arr, quiz_descr, ARRAY_SIZE(quiz_descr), &quizArray[i]); - if (ret == 0) break; // End of array - if (ret < 0) { - LOG_ERR("Parse error at index %zu: %d", i, ret); - // Free allocated quiz before returning - free(quizArray); - return ret; + for (int i = 0; i < POKEMONLOCATIONS; i++) + { + pokemonLocation[i].lat = locArray[i].x; + pokemonLocation[i].longi = locArray[i].y; + pokemonLocation[i].caught = false; + pokemonLocation[i].fled = false; + pokemonLocation[i].id = (uint8_t)i; + } + + for (int i = 0; i < POKEMON; i++) + { + pokemonToCatch[i] = rand() % POKEMONLOCATIONS; // Randomly select pokemon locations + } + + free(locArray); + return 0; +} + +/** + * @brief initialises variables, locations and hints used + * @returns 0 if everything went as expected + */ +int initMg() +{ + int err; + + // init variables + balls = BALLS; + pokemonCaught = 0; + pokemonFled = 0; + game_ongoing_pokemon = true; + catchEvent = false; + startTime = k_uptime_get(); // Get the current time in milliseconds + + lcdEnable(); + lcdStringWrite("Vind en vang de pokemon!"); + int soundSpeed = 100; + playSound(START_GAME_SOUND, &soundSpeed); + k_msleep(3000); + + err = selectLocations(); + if (err != 0) { + LOG_ERR("Failed to select locations: %d", err); + return err; // Return error if location selection fails + } + return 0; +} + +/** + * @brief pushes matrix to ledmatrix and sets the displayMatrix to 0 + * @returns 0 if everything went as expected + */ +void setMatrix() +{ + ledmatrixSetMutexValue(displayMatrix); + + // clear displayMatrix + memset(displayMatrix, 0, sizeof(displayMatrix)); +} + +/** + * @brief displays the pokemon location on the 16x16 LED + * @param x center x coordinate of the pokemon on the 16x16 LED + * @param y center y coordinate of the pokemon on the 16x16 LED + * @returns 0 if everything went as expected + * @note Advisory position is to not go below Y 8 + */ +int displayPokemon(uint8_t x, uint8_t y) +{ + int8_t offsetX = -3; // The offset is used so that the pokemon doesn't appear half over the edge of the display. This way the pokemon is always fully visible. + int8_t offsetY = -3; + for (int i = 0; i < 5; i++) + { + displayMatrix[(y+offsetY+i)] += pokemonMatrixLocation[i] << (16-x+offsetX); + } + return 0; +} + +/** + * @brief displays the direction aimed on the 16x16 LED + * @param angle angle in 15 degree increments 0 is 45 degrees to the left and 15 is 45 degrees to the right + * @returns 0 if everything went as expected + */ +int displayAimDirection(char angle) +{ + // Map the angle to a direction (-7 to +7) + int direction = (angle * 14) / 15 - 7; // Map angle (0-15) to range (-7 to +7) + + // Draw the line on the bottom 8 rows + for (int y = 15; y >= AIM_DIRECTION_OFFSET; y--) { + int curveFactor = (15 - y) * (15 - y) / 16; // Add a curve factor + if (angle > 7) { + curveFactor *= -1; + } + int x = AIM_DIRECTION_OFFSET + direction * (15 - y) / 7 + CURVE_DAMPEN * curveFactor; // Adjust x with curve + if (x >= 0 && x < 16) { + displayMatrix[y] |= 1 << (15 - x); // Set the bit at position x } } - //LOG_INF("%s", quizArray[1].question); - *count = i; - *questions = quizArray; -#endif + + // cleanup curve + for (int y = AIM_DIRECTION_OFFSET + 1; y < 2*AIM_DIRECTION_OFFSET-1; y++) + { + if (( (angle <= 7) && (displayMatrix[y] < displayMatrix[y+1])) || ((angle > 7) && (displayMatrix[y] > displayMatrix[y+1]))) + displayMatrix[y] = displayMatrix[y+1]; + } + return 0; } +/* + * @brief displays the timing to throw the ball on the 16x16 LED + * @param[in] time time to display on a scale of 0-10 + * @returns 0 if everything went as expected + */ +int displayTiming(char time) +{ + // Initialize the LED circle array and temporary variable + uint8_t ledCircle[8] = {0}; + uint32_t temp = 0; + + // Generate the bit pattern based on the input time + for (int i = 0; i <= time; i++) + { + temp = (temp << 3) | 0b111; // Append 3 bits of 1 for each step + } + + // Process the first 4 bytes of the temp variable + for (int i = 0; i < 4; i++) { + // Extract and reverse the bits of the current byte + uint8_t currentByte = (temp >> (i * 8)) & 0xFF; // Extract the current byte + uint8_t reversedByte = ((currentByte & 0xF0) >> 4) | ((currentByte & 0x0F) << 4); + reversedByte = ((reversedByte & 0xCC) >> 2) | ((reversedByte & 0x33) << 2); + reversedByte = ((reversedByte & 0xAA) >> 1) | ((reversedByte & 0x55) << 1); + + // Assign the reversed byte to the second half of the LED circle + ledCircle[i + 4] = reversedByte; + + // Assign the original byte to the first half of the LED circle + ledCircle[i] = (temp >> (24 - (i * 8))) & 0xFF; + } + + // Update the LED circle with the generated values + ledcircleSetMutexValue(ledCircle); + + return 0; +} + +/** + * @brief displays the ball throw animation on the 16x16 LED + * @returns 0 if everything went as expected + * @note overrided previous display on the ledmatrix + */ +int displayBallThrow(void) +{ + // technically could've only defined 1/4th of the array and mirrored it(twice) + uint16_t ballThrown[BALLTHROWNARRAYSIZE][16] = { + { + 0, 0, 0, 0, 0, 0, + 0b0000000110000000, + 0b0000001111000000, + 0b0000001111000000, + 0b0000000110000000, + 0, 0, 0, 0, 0, 0 + }, + { + 0, 0, 0, 0, 0, + 0b0000000110000000, + 0b0000001001000000, + 0b0000010110100000, + 0b0000010110100000, + 0b0000001001000000, + 0b0000000110000000, + 0, 0, 0, 0, 0 + }, + { + 0, 0, 0, + 0b0000001111000000, + 0b0000010000100000, + 0b0000100000010000, + 0b0001000110001000, + 0b0001001111001000, + 0b0001001111001000, + 0b0001000110001000, + 0b0000100000010000, + 0b0000010000100000, + 0b0000001111000000, + 0, 0, 0 + }, + { + 0b0000000000000000, + 0b0000001111000000, + 0b0000110000110000, + 0b0001000000001000, + 0b0010001111000100, + 0b0010010000100100, + 0b0100100110010010, + 0b0100101111010010, + 0b0100101111010010, + 0b0100100110010010, + 0b0010010000100100, + 0b0010001111000100, + 0b0001000000001000, + 0b0000110000110000, + 0b0000001111000000, + 0b00000000000000000 + }, + }; + // TODO: Make non-blocking + for ( int i = 0; i < 4; i++) + { + ledmatrixSetMutexValue(ballThrown[i]); + k_msleep(250); + } + return 0; +} + +/** + * @brief Calculates the score based on how well the angle aims at the target x. + * @param x The target x-coordinate. + * @param angle The angle in 15-degree increments (0-15). + * @return A score where lower values are better + * @note if the score is -1, the ball missed the pokemon + */ +int calculateAimScore(int x, int angle) +{ + // Map the angle to a direction (-7 to +7) + int direction = (angle * 14) / 15 - 7; // Map angle (0-15) to range (-7 to +7) + int projectedX = 8 + direction; // Centered around 8 (middle of 16x16 matrix) + + // Calculate the distance from the target x + int distance = abs(projectedX - x); + + return distance < 5 ? distance : -1; // Return a score +} + +/** + * @brief checks if the ball hit the pokemon based of the aim and timing + * @param x horizontal position of the pokemon + * @param time the time at which the ball was thrown 10 is the best time 0 is the worst + * @param angle angle of the ball throw + * @return true if the ball hit the pokemon, false if it missed + */ +bool checkHit(char x, char time, char angle) +{ + int aimScore = calculateAimScore(x, angle); + if (aimScore == -1) // check if the ball missed the pokemon + return false; + + displayBallThrow(); // display the ball throw animation + memset(displayMatrix, 0, sizeof(displayMatrix)); // clear the display matrix + setMatrix(); + + int catchChance = aimScore + time; // calculate the catch chance max 15 + if (catchChance >= 15) // check if the catch chance is greater or equal to 15 then the pokemon is caught + return true; + + float randomValue = (float)rand() / RAND_MAX; // convert to float + float probability = 1.0 - exp(-0.25 * catchChance + 1); // calculate the probability of catching the pokemon + return randomValue < probability; // if the random value is less than the probability then the pokemon is caught +} + +/** + * @brief converts the roll value to an angle between 0 and 15 + * @param roll the roll value from the gyroscope + * @return the angle between 0 and 15 + */ +char rollToAngle(int roll) +{ + // Map the roll value to an angle between 0 and 15 + if (roll < -45) + return 0; + else if (roll > 45) + return 15; + else + return (roll + 45) / 6; // Map to range 0-15 +} + +/** + * @brief checks if the catching minigame is ongoing + * @returns 0 if everything went as expected + */ +int catchingMg() +{ + uint8_t matrixLedsOn[] = {0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t matrixLedsOff[] = {0x00, 0x00, 0x00, 0x00}; + bool catchingMgOngoing = true; + // int err; + uint8_t attemptCounter = 0; + int roll = 0; + char angle = 0; // angle in which the box is tilted (0 to 15) + char time = 0; // timing to throw the ball (0 to 10) + + uint8_t attemptsPermitted = 1 + (rand() / MAX_ATTEMPTS); // random number + bool caughtPokemon = false; + // generate pokemon locations on matrix + uint8_t pokemonLocationX = 3 + rand()%11; + uint8_t pokemonLocationY = 3 + rand()%3; + + while (catchingMgOngoing == true) + { + // update time + nonBlockingTimeHandler(&time); + + if (time == TIME_MAX) + lcdStringWrite("Kantel om te richten"); + if (time == TIME_MIN) + lcdStringWrite("Gooi met de knoppen"); + + btnmatrix_outSetMutexValue(matrixLedsOn); // set all LEDs to 1 + + // check if the pokemon fled + if (attemptCounter > attemptsPermitted) + { + lcdStringWrite("Pokemon is weg gerend!"); + playSound(1, NULL); // play sound flee + pokemonFled++; + k_msleep(1000); + catchingMgOngoing = false; + } + + if (checkBallsLeft() == false) { + catchingMgOngoing = false; + } + + // get the angle of the box + gyroscope_get_roll(&roll); + angle = rollToAngle(roll); // convert roll to angle + + displayPokemon(pokemonLocationX, pokemonLocationY); + displayAimDirection(angle); + displayTiming(time); + + setMatrix(); + + + if (isAnyButtonPressed()) + { + balls--; + attemptCounter++; + btnmatrix_outSetMutexValue(matrixLedsOff); // set all LEDs to 0 + + caughtPokemon = checkHit(pokemonLocationX, time, angle); + + if (caughtPokemon == true) + { + LOG_INF("Caught the pokemon! Attempt: %d", attemptCounter); + lcdStringWrite("Je hebt een pokemon gevangen!"); + playSound(0, NULL); // play sound catch + pokemonCaught++; + k_msleep(1000); + catchingMgOngoing = false; + } + else + { + LOG_INF("Missed the pokemon! Attempt: %d", attemptCounter); + lcdStringWrite("Mis! Probeer opnieuw!"); + playSound(2, NULL); // catch fail sound + k_msleep(1000); + } + } + } + + // reset the display + memset(displayMatrix, 0, sizeof(displayMatrix)); + setMatrix(); + // reset the ledcircle + uint8_t ledCircle[8] = {0}; + ledcircleSetMutexValue(ledCircle); // set all LEDs to 0 + + return attemptCounter; +} + +// !NOTE this is basically just the function idle also uses thus it should be moved to a common place instead. +int8_t set_Pokemon_Hint(int pokemonIndex) +{ + const int64_t currLat = getLatitude(); // Get the current latitude + const int64_t currLon = getLongitude(); // Get the current longitude + if ( currLat == 0 && currLon == 0) { // GPS doesn't have lock + LOG_ERR("GPS does not have a lock!\n"); + return -1; // Could not set hint if GPS is not locked + } + int dir = 0; + int distMeters = 0; + lcdStringWrite("Volg het licht!"); + distMeters = getDistanceMeters(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonIndex].lat), nanoDegToLdDeg(pokemonLocation[pokemonIndex].longi)); + dir = getAngle(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonIndex].lat), nanoDegToLdDeg(pokemonLocation[pokemonIndex].longi)); + set_led_circle_dir_dist(get_relative_dir(dir), distMeters); // Set the led circle direction and distance + return 0; // Successfully set the hint +} +/** + * @brief checks if a pokemon is nearby + * @returns true if there a pokemon close enough to trigger catchPokemonEvent false if there are no pokemon nearby + */ +bool pokemonNearby() +{ + // Normally this should be done in one-liners but that is also blocking. For now we do it here for clarity. + lcdStringWrite("Vind en vang de Pokemon!"); + k_msleep(1000); // Wait for a second to show the message + lcdStringWrite("Door in het park"); + k_msleep(1000); + lcdStringWrite("te lopen.."); + k_msleep(1000); -int playMg4(uint8_t question_nr) { - uint32_t score = 1000; - uint8_t *abcBtn; - bool showQuestion = true; - bool correct = false; - bool buttonReleased = true; - static uint8_t questionIndex = 0; - if(question_nr != 0){ - questionIndex = question_nr; - } - - struct Quiz *quizzes = NULL; - size_t count = 0; - size_t maxQuestions = 32; - char buf[64]; - lcdEnable(); - int res = trivia_load(3, &quizzes, &count, maxQuestions); - if (res < 0) { - sprintf(buf, "failed to load trivia: %d", res); - LOG_INF("%s", buf); - k_msleep(3000); - return res; - } - - - sprintf(buf, "Total questions loaded =: %d ", count ); - LOG_INF(" %s", buf); - - - show_oneliners(oneLinersMG4 , MG4_ONELINERS); - lcdEnable(); - k_timer_start(&secTimerMg4, K_MSEC(1000), K_NO_WAIT); - abcledsSet('a',true); - abcledsSet('b',true); - abcledsSet('c',true); - while (!(k_timer_status_get(&secTimerMg4) > 0)){native_loop();} - if( questionIndex < AMOUNT_QUESTIONS) - { - correct = false; - while (!correct) - { - native_loop(); - - //show output - if (showQuestion) - { - showQuestion = false; - sprintf(buf, "%s ", quizzes[questionIndex].question); - - lcdStringWrite(buf); - k_timer_start(&secTimerMg4, K_MSEC(1000), K_NO_WAIT); - while (!(k_timer_status_get(&secTimerMg4) > 0)){native_loop();} - - char *answers[AMOUNT_ANSWERS] = { - quizzes[questionIndex].answerA, - quizzes[questionIndex].answerB, - quizzes[questionIndex].answerC - }; - - for (uint8_t answersIndex = 0; answersIndex < AMOUNT_ANSWERS; answersIndex++) - { - //sprintf(buf, "%s ",answers[answersIndex]); - lcdStringWrite(answers[answersIndex]); - k_timer_start(&secTimerMg4, K_MSEC(2000), K_NO_WAIT); - while (!(k_timer_status_get(&secTimerMg4) > 0)){native_loop();} - } - - lcdStringWrite(" Antwoord A, B of C "); - - } - - //check for input - abcBtn = abcbtnGetMutexValue(); - if(abcBtn[2] && abcBtn[1] && abcBtn[0]) - { - buttonReleased = true; - } - if ((!abcBtn[2] || !abcBtn[1] || !abcBtn[0]) && buttonReleased) - { - buttonReleased = false; - - if (!abcBtn[quizzes[questionIndex].correct]) - { - lcdStringWrite("Correct!"); - correct = true; - } - else - { - lcdStringWrite("Incorrect!"); - score -= 400; - } - - k_timer_start(&secTimerMg4, K_MSEC(1000), K_NO_WAIT); - while (!(k_timer_status_get(&secTimerMg4) > 0)){native_loop();} - showQuestion = true; - } - } - } - if (question_nr > 0 ){ - questionIndex = 0; // resets this after use this allows question 0 to be triggered - }else{ - questionIndex++; // in case question_nr is not used the program this is the backup - } - abcledsSet('a',false); - abcledsSet('b',false); - abcledsSet('c',false); - lcdDisable(); - lcdClear(); - if (score < 0){ - score = 0; - } - return score; + for (int i = 0; i < (POKEMON); i++) + { + // check if the pokemon is within the distance + int64_t currLat = getLatitude(); + int64_t currLon = getLongitude(); + if (currLat == 0 && currLon == 0) // GPS doesn't have lock + { + LOG_ERR("GPS does not have a lock!\n"); + // lcdStringWrite("GPS heeft geen fix.."); + // k_msleep(1000); + // lcdStringWrite("GPS heeft geen fix.."); + // k_msleep(1000); + return false; // No pokemon nearby if GPS is not locked + } + + int distance = getDistanceMeters(nanoDegToLdDeg(currLat), nanoDegToLdDeg(currLon), nanoDegToLdDeg(pokemonLocation[pokemonToCatch[i]].lat), nanoDegToLdDeg(pokemonLocation[pokemonToCatch[i]].longi)); + if (distance < POKEMON_DISTANCE) + { + LOG_INF("Pokemon with ID %d is nearby at a distance of %d meters", pokemonLocation[i].id, distance); + return true; + } + } + return false; } + +/** + * @brief checks if a pokemon is nearby and triggers the catchPokemonEvent + * @returns 0 if everything went as expected + */ +int pokemonGame() +{ + static int lastEventTime = 0; + lastEventTime = getHour() * 60 + getMinute(); // store the last event time in minutes + const int currentTime = getHour() * 60 + getMinute(); // get the current time in minutes + + // check if game finish conditions have been met + if(checkBallsLeft() == false || checkPokemonLeft() == false) + { + game_ongoing_pokemon = false; + return 0; + } + + if ((currentTime - lastEventTime) > HINT_COOLDOWN / 2) + ledcircleSetMutexValue(NULL); // clear the ledcircle if the cooldown is over + + // check if there is a pokemon nearby + if (pokemonNearby()) + { + LOG_DBG("Pokemon found!"); + playSound(3, NULL); // play pokemon appeared sound + catchEvent = true; + } + else if ((currentTime - lastEventTime) > HINT_COOLDOWN) // if no pokemon is found in the last HINT COOLDOWN give a hint + { + // sound to alert the player that a hint is given + int noteDelayMs = 50; // delay in milliseconds for the sound + playSound(3, ¬eDelayMs); // play pokemon appeared sound really quickly to alert the player + + lastEventTime = currentTime; // update the last event time + bool existingPokemon = false; + // hint to the player where a pokemon could be + while (existingPokemon == false) // This is possibly very slow if a player is unlucky + { + int index = pokemonToCatch[rand() % POKEMON]; // random index for the pokemon location + if (!pokemonLocation[index].caught && !pokemonLocation[index].fled && pokemonCaught < POKEMON) // check if the pokemon was not caught and didn't flee. Also double check that the player hasn't caught all pokemon yet + { + set_Pokemon_Hint(index); + + existingPokemon = true; // pokemon exists + } + } + } + + return 0; +} + +int calculateScore(int totalAttempts) +{ + uint64_t totalTime = k_uptime_get() - startTime; // calculate the total time in milliseconds + int score = 0; + + // formula used: + // Score = ((Caught / totalPokemon) * 400) / (sqrt((0.3*attempts)/(caught/totalpokemon) * ((timeTaken+ 3 * minute)/minute) * 10 - 150 * scoreScaler + score = ((pokemonCaught / (double)POKEMON) * 400) / (sqrt(0.3 * (totalAttempts / ((double)pokemonCaught / (double)POKEMON))) * ((totalTime + 180000) / 60000) * 10 - 150) * (MAX_SCORE / 1000); + + if (score < 0) + score = 0; + if (score > MAX_SCORE) + score = MAX_SCORE; + return score; +} + +/** + * @brief starts the catch the pokemon game + * @returns 0 if everything went as expected + * @note this function is called from the main thread + */ +int playMg4() +{ + int totalAttepts = 0; +#if defined(CONFIG_BOARD_NUCLEO_H743ZI) + int err; + err = initMg(); + if (err) + { + LOG_ERR("Init error: %d", err); + return err; + } + + int score[POKEMON] = {0}; + while (game_ongoing_pokemon) + { + // add timer check if players are playing for more than 15 minutes. + if(!catchEvent) + { + err = pokemonGame(); + if (err) + { + LOG_ERR("Pokemon game error: %d", err); + // lcdStringWrite("Pokemon Error"); + // k_msleep(1000); + } + } + else + { + catchEvent = false; + int minigameNr = pokemonCaught + pokemonFled; // the minigame number is the amount of pokemon caught + fled since one of these always happens + score[minigameNr] = catchingMg(); + } + + // delay + } + + for (int i = 0; i < POKEMON; i++) + { + totalAttepts += score[i]; + } + + lcdClear(); +#endif + return calculateScore(totalAttepts); +} \ No newline at end of file diff --git a/Project/states/src/trivia.c b/Project/states/src/trivia.c new file mode 100644 index 0000000..d0e0852 --- /dev/null +++ b/Project/states/src/trivia.c @@ -0,0 +1,201 @@ +#include "trivia.h" + + +#ifdef CONFIG_ARCH_POSIX +#define native_loop() k_sleep(K_MSEC(1)) +#else +#define native_loop() +#endif +K_TIMER_DEFINE(secTimerTrivia, NULL, NULL); +LOG_MODULE_REGISTER(trivia); + +char *triviaThreads[triviaThreadCount] = {"startbtn", "buzzers", "abcbtn"}; + +void getTriviaThreads(char ***names, unsigned *amount) { + *names = triviaThreads; + *amount = triviaThreadCount; +} + +#define MG4_ONELINERS 3 +char oneLinersMG4[MG4_ONELINERS][32] = { + "Trivia!", + "Vragen over Den Bosch", + "& Vragen over Avans" +}; + +int trivia_load(uint16_t type, struct Quiz **questions, size_t *count, size_t maxQuestions) +{ +#if defined(CONFIG_BOARD_NUCLEO_H743ZI) + char json_buf[BUFFER_SIZE]; + size_t len = 0; + size_t i = 0; + int ret = sd_get_buffer(type, json_buf, &len, BUFFER_SIZE); + if (ret != 0){ + return ret; + } + + if (len >= BUFFER_SIZE) { + LOG_ERR("JSON too large for buffer"); + return -3; + } + json_buf[len] = '\0'; // Ensure null-termination + + LOG_WRN("%s",json_buf); + struct Quiz *quizArray = malloc(maxQuestions * sizeof(struct Quiz)); + if (!quizArray){ + free(quizArray); + return -2; + } + + static const struct json_obj_descr quiz_descr[] = { + JSON_OBJ_DESCR_PRIM(struct Quiz, question, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct Quiz, answerA, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct Quiz, answerB, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct Quiz, answerC, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct Quiz, correct, JSON_TOK_NUMBER), + }; + + + struct json_obj json_arr; + ret = json_arr_separate_object_parse_init(&json_arr, json_buf, len); + if (ret < 0) { + LOG_ERR("Parse init error: %d", ret); + free(quizArray); + return ret; + } + + + for (; i < maxQuestions; ++i) { + memset(&quizArray[i], 0, sizeof(struct Quiz)); + ret = json_arr_separate_parse_object(&json_arr, quiz_descr, ARRAY_SIZE(quiz_descr), &quizArray[i]); + if (ret == 0) break; // End of array + if (ret < 0) { + LOG_ERR("Parse error at index %zu: %d", i, ret); + // Free allocated quiz before returning + free(quizArray); + return ret; + } + } + //LOG_INF("%s", quizArray[1].question); + *count = i; + *questions = quizArray; +#endif + return 0; +} + + + + +int playTrivia(uint8_t question_nr) { + uint32_t score = 1000; + uint8_t *abcBtn; + bool showQuestion = true; + bool correct = false; + bool buttonReleased = true; + static uint8_t questionIndex = 0; + if(question_nr != 0){ + questionIndex = question_nr; + } + + struct Quiz *quizzes = NULL; + size_t count = 0; + size_t maxQuestions = 32; + char buf[64]; + lcdEnable(); + int res = trivia_load(3, &quizzes, &count, maxQuestions); + if (res < 0) { + sprintf(buf, "failed to load trivia: %d", res); + LOG_INF("%s", buf); + k_msleep(3000); + return res; + } + + + sprintf(buf, "Total questions loaded =: %d ", count ); + LOG_INF(" %s", buf); + + + show_oneliners(oneLinersMG4 , MG4_ONELINERS); + lcdEnable(); + k_timer_start(&secTimerTrivia, K_MSEC(1000), K_NO_WAIT); + abcledsSet('a',true); + abcledsSet('b',true); + abcledsSet('c',true); + while (!(k_timer_status_get(&secTimerTrivia) > 0)){native_loop();} + if( questionIndex < AMOUNT_QUESTIONS) + { + correct = false; + while (!correct) + { + native_loop(); + + //show output + if (showQuestion) + { + showQuestion = false; + sprintf(buf, "%s ", quizzes[questionIndex].question); + + lcdStringWrite(buf); + k_timer_start(&secTimerTrivia, K_MSEC(1000), K_NO_WAIT); + while (!(k_timer_status_get(&secTimerTrivia) > 0)){native_loop();} + + char *answers[AMOUNT_ANSWERS] = { + quizzes[questionIndex].answerA, + quizzes[questionIndex].answerB, + quizzes[questionIndex].answerC + }; + + for (uint8_t answersIndex = 0; answersIndex < AMOUNT_ANSWERS; answersIndex++) + { + //sprintf(buf, "%s ",answers[answersIndex]); + lcdStringWrite(answers[answersIndex]); + k_timer_start(&secTimerTrivia, K_MSEC(2000), K_NO_WAIT); + while (!(k_timer_status_get(&secTimerTrivia) > 0)){native_loop();} + } + + lcdStringWrite(" Antwoord A, B of C "); + + } + + //check for input + abcBtn = abcbtnGetMutexValue(); + if(abcBtn[2] && abcBtn[1] && abcBtn[0]) + { + buttonReleased = true; + } + if ((!abcBtn[2] || !abcBtn[1] || !abcBtn[0]) && buttonReleased) + { + buttonReleased = false; + + if (!abcBtn[quizzes[questionIndex].correct]) + { + lcdStringWrite("Correct!"); + correct = true; + } + else + { + lcdStringWrite("Incorrect!"); + score -= 400; + } + + k_timer_start(&secTimerTrivia, K_MSEC(1000), K_NO_WAIT); + while (!(k_timer_status_get(&secTimerTrivia) > 0)){native_loop();} + showQuestion = true; + } + } + } + if (question_nr > 0 ){ + questionIndex = 0; // resets this after use this allows question 0 to be triggered + }else{ + questionIndex++; // in case question_nr is not used the program this is the backup + } + abcledsSet('a',false); + abcledsSet('b',false); + abcledsSet('c',false); + lcdDisable(); + lcdClear(); + if (score < 0){ + score = 0; + } + return score; +} From 5990294ef516179fe877c1301e41cc92e6b5f2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Thu, 17 Jul 2025 16:10:59 +0200 Subject: [PATCH 4/4] Code cleanup --- Project/main/src/statemachine.c | 29 +++++------------------------ Project/prj.conf | 2 +- Project/states/src/idle.c | 6 +----- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/Project/main/src/statemachine.c b/Project/main/src/statemachine.c index 07dc4ab..244de6b 100644 --- a/Project/main/src/statemachine.c +++ b/Project/main/src/statemachine.c @@ -243,28 +243,16 @@ bool check_end_time_reached() { void startStatemachine() { bool statemachine_ongoing = 1; - //bool test = 0; statemachineStates current_state = init_state; int mgID = 0; uint8_t trivia_ID; while(statemachine_ongoing){ -// #ifndef CONFIG_TESTMODE -// statemachine_ongoing = check_end_time_reached(); - // if(check_end_time_reached()){ - // current_state = end_game_state; - // } -// #endif -// #if defined(CONFIG_TESTMODE) -// if(test){ -// LOG_INF("Time reached"); -// statemachine_ongoing = 0; -// } -// else{ -// LOG_INF("Time will be reached"); -// statemachine_ongoing = 0; -// } -// #endif +#ifndef CONFIG_TESTMODE + if(check_end_time_reached()){ + current_state = end_game_state; + } +#endif switch (current_state) { case init_state: init_stateFunction(¤t_state); @@ -289,11 +277,4 @@ void startStatemachine() { break; } } - - LOG_INF("Score after 0 games: %d", sd_get_score()); - - end_game_stateFunction(¤t_state); - while(1){ - exit_stateFunction(¤t_state); - } } \ No newline at end of file diff --git a/Project/prj.conf b/Project/prj.conf index d12f294..eaa37f0 100644 --- a/Project/prj.conf +++ b/Project/prj.conf @@ -9,4 +9,4 @@ CONFIG_SHELL=y CONFIG_SHELL_THREAD_PRIORITY_OVERRIDE=y #CONFIG_SHELL_VT100_COLORS=n -CONFIG_TESTMODE=y \ No newline at end of file +CONFIG_TESTMODE=n \ No newline at end of file diff --git a/Project/states/src/idle.c b/Project/states/src/idle.c index 9ccdc67..d78df19 100644 --- a/Project/states/src/idle.c +++ b/Project/states/src/idle.c @@ -68,11 +68,9 @@ int playIdle() { uint8_t ledcircleOff[8] = {0}; #if defined(CONFIG_TESTMODE) - static int testIndex = 0; + static int testIndex = 1; char lcd_msg[32]; - //convert the testIndex from code's 0-9 back to human 1-10 - testIndex++; lcdEnable(); lcdStringWrite("Selecteer een spel met A en C"); k_msleep(3000); @@ -117,8 +115,6 @@ int playIdle() { lcdClear(); lcdDisable(); k_msleep(100); - //convert the testIndex from human 1-10 back to code's 0-9 - //testIndex--; return testIndex; #endif