mirror of
https://github.com/firewalkwithm3/Sensor-Watch.git
synced 2024-11-22 11:10:29 +08:00
Merge pull request #238 from joeycastillo/randonaut-and-geomancy
Randonaut and geomancy
This commit is contained in:
commit
33c2d7dd61
|
@ -111,6 +111,9 @@ SRCS += \
|
|||
../watch_faces/complication/invaders_face.c \
|
||||
../watch_faces/clock/world_clock2_face.c \
|
||||
../watch_faces/complication/time_left_face.c \
|
||||
../watch_faces/complication/randonaut_face.c \
|
||||
../watch_faces/complication/toss_up_face.c \
|
||||
../watch_faces/complication/geomancy_face.c \
|
||||
# New watch faces go above this line.
|
||||
|
||||
# Leave this line at the bottom of the file; it has all the targets for making your project.
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
#include "invaders_face.h"
|
||||
#include "world_clock2_face.h"
|
||||
#include "time_left_face.h"
|
||||
#include "randonaut_face.h"
|
||||
#include "toss_up_face.h"
|
||||
#include "geomancy_face.h"
|
||||
#include "dual_timer_face.h"
|
||||
// New includes go above this line.
|
||||
|
||||
|
|
363
movement/watch_faces/complication/geomancy_face.c
Normal file
363
movement/watch_faces/complication/geomancy_face.c
Normal file
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "toss_up_face.h"
|
||||
#include "geomancy_face.h"
|
||||
|
||||
// CONSTANTS //////////////////////////////////////////////////////////////////
|
||||
|
||||
// The Bagua 八卦 Trigrams encoded as 3bit tribbles, represented as binary integer
|
||||
static const uint32_t bagua = 0b00000101001110010111011100000000;
|
||||
|
||||
// The King Wen Sequence 文王卦序 of the I Ching 易經 Hexagrams 卦 encoded as an array
|
||||
// of decimal integers in the order of two combined Trigram tribbles from 0b000000 to
|
||||
// 0b111111
|
||||
static const uint8_t wen_order[] = {
|
||||
1, 22, 7, 19, 15, 34, 44, 11,
|
||||
14, 51, 38, 52, 61, 55, 30, 32,
|
||||
6, 3, 28, 58, 39, 63, 46, 5,
|
||||
45, 17, 47, 56, 31, 49, 27, 43,
|
||||
23, 26, 2, 41, 50, 20, 16, 24,
|
||||
35, 21, 62, 36, 54, 29, 48, 12,
|
||||
18, 40, 59, 60, 53, 37, 57, 9,
|
||||
10, 25, 4, 8, 33, 13, 42, 0
|
||||
};
|
||||
|
||||
// The geomantic figures encoded as 4 bit nibbles, represented as hexadecimal integer
|
||||
static const uint64_t geomantic = 0x4ABF39D25E76C180;
|
||||
|
||||
// Abbreviations of the Names of the Geomantic Figures in the order of the 4 bit nibbles
|
||||
// from 0b0000 to 0b1111
|
||||
static const char figures[16][2] = {
|
||||
"VI" /* Via */, "Hd" /* Head of the Dragon */, "PA" /* Puella */, "GF" /* Greater Fortune*/,
|
||||
"PR" /* Puer */, "AQ" /* Acquisitio */, "CA" /* Carcer */, "TR" /* Tristitia */,
|
||||
"Td" /* Tail of the Dragon */, "CO" /* Conjunctio */, "AM" /* Amissio */, "AL" /* Albus */,
|
||||
"LF" /* Lesser Fortune */, "RU" /* Rubeus */, "LA" /* Laetitia */, "PO" /* Populus */
|
||||
};
|
||||
|
||||
// DECLARATIONS ///////////////////////////////////////////////////////////////
|
||||
|
||||
static void geomancy_face_display();
|
||||
static nibble_t _geomancy_pick_figure();
|
||||
static tribble_t _iching_pick_trigram();
|
||||
static uint8_t _iching_form_hexagram();
|
||||
static void _geomancy_display(nibble_t code);
|
||||
static void _display_hexagram(uint8_t hexagram, char* str);
|
||||
static void _fix_broken_line(uint8_t hexagram);
|
||||
static void _throw_animation(geomancy_state_t *state);
|
||||
|
||||
// WATCH FACE FUNCTIONS ///////////////////////////////////////////////////////
|
||||
|
||||
void geomancy_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(geomancy_state_t));
|
||||
memset(*context_ptr, 0, sizeof(geomancy_state_t));
|
||||
}
|
||||
}
|
||||
|
||||
void geomancy_face_activate(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
}
|
||||
|
||||
bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||
geomancy_state_t *state = (geomancy_state_t *)context;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
state->animate = false;
|
||||
state->animation = 0;
|
||||
watch_display_string(" IChing", 0);
|
||||
break;
|
||||
case EVENT_TICK:
|
||||
if ( state->animate ) {
|
||||
state->animation = (state->animation + 1) % 39;
|
||||
geomancy_face_display(state);
|
||||
}
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_DOWN:
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_UP:
|
||||
if ( state->animate ) break;
|
||||
if ( state->mode <= 1 ) state->mode = 2;
|
||||
else if ( state->mode >= 2 ) state->mode = 0;
|
||||
geomancy_face_display(state);
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP:
|
||||
if ( state->animate ) break;
|
||||
switch ( state->mode ) {
|
||||
case 0:
|
||||
state->mode++;
|
||||
// fall through
|
||||
case 1:
|
||||
state->animate = true;
|
||||
state->i_ching_hexagram = _iching_form_hexagram();
|
||||
break;
|
||||
case 2:
|
||||
state->mode++;
|
||||
// fall through
|
||||
case 3:
|
||||
state->animate = true;
|
||||
state->geomantic_figure = _geomancy_pick_figure().bits;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
geomancy_face_display(state);
|
||||
break;
|
||||
case EVENT_ALARM_LONG_PRESS:
|
||||
if ( state->animate ) break;
|
||||
state->caption = !state->caption;
|
||||
watch_display_string(" ", 0);
|
||||
geomancy_face_display(state);
|
||||
break;
|
||||
default:
|
||||
return movement_default_loop_handler(event, settings);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void geomancy_face_resign(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
}
|
||||
|
||||
// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
|
||||
|
||||
/** @brief display handler */
|
||||
static void geomancy_face_display(geomancy_state_t *state);
|
||||
static void geomancy_face_display(geomancy_state_t *state) {
|
||||
char token[7] = {0};
|
||||
nibble_t figure = *((nibble_t*) &state->geomantic_figure);
|
||||
switch ( state->mode ) {
|
||||
case 0:
|
||||
watch_display_string(" IChing", 0);
|
||||
break;
|
||||
case 1:
|
||||
_throw_animation(state);
|
||||
if ( !state->animate ) {
|
||||
_display_hexagram(state->i_ching_hexagram, token);
|
||||
watch_display_string(token, 4);
|
||||
_fix_broken_line(state->i_ching_hexagram);
|
||||
if (state->caption) {
|
||||
sprintf(token, "%2d", wen_order[state->i_ching_hexagram] + 1);
|
||||
watch_display_string(token, 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
watch_display_string(" GeomCy", 0);
|
||||
break;
|
||||
case 3:
|
||||
_throw_animation(state);
|
||||
if ( !state->animate ) {
|
||||
if ( state->caption ) {
|
||||
sprintf(token, "%c%c", figures[state->geomantic_figure][0], figures[state->geomantic_figure][1]);
|
||||
watch_display_string(token, 0);
|
||||
}
|
||||
_geomancy_display(figure);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief screen clearing animation between castings */
|
||||
static void _throw_animation(geomancy_state_t *state) {
|
||||
movement_request_tick_frequency(16);
|
||||
switch ( state->animation ) {
|
||||
case 0:
|
||||
watch_set_pixel(0, 22);
|
||||
break;
|
||||
case 1:
|
||||
watch_set_pixel(2, 22);
|
||||
watch_set_pixel(2, 23);
|
||||
watch_clear_pixel(0, 22);
|
||||
break;
|
||||
case 2:
|
||||
watch_set_pixel(1, 22);
|
||||
watch_set_pixel(0, 23);
|
||||
break;
|
||||
case 3:
|
||||
watch_set_pixel(2, 0);
|
||||
watch_set_pixel(1, 0);
|
||||
watch_set_pixel(2, 21);
|
||||
watch_set_pixel(1, 21);
|
||||
watch_clear_pixel(2, 22);
|
||||
watch_clear_pixel(1, 22);
|
||||
watch_clear_pixel(2, 23);
|
||||
watch_clear_pixel(0, 23);
|
||||
watch_clear_pixel(1, 23);
|
||||
break;
|
||||
case 4:
|
||||
watch_set_pixel(1, 17);
|
||||
watch_set_pixel(0, 20);
|
||||
watch_set_pixel(2, 10);
|
||||
watch_set_pixel(0, 1);
|
||||
break;
|
||||
case 5:
|
||||
watch_clear_pixel(2, 21);
|
||||
watch_clear_pixel(1, 21);
|
||||
watch_clear_pixel(2, 0);
|
||||
watch_clear_pixel(1, 0);
|
||||
watch_clear_pixel(1, 20);
|
||||
watch_clear_pixel(2, 20);
|
||||
watch_clear_pixel(0, 21);
|
||||
watch_clear_pixel(1, 1);
|
||||
watch_clear_pixel(0, 0);
|
||||
watch_clear_pixel(2, 1);
|
||||
watch_set_pixel(2, 19);
|
||||
watch_set_pixel(0, 19);
|
||||
watch_set_pixel(1, 2);
|
||||
watch_set_pixel(0, 2);
|
||||
break;
|
||||
case 6:
|
||||
watch_clear_pixel(1, 17);
|
||||
watch_clear_pixel(0, 20);
|
||||
watch_clear_pixel(2, 10);
|
||||
watch_clear_pixel(0, 1);
|
||||
watch_set_pixel(2, 18);
|
||||
watch_set_pixel(0, 18);
|
||||
watch_set_pixel(2, 3);
|
||||
watch_set_pixel(0, 4);
|
||||
break;
|
||||
case 7:
|
||||
watch_clear_pixel(2, 19);
|
||||
watch_clear_pixel(0, 19);
|
||||
watch_clear_pixel(1, 18);
|
||||
watch_clear_pixel(1, 19);
|
||||
watch_clear_pixel(1, 2);
|
||||
watch_clear_pixel(0, 2);
|
||||
watch_clear_pixel(1, 3);
|
||||
watch_clear_pixel(0, 3);
|
||||
watch_clear_pixel(2, 2);
|
||||
watch_set_pixel(1, 4);
|
||||
watch_set_pixel(0, 5);
|
||||
break;
|
||||
case 8:
|
||||
watch_clear_pixel(2, 18);
|
||||
watch_clear_pixel(0, 18);
|
||||
watch_clear_pixel(2, 3);
|
||||
watch_clear_pixel(0, 4);
|
||||
watch_set_pixel(2, 5);
|
||||
watch_set_pixel(1, 6);
|
||||
break;
|
||||
case 9:
|
||||
watch_clear_pixel(1, 4);
|
||||
watch_clear_pixel(0, 5);
|
||||
watch_clear_pixel(1, 5);
|
||||
watch_clear_pixel(2, 4);
|
||||
watch_clear_pixel(0, 6);
|
||||
break;
|
||||
case 10:
|
||||
watch_clear_pixel(2, 5);
|
||||
watch_clear_pixel(1, 6);
|
||||
break;
|
||||
case 11:
|
||||
state->animate = false;
|
||||
state->animation = 0;
|
||||
movement_request_tick_frequency(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// I CHING FUNCTIONS //////////////////////////////////////////////////////////
|
||||
|
||||
/** @brief form a trigram from three random bit picks
|
||||
*/
|
||||
static tribble_t _iching_pick_trigram(void) {
|
||||
uint8_t index = (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
|
||||
tribble_t trigram = {(bagua >> (3 * index)) & 0b111};
|
||||
return trigram;
|
||||
}
|
||||
|
||||
/** @brief form a hexagram from two trigrams
|
||||
*/
|
||||
static uint8_t _iching_form_hexagram(void);
|
||||
static uint8_t _iching_form_hexagram(void) {
|
||||
tribble_t inner = _iching_pick_trigram();
|
||||
tribble_t outer = _iching_pick_trigram();
|
||||
uint8_t hexagram = (inner.bits << 3) | outer.bits;
|
||||
return hexagram;
|
||||
}
|
||||
|
||||
/** @brief display hexagram
|
||||
* @details | for unbroken lines and Ξ for broken lines, left of display is bottom
|
||||
*/
|
||||
static void _display_hexagram(uint8_t hexagram, char* str);
|
||||
static void _display_hexagram(uint8_t hexagram, char* str) {
|
||||
str[6] = '\0'; // Null-terminate the string
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (hexagram & (1 << (5 - i))) {
|
||||
str[i] = '1';
|
||||
} else {
|
||||
str[i] = '=';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief when Ξ digits show as = then manually add a line on top
|
||||
*/
|
||||
static void _fix_broken_line(uint8_t hexagram) {
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (!(hexagram & (1 << (5 - i)))) {
|
||||
if ( i == 1 ) watch_set_pixel(2, 20);
|
||||
if ( i == 3 ) watch_set_pixel(2, 1);
|
||||
if ( i == 4 ) watch_set_pixel(2, 2);
|
||||
if ( i == 5 ) watch_set_pixel(2, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GEOMANCY FUNCTIONS /////////////////////////////////////////////////////////
|
||||
|
||||
/** @brief choose a geomantic figure from four random bits
|
||||
* @details 0 represents · and 1 represents : counting from the bottom
|
||||
*/
|
||||
static nibble_t _geomancy_pick_figure(void);
|
||||
static nibble_t _geomancy_pick_figure(void) {
|
||||
uint8_t index = (divine_bit() << 3) | (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
|
||||
nibble_t figure = {(geomantic >> (4 * (15 - index))) & 0xF};
|
||||
return figure;
|
||||
}
|
||||
|
||||
/** @brief display the geomantic figure, left of display is bottom
|
||||
*/
|
||||
static void _geomancy_display(nibble_t code) {
|
||||
// draw geomantic figures
|
||||
bool row1 = (code.bits >> 3) & 1;
|
||||
bool row2 = (code.bits >> 2) & 1;
|
||||
bool row3 = (code.bits >> 1) & 1;
|
||||
bool row4 = code.bits & 1;
|
||||
|
||||
if ( row1 ) watch_set_pixel(1, 18); else watch_set_pixel(1, 19);
|
||||
if ( row2 ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21);} else watch_set_pixel(1, 20);
|
||||
if ( row3 ) watch_set_pixel(0, 22); else watch_set_pixel(1, 23);
|
||||
if ( row4 ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0);} else watch_set_pixel(1, 1);
|
||||
}
|
99
movement/watch_faces/complication/geomancy_face.h
Normal file
99
movement/watch_faces/complication/geomancy_face.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef GEOMANCY_FACE_H_
|
||||
#define GEOMANCY_FACE_H_
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
/*
|
||||
* GEOMANCY WATCH FACE
|
||||
*
|
||||
* A simple and straightforward watch face for the ancient Eastern geomantic divination system
|
||||
* of I Ching and the western system of "Geomancy". It is an optional addition to the Toss Up
|
||||
* Face.
|
||||
*
|
||||
* The LIGHT button toggles between the two systems of geomancy.
|
||||
*
|
||||
* The ALARM button casts an I Ching hexagram or Geomantic figure based on drawing virtual
|
||||
* stalks from the True Random Number Generator in the Sensor Watch.
|
||||
*
|
||||
* The figures are flipped 90 degrees clockwise, so the left side is the bottom and the
|
||||
* right side the top.
|
||||
*
|
||||
* LONG PRESSING ALARM toggles the display of the King Wen sequence index for the cast I Ching
|
||||
* Hexagram (https://en.wikipedia.org/wiki/King_Wen_sequence )or the abbreviated name for the
|
||||
* cast Geomantic Figure:
|
||||
*
|
||||
* GF - Greater Fortune (Fortuna Major)
|
||||
* LF - Lesser Fortune (Fortuna Minor)
|
||||
* PO - Populus
|
||||
* VI - Via
|
||||
* AL - Albus
|
||||
* CO - Conjunctio
|
||||
* PA - Puella
|
||||
* AM - Amissio
|
||||
* PR - Puer
|
||||
* RU - Rubeus
|
||||
* AQ - Acquisitio
|
||||
* LA - Laetitia
|
||||
* TR - Tristitia
|
||||
* CA - Carcer
|
||||
* HD - Head of the Dragon (Caput Draconis)
|
||||
* TD - Tail of the Dragon (Cauda Draconis)
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t bits : 4;
|
||||
} nibble_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t bits : 3;
|
||||
} tribble_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t mode : 3;
|
||||
uint8_t geomantic_figure;
|
||||
uint8_t i_ching_hexagram : 6;
|
||||
bool caption;
|
||||
uint8_t animation;
|
||||
bool animate;
|
||||
} geomancy_state_t;
|
||||
|
||||
void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void geomancy_face_activate(movement_settings_t *settings, void *context);
|
||||
bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void geomancy_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
#define geomancy_face ((const watch_face_t){ \
|
||||
geomancy_face_setup, \
|
||||
geomancy_face_activate, \
|
||||
geomancy_face_loop, \
|
||||
geomancy_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // GEOMANCY_FACE_H_
|
||||
|
414
movement/watch_faces/complication/randonaut_face.c
Normal file
414
movement/watch_faces/complication/randonaut_face.c
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Emulator only: need time() to seed the random number generator.
|
||||
#if __EMSCRIPTEN__
|
||||
#include <time.h>
|
||||
#else
|
||||
#include "saml22j18a.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "filesystem.h"
|
||||
#include "randonaut_face.h"
|
||||
|
||||
#define R 6371 // Earth's radius in km
|
||||
#define PI 3.14159265358979323846
|
||||
|
||||
static void _get_location_from_file(randonaut_state_t *state);
|
||||
static void _save_point_to_file(randonaut_state_t *state);
|
||||
static void _get_entropy(randonaut_state_t *state);
|
||||
static void _generate_blindspot(randonaut_state_t *state);
|
||||
static void _randonaut_face_display(randonaut_state_t *state);
|
||||
static void _generate_blindspot(randonaut_state_t *state);
|
||||
static uint32_t _get_pseudo_entropy(uint32_t max);
|
||||
static uint32_t _get_true_entropy(void);
|
||||
static void _get_entropy(randonaut_state_t *state);
|
||||
|
||||
// MOVEMENT WATCH FACE FUNCTIONS //////////////////////////////////////////////
|
||||
|
||||
void randonaut_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(randonaut_state_t));
|
||||
memset(*context_ptr, 0, sizeof(randonaut_state_t));
|
||||
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
|
||||
}
|
||||
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
|
||||
}
|
||||
|
||||
void randonaut_face_activate(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
randonaut_state_t *state = (randonaut_state_t *)context;
|
||||
_get_location_from_file(state);
|
||||
state->face.mode = 0;
|
||||
state->radius = 1000;
|
||||
_get_entropy(state);
|
||||
state->chance = true;
|
||||
// Handle any tasks related to your watch face coming on screen.
|
||||
}
|
||||
|
||||
bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||
randonaut_state_t *state = (randonaut_state_t *)context;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
// Show your initial UI here.
|
||||
break;
|
||||
case EVENT_TICK:
|
||||
// If needed, update your display here.
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_DOWN:
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_UP:
|
||||
switch ( state->face.mode ) {
|
||||
case 0: // home
|
||||
state->face.mode = 2; //point
|
||||
state->face.location_format = 0; // title
|
||||
break;
|
||||
case 1: // generate
|
||||
state->face.mode = 0; //home
|
||||
break;
|
||||
case 2: // point
|
||||
state->face.mode = 0; //home
|
||||
break;
|
||||
case 3: // setup radius
|
||||
state->face.mode = 4; // toggle to RNG
|
||||
break;
|
||||
case 4: // setup RNG
|
||||
state->face.mode = 3; // toggle to Radius
|
||||
break;
|
||||
case 5: // data processing
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_LIGHT_LONG_PRESS:
|
||||
switch ( state->face.mode ) {
|
||||
case 3: // setup
|
||||
case 4:
|
||||
state->face.mode = 0; //home
|
||||
break;
|
||||
default:
|
||||
state->face.mode = 3; //setup
|
||||
watch_clear_display();
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP:
|
||||
switch ( state->face.mode ) {
|
||||
case 0: //home
|
||||
state->face.mode = 1; // generate
|
||||
break;
|
||||
case 2: // point
|
||||
state->face.location_format = (( state->face.location_format + 1) % (7));
|
||||
if ( state->face.location_format == 0 )
|
||||
state->face.location_format++;
|
||||
break;
|
||||
case 3: //setup radius
|
||||
state->radius += 500;
|
||||
if ( state->radius > 10000 )
|
||||
state->radius = 1000;
|
||||
break;
|
||||
case 4: //setup RNG
|
||||
state->face.rng = (state->face.rng + 1) % 3;
|
||||
switch ( state->face.rng ) {
|
||||
case 0:
|
||||
state->chance = true;
|
||||
break;
|
||||
case 1:
|
||||
state->chance = false;
|
||||
state->quantum = true;
|
||||
break;
|
||||
case 2:
|
||||
state->chance = false;
|
||||
state->quantum = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 5: // data processing
|
||||
_save_point_to_file(state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_LONG_PRESS:
|
||||
if ( state->face.mode == 5 )
|
||||
state->face.mode = 0; // home
|
||||
else
|
||||
state->face.mode = 5; // data processing
|
||||
break;
|
||||
case EVENT_TIMEOUT:
|
||||
// Your watch face will receive this event after a period of inactivity. If it makes sense to resign,
|
||||
// you may uncomment this line to move back to the first watch face in the list:
|
||||
// movement_move_to_face(0);
|
||||
break;
|
||||
case EVENT_LOW_ENERGY_UPDATE:
|
||||
// If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute.
|
||||
// Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds.
|
||||
// You should also consider starting the tick animation, to show the wearer that this is sleep mode:
|
||||
// watch_start_tick_animation(500);
|
||||
break;
|
||||
default:
|
||||
// Movement's default loop handler will step in for any cases you don't handle above:
|
||||
// * EVENT_LIGHT_BUTTON_DOWN lights the LED
|
||||
// * EVENT_MODE_BUTTON_UP moves to the next watch face in the list
|
||||
// * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured)
|
||||
// You can override any of these behaviors by adding a case for these events to this switch statement.
|
||||
return movement_default_loop_handler(event, settings);
|
||||
}
|
||||
|
||||
_randonaut_face_display(state);
|
||||
|
||||
// return true if the watch can enter standby mode. Generally speaking, you should always return true.
|
||||
// Exceptions:
|
||||
// * If you are displaying a color using the low-level watch_set_led_color function, you should return false.
|
||||
// * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false.
|
||||
// Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or
|
||||
// movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions.
|
||||
return true;
|
||||
}
|
||||
|
||||
void randonaut_face_resign(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
|
||||
// handle any cleanup before your watch face goes off-screen.
|
||||
}
|
||||
|
||||
// PRIVATE STATIC FUNCTIONS ///////////////////////////////////////////////////
|
||||
|
||||
/** @brief display handler
|
||||
*/
|
||||
static void _randonaut_face_display(randonaut_state_t *state) {
|
||||
char buf[12];
|
||||
watch_clear_colon();
|
||||
switch ( state->face.mode ) {
|
||||
case 0: //home
|
||||
sprintf(buf, "RA Rando");
|
||||
break;
|
||||
case 1: //generate
|
||||
if ( state->quantum )
|
||||
// All Hail Steve /;[;[/.;]/[.;[/;/;/;/;.;.];.]]--=/
|
||||
for ( uint8_t c = 100; c > 0; c--) {
|
||||
watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0x33-0x1C));
|
||||
watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409));
|
||||
watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xE +9));
|
||||
watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23));
|
||||
watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11));
|
||||
if( c < 70 ) {
|
||||
watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(12+7+11));
|
||||
}
|
||||
if ( c < 60 ) {
|
||||
watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xD68-0xD4A));
|
||||
}
|
||||
if ( c < 50 ) {
|
||||
watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(14+9));
|
||||
}
|
||||
delay_ms(_get_pseudo_entropy(c)+20);
|
||||
if ( c < 30 ) {
|
||||
watch_display_string(" ",_get_pseudo_entropy(10));
|
||||
}
|
||||
watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(3432-3409));
|
||||
watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(51-28));
|
||||
watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23));
|
||||
if ( c < 20 ) {
|
||||
watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(51-28));
|
||||
watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(14+9));
|
||||
watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0xD68-0xD4A));
|
||||
watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409));
|
||||
watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11));
|
||||
watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(51-28));
|
||||
}
|
||||
}
|
||||
else
|
||||
for ( uint8_t c = 30; c > 0; c--) {
|
||||
watch_display_string("1", _get_pseudo_entropy(10));
|
||||
watch_display_string("0", _get_pseudo_entropy(10));
|
||||
watch_display_string("11", _get_pseudo_entropy(10));
|
||||
watch_display_string("00", _get_pseudo_entropy(10));
|
||||
delay_ms(50);
|
||||
watch_display_string(" ", _get_pseudo_entropy(10));
|
||||
watch_display_string(" ", _get_pseudo_entropy(10));
|
||||
watch_display_string(" ", _get_pseudo_entropy(10));
|
||||
watch_display_string(" ", _get_pseudo_entropy(10));
|
||||
}
|
||||
_generate_blindspot(state);
|
||||
watch_clear_display();
|
||||
state->face.mode = 2; // point
|
||||
state->face.location_format = 1; // distance
|
||||
watch_display_string("RA Found", 0);
|
||||
delay_ms(500);
|
||||
sprintf(buf, "RA Found");
|
||||
break;
|
||||
case 2: //point
|
||||
switch ( state->face.location_format ) {
|
||||
case 0:
|
||||
sprintf(buf, "RA Point");
|
||||
break;
|
||||
case 1: // distance to point
|
||||
watch_clear_display();
|
||||
sprintf(buf, "DI m %d", state->point.distance );
|
||||
break;
|
||||
case 2: // bearing relative to point
|
||||
watch_clear_display();
|
||||
sprintf(buf, "BE # %d", state->point.bearing );
|
||||
break;
|
||||
case 3: // latitude DD._____
|
||||
sprintf(state->scratchpad, "%07d", abs(state->point.latitude));
|
||||
sprintf(buf, "LA #%c %c%c ", state->point.latitude < 0 ? '-' : '+', state->scratchpad[0], state->scratchpad[1]);
|
||||
break;
|
||||
case 4: // latitude __.DDDDD
|
||||
sprintf(buf, "LA , %c%c%c%c%c", state->scratchpad[2], state->scratchpad[3],state->scratchpad[4], state->scratchpad[5],state->scratchpad[6]);
|
||||
break;
|
||||
case 5: // longitude DD._____
|
||||
sprintf(state->scratchpad, "%08d", abs(state->point.longitude));
|
||||
sprintf(buf, "LO #%c%c%c%c ", state->point.longitude < 0 ? '-' : '+',state->scratchpad[0], state->scratchpad[1], state->scratchpad[2]);
|
||||
break;
|
||||
case 6: // longitude __.DDDDD
|
||||
sprintf(buf, "LO , %c%c%c%c%c", state->scratchpad[3], state->scratchpad[4],state->scratchpad[5], state->scratchpad[6],state->scratchpad[7]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3: // setup radius
|
||||
watch_set_colon();
|
||||
if ( state->radius < 10000 )
|
||||
sprintf(buf, "RA m %d ", state->radius);
|
||||
else
|
||||
sprintf(buf, "RA m%d ", state->radius);
|
||||
break;
|
||||
case 4: // setup RNG
|
||||
sprintf(buf, "RN G %s ", state->chance ? "Chnce" : (state->quantum ? "True" : "Psudo"));
|
||||
break;
|
||||
case 5: // data processing
|
||||
sprintf(buf, "WR File ");
|
||||
}
|
||||
watch_display_string(buf, 0);
|
||||
}
|
||||
|
||||
/** @brief Official Randonautica Blindspot Algorithm
|
||||
*/
|
||||
static void _generate_blindspot(randonaut_state_t *state) {
|
||||
|
||||
_get_entropy(state);
|
||||
|
||||
double lat = (double)state->location.latitude / 100000;
|
||||
double lon = (double)state->location.longitude / 100000;
|
||||
uint16_t radius = state->radius;
|
||||
|
||||
const double random_distance = radius * sqrt( (double)state->entropy / INT32_MAX ) / 1000.0;
|
||||
const double random_bearing = 2.0 * PI * (double)state->entropy / INT32_MAX;
|
||||
|
||||
const double phi = lat * PI / 180;
|
||||
const double lambda = lon * PI / 180;
|
||||
const double alpha = random_distance / R;
|
||||
|
||||
lat = asin( sin(phi) * cos(alpha) + cos(phi) * sin(alpha) * cos(random_bearing) );
|
||||
lon = lambda + atan2( sin(random_bearing) * sin(alpha) * cos(phi), cos(alpha) - sin(phi) * sin( lat ));
|
||||
|
||||
state->point.latitude = (int)round(lat * (180 / PI) * 100000);
|
||||
state->point.longitude = (int)round(lon * (180 / PI) * 100000);
|
||||
state->point.distance = random_distance * 1000;
|
||||
state->point.bearing = (uint16_t)round(random_bearing * (180 / PI) < 0 ? random_bearing * (180 / PI) + 360 : random_bearing * (180 / PI));
|
||||
}
|
||||
|
||||
|
||||
/** @brief pseudo random number generator
|
||||
*/
|
||||
static uint32_t _get_pseudo_entropy(uint32_t max) {
|
||||
#if __EMSCRIPTEN__
|
||||
return rand() % max;
|
||||
#else
|
||||
return arc4random_uniform(max);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** @brief true random number generator
|
||||
*/
|
||||
static 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
|
||||
}
|
||||
|
||||
/** @brief get location from place.loc
|
||||
*/
|
||||
static void _get_location_from_file(randonaut_state_t *state) {
|
||||
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
|
||||
coordinate_t place;
|
||||
if (filesystem_file_exists("place.loc")) {
|
||||
if (filesystem_read_file("place.loc", (char*)&place, sizeof(place)))
|
||||
state->location = place;
|
||||
} else {
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
state->location.latitude = movement_location.bit.latitude * 1000;
|
||||
state->location.longitude = movement_location.bit.longitude * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief save generated point to place.loc
|
||||
*/
|
||||
static void _save_point_to_file(randonaut_state_t *state) {
|
||||
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
coordinate_t place;
|
||||
place.latitude = state->point.latitude;
|
||||
place.longitude = state->point.longitude;
|
||||
if (filesystem_write_file("place.loc", (char*)&place, sizeof(place))) {
|
||||
delay_ms(100);
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
} else {
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
delay_ms(500);
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief get pseudo/quantum entropy and filter modulo bias
|
||||
*/
|
||||
static void _get_entropy(randonaut_state_t *state) {
|
||||
if ( state->chance ) {
|
||||
state->quantum = (bool)(state->entropy % 2);
|
||||
}
|
||||
do {
|
||||
if ( !state->quantum ) {
|
||||
state->entropy = _get_pseudo_entropy(INT32_MAX);
|
||||
} else {
|
||||
state->entropy = _get_true_entropy();
|
||||
}
|
||||
} while (state->entropy >= INT32_MAX || state->entropy <= 0);
|
||||
state->entropy %= INT32_MAX;
|
||||
}
|
113
movement/watch_faces/complication/randonaut_face.h
Normal file
113
movement/watch_faces/complication/randonaut_face.h
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef RANDONAUT_FACE_H_
|
||||
#define RANDONAUT_FACE_H_
|
||||
|
||||
#include "movement.h"
|
||||
#include "place_face.h"
|
||||
|
||||
/*
|
||||
* RANDONAUT FACE
|
||||
* ==============
|
||||
*
|
||||
* Randonauting is a way to turn the world around you into an adventure and get the user outside
|
||||
* of their day-to-day routine by using a random number generator to derive a coordinate to journey
|
||||
* to. In Randonauts lore so-called "Blind Spots" are places you cannot reach methodologically. They
|
||||
* may exist in your own backyard for your whole life and you will never even notice them, because
|
||||
* you simply have no reason to go to that exact place or look in its direction. Since the very
|
||||
* limitations of our behavioral algorithms are the reason for the existence of blindspots, they
|
||||
* can only be found using a randomizer.
|
||||
*
|
||||
* This watch face generates a random location based on the watch's location and a set radius using
|
||||
* the official Randonautica Blind Spot algorithm.
|
||||
*
|
||||
* The ALARM button starts the random location generation and then automatically displays the found
|
||||
* Blind Spot.
|
||||
*
|
||||
* By pressing ALARM again the user can flip through different pieces of information about the Blind
|
||||
* Spot: Distance (DI), Bearing Degree (BE), Latitude degrees and decimal digits (LA), Longitude
|
||||
* degrees and decimal digits (LO).
|
||||
*
|
||||
* Pressing LIGHT switches between generating a new blind spot ("Rando") and displaying the info of
|
||||
* the last generated one ("Point").
|
||||
*
|
||||
* LONG PRESSING LIGHT toggles setup mode. Here pressing LIGHT switches between setting the desired
|
||||
* radius (RA) and setting the random number generator (RNG) for generating the blind spot.
|
||||
*
|
||||
* ALARM changes the values respectively:
|
||||
*
|
||||
* - The radius can be set in 500 meter steps between 1000 and 10,000 meters
|
||||
*
|
||||
* - The RNG can be set to "true" which utilizes the SAML22J's internal True Random Number Generator
|
||||
* - Setting it to "psudo" will use the pseudorandom number generation algorithm arc4random
|
||||
* - Setting it to "chance" will randomly chose either of the RNGs for each generation (default)
|
||||
*
|
||||
* LONG PRESSING ALARM toggles DATA mode in which the currently generated Blind Spot coordinate can
|
||||
* be written to the <place.loc> file on the watch (press ALARM) and set as active high precision
|
||||
* location used by other watch faces. It does not overwrite the low precision location information
|
||||
* in the watch register commonly used for astronomical watch faces.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t mode :3;
|
||||
uint8_t location_format :3;
|
||||
uint8_t rng: 2;
|
||||
} randonaut_face_mode_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t latitude : 26;
|
||||
int32_t longitude : 26;
|
||||
uint16_t distance : 14;
|
||||
uint16_t bearing : 9;
|
||||
} randonaut_coordinate_t;
|
||||
|
||||
typedef struct {
|
||||
// Anything you need to keep track of, put it here!
|
||||
coordinate_t location;
|
||||
randonaut_coordinate_t point;
|
||||
uint16_t radius : 14;
|
||||
uint32_t entropy;
|
||||
bool quantum;
|
||||
bool chance;
|
||||
randonaut_face_mode_t face;
|
||||
char scratchpad[10];
|
||||
} randonaut_state_t;
|
||||
|
||||
void randonaut_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void randonaut_face_activate(movement_settings_t *settings, void *context);
|
||||
bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void randonaut_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
#define randonaut_face ((const watch_face_t){ \
|
||||
randonaut_face_setup, \
|
||||
randonaut_face_activate, \
|
||||
randonaut_face_loop, \
|
||||
randonaut_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // RANDONAUT_FACE_H_
|
||||
|
793
movement/watch_faces/complication/toss_up_face.c
Normal file
793
movement/watch_faces/complication/toss_up_face.c
Normal file
|
@ -0,0 +1,793 @@
|
|||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "toss_up_face.h"
|
||||
#if __EMSCRIPTEN__
|
||||
#include <time.h>
|
||||
#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);
|
||||
}
|
||||
}
|
112
movement/watch_faces/complication/toss_up_face.h
Normal file
112
movement/watch_faces/complication/toss_up_face.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef TOSS_UP_FACE_H_
|
||||
#define TOSS_UP_FACE_H_
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
/*
|
||||
* TOSS UP FACE
|
||||
* ============
|
||||
*
|
||||
* Playful watch face for games of chance or divination using coins or dice.
|
||||
*
|
||||
* LIGHT switches between Coins and Dice mode
|
||||
*
|
||||
* COINS
|
||||
* =====
|
||||
*
|
||||
* ALARM tosses a coin. If it lands on heads it gets sorted to the left side of the
|
||||
* display, if it lands on tails then sorted to the right side.
|
||||
*
|
||||
* LONG PRESSING ALARM adds up to 5 more coins to the toss for more nuance in the decision
|
||||
* making (e.g. three heads vs two tails could be read as "yes, but with serious doubts").
|
||||
*
|
||||
* LONG PRESSING LIGHT flips through additional style for the coins from the default Ө/O
|
||||
* to H/T (heads/tails), Y/N (yes/no), E/Ǝ, C/Ↄ
|
||||
*
|
||||
* LONG PRESSING ALARM on the "Coins" title page resets to one coin.
|
||||
* LONG PRESSING LIGHT on the "Coins" title page resets the style to Ө/O
|
||||
*
|
||||
* DICE
|
||||
* ====
|
||||
*
|
||||
* ALARM rolls a six sided dice.
|
||||
*
|
||||
* LONG PRESSING ALARM adds up to 2 more dice to the roll.
|
||||
*
|
||||
* LONG PRESSING LIGHT flips through other available polyhedral dice types with less or more
|
||||
* than the default 6 sides. The options are D2, D4, D6, D8, D10, D12, D20, D24, D30, D32, D36,
|
||||
* D48, and a hypothetical D99.
|
||||
*
|
||||
* When more than one dice is used for a roll this changes only the last added dice. (see Note
|
||||
* below)
|
||||
*
|
||||
* LONG PRESSING ALARM on the "Dice" title page resets to one dice.
|
||||
* LONG PRESSING LIGHT on the "Dice" title page resets the dice to D6.
|
||||
*
|
||||
* Please Note: If you need let's say a D8, D12, and D20 for your rolls then the procedure to
|
||||
* set this up would be as follows: from the default screen where you can roll the one D6 dice
|
||||
* you would LONG PRESS LIGHT a few times to change the D6 to a D8, then LONG PRESS ALARM to add
|
||||
* a second dice, LONG PRESS LIGHT again until the second dice changes to D12, then LONG PRESS
|
||||
* ALARM to add the third dice and LONG PRESS LIGHT again a few times until it becomes a D20.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
// Anything you need to keep track of, put it here!
|
||||
uint32_t entropy;
|
||||
uint8_t mode : 4; // 1 coin, 2 coins, 3 coins, 4 coins, dice, iching, geomnc
|
||||
bool setup;
|
||||
bool coins[6];
|
||||
uint8_t coin_num : 3;
|
||||
char coin_style[2];
|
||||
uint8_t coinface : 3;
|
||||
uint8_t dice[3];
|
||||
uint8_t dice_num : 2;
|
||||
uint8_t dd : 6;
|
||||
uint8_t dice_sides[3];
|
||||
uint8_t animation;
|
||||
bool animate;
|
||||
} toss_up_state_t;
|
||||
|
||||
uint32_t get_true_entropy(void);
|
||||
uint8_t divine_bit(void);
|
||||
uint8_t roll_dice(uint8_t sides);
|
||||
void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void toss_up_face_activate(movement_settings_t *settings, void *context);
|
||||
bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void toss_up_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
#define toss_up_face ((const watch_face_t){ \
|
||||
toss_up_face_setup, \
|
||||
toss_up_face_activate, \
|
||||
toss_up_face_loop, \
|
||||
toss_up_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // TOSS_UP_FACE_H_
|
||||
|
231
movement/watch_faces/settings/place_face.h
Normal file
231
movement/watch_faces/settings/place_face.h
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef place_FACE_H_
|
||||
#define place_FACE_H_
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
/*
|
||||
* PLACE FACE
|
||||
* ==========
|
||||
*
|
||||
* Based on and expanded from the Sunrise/Sunset face. Outsourced the location setting functionality to
|
||||
* its own face. Also serves as a converter between different coordinate notation formats.
|
||||
*
|
||||
* With the LIGHT button each place coordinate can be shown and edited in 4 different display modes:
|
||||
*
|
||||
* 1) Decimal Latitude and Longitude (WGS84) up to 5 decimal points
|
||||
* 2) Latitude and Longitude (WGS84) in traditional DD°MM'SS" notation
|
||||
* 3) Ten digit Open Location Code (aka. PlusCode) format
|
||||
* 4) Ten digit Geohash format
|
||||
*
|
||||
* Using the ALARM button the user can flip through 2 pages of coordinate info to see the first and
|
||||
* second sets of digits.
|
||||
*
|
||||
* (please also refer to the notes on precision below)
|
||||
*
|
||||
* Editing Mode
|
||||
* ============
|
||||
*
|
||||
* A LONG PRESS of the LIGHT button toggles editing mode for each of the selected notations.
|
||||
*
|
||||
* In this mode LIGHT moves the cursor and ALARM changes the letter cycling through the available
|
||||
* alphabet or numbers.
|
||||
*
|
||||
* When OLC or Geohash display are edited, Digit Info mode is activated. It serves as a workaround
|
||||
* for the limitation of how ambiguously alphanumeric characters are displayed on the main seven segment
|
||||
* digits of the watch face ( S or 5, I or 1, U or W?).
|
||||
*
|
||||
* The selected letter is also shown in the much easier to read alphanumeric 8 segment weekday digit above.
|
||||
* In addition the '24H' indicator is active when the selected digit represents a number and the 'PM'
|
||||
* indicator for a letter.
|
||||
*
|
||||
* A LONG PRESS of LIGHT saves the changes.
|
||||
*
|
||||
* Coordinates are read or stored to both the traditional internal location register and a file on
|
||||
* the LFS file system ("place.loc"). By default the Watch Face loads the coordinates from file
|
||||
* when activated. If no file is present, the coordinates are loaded from the register.
|
||||
* (please also see the notes on precision below)
|
||||
*
|
||||
* Auxiliary Mode: Digit Info
|
||||
* ==========================
|
||||
*
|
||||
* A LONG PRESS of the ALARM button toggles Digit Info mode when OLC or Geohash display is active.
|
||||
* (LAP indicator is on) It is a means of being able to see the detailed Digit Info as described above
|
||||
* but without the risk of accidentally editing any of digits.
|
||||
*
|
||||
* Both ALARM and LIGHT buttons can be used to flip through the letters.
|
||||
*
|
||||
* Notes on Coordinate Precision
|
||||
* =============================
|
||||
*
|
||||
* The common WGS84 Latitude and Longitude degrees naturally do not represent meters in distance
|
||||
* on the ground. 1° Longitude on the equatorial line equals a width of 111.32 kilometers, but
|
||||
* at 40° latitude further North or South it is approximately 85 kilometers wide. The closer to
|
||||
* the poles the narrower (and more precise) the latitude degrees get.
|
||||
*
|
||||
* The Sensor Watch's traditional 16bit location register only stores latitudes and longitudes
|
||||
* with two decimal points. That equals a longitudal precision of 36 arc seconds, or ~1111 meters
|
||||
* at the equator - precise enough for astronomical calculations, but not if you want to store the
|
||||
* location of let's say a building.
|
||||
*
|
||||
* Hence we propose the <place.loc> file that serves the same purpose, but with a precision of
|
||||
* five decimal digits. That equals 0.04 arc seconds or 1.11 meters at the equator.
|
||||
*
|
||||
* Please also note that the different notations of this watch face also have varying magnitudes
|
||||
* of precision:
|
||||
*
|
||||
* | Format | Notation | Precision at Equator | Precision at 67° N/S |
|
||||
* | ------------------ | ---------------------- | -------------------- | -------------------- |
|
||||
* | 2d. Decimal LatLon | 29.98, 31.13 | 1111.320 m | 435.125 m |
|
||||
* | 5d. Decimal LatLon | 29.97916, 31.13417 | 1.111 m | 0.435 m |
|
||||
* | DMS LatLon | N 29°58′45″, E 31°8′3″ | 30.833 m | 12.083 m |
|
||||
* | Open Location Code | 7GXHX4HM+MM | 13.875 m | 13.875 m |
|
||||
* | Geohash | stq4s3x1qu | 1.189 m | 0.596 m |
|
||||
*
|
||||
* Since all notations are internally converted into degrees with 5 decimal points, expect some
|
||||
* rounding errors when editing or loading the coordinates in other notation formats.
|
||||
*
|
||||
*/
|
||||
|
||||
static const char olc_alphabet[20] = "23456789CFGHJMPQRUWX";
|
||||
static const char geohash_alphabet[32] = "0123456789bCdEfGhjkmnpqrstuVwxyz";
|
||||
|
||||
typedef struct {
|
||||
uint8_t sign: 1; // 0-1
|
||||
uint8_t hundreds: 1; // 0-1
|
||||
uint8_t tens: 4; // 0-9
|
||||
uint8_t ones: 4; // 0-9
|
||||
uint8_t d01: 4; // 0-9
|
||||
uint8_t d02: 4; // 0-9
|
||||
uint8_t d03: 4; // 0-9
|
||||
uint8_t d04: 4; // 0-9
|
||||
uint8_t d05: 4; // 0-9
|
||||
} place_format_decimal_latlon_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sign: 1; // 0-1
|
||||
uint8_t hundreds: 1; // 0-1
|
||||
uint8_t tens: 4; // 0-9
|
||||
uint8_t ones: 4; // 0-9
|
||||
uint8_t mins_tens: 3; // 0-5
|
||||
uint8_t mins_ones: 4; // 0-9
|
||||
uint8_t secs_tens: 3; // 0-5
|
||||
uint8_t secs_ones: 4; // 0-9
|
||||
} place_format_dms_latlon_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t lat1: 5; // 2-X
|
||||
uint8_t lon1: 5; // 2-X
|
||||
uint8_t lat2: 5; // 2-X
|
||||
uint8_t lon2: 5; // 2-X
|
||||
uint8_t lat3: 5; // 2-X
|
||||
uint8_t lon3: 5; // 2-X
|
||||
uint8_t lat4: 5; // 2-X
|
||||
uint8_t lon4: 5; // 2-X
|
||||
uint8_t lat5: 5; // 2-X
|
||||
uint8_t lon5: 5; // 2-X
|
||||
} place_format_olc_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t latitude : 25;
|
||||
int32_t longitude : 26;
|
||||
} coordinate_t;
|
||||
|
||||
typedef struct {
|
||||
place_format_decimal_latlon_t latitude;
|
||||
place_format_decimal_latlon_t longitude;
|
||||
} place_coordinate_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t d01: 6; // 0-z
|
||||
uint8_t d02: 6; // 0-z
|
||||
uint8_t d03: 6; // 0-z
|
||||
uint8_t d04: 6; // 0-z
|
||||
uint8_t d05: 6; // 0-z
|
||||
uint8_t d06: 6; // 0-z
|
||||
uint8_t d07: 6; // 0-z
|
||||
uint8_t d08: 6; // 0-z
|
||||
uint8_t d09: 6; // 0-z
|
||||
uint8_t d10: 6; // 0-z
|
||||
} place_format_geohash_t;
|
||||
|
||||
typedef struct {
|
||||
double max;
|
||||
double min;
|
||||
} place_format_geohash_interval;
|
||||
|
||||
typedef struct {
|
||||
uint8_t min_digit : 1;
|
||||
uint8_t max_digit : 3;
|
||||
} place_mode_schema_page_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t max_page : 3;
|
||||
place_mode_schema_page_t page[4];
|
||||
} place_mode_schema_mode_t;
|
||||
|
||||
enum place_modes_e {
|
||||
MODE_DECIMAL = 0,
|
||||
MODE_DMS,
|
||||
MODE_OLC,
|
||||
MODE_GEOHASH
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
enum place_modes_e mode;
|
||||
uint8_t page : 3;
|
||||
int8_t active_digit: 4;
|
||||
bool edit;
|
||||
bool digit_info;
|
||||
place_format_decimal_latlon_t working_latitude;
|
||||
place_format_decimal_latlon_t working_longitude;
|
||||
place_format_dms_latlon_t working_dms_latitude;
|
||||
place_format_dms_latlon_t working_dms_longitude;
|
||||
place_format_olc_t working_pluscode;
|
||||
place_format_geohash_t working_geohash;
|
||||
place_mode_schema_mode_t modes[4];
|
||||
} place_state_t;
|
||||
|
||||
// PUBLIC WATCH FACE FUNCTIONS ////////////////////////////////////////////////
|
||||
|
||||
void place_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void place_face_activate(movement_settings_t *settings, void *context);
|
||||
bool place_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void place_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
void place_latlon_to_olc(char *pluscode, double latitude, double longitude);
|
||||
void place_latlon_to_geohash(char *geohash, double latitude, double longitude);
|
||||
|
||||
#define place_face ((const watch_face_t){ \
|
||||
place_face_setup, \
|
||||
place_face_activate, \
|
||||
place_face_loop, \
|
||||
place_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // place_FACE_H_
|
||||
|
Loading…
Reference in a new issue