/* * MIT License * * Copyright (c) 2023 Tobias Raayoni Last / @randogoth * * 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. */ #include #include #include "toss_up_face.h" #if __EMSCRIPTEN__ #include #else #include "saml22j18a.h" #endif static const char heads[] = { '8', 'h', '4', 'E', '(' }; static const char tails[] = { '0', '+', 'N', '3', ')' }; static const uint8_t dd[] = {2, 4, 6, 8, 10,12,20,24,30,32,36,48,99}; static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice); static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails); void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state); static void _toss_up_face_display(toss_up_state_t *state); static void _dice_animation(toss_up_state_t *state); static void _coin_animation(toss_up_state_t *state); // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) watch_face_index; (void) settings; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(toss_up_state_t)); memset(*context_ptr, 0, sizeof(toss_up_state_t)); toss_up_state_t *state = (toss_up_state_t *)*context_ptr; // defaults state->coin_num = 1; state->dice_num = 1; state->dice_sides[0] = 6; state->dice_sides[1] = 6; state->dice_sides[2] = 6; state->coin_style[0] = '8'; state->coin_style[1] = '0'; } } void toss_up_face_activate(movement_settings_t *settings, void *context) { (void) settings; (void) context; } bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { toss_up_state_t *state = (toss_up_state_t *)context; uint8_t i = 0; switch (event.event_type) { case EVENT_ACTIVATE: watch_display_string(" Coins ", 0); break; case EVENT_TICK: if ( state->animate ) { state->animation = (state->animation + 1); _toss_up_face_display(state); } break; case EVENT_LIGHT_BUTTON_DOWN: break; case EVENT_LIGHT_BUTTON_UP: if ( state->animate ) break; // change between coins and dice if ( state->mode <= 1 ) state->mode = 2; else if ( state->mode >= 2 ) state->mode = 0; _toss_up_face_display(state); break; case EVENT_ALARM_BUTTON_UP: // toss if ( state->animate ) break; switch (state->mode) { case 0: state->mode++; // fall through case 1: state->animate = true; for (i = 0; i < state->coin_num; i++) { state->coins[i] = divine_bit(); } break; case 2: state->mode++; // fall through case 3: state->animate = true; for (i = 0; i < state->dice_num; i++) { state->dice[i] = roll_dice(state->dice_sides[i]); } break; default: break; } _toss_up_face_display(state); break; case EVENT_LIGHT_LONG_PRESS: if ( state->animate ) break; state->animate = false; switch (state->mode) { case 0: // change to default coin style state->coin_style[0] = heads[0]; state->coin_style[1] = tails[0]; state->coinface = 0; break; case 1: // change the coin style state->coinface = (state->coinface + 1) % 5; state->coin_style[0] = heads[state->coinface]; state->coin_style[1] = tails[state->coinface]; break; case 2: // change to default dice sides state->dice_sides[0] = 6; state->dice_sides[1] = 6; state->dice_sides[2] = 6; state->dd = 0; break; case 3: // change the sides of the dice state->dd = (state->dd + 1) % 13; state->dice_sides[state->dice_num-1] = dd[state->dd]; state->dice[state->dice_num-1] = dd[state->dd]; break; default: break; } _toss_up_face_display(state); break; case EVENT_ALARM_LONG_PRESS: if ( state->animate ) break; state->animate = false; switch (state->mode) { case 0: // back to one coin state->coin_num = 1; break; case 1: // up to 6 coins total state->coin_num = (state->coin_num % 6) + 1; break; case 2: // back to one dice state->dice_num = 1; break; case 3: // add up to 3 dice total state->dice_num = (state->dice_num % 3) + 1; state->dd = 0; break; default: break; } _toss_up_face_display(state); break; default: return movement_default_loop_handler(event, settings); } return true; } void toss_up_face_resign(movement_settings_t *settings, void *context) { (void) settings; (void) context; } // STATIC FUNCTIONS /////////////////////////////////////////////////////////// /** @brief handles the display */ static void _toss_up_face_display(toss_up_state_t *state) { char buf[11] = {0}; char token[7] = {0}; switch ( state->mode ) { case 0: // coins title sprintf(buf, " Coins "); break; case 1: // coins divination _coin_animation(state); if ( !state->animate ) { watch_clear_display(); _display_coins(token, state->coins, state->coin_num, state); sprintf(buf, " %s", token); } break; case 2: // dice title sprintf(buf, " Dice "); break; case 3: // dice divination _dice_animation(state); if ( !state->animate ) { _roll_dice_multiple(token, state->dice, state->dice_num + 1); sprintf(buf, " %s", token); } break; default: break; } watch_display_string(buf, 0); } /** @brief divination method to derive a bit from 32 TRNG bits */ uint8_t divine_bit(void) { uint32_t stalks; do { // modulo bias filter stalks = get_true_entropy(); // get 32 TRNG bits as stalks } while (stalks >= INT32_MAX || stalks <= 0); uint8_t pile1_xor = 0; uint8_t pile2_xor = 0; // Divide the stalks into two piles, alternating ends for (uint8_t i = 0; i < 16; i++) { uint8_t left_bit = (stalks >> (31 - 2*i)) & 1; uint8_t right_bit = (stalks >> (30 - 2*i)) & 1; if (i % 2 == 0) { pile1_xor ^= left_bit; pile2_xor ^= right_bit; } else { pile1_xor ^= right_bit; pile2_xor ^= left_bit; } } // Take the XOR of the pile results uint8_t result_xor = pile1_xor ^ pile2_xor; // Output 1 if result_xor is 1, 0 otherwise return result_xor; } /** @brief get 32 True Random Number bits */ uint32_t get_true_entropy(void) { #if __EMSCRIPTEN__ return rand() % INT32_MAX; #else hri_mclk_set_APBCMASK_TRNG_bit(MCLK); hri_trng_set_CTRLA_ENABLE_bit(TRNG); while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready hri_trng_clear_CTRLA_ENABLE_bit(TRNG); hri_mclk_clear_APBCMASK_TRNG_bit(MCLK); return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it #endif } // COIN FUNCTIONS ///////////////////////////////////////////////////////////// /** @brief sort tossed coins into a pile of heads and a pile of tails */ static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails) { uint8_t num_ones = 0; for (uint8_t i = 0; i < num_bits; i++) { if ((bits >> i) & 1) { *token++ = *heads; num_ones++; } } if ( num_bits < 6 ) { for (uint8_t i = 0; i < (6 - num_bits); i++) { *token++ = ' '; } } for (uint8_t i = 0; i < (num_bits - num_ones); i++) { *token++ = *tails; } } /** @brief convert bool array of coinflips to integer for sorting */ void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state) { uint8_t bits = 0; for (uint8_t i = 0; i < length; i++) { if (bit_array[i]) { bits |= (1 << (length - 1 - i)); } } _sort_coins(token, length, bits, &state->coin_style[0], &state->coin_style[1]); } /** @brief coin animation */ static void _coin_animation(toss_up_state_t *state) { bool heads = false; bool tails = false; for (uint8_t i = 0; i < state->coin_num; i++) { if (state->coins[i] == true) { heads = true; } else { tails = true; } } movement_request_tick_frequency(32); switch ( state->animation ) { case 0: watch_display_string(" ", 4); if ( heads ) { watch_set_pixel(0, 18); watch_set_pixel(2, 18); } else { state->animation = 12; } break; case 1: if ( heads ) { watch_set_pixel(1, 18); } break; case 2: if ( heads ) { watch_set_pixel(0, 19); watch_set_pixel(2, 19); } break; case 3: if ( heads ) { watch_clear_pixel(0, 18); watch_clear_pixel(2, 18); } break; case 4: if ( heads ) { watch_clear_pixel(1, 18); } break; case 5: if ( heads ) { watch_clear_pixel(0, 19); watch_clear_pixel(2, 19); watch_set_pixel(1, 17); watch_set_pixel(0, 20); } break; case 6: if ( heads ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21); } break; case 7: if ( heads ) { watch_set_pixel(1, 21); watch_set_pixel(2, 21); } break; case 8: if ( heads ) { watch_clear_pixel(1, 17); watch_clear_pixel(0, 20); } break; case 9: if ( heads ) { watch_clear_pixel(2, 20); watch_clear_pixel(0, 21); } break; case 10: if ( heads ) { watch_clear_pixel(1, 21); watch_clear_pixel(2, 21); watch_set_pixel(1, 22); watch_set_pixel(2, 22); } break; case 11: if ( heads ) { watch_set_pixel(0, 22); } break; case 12: if ( heads ) { watch_set_pixel(2, 23); watch_set_pixel(0, 23); } if ( tails ) { watch_set_pixel(0, 18); watch_set_pixel(2, 18); } break; case 13: if ( heads ) { watch_clear_pixel(1, 22); watch_clear_pixel(2, 22); } if ( tails ) { watch_set_pixel(1, 18); } break; case 14: if ( heads ) { watch_clear_pixel(0, 22); } if ( tails ) { watch_set_pixel(0, 19); watch_set_pixel(2, 19); } break; case 15: if ( heads ) { watch_clear_pixel(2, 23); watch_clear_pixel(0, 23); watch_set_pixel(2, 0); watch_set_pixel(1, 0); } if ( tails ) { watch_clear_pixel(0, 18); watch_clear_pixel(2, 18); } break; case 16: if ( heads ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0); } if ( tails ) { watch_clear_pixel(1, 18); } break; case 17: if ( heads ) { watch_set_pixel(2, 10); watch_set_pixel(0, 1); } if ( tails ) { watch_clear_pixel(0, 19); watch_clear_pixel(2, 19); watch_set_pixel(1, 17); watch_set_pixel(0, 20); } break; case 18: if ( heads ) { watch_clear_pixel(2, 0); watch_clear_pixel(1, 0); } if ( tails ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21); } break; case 19: if ( heads ) { watch_clear_pixel(2, 1); watch_clear_pixel(0, 0); } if ( tails ) { watch_set_pixel(1, 21); watch_set_pixel(2, 21); } break; case 20: if ( heads ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0); } if ( tails ) { watch_clear_pixel(1, 17); watch_clear_pixel(0, 20); } break; case 21: if ( heads ) { watch_set_pixel(2, 0); watch_set_pixel(1, 0); } if ( tails ) { watch_clear_pixel(2, 20); watch_clear_pixel(0, 21); } break; case 22: if ( heads ) { watch_clear_pixel(2, 10); watch_clear_pixel(0, 1); } if ( tails ) { watch_clear_pixel(1, 21); watch_clear_pixel(2, 21); watch_set_pixel(1, 22); watch_set_pixel(2, 22); } break; case 23: if ( heads ) { watch_clear_pixel(2, 1); watch_clear_pixel(0, 0); } if ( tails ) { watch_set_pixel(0, 22); } break; case 24: if ( heads ) { watch_set_pixel(2, 23); watch_set_pixel(0, 23); watch_clear_pixel(2, 0); watch_clear_pixel(1, 0); } if ( tails ) { watch_set_pixel(2, 23); watch_set_pixel(0, 23); } break; case 25: if ( heads ) { watch_set_pixel(0, 22); } if ( tails ) { watch_clear_pixel(1, 22); watch_clear_pixel(2, 22); } break; case 26: if ( heads ) { watch_set_pixel(1, 22); watch_set_pixel(2, 22); } if ( tails ) { watch_clear_pixel(0, 22); } break; case 27: if ( heads ) { watch_clear_pixel(2, 23); watch_clear_pixel(0, 23); } if ( tails ) { watch_clear_pixel(2, 23); watch_clear_pixel(0, 23); watch_set_pixel(2, 0); watch_set_pixel(1, 0); } break; case 28: if ( heads ) { watch_clear_pixel(0, 22); } if ( tails ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0); } break; case 29: if ( heads ) { watch_set_pixel(1, 21); watch_set_pixel(2, 21); watch_clear_pixel(1, 22); watch_clear_pixel(2, 22); } if ( tails ) { watch_set_pixel(2, 10); watch_set_pixel(0, 1); } break; case 30: if ( heads ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21); } if ( tails ) { watch_clear_pixel(1, 0); watch_clear_pixel(2, 0); } break; case 31: if ( heads ) { watch_set_pixel(1, 17); watch_set_pixel(0, 20); } if ( tails ) { watch_clear_pixel(2, 1); watch_clear_pixel(0, 0); } break; case 32: if ( heads ) { watch_clear_pixel(1, 21); watch_clear_pixel(2, 21); } if ( tails ) { watch_clear_pixel(2, 10); watch_clear_pixel(0, 1); watch_set_pixel(0, 2); watch_set_pixel(1, 2); } break; case 33: if ( heads ) { watch_clear_pixel(2, 20); watch_clear_pixel(0, 21); } if ( tails ) { watch_set_pixel(2, 2); watch_set_pixel(0, 3); } break; case 34: if ( heads ) { watch_set_pixel(0, 19); watch_set_pixel(2, 19); watch_clear_pixel(1, 17); watch_clear_pixel(0, 20); } if ( tails ) { watch_set_pixel(2, 3); watch_set_pixel(0, 4); } break; case 35: if ( heads ) { watch_set_pixel(1, 18); } if ( tails ) { watch_clear_pixel(1, 2); watch_clear_pixel(0, 2); } break; case 36: if ( heads ) { watch_set_pixel(0, 18); watch_set_pixel(2, 18); } if ( tails ) { watch_clear_pixel(2, 2); watch_clear_pixel(0, 3); } break; case 37: if ( heads ) { watch_clear_pixel(0, 19); watch_clear_pixel(2, 19); } if ( tails ) { watch_clear_pixel(2, 3); watch_clear_pixel(0, 4); watch_set_pixel(1, 4); watch_set_pixel(0, 5); } break; case 38: if ( heads ) { watch_clear_pixel(1, 18); } if ( tails ) { watch_set_pixel(2, 4); watch_set_pixel(0, 6); } break; case 39: if ( heads ) { watch_clear_pixel(0, 18); watch_clear_pixel(2, 18); } if ( tails ) { watch_set_pixel(1, 6); watch_set_pixel(2, 5); } state->animate = false; state->animation = 0; movement_request_tick_frequency(1); } } // DICE FUNCTIONS ///////////////////////////////////////////////////////////// /** @brief rolls a dice */ uint8_t roll_dice(uint8_t sides) { uint8_t bits_needed = 0; uint8_t temp_sides = sides - 1; uint8_t result = 0; while (temp_sides > 0) { bits_needed++; // how many bits do we need to represent this number? temp_sides >>= 1; // Shift right to check the next bit } do { result = 0; for (int i = 0; i < bits_needed; i++) { result <<= 1; // Shift left to make room for the next bit result |= divine_bit(); // Add the next bit to the result } } while ( result > sides -1 ); return result + 1; // Add 1 to convert the range from 0 to sides-1 to 1 to sides } /** @brief roll multiple dice and print a char array for displaying them */ static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice) { // initialize the result array to all spaces memset(result, ' ', 6); // roll the dice and write the result to the result array for (uint8_t i = 0; i < num_dice-1; i++) { uint8_t dice_result = dice[i]; uint8_t tens_digit = dice_result / 10; uint8_t ones_digit = dice_result % 10; result[(i * 2)] = tens_digit == 0 ? ' ' : (char)('0' + tens_digit); result[(i * 2) + 1] = (char)('0' + ones_digit); } } /** @brief dice animation */ static void _dice_animation(toss_up_state_t *state) { watch_display_string(" ", 4); for (uint8_t i = 0; i < state->dice_num; i++) { watch_display_string("0",i*2 + 5); } movement_request_tick_frequency(16); switch ( state->animation ) { case 0: watch_clear_pixel(1, 17); watch_clear_pixel(0, 0); watch_clear_pixel(1, 6); break; case 1: watch_clear_pixel(2, 20); watch_clear_pixel(1, 0); watch_clear_pixel(0, 6); break; case 2: watch_clear_pixel(2, 21); watch_clear_pixel(2, 0); watch_clear_pixel(0, 5); break; case 3: watch_clear_pixel(1, 21); watch_clear_pixel(2, 1); watch_clear_pixel(1, 4); break; case 4: watch_clear_pixel(0, 21); watch_clear_pixel(2, 10); watch_clear_pixel(2, 4); break; case 5: watch_clear_pixel(0, 20); watch_clear_pixel(0, 1); watch_clear_pixel(2, 5); break; case 6: watch_clear_pixel(1, 17); watch_clear_pixel(0, 0); watch_clear_pixel(1, 6); break; case 7: watch_clear_pixel(2, 20); watch_clear_pixel(1, 0); watch_clear_pixel(0, 6); break; case 8: watch_clear_pixel(2, 21); watch_clear_pixel(2, 0); watch_clear_pixel(0, 5); break; case 9: watch_clear_pixel(1, 21); watch_clear_pixel(2, 1); watch_clear_pixel(1, 4); break; case 10: watch_clear_pixel(0, 21); watch_clear_pixel(2, 10); watch_clear_pixel(2, 4); break; case 11: watch_clear_pixel(0, 20); watch_clear_pixel(0, 1); watch_clear_pixel(2, 5); state->animate = false; state->animation = 0; movement_request_tick_frequency(1); } }