Initial triangle progression

This commit is contained in:
RandyTheSilly committed 2026-10-30 19:54:57 -04:00
1 parent afa6976f1a
commit 23518c7af9
4 files changed
+197 -180

No files matched your search

+2 -1
View File
@@ -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"
+181 -176
View File
@@ -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
+2 -1
View File
@@ -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!");
+12 -2
View File
@@ -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."
}
]
},