From 23518c7af911055d5246881d7437d2bac98f0452 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Fri, 30 Oct 2026 19:54:57 -0400 Subject: [PATCH] Initial triangle progression --- package.json | 3 +- src/c/main.c | 357 +++++++++++++++++++++++---------------------- src/c/settings.h | 3 +- src/pkjs/config.js | 14 +- 4 files changed, 197 insertions(+), 180 deletions(-) diff --git a/package.json b/package.json index 3de6acb..22cf022 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "PKJS_READY", "PKJS_ALARM_RESET", "PKJS_TRIANGLE_COUNT", - "PKJS_TRIANGLE_MIN_SECS", + "PKJS_TRIANGLE_SECS_MIN", + "PKJS_TRIANGLE_SECS_STEP", "PKJS_PAVLOK_ENABLE", "PKJS_PAVLOK_ZAP_INTENSITY", "PKJS_PAVLOK_ZAP" diff --git a/src/c/main.c b/src/c/main.c index 8bf5b72..45cc264 100644 --- a/src/c/main.c +++ b/src/c/main.c @@ -15,7 +15,9 @@ static Window *s_status_window; static Window *s_drop_window; static Layer *s_top_bar_layer; static Layer *s_bottom_bar_layer; +// static TextLayer *s_time_txt_layer; static TextLayer *s_countdown_txt_layer; +// static TextLayer *s_progress_txt_layer; static TextLayer *s_status_txt_layer; //// setter static BitmapLayer *s_clock_bmp_layer; @@ -263,7 +265,8 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) { apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_INTENSITY, &settings.PavlokIntensity); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_COUNT, &settings.TriangleCount); - apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_MIN_SECS, &settings.TriangeMinSecs); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_MIN, &settings.TriangleSecsMin); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_STEP, &settings.TriangleSecsStep); persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } @@ -286,176 +289,6 @@ static void send_zap(uint8_t intensity) { ////////////////////// ALARM ////////////////////// -static void awareness_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { - s_awareness_countdown_int--; - snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); - text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); - if (s_awareness_countdown_int == 0) { - send_zap(settings.PavlokIntensity); - // TODO demote and reset countdown int - } -} - -static void graph_layer_update_proc(Layer *layer, GContext *ctx) { - GRect bounds = layer_get_bounds(layer); - static const uint8_t s_pixel_gap = PBL_DISPLAY_WIDTH / 20; - graphics_context_set_stroke_color(ctx, GColorElectricBlue); - for (int i = s_pixel_gap; i < PBL_DISPLAY_WIDTH; i += s_pixel_gap) { - graphics_draw_line(ctx, GPoint(0, i), GPoint(bounds.size.w, i)); // horizontal line - graphics_draw_line(ctx, GPoint(i, 0), GPoint(i, bounds.size.h)); // vertical line - } -} - -static void drop_layer_update_proc(Layer *layer, GContext *ctx) { - graphics_context_set_fill_color(ctx, GColorWhite); - graphics_fill_rect(ctx, layer_get_bounds(layer), 5, GCornersAll); -} - -static void division_layer_update_proc(Layer *layer, GContext *ctx) { - GRect bounds = layer_get_bounds(layer); - graphics_context_set_stroke_color(ctx, GColorWhite); - graphics_draw_line(ctx, GPoint(bounds.origin.x, bounds.origin.y), GPoint(bounds.origin.x + bounds.size.w, bounds.origin.y)); -} - -static void alarm_highlight_layer_update_proc(Layer *layer, GContext *ctx) { - graphics_context_set_stroke_color(ctx, GColorRed); - graphics_context_set_stroke_width(ctx, 3); - graphics_draw_rect(ctx, layer_get_bounds(layer)); -} - -static void set_alarm_highlighter_frame() { - switch (s_highlight_pos) { - case 0: - layer_set_frame(s_alarm_highlight_layer, s_drop_function_grect); - break; - case 1: - layer_set_frame(s_alarm_highlight_layer, s_drop_over_grect); - break; - case 2: - layer_set_frame(s_alarm_highlight_layer, s_drop_under_grect); - } -} - -static void alarm_up_click_handler(void *recognizer, void *ctx) { - if (s_highlight_pos == 0) { - s_highlight_pos = 2; - } else { - s_highlight_pos--; - } - set_alarm_highlighter_frame(); -} - -static void alarm_down_click_handler(void *recognizer, void *ctx) { - if (s_highlight_pos == 2) { - s_highlight_pos = 0; - } else { - s_highlight_pos++; - } - set_alarm_highlighter_frame(); -} - -static void alarm_select_click_handler(void *recognizer, void *ctx) { - window_stack_push(s_drop_window, true); -} - -static void alarm_select_hold_handler(void *recognizer, void *ctx) { - // check solution - for (int i = 0; i < 3; ++i) { - if (s_selected_indices[i] != s_solution_indices[i]) { - push_status_window(STATUS_MATH_FAILURE); - return; - } - } - - // solution is correct, meaning a non-recovery alarm needs scheduled; - // set dummy handler to avoid overwriting an important alarm with a - // no-longer-important recovery alarm - wakeup_service_subscribe(NULL); - - if (s_awareness_countdown_int > 0) { - // TODO promote OR disarm - // if (s_current_triangle == settings.TriangleCount) { - tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window - if (schedule_alarm(WAKE_ALARM)) { - push_status_window(STATUS_CONFIRMATION); - } - // } else { - // GENERATE NEW TRIANLGE AND SET TIME LIMIT - // } - } -} - -static void alarm_click_config_provider(void *ctx) { - window_single_click_subscribe(BUTTON_ID_BACK, NULL); - window_single_click_subscribe(BUTTON_ID_UP, alarm_up_click_handler); - window_single_click_subscribe(BUTTON_ID_DOWN, alarm_down_click_handler); - window_single_click_subscribe(BUTTON_ID_SELECT, alarm_select_click_handler); - window_long_click_subscribe(BUTTON_ID_SELECT, 2500, alarm_select_hold_handler, NULL); -} - -static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { - if (s_highlight_pos == 0) { - return 3; - } else { - return 2; - } -} - -static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { - switch (s_highlight_pos) { - case 0: - menu_cell_basic_draw(ctx, cell_layer, s_options_function[cell_index->row], NULL, NULL); - break; - case 1: - menu_cell_basic_draw(ctx, cell_layer, s_options_over[cell_index->row], NULL, NULL); - break; - case 2: - menu_cell_basic_draw(ctx, cell_layer, s_options_under[cell_index->row], NULL, NULL); - } -} - -static void menu_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { - switch (s_highlight_pos) { - case 0: - text_layer_set_text(s_drop_txt_function_layer, s_options_function[cell_index->row]); - break; - case 1: - text_layer_set_text(s_drop_txt_over_layer, s_options_over[cell_index->row]); - break; - case 2: - text_layer_set_text(s_drop_txt_under_layer, s_options_under[cell_index->row]); - } - s_selected_indices[s_highlight_pos] = cell_index->row; - window_stack_remove(s_drop_window, true); -} - -// when the user presses select, call this to load a menu; -// this function will determine the type of menu to load by checking the highlight position; -// once the user presses select in this menu, the result is written to a text layer and the menu is exited -static void drop_menu_window_load(Window *window) { - Layer *window_layer = window_get_root_layer(window); - s_drop_menu_layer = menu_layer_create(layer_get_bounds(window_layer)); - menu_layer_set_callbacks(s_drop_menu_layer, NULL, (MenuLayerCallbacks){ - .get_num_rows = menu_get_num_rows, - .draw_row = menu_draw_row, - .select_click = menu_select, - }); - menu_layer_set_click_config_onto_window(s_drop_menu_layer, window); - layer_add_child(window_layer, menu_layer_get_layer(s_drop_menu_layer)); -} - -static void drop_menu_window_unload(Window *window) { - menu_layer_destroy(s_drop_menu_layer); - s_drop_menu_layer = NULL; -} - -static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) { - if (wakeup_reason == WAKE_RECOVERY_ALARM) { - // if recovery alarm, re-schedule it - schedule_alarm(wakeup_reason); - } -} - static void update_for_new_triangle() { // pick & display 2 side lengths to the user static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8]; @@ -579,14 +412,182 @@ static void update_for_new_triangle() { }; // setup countdown - // TODO vary time based on progress: - // Store time step per triangle - // Determine time remaining from min+(s_total_count-s_current_triangle)*step - s_awareness_countdown_int = settings.TriangeMinSecs; + s_awareness_countdown_int = settings.TriangleSecsMin + ((settings.TriangleCount - s_current_triangle_num) * settings.TriangleSecsStep); snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); } +static void awareness_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { + s_awareness_countdown_int--; + snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); + text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); + if (s_awareness_countdown_int == 0) { + send_zap(settings.PavlokIntensity); + if (s_current_triangle_num > 1) { + s_current_triangle_num--; + } + right_triangle_layer_regen(s_triangle_layer); + update_for_new_triangle(); + } +} + +static void graph_layer_update_proc(Layer *layer, GContext *ctx) { + GRect bounds = layer_get_bounds(layer); + static const uint8_t s_pixel_gap = PBL_DISPLAY_WIDTH / 20; + graphics_context_set_stroke_color(ctx, GColorElectricBlue); + for (int i = s_pixel_gap; i < PBL_DISPLAY_WIDTH; i += s_pixel_gap) { + graphics_draw_line(ctx, GPoint(0, i), GPoint(bounds.size.w, i)); // horizontal line + graphics_draw_line(ctx, GPoint(i, 0), GPoint(i, bounds.size.h)); // vertical line + } +} + +static void drop_layer_update_proc(Layer *layer, GContext *ctx) { + graphics_context_set_fill_color(ctx, GColorWhite); + graphics_fill_rect(ctx, layer_get_bounds(layer), 5, GCornersAll); +} + +static void division_layer_update_proc(Layer *layer, GContext *ctx) { + GRect bounds = layer_get_bounds(layer); + graphics_context_set_stroke_color(ctx, GColorWhite); + graphics_draw_line(ctx, GPoint(bounds.origin.x, bounds.origin.y), GPoint(bounds.origin.x + bounds.size.w, bounds.origin.y)); +} + +static void alarm_highlight_layer_update_proc(Layer *layer, GContext *ctx) { + graphics_context_set_stroke_color(ctx, GColorRed); + graphics_context_set_stroke_width(ctx, 3); + graphics_draw_rect(ctx, layer_get_bounds(layer)); +} + +static void set_alarm_highlighter_frame() { + switch (s_highlight_pos) { + case 0: + layer_set_frame(s_alarm_highlight_layer, s_drop_function_grect); + break; + case 1: + layer_set_frame(s_alarm_highlight_layer, s_drop_over_grect); + break; + case 2: + layer_set_frame(s_alarm_highlight_layer, s_drop_under_grect); + } +} + +static void alarm_up_click_handler(void *recognizer, void *ctx) { + if (s_highlight_pos == 0) { + s_highlight_pos = 2; + } else { + s_highlight_pos--; + } + set_alarm_highlighter_frame(); +} + +static void alarm_down_click_handler(void *recognizer, void *ctx) { + if (s_highlight_pos == 2) { + s_highlight_pos = 0; + } else { + s_highlight_pos++; + } + set_alarm_highlighter_frame(); +} + +static void alarm_select_click_handler(void *recognizer, void *ctx) { + window_stack_push(s_drop_window, true); +} + +static void alarm_select_hold_handler(void *recognizer, void *ctx) { + // check solution + for (int i = 0; i < 3; ++i) { + if (s_selected_indices[i] != s_solution_indices[i]) { + push_status_window(STATUS_MATH_FAILURE); + return; + } + } + + if (s_awareness_countdown_int > 0) { + if (s_current_triangle_num == settings.TriangleCount) { + tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window + wakeup_service_subscribe(NULL); // ignore wakeup events to avoid overwriting new alarm + if (schedule_alarm(WAKE_ALARM)) { + push_status_window(STATUS_CONFIRMATION); + } + } else { + s_current_triangle_num++; + right_triangle_layer_regen(s_triangle_layer); + update_for_new_triangle(); + } + } +} + +static void alarm_click_config_provider(void *ctx) { + window_single_click_subscribe(BUTTON_ID_BACK, NULL); + window_single_click_subscribe(BUTTON_ID_UP, alarm_up_click_handler); + window_single_click_subscribe(BUTTON_ID_DOWN, alarm_down_click_handler); + window_single_click_subscribe(BUTTON_ID_SELECT, alarm_select_click_handler); + window_long_click_subscribe(BUTTON_ID_SELECT, 2500, alarm_select_hold_handler, NULL); +} + +static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { + if (s_highlight_pos == 0) { + return 3; + } else { + return 2; + } +} + +static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { + switch (s_highlight_pos) { + case 0: + menu_cell_basic_draw(ctx, cell_layer, s_options_function[cell_index->row], NULL, NULL); + break; + case 1: + menu_cell_basic_draw(ctx, cell_layer, s_options_over[cell_index->row], NULL, NULL); + break; + case 2: + menu_cell_basic_draw(ctx, cell_layer, s_options_under[cell_index->row], NULL, NULL); + } +} + +static void menu_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { + switch (s_highlight_pos) { + case 0: + text_layer_set_text(s_drop_txt_function_layer, s_options_function[cell_index->row]); + break; + case 1: + text_layer_set_text(s_drop_txt_over_layer, s_options_over[cell_index->row]); + break; + case 2: + text_layer_set_text(s_drop_txt_under_layer, s_options_under[cell_index->row]); + } + s_selected_indices[s_highlight_pos] = cell_index->row; + window_stack_remove(s_drop_window, true); +} + +// when the user presses select, call this to load a menu; +// this function will determine the type of menu to load by checking the highlight position; +// once the user presses select in this menu, the result is written to a text layer and the menu is exited +static void drop_menu_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + s_drop_menu_layer = menu_layer_create(layer_get_bounds(window_layer)); + menu_layer_set_callbacks(s_drop_menu_layer, NULL, (MenuLayerCallbacks){ + .get_num_rows = menu_get_num_rows, + .draw_row = menu_draw_row, + .select_click = menu_select, + }); + menu_layer_set_click_config_onto_window(s_drop_menu_layer, window); + layer_add_child(window_layer, menu_layer_get_layer(s_drop_menu_layer)); +} + +static void drop_menu_window_unload(Window *window) { + menu_layer_destroy(s_drop_menu_layer); + s_drop_menu_layer = NULL; +} + +static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) { + if (wakeup_reason == WAKE_RECOVERY_ALARM) { + // if recovery alarm, re-schedule it + schedule_alarm(wakeup_reason); + } +} + static void alarm_window_load() { wakeup_service_subscribe(live_wakeup_handler_for_alarm); @@ -744,6 +745,7 @@ static void push_alarm_window() { send_zap(settings.PavlokIntensity); } + s_current_triangle_num = 1; window_set_window_handlers( s_window, (WindowHandlers){.load = alarm_window_load, .unload = alarm_window_unload}); @@ -927,6 +929,8 @@ static void setter_window_load() { layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); update_countdown_display(); + // progress + // highlighter s_setter_highlight_layer = layer_create(GRect(0, s_top_bar_grect.size.h, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - (2 * s_top_bar_grect.size.h))); layer_set_update_proc(s_setter_highlight_layer, setter_highlight_layer_update_proc); @@ -986,7 +990,8 @@ static void init() { settings.PavlokEnabled = false; settings.PavlokIntensity = 50; settings.TriangleCount = 5; - settings.TriangeMinSecs = 25; + settings.TriangleSecsMin = 25; + settings.TriangleSecsStep = 2; } // read even when opening setter to ensure a recovery alarm is not able to be overwritten diff --git a/src/c/settings.h b/src/c/settings.h index d1d4748..7b8316d 100644 --- a/src/c/settings.h +++ b/src/c/settings.h @@ -12,7 +12,8 @@ typedef struct AppSettings { bool PavlokEnabled; uint8_t PavlokIntensity; uint8_t TriangleCount; - uint8_t TriangeMinSecs; + uint8_t TriangleSecsMin; + uint8_t TriangleSecsStep; } __attribute__((__packed__)) AppSettings; _Static_assert(sizeof(AppSettings) <= 256, "AppSettings exceeds Pebble 256-byte persist limit!"); diff --git a/src/pkjs/config.js b/src/pkjs/config.js index 0fac13d..3ff7fcf 100644 --- a/src/pkjs/config.js +++ b/src/pkjs/config.js @@ -62,14 +62,24 @@ module.exports = [ "description": "Number of triangles to solve before disarming the alarm." }, { - "id": "triangle_min_secs", + "id": "triangle_secs_min", "type": "slider", - "messageKey": "PKJS_TRIANGLE_MIN_SECS", + "messageKey": "PKJS_TRIANGLE_SECS_MIN", "defaultValue": 25, "min": 5, "max": 60, "label": "Minimum Time Limit (Seconds)", "description": "Time limit the last/fastest triangle must be solved within. All other time limits are calculated based on this one." + }, + { + "id": "triangle_secs_step", + "type": "slider", + "messageKey": "PKJS_TRIANGLE_SECS_STEP", + "defaultValue": 2, + "min": 0, + "max": 15, + "label": "Time Limit Step (Seconds)", + "description": "Number of seconds each triangle's time limit differs by." } ] },