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
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
### 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).
### 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?
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",
"name": "FONT_MANSALVA_REGULAR_22",
"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_alarm_highlight_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_b_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);
// 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);
// triangle measurements text layers
//// 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_font(s_side_a_txt_layer, s_font_mansalva_small);
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));
//// 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_font(s_side_b_txt_layer, s_font_mansalva_small);
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));
//// 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_font(s_side_c_txt_layer, s_font_mansalva_small);
layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer));
//// 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;
switch (ignored_side) {
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_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_b_buffer;
@@ -569,6 +570,7 @@ static void alarm_window_load() {
s_options_under[1] = side_c_buffer;
break;
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_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_a_buffer;
@@ -577,6 +579,7 @@ static void alarm_window_load() {
s_options_under[1] = side_c_buffer;
break;
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_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
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_b_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_division_layer);
text_layer_destroy(s_parenthesis_txt_layer);
+26 -63
View File
@@ -1,27 +1,23 @@
#include "triangles.h"
#include <pebble.h>
static void triangle_layer_update_proc(Layer *layer, GContext *ctx) {
// retrieve the TriangleLayer pointer stored in the layer's data slot
TriangleLayer *tri = *(TriangleLayer **)layer_get_data(layer);
static void right_triangle_layer_update_proc(Layer *layer, GContext *ctx) {
// retrieve the RightTriangleLayer pointer stored in the layer's data slot
RightTriangleLayer *tri = *(RightTriangleLayer **)layer_get_data(layer);
if (!tri)
return;
GRect bounds = layer_get_bounds(layer);
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 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
int32_t sin_left = sin_lookup(((int32_t)left_angle * TRIG_MAX_ANGLE) / 360);
int32_t cos_left = cos_lookup(((int32_t)left_angle * TRIG_MAX_ANGLE) / 360);
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);
// compute height and apex offset from side lengths
uint16_t height = (uint16_t)(((uint32_t)tri->side_b * tri->side_c) / tri->side_a);
uint16_t apex_offset = (uint16_t)(((uint32_t)tri->side_c * tri->side_c) / tri->side_a);
// 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 margin = (bounds.size.w / 2) - (padded_base / 2);
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_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(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) {
TriangleLayer *tri = malloc(sizeof(TriangleLayer));
RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
RightTriangleLayer *tri = malloc(sizeof(RightTriangleLayer));
if (!tri)
return NULL;
@@ -45,63 +41,30 @@ TriangleLayer *triangle_layer_create(GRect frame, GColor color) {
tri->color = color;
// determine angles
uint8_t r1, r2;
do {
r1 = 5 + (rand() % 166); // 5170 (angle_A ≥ 5)
r2 = (r1 + 5) + (rand() % (171 - r1)); // r1+5 … 175 (angles B,C ≥ 5)
} while (r1 == 60 && r2 == 120); // ensure never equilateral
tri->angle_A = 90;
tri->angle_B = rand() % 86; // acute angle at left base vertex
tri->angle_C = 90 - tri->angle_B; // acute angle at right base vertex
tri->angle_A = r1;
tri->angle_B = r2 - r1;
tri->angle_C = 180 - r2;
// choose hypotenuse length randomly
uint16_t min_hyp = (frame.size.w < frame.size.h) ? frame.size.w / 2 : frame.size.h / 2;
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)
int32_t sin_A = sin_lookup(((int32_t)tri->angle_A * TRIG_MAX_ANGLE) / 360);
// legs: side_b = hyp × sin(B), side_c = hyp × sin(C) = hyp × cos(B)
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);
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;
if (sin_B > max_sin)
max_sin = sin_B;
if (sin_C > max_sin)
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;
}
// create layer with room for a pointer-sized data slot & store the RightTriangleLayer* there
tri->layer = layer_create_with_data(frame, sizeof(RightTriangleLayer *));
*(RightTriangleLayer **)layer_get_data(tri->layer) = tri;
layer_set_update_proc(tri->layer, right_triangle_layer_update_proc);
return tri;
}
void triangle_layer_destroy(TriangleLayer *tri) {
void right_triangle_layer_destroy(RightTriangleLayer *tri) {
if (!tri)
return;
layer_destroy(tri->layer);
+3 -3
View File
@@ -14,7 +14,7 @@ typedef struct {
uint16_t side_a;
uint16_t side_b;
uint16_t side_c;
} __attribute__((__packed__)) TriangleLayer;
} __attribute__((__packed__)) RightTriangleLayer;
TriangleLayer *triangle_layer_create(GRect frame, GColor color);
void triangle_layer_destroy(TriangleLayer *tri);
RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color);
void right_triangle_layer_destroy(RightTriangleLayer *tri);