diff options
author | thg191 <32111025+RighteousLightning@users.noreply.github.com> | 2023-03-11 21:12:00 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-11 16:12:00 -0500 |
commit | 7584f9bf98ea0d42a3c02e92d24f541a54836d8b (patch) | |
tree | 62b0325d0907133a8014c56a89e90188920c33eb /movement | |
parent | b7419866d92c489925f7ff90c9ddf8f03d3f9a65 (diff) | |
download | Sensor-Watch-7584f9bf98ea0d42a3c02e92d24f541a54836d8b.tar.gz Sensor-Watch-7584f9bf98ea0d42a3c02e92d24f541a54836d8b.tar.bz2 Sensor-Watch-7584f9bf98ea0d42a3c02e92d24f541a54836d8b.zip |
discgolf_face initial commit (#207)
* discgolf_face initial commit
* Comment on wrong line
* updated drawing method and added beeps
* Put description in appropriate file, added license
* fixed for loops that didn't cover whole array, long mode press snaps back to default face
---------
Co-authored-by: joeycastillo <joeycastillo@utexas.edu>
Diffstat (limited to 'movement')
-rw-r--r-- | movement/make/Makefile | 1 | ||||
-rw-r--r-- | movement/movement_faces.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/complication/discgolf_face.c | 326 | ||||
-rw-r--r-- | movement/watch_faces/complication/discgolf_face.h | 95 |
4 files changed, 423 insertions, 0 deletions
diff --git a/movement/make/Makefile b/movement/make/Makefile index aa222627..3ce60dc2 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -96,6 +96,7 @@ SRCS += \ ../watch_faces/complication/morsecalc_face.c \ ../watch_faces/complication/rpn_calculator_face.c \ ../watch_faces/complication/ships_bell_face.c \ + ../watch_faces/complication/discgolf_face.c \ ../watch_faces/complication/habit_face.c \ ../watch_faces/clock/repetition_minute_face.c \ # New watch faces go above this line. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index cd58ceee..6ae0ec0f 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -74,6 +74,7 @@ #include "morsecalc_face.h" #include "rpn_calculator_face.h" #include "ships_bell_face.h" +#include "discgolf_face.h" #include "habit_face.h" #include "repetition_minute_face.h" // New includes go above this line. diff --git a/movement/watch_faces/complication/discgolf_face.c b/movement/watch_faces/complication/discgolf_face.c new file mode 100644 index 00000000..0852bf1f --- /dev/null +++ b/movement/watch_faces/complication/discgolf_face.c @@ -0,0 +1,326 @@ +#include <stdlib.h> +#include <string.h> +#include "discgolf_face.h" +#include "watch.h" // Remember to change number of courses in this file +#include "watch_utility.h" + +/* + * Keep track of scores in discgolf or golf! + * The watch face operates in three different modes: + * + * - dg_setting: Select a course + * Enter this mode by holding down the light button. The screen will display + * the label for the hole and the lowest score since last boot. + * Press alarm to loop through the holes. Press the light button to make a + * selection. This will reset all scores and start a new game in dg_idle mode. + * + * -dg_idle: We're playing a hole + * This either shows your current score relative to par, or the score for a + * particular hole. + * At the start of a game, press alarm to loop through the holes and leave it + * your starting hole. For optimal experience, play the course linearly after that + * If you're viewing the hole you're supposed to be playing, the watch face will + * display your score relative to par. + * Use the alarm button to view other holes than the one you're playing, in which + * case the input score for that hole will be displayed, in case it needs changing. + * Long press the alarm button to snap back to currently playing hole. + * To input scores for a hole in this mode, press the light button. + * + * -dg_scoring: Input score for a hole + * In this mode, if the score is 0 (hasn't been entered during this round), + * it will blink, indicating we're in scoring mode. Press the alarm button + * to increment the score up until 15, in which case it loops back to 0. + * Press the light button to save the score for that hole, advance one hole + * if you're not editing an already input score, and returning to idle mode. + * + * When all scores have been entered, the LAP indicator turns on. At that point, if we enter + * dg_setting to select a course, the score for that round is evaluated against the current + * lowest score for that course, and saved if it is better. +*/ +/////////////////////////////////////////////////////////////////////////////////////////////// +// Enter course data +/* Initialize lowest scores with a high number */ +int8_t best[courses]; + +static const uint8_t pars[][18] = { + { 3, 3, 4, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, // Grafarholt + { 3, 4, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3 }, // Gufunes + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Vífilsstaðir + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Dalvegur + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Laugardalur + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Guðmundarlundur + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Víðistaðatún + { 3, 3, 3, 4, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Fossvogur + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Klambratún + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Seljahverfi + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Fella- og Hóla +}; +static const uint8_t holes[] = { // Number of holes on each course + 18, + 18, + 10, + 10, + 10, + 10, + 9, + 9, + 9, + 9, + 9 +}; +/* Two-letter descriptive labels, second field can only be A, B, C, D, E, F, H, I, J, L, N, O, R, T, U and X. */ +static const char labels[][2] = { + { 'G', 'H' }, + { 'G', 'N' }, + { 'V', 'I' }, + { 'D', 'V' }, + { 'L', 'A' }, + { 'G', 'L' }, + { 'V', 'T' }, + { 'F', 'V' }, + { 'K', 'T' }, + { 'S', 'E' }, + { 'F', 'H' } +}; + +// End of course data +/////////////////////////////////////////////////////////////////////////////////////////////// + +/* Beep function */ +static inline void beep(movement_settings_t *settings) { + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); +} + +/* Prep for a new round */ +static inline void reset(discgolf_state_t *state) { + for (int i = 0; i < holes[state->course]; i++) { + state->scores[i] = 0; + } + state->hole = 1; + watch_clear_indicator(WATCH_INDICATOR_LAP); + return; +} + +/* Total number of throws so far */ +static inline uint8_t score_sum(discgolf_state_t *state) { + uint8_t sum = 0; + for (int i = 0; i < holes[state->course]; i++) { + sum = sum + state->scores[i]; + } + return sum; +} + +/* Count how many holes have been played */ +static inline uint8_t count_played(discgolf_state_t *state) { + uint8_t holes_played = 0; + for (int i = 0; i < holes[state->course]; i++) { + if (state->scores[i] > 0) holes_played++; + } + return holes_played; +} + + +/* Calculate the current score relative to par */ +static inline int8_t calculate_score(discgolf_state_t *state) { + uint8_t par_sum = 0; + uint8_t score_sum = 0; + + for (int i = 0; i < holes[state->course]; i++) { + if (state->scores[i] > 0) { + par_sum = par_sum + pars[state->course][i]; + score_sum = score_sum + state->scores[i]; + } + } + return score_sum - par_sum; +} + +/* Store score if it's the best so far */ +static inline void store_best(discgolf_state_t *state) { + uint8_t played = count_played(state); + if ( played == holes[state->course] ) { + int8_t high_score = calculate_score(state); + if (high_score < best[state->course] ) { + best[state->course] = high_score; + } + } +} + +/* Configuration at boot, the high score array can be initialized with your high scores if they're known */ +void discgolf_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(discgolf_state_t)); + discgolf_state_t *state = (discgolf_state_t *)*context_ptr; + memset(*context_ptr, 0, sizeof(discgolf_state_t)); + state->hole = 1; + state->course = 0; + state->playing = holes[state->course] + 1; + for (int i = 0; i < courses; i++) best[i] = 99; + state->mode = dg_setting; + } +} + +void discgolf_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + watch_clear_colon(); + discgolf_state_t *state = (discgolf_state_t *)context; + + /* If we were playing, go to current hole */ + if (state->playing <= holes[0]) { + state->hole = state->playing; + } + /* Set LAP if round finished */ + if (count_played(state) == holes[state->course] ) { + watch_set_indicator(WATCH_INDICATOR_LAP); + } + movement_request_tick_frequency(4); +} + +bool discgolf_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + + discgolf_state_t *state = (discgolf_state_t *)context; + + switch (event.event_type) { + case EVENT_TIMEOUT: + /* Snap to first screen if we're not playing*/ + if ( count_played(state) == holes[state->course] || state->mode == dg_setting) { + movement_move_to_face(0); + } + /* Cancel scoring if timed out */ + if (state->mode == dg_scoring) { + state->scores[state->hole] = 0; + state->mode = dg_idle; + } + break; + /* Advance if not scoring */ + case EVENT_MODE_BUTTON_UP: + if ( state->mode != dg_scoring ) { + movement_move_to_next_face(); + } + break; + /* Go to default face if not scoring */ + case EVENT_MODE_LONG_PRESS: + if ( state->mode != dg_scoring ) { + movement_move_to_face(0); + } + break; + case EVENT_LIGHT_BUTTON_UP: + switch ( state->mode ) { + case dg_idle: + /* Check if selected hole is the first one */ + if ( score_sum(state) == 0 ) { + state->playing = state->hole; + } + /* Enter scoring mode */ + state->mode = dg_scoring; + break; + case dg_scoring: + /* Set the LAP indicator if all scores are entered */ + if (count_played(state) == holes[state->course] ) { + watch_set_indicator(WATCH_INDICATOR_LAP); + } + /* Advance to next hole if not editing previously set score */ + if ( state->hole == state->playing ) { + if (state->hole < holes[state->course]) state->hole++; + else state->hole = 1; + if (state->playing < holes[state->course]) state->playing++; + else state->playing = 1; + } + /* Return to idle */ + state->mode = dg_idle; + break; + case dg_setting: + /* Return to idle */ + state->playing = holes[state->course] + 1; + state->mode = dg_idle; + break; + } + beep(settings); + break; + case EVENT_ALARM_BUTTON_UP: + switch (state->mode) { + /* Setting, loop through courses */ + case dg_setting: + state->course = (state->course + 1) % courses; + break; + /* Scoring, increment score for current hole */ + case dg_scoring: + state->scores[state->hole - 1] = (state->scores[state->hole - 1] + 1) % 16; // Loop around at 15 + break; + /* Idle, loop through holes */ + case dg_idle: + if (state->hole < holes[state->course]) { + state->hole++; + } else { state->hole = 1; } + break; + } + break; + case EVENT_LIGHT_LONG_PRESS: + /* Enter setting mode, reset state */ + if ( state->mode == dg_idle ) { + state->mode = dg_setting; + store_best(state); + reset(state); + beep(settings); + } + break; + case EVENT_ALARM_LONG_PRESS: + /* Snap back to currently playing hole if we've established one*/ + if ( (state->mode == dg_idle) && (state->hole != state->playing) && (state->playing <= holes[state->course]) ) { + state->hole = state->playing; + beep(settings); + } + break; + default: + break; + } + + char buf[21]; + char prefix; + int8_t diff; + + switch (state->mode) { + /* Setting mode, display course label and high score */ + case dg_setting: + if ( best[state->course] < 0 ) { + prefix = '-'; + } else { prefix = ' '; } + sprintf(buf, "%c%c %c%2d ", labels[state->course][0], labels[state->course][1], prefix, abs(best[state->course])); + break; + /* Idle, show relative or input score */ + case dg_idle: + if (state->hole == state->playing) { + diff = calculate_score(state); + if ( diff < 0 ) { + prefix = '-'; + } else { prefix = ' '; } + sprintf(buf, "%c%c%2d %c%2d ", labels[state->course][0], labels[state->course][1], state->hole, prefix, abs(diff)); + } else { + sprintf(buf, "%c%c%2d %2d ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]); + } + break; + /* Scoring, show set score */ + case dg_scoring: + sprintf(buf, "%c%c%2d %2d ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]); + break; + } + + /* Blink during scoring */ + if (event.subsecond % 2 && state->mode == dg_scoring) { + buf[6] = buf[7] = ' '; + } + + /* Draw screen */ + watch_display_string(buf, 0); + + return true; +} + +void discgolf_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + watch_clear_indicator(WATCH_INDICATOR_LAP); +} diff --git a/movement/watch_faces/complication/discgolf_face.h b/movement/watch_faces/complication/discgolf_face.h new file mode 100644 index 00000000..5e8068e0 --- /dev/null +++ b/movement/watch_faces/complication/discgolf_face.h @@ -0,0 +1,95 @@ +/* + * MIT License + * + * Copyright (c) 2023 Þorsteinn Jón Gautason + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +///////////////////////////////////////////////////////////////////////////////////// + +/* + * Keep track of scores in discgolf or golf! + * The watch face operates in three different modes: + * + * - dg_setting: Select a course + * Enter this mode by holding down the light button. The screen will display + * the label for the hole and the lowest score since last boot. + * Press alarm to loop through the holes. Press the light button to make a + * selection. This will reset all scores and start a new game in dg_idle mode. + * + * -dg_idle: We're playing a hole + * This either shows your current score relative to par, or the score for a + * particular hole. + * At the start of a game, press alarm to loop through the holes and leave it + * your starting hole. For optimal experience, play the course linearly after that + * If you're viewing the hole you're supposed to be playing, the watch face will + * display your score relative to par. + * Use the alarm button to view other holes than the one you're playing, in which + * case the input score for that hole will be displayed, in case it needs changing. + * Long press the alarm button to snap back to currently playing hole. + * To input scores for a hole in this mode, press the light button. + * + * -dg_scoring: Input score for a hole + * In this mode, if the score is 0 (hasn't been entered during this round), + * it will blink, indicating we're in scoring mode. Press the alarm button + * to increment the score up until 15, in which case it loops back to 0. + * Press the light button to save the score for that hole, advance one hole + * if you're not editing an already input score, and returning to idle mode. + * + * When all scores have been entered, the LAP indicator turns on. At that point, if we enter + * dg_setting to select a course, the score for that round is evaluated against the current + * lowest score for that course, and saved if it is better. +*/ + + +#ifndef DISCGOLF_FACE_H_ +#define DISCGOLF_FACE_H_ + +#include "movement.h" +#define courses 11 + +typedef enum { + dg_setting, // We are selecting a course + dg_scoring, // We are inputting our score + dg_idle, // We have input our score and are playing a hole +} discgolf_mode_t; + +typedef struct { + uint8_t course; // Index for course selection, from 0 + uint8_t hole; // Index for current hole, from 1 + uint8_t playing; // Current hole + int scores[18]; // Scores for each played hole + discgolf_mode_t mode; // Watch face mode +} discgolf_state_t; + +void discgolf_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void discgolf_face_activate(movement_settings_t *settings, void *context); +bool discgolf_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void discgolf_face_resign(movement_settings_t *settings, void *context); + +#define discgolf_face ((const watch_face_t){ \ + discgolf_face_setup, \ + discgolf_face_activate, \ + discgolf_face_loop, \ + discgolf_face_resign, \ + NULL, \ +}) + +#endif // DISCGOLF_FACE_H_ |