Limit triangle generator to right triangles

This commit is contained in:
2026-08-07 22:03:21 -04:00
parent 10edcf6305
commit d32f7f1b4b
5 changed files with 42 additions and 76 deletions
+2 -2
View File
@@ -1,11 +1,11 @@
# Trigalarm # Trigalarm
A habit-correcting alarm for heavy sleepers. Cannot be shut off until you describe a triangle. A habit-correcting alarm for heavy sleepers. Cannot be shut off until you solve for theta in a right triangle.
## The Pitch ## The Pitch
### Simple ### Simple
The app only has one page. You can only set one alarm. There is no snooze, there is no weekly schedule, and there is no way to disable the alarm (you'll always have one set - every day - like it or not). The app only has one page. You can only set one alarm. There is no snooze, there is no weekly schedule, and there is no way to disable the alarm (you'll always have one set - every day - like it or not).
### Persistent ### Persistent
The alarm cannot be stopped until you describe a randomized triangle. Even if you force-close the app, it will re-open itself until you correctly describe the triangle. You must identify the type of triangle and provide the equation to solve for θ. The alarm cannot be stopped until you solve for theta in a randomized right triangle. Even if you force-close the app, it will re-open itself until you complete the equation.
## Why Trigonometry? ## Why Trigonometry?
Because it requires the perfect amount of thought to activate your brain in the morning. If the app gave you a more typical math problem, you could just mindlessly punch it into a calculator. Trigonometry still requires a base amount of thought ("Do I need sine, cosine, or tangent?") before a calculator is of any use. Because it requires the perfect amount of thought to activate your brain in the morning. If the app gave you a more typical math problem, you could just mindlessly punch it into a calculator. Trigonometry still requires a base amount of thought ("Do I need sine, cosine, or tangent?") before a calculator is of any use.
+1 -1
View File
@@ -68,7 +68,7 @@
] ]
}, },
{ {
"characterRegex": "[?ntype:θ=acuobsrigh¯¹0123456789]", "characterRegex": "[°ntype:θ=acuobsrigh¯¹0123456789]",
"file": "mansalva_regular.ttf", "file": "mansalva_regular.ttf",
"name": "FONT_MANSALVA_REGULAR_22", "name": "FONT_MANSALVA_REGULAR_22",
"type": "font" "type": "font"
+10 -7
View File
@@ -36,7 +36,7 @@ static TextLayer *s_drop_txt_under_layer;
static Layer *s_division_layer; static Layer *s_division_layer;
static Layer *s_alarm_highlight_layer; static Layer *s_alarm_highlight_layer;
static MenuLayer *s_drop_menu_layer; static MenuLayer *s_drop_menu_layer;
static TriangleLayer *s_triangle_layer; static RightTriangleLayer *s_triangle_layer;
static TextLayer *s_side_a_txt_layer; static TextLayer *s_side_a_txt_layer;
static TextLayer *s_side_b_txt_layer; static TextLayer *s_side_b_txt_layer;
static TextLayer *s_side_c_txt_layer; static TextLayer *s_side_c_txt_layer;
@@ -535,32 +535,33 @@ static void alarm_window_load() {
layer_add_child(window_layer, s_alarm_highlight_layer); layer_add_child(window_layer, s_alarm_highlight_layer);
// triangle // triangle
s_triangle_layer = triangle_layer_create(GRect(0, s_top_bar_grect.size.h + 1, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - (s_top_bar_grect.size.h * 2)), GColorBlack); s_triangle_layer = right_triangle_layer_create(GRect(0, s_top_bar_grect.size.h + 1, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - (s_top_bar_grect.size.h * 2)), GColorBlack);
layer_add_child(window_layer, s_triangle_layer->layer); layer_add_child(window_layer, s_triangle_layer->layer);
// triangle measurements text layers // triangle measurements text layers
//// side_a //// side_a
s_side_a_txt_layer = text_layer_create(GRect(0, s_bottom_bar_grect.origin.y - 27, PBL_DISPLAY_WIDTH, 23)); s_side_a_txt_layer = text_layer_create(GRect(0, s_bottom_bar_grect.origin.y - 25, PBL_DISPLAY_WIDTH, 23));
text_layer_set_background_color(s_side_a_txt_layer, GColorClear); text_layer_set_background_color(s_side_a_txt_layer, GColorClear);
text_layer_set_font(s_side_a_txt_layer, s_font_mansalva_small); text_layer_set_font(s_side_a_txt_layer, s_font_mansalva_small);
text_layer_set_text_alignment(s_side_a_txt_layer, GTextAlignmentCenter); text_layer_set_text_alignment(s_side_a_txt_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(s_side_a_txt_layer)); layer_add_child(window_layer, text_layer_get_layer(s_side_a_txt_layer));
//// side_b //// side_b
s_side_b_txt_layer = text_layer_create(GRect(-4, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_WIDTH, 23)); s_side_b_txt_layer = text_layer_create(GRect(-3, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_WIDTH, 23));
text_layer_set_background_color(s_side_b_txt_layer, GColorClear); text_layer_set_background_color(s_side_b_txt_layer, GColorClear);
text_layer_set_font(s_side_b_txt_layer, s_font_mansalva_small); text_layer_set_font(s_side_b_txt_layer, s_font_mansalva_small);
text_layer_set_text_alignment(s_side_b_txt_layer, GTextAlignmentRight); text_layer_set_text_alignment(s_side_b_txt_layer, GTextAlignmentRight);
layer_add_child(window_layer, text_layer_get_layer(s_side_b_txt_layer)); layer_add_child(window_layer, text_layer_get_layer(s_side_b_txt_layer));
//// side_c //// side_c
s_side_c_txt_layer = text_layer_create(GRect(4, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_WIDTH, 23)); s_side_c_txt_layer = text_layer_create(GRect(3, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_WIDTH, 23));
text_layer_set_background_color(s_side_c_txt_layer, GColorClear); text_layer_set_background_color(s_side_c_txt_layer, GColorClear);
text_layer_set_font(s_side_c_txt_layer, s_font_mansalva_small); text_layer_set_font(s_side_c_txt_layer, s_font_mansalva_small);
layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer)); layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer));
//// display 2 side lengths to the user //// display 2 side lengths to the user
static char side_a_buffer[3], side_b_buffer[3], side_c_buffer[3]; static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8], angle_A_buffer[8], angle_B_buffer[8], angle_C_buffer[8];
uint8_t ignored_side = rand() % 3; uint8_t ignored_side = rand() % 3;
switch (ignored_side) { switch (ignored_side) {
case 0: case 0:
APP_LOG(APP_LOG_LEVEL_DEBUG, "b,c");
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b); snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c); snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_b_buffer; s_options_over[0] = side_b_buffer;
@@ -569,6 +570,7 @@ static void alarm_window_load() {
s_options_under[1] = side_c_buffer; s_options_under[1] = side_c_buffer;
break; break;
case 1: case 1:
APP_LOG(APP_LOG_LEVEL_DEBUG, "a,c");
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a); snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c); snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_a_buffer; s_options_over[0] = side_a_buffer;
@@ -577,6 +579,7 @@ static void alarm_window_load() {
s_options_under[1] = side_c_buffer; s_options_under[1] = side_c_buffer;
break; break;
case 2: case 2:
APP_LOG(APP_LOG_LEVEL_DEBUG, "a,b");
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a); snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b); snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
s_options_over[0] = side_a_buffer; s_options_over[0] = side_a_buffer;
@@ -611,7 +614,7 @@ static void alarm_window_unload() {
text_layer_destroy(s_side_a_txt_layer); text_layer_destroy(s_side_a_txt_layer);
text_layer_destroy(s_side_b_txt_layer); text_layer_destroy(s_side_b_txt_layer);
text_layer_destroy(s_side_c_txt_layer); text_layer_destroy(s_side_c_txt_layer);
triangle_layer_destroy(s_triangle_layer); right_triangle_layer_destroy(s_triangle_layer);
layer_destroy(s_alarm_highlight_layer); layer_destroy(s_alarm_highlight_layer);
layer_destroy(s_division_layer); layer_destroy(s_division_layer);
text_layer_destroy(s_parenthesis_txt_layer); text_layer_destroy(s_parenthesis_txt_layer);
+26 -63
View File
@@ -1,27 +1,23 @@
#include "triangles.h" #include "triangles.h"
#include <pebble.h> #include <pebble.h>
static void triangle_layer_update_proc(Layer *layer, GContext *ctx) { static void right_triangle_layer_update_proc(Layer *layer, GContext *ctx) {
// retrieve the TriangleLayer pointer stored in the layer's data slot // retrieve the RightTriangleLayer pointer stored in the layer's data slot
TriangleLayer *tri = *(TriangleLayer **)layer_get_data(layer); RightTriangleLayer *tri = *(RightTriangleLayer **)layer_get_data(layer);
if (!tri) if (!tri)
return; return;
GRect bounds = layer_get_bounds(layer); GRect bounds = layer_get_bounds(layer);
graphics_context_set_stroke_color(ctx, tri->color); graphics_context_set_stroke_color(ctx, tri->color);
// side_a is always the longest side (horizontal base) // side a guaranteed longest
uint16_t base_side = tri->side_a; uint16_t base_side = tri->side_a;
uint16_t left_side = tri->side_c; // C → A (side c connects vertices B and A)
uint8_t left_angle = tri->angle_B; // at vertex B
// calculate height and horizontal apex offset first // compute height and apex offset from side lengths
int32_t sin_left = sin_lookup(((int32_t)left_angle * TRIG_MAX_ANGLE) / 360); uint16_t height = (uint16_t)(((uint32_t)tri->side_b * tri->side_c) / tri->side_a);
int32_t cos_left = cos_lookup(((int32_t)left_angle * TRIG_MAX_ANGLE) / 360); uint16_t apex_offset = (uint16_t)(((uint32_t)tri->side_c * tri->side_c) / tri->side_a);
uint16_t height = (uint16_t)(((uint32_t)left_side * (uint32_t)sin_left) / TRIG_MAX_RATIO);
uint16_t apex_offset = (uint16_t)(((uint32_t)left_side * (uint32_t)cos_left) / TRIG_MAX_RATIO);
// center the triangle both horizontally and vertically // center both horizontally and vertically
uint16_t padded_base = (base_side % 2 == 0) ? base_side : base_side + 1; uint16_t padded_base = (base_side % 2 == 0) ? base_side : base_side + 1;
uint16_t margin = (bounds.size.w / 2) - (padded_base / 2); uint16_t margin = (bounds.size.w / 2) - (padded_base / 2);
uint16_t start_x = margin + 1; uint16_t start_x = margin + 1;
@@ -30,14 +26,14 @@ static void triangle_layer_update_proc(Layer *layer, GContext *ctx) {
uint16_t apex_x = start_x + apex_offset; uint16_t apex_x = start_x + apex_offset;
uint16_t apex_y = base_y - height; uint16_t apex_y = base_y - height;
// draw the three sides // draw sides
graphics_draw_line(ctx, GPoint(start_x, base_y), GPoint(end_x, base_y)); graphics_draw_line(ctx, GPoint(start_x, base_y), GPoint(end_x, base_y));
graphics_draw_line(ctx, GPoint(start_x, base_y), GPoint(apex_x, apex_y)); graphics_draw_line(ctx, GPoint(start_x, base_y), GPoint(apex_x, apex_y));
graphics_draw_line(ctx, GPoint(end_x, base_y), GPoint(apex_x, apex_y)); graphics_draw_line(ctx, GPoint(end_x, base_y), GPoint(apex_x, apex_y));
} }
TriangleLayer *triangle_layer_create(GRect frame, GColor color) { RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
TriangleLayer *tri = malloc(sizeof(TriangleLayer)); RightTriangleLayer *tri = malloc(sizeof(RightTriangleLayer));
if (!tri) if (!tri)
return NULL; return NULL;
@@ -45,63 +41,30 @@ TriangleLayer *triangle_layer_create(GRect frame, GColor color) {
tri->color = color; tri->color = color;
// determine angles // determine angles
uint8_t r1, r2; tri->angle_A = 90;
do { tri->angle_B = rand() % 86; // acute angle at left base vertex
r1 = 5 + (rand() % 166); // 5170 (angle_A ≥ 5) tri->angle_C = 90 - tri->angle_B; // acute angle at right base vertex
r2 = (r1 + 5) + (rand() % (171 - r1)); // r1+5 … 175 (angles B,C ≥ 5)
} while (r1 == 60 && r2 == 120); // ensure never equilateral
tri->angle_A = r1; // choose hypotenuse length randomly
tri->angle_B = r2 - r1; uint16_t min_hyp = (frame.size.w < frame.size.h) ? frame.size.w / 2 : frame.size.h / 2;
tri->angle_C = 180 - r2; uint16_t max_hyp = (frame.size.w < frame.size.h) ? frame.size.w : frame.size.h;
tri->side_a = min_hyp + (rand() % (max_hyp - min_hyp + 1));
// determine side lengths (law of sines) // legs: side_b = hyp × sin(B), side_c = hyp × sin(C) = hyp × cos(B)
int32_t sin_A = sin_lookup(((int32_t)tri->angle_A * TRIG_MAX_ANGLE) / 360);
int32_t sin_B = sin_lookup(((int32_t)tri->angle_B * TRIG_MAX_ANGLE) / 360); int32_t sin_B = sin_lookup(((int32_t)tri->angle_B * TRIG_MAX_ANGLE) / 360);
int32_t sin_C = sin_lookup(((int32_t)tri->angle_C * TRIG_MAX_ANGLE) / 360); int32_t sin_C = sin_lookup(((int32_t)tri->angle_C * TRIG_MAX_ANGLE) / 360);
tri->side_b = (uint16_t)(((uint32_t)sin_B * tri->side_a) / TRIG_MAX_RATIO);
tri->side_c = (uint16_t)(((uint32_t)sin_C * tri->side_a) / TRIG_MAX_RATIO);
int32_t max_sin = sin_A; // create layer with room for a pointer-sized data slot & store the RightTriangleLayer* there
if (sin_B > max_sin) tri->layer = layer_create_with_data(frame, sizeof(RightTriangleLayer *));
max_sin = sin_B; *(RightTriangleLayer **)layer_get_data(tri->layer) = tri;
if (sin_C > max_sin) layer_set_update_proc(tri->layer, right_triangle_layer_update_proc);
max_sin = sin_C;
// longest side randomly chosen
uint16_t min_length_of_longest_side = (frame.size.w < frame.size.h) ? frame.size.w / 2 : frame.size.h / 2;
uint16_t max_length_of_longest_side = (frame.size.w < frame.size.h) ? frame.size.w : frame.size.h;
uint16_t largest_side = min_length_of_longest_side + (rand() % (max_length_of_longest_side - min_length_of_longest_side + 1));
tri->side_a = (uint16_t)(((uint32_t)sin_A * largest_side) / (uint32_t)max_sin);
tri->side_b = (uint16_t)(((uint32_t)sin_B * largest_side) / (uint32_t)max_sin);
tri->side_c = (uint16_t)(((uint32_t)sin_C * largest_side) / (uint32_t)max_sin);
// create layer with room for a pointer-sized data slot & store the TriangleLayer* there
tri->layer = layer_create_with_data(frame, sizeof(TriangleLayer *));
*(TriangleLayer **)layer_get_data(tri->layer) = tri;
layer_set_update_proc(tri->layer, triangle_layer_update_proc);
// ensure side_a is always longest
if (tri->side_b > tri->side_a) {
uint16_t tmp_s = tri->side_a;
tri->side_a = tri->side_b;
tri->side_b = tmp_s;
uint8_t tmp_a = tri->angle_A;
tri->angle_A = tri->angle_B;
tri->angle_B = tmp_a;
}
if (tri->side_c > tri->side_a) {
uint16_t tmp_s = tri->side_a;
tri->side_a = tri->side_c;
tri->side_c = tmp_s;
uint8_t tmp_a = tri->angle_A;
tri->angle_A = tri->angle_C;
tri->angle_C = tmp_a;
}
return tri; return tri;
} }
void triangle_layer_destroy(TriangleLayer *tri) { void right_triangle_layer_destroy(RightTriangleLayer *tri) {
if (!tri) if (!tri)
return; return;
layer_destroy(tri->layer); layer_destroy(tri->layer);
+3 -3
View File
@@ -14,7 +14,7 @@ typedef struct {
uint16_t side_a; uint16_t side_a;
uint16_t side_b; uint16_t side_b;
uint16_t side_c; uint16_t side_c;
} __attribute__((__packed__)) TriangleLayer; } __attribute__((__packed__)) RightTriangleLayer;
TriangleLayer *triangle_layer_create(GRect frame, GColor color); RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color);
void triangle_layer_destroy(TriangleLayer *tri); void right_triangle_layer_destroy(RightTriangleLayer *tri);