diff --git a/prime_12h_clock_face.c b/prime_12h_clock_face.c new file mode 100644 index 000000000..1c3d8b947 --- /dev/null +++ b/prime_12h_clock_face.c @@ -0,0 +1,327 @@ +/* + * MIT License + * + * Copyright (c) 2024 Klingon Jane, based on simple_clock_face by: + * Copyright (c) 2022 Joey Castillo + * + * 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. + */ + +/* +This is the 12 hour version of the prime clock face. +In 12 hour mode, the data required is reduced by half. +*/ + +#include +#include +#include "prime_12h_clock_face.h" +#include "watch.h" +#include "watch_utility.h" +#include "watch_private_display.h" + +// Each minute has one of three possible patterns based on modulus of hours + minutes +const uint8_t primes_12h_lookup [ 3 ] [ 16 ] = { {1,7,11,13,17,19,23,29,31,37,41,43,47,49,53,59}, + {1,3,7,9,13,19,21,27,31,33,37,39,43,49,51,57}, + {3,9,11,17,21,23,27,29,33,39,41,47,51,53,57,59} }; + +// Each uint16_t contains one minute of data. +// This is a binary bitfield ( 1 if prime ) for each of the 16 candidate numbers. +const uint16_t primes_12h_bitfield [ 720 ] = { + 3084,38661,55364,38739,57792,33161,51605, 9126,54016,44042,51265,41272, +55312,12444, 6724,24738,16664, 2353, 3029,42851,11782,45117,13893, 6337, +50777,11853,54829,38994, 9394,50294,13839,38995,42160, 3480,21532,16968, + 8880,53430,34178, 855,20870,59394,28740, 3185,13159,63010,25552,55128, +20944,23200,16664,11589,34120,41386,22673,37956,15523,12160,32811,33387, + 6324,14555, 8993,60768, 3334,29260, 1604,63794,40972,35473,33149,47173, + 2132, 5240, 549,49337,12317,52369,38425, 2692,19858,59814,36992,12294, +11522,11525,23664, 5722,49174, 5189,59101,35088,16523,17236,37136,44296, + 483,23584,33713,16526,10467,18014, 6817,33424, 6260, 6474,33092, 8712, +39954,18546,34576,10332,23200,47183,27174, 1600,39499,36105,13345,23346, +37004, 3642,42021, 5226, 8578,57994,11008,32918,46731, 9988,18992,47664, +62304,34532, 0, 7452,45189, 5360,45072,32772,53443,35888,50706,42099, + 3277, 1798,10519, 8250, 6915,18190,25288,24628, 2085,63893,32922, 2449, + 5881,59474,20678, 1860,35616,52448,49436,17587,17041,10797,24854,51857, +15394,10288,45152,58518,49345, 8812,12899, 6092, 9,53632,19723, 4388, + 3352,12516,18184,53248,33248,35234, 3208,37378,29929,10882,23084,11544, +37843,21760,18444,14452,40031,33440,28697,60485, 2168,35073,17010,41794, +54798,16642,11268,21675,42640,19137,25650,37129, 579, 368,21573, 3088, +25244,40988,16385,21008,42200,33253,41031,20480,24592,14117,23864,17541, +22568,41546,16394, 3552,35072, 3762, 2706,25889,34056,50180, 9592,59392, +14640,53733, 452,34196,33876,30866, 6208,17474,43584,20642,39169,50952, +18057,15490,11764, 4637,37642, 8304,41673, 9354,49352,10321,57921, 81, +24832,19605, 2440,18774,33306,50211, 6153,12441, 2817,41058,17959,24710, +25518,33072,41264,37480,41485,19009, 4104, 4452,13435,36377, 4561,16600, + 4480,42034,57417, 2123,43195,24336,17924, 5253, 2922, 284,11002, 9155, + 1688,10887,53290,19216,45200, 594,45911,34466,32788,19065,53637,13456, +16708,39680, 1035,55334,54682,20649, 2600, 8840,27149, 4000, 565, 6245, +32849,10757,41801,18753,16545, 721,33152, 8594, 3076,21276, 6248,34128, +45207, 5290,26147, 8331, 1588,28706, 848, 675,13057,16392, 729, 2058, +24596,17633, 8645, 5349,34834,24674,12906,56416,22922, 443,23944,20688, +18475,17980, 9923,16600,53920, 8835,35044, 8330,27712,58481,33316,22688, +42504,45000,19116,25730, 2304, 1070,13388, 3338,22816, 2059,12532,17930, + 320,39169,14424, 2725,32808,58183, 7224, 1360,35328,16778,41240,20688, +21532,20562,45152,22532, 6232,43265,33067,24620,25644,16844, 8230,37306, +37312,34209, 1692, 8712, 8814,21186, 1287,26453,50356, 4430, 8464, 1800, + 4624,29702,19276, 3210,12418,33936,20693,12809, 9002, 8622,15737,33057, + 801,59650,34889,47434,12964, 9351,10760,19361,19806, 9856, 1906,26629, +16400,41346,49721,49392, 4620, 2066,34851, 529,37862,17924,43859,33792, + 9754, 561,37392, 789,53268, 5568, 2740,12346,46405,37171,57728,17713, +49222, 4136, 9416,35091,39625, 786,15044, 8536, 6373, 4365,23472, 778, +49527, 8388, 2633,49160, 3079, 2532,38192, 8458, 3377, 4375, 552,46800, +27264, 266,57636, 1160, 2368,24837, 1615,55368, 5379,18470,22242,26892, +33065,49158,20064,16978,41289,54352,53249,19218,30336, 738, 164,57505, + 643, 2083, 4166, 9805,41484,12318,16547,29132, 3920,40328,24658, 2146, +13924,25094, 83,18695,35634, 588, 3241,10628, 4505, 4955,32819,51372, +45090,12419, 522,37392,34829,45685, 8280, 3329,20677,38292, 4178,42046, + 78,36128, 8356, 1939, 9285,13921, 3588,17048,35177,10311,46473,33425, +17932,39713,22534, 1,33924,16780,12293, 1056, 784,41508,20530,24708, +53296, 33, 3597,16434,43283,12394, 6916, 2150,32784,29840,37408,35031, +24963,43276, 864, 5124,12996,34656, 9221,17867,24619,17025, 136,18570, +37416, 160,17984,33332,34592,37507, 7331,24964, 8308, 7034,27008,12522, + 8396, 5713, 6536, 786,50756,17216,18896,57384, 288, 8853,30282,11650, +32897,34819,36931,50176,35074,18681, 5756,63828, 664,50368,16912,35857, +18480,41328, 5160,18707, 3337,36880,32921, 2212,16420,10240,44057,45072, +35138, 1033,32881,12648,41541, 5611,32912,20552,34067,21824,58182, 771, +13332,32834, 2338, 8480, 401,20997, 5218,11352, 6572, 7858, 2165,21024, +32896,62168,11793,53571, 1868, 84,18432,25928,14292,12560,16687, 2160, +26625,33419,12294,19489,10912,25232,41235, 4304,33419, 1094,43685,14409, +17185,34725, 8601,17732, 65,11056,12552,31755,35264, 3208, 4609,41497, +20576,16428, 8329, 9531, 4453, 8837,17602,18572,38488,19222, 82,34256 +}; + + +// Read an entry from the primes bitfield containing one minutes worth of bitwise data in a uint16_t. +static void pc_read_bitfield(prime_12h_clock_state_t *state, watch_date_time *date_time) { + uint8_t hours; + uint16_t tableIndex; + + hours = date_time->unit.hour; + hours = hours % 12; + if ( hours == 0 ) hours = 12; + + state->my_mod = ( hours + date_time->unit.minute ) % 3; // There are three different 'pattern' types for a given minute. + tableIndex = ( hours - 1 ) * 60 + date_time->unit.minute; + state->seconds_bitfield = primes_12h_bitfield [ tableIndex ]; // Exact bitfield for this minute + state->indexer = 0; // Haven't started looking for next prime second yet. + state->next_required = true; // We must calculate next prime second + state->this_minute_displayed = false; +} + +// Calculate next prime second, starting/including current second +static void pc_next_prime_second(prime_12h_clock_state_t *state, watch_date_time *date_time) { + bool done = false; + state->next_prime_second = 61; // In case there are no more this minute + while ( !done ) { + if ( primes_12h_lookup [ state->my_mod ] [ state->indexer ] >= date_time->unit.second ) { // High enough, can use it if its prime. + if ( state->seconds_bitfield & ( 1 << state->indexer ) ) { // It's prime, we're done. + state->next_prime_second = primes_12h_lookup [state->my_mod][ state->indexer ]; + done = true; + } + } + state->indexer++; + if ( state->indexer > 15 ) done = true; + } + state->next_required = false; +} + +static void _update_alarm_indicator(bool settings_alarm_enabled, prime_12h_clock_state_t *state) { + state->alarm_enabled = settings_alarm_enabled; + if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); +} + +void prime_12h_clock_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(prime_12h_clock_state_t)); + prime_12h_clock_state_t *state = (prime_12h_clock_state_t *)*context_ptr; + state->signal_enabled = false; + state->watch_face_index = watch_face_index; + } +} + +void prime_12h_clock_face_activate(movement_settings_t *settings, void *context) { + prime_12h_clock_state_t *state = (prime_12h_clock_state_t *)context; + + if (watch_tick_animation_is_running()) watch_stop_tick_animation(); + + state->mode_24h = settings->bit.clock_mode_24h; + // handle chime indicator + if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); + else watch_clear_indicator(WATCH_INDICATOR_BELL); + + // show alarm indicator if there is an active alarm + _update_alarm_indicator(settings->bit.alarm_enabled, state); + + watch_set_colon(); + + // this ensures that none of the timestamp fields will match, so we can re-render them all. + state->previous_date_time = 0xFFFFFFFF; + state->force_print = true; + state->this_minute_displayed = false; + state->this_hour_displayed = false; +} + +bool prime_12h_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + prime_12h_clock_state_t *state = (prime_12h_clock_state_t *)context; + char buf[11]; + char myName[7]; + uint8_t pos; + + watch_date_time date_time; + uint32_t previous_date_time; + switch (event.event_type) { + case EVENT_ACTIVATE: + case EVENT_TICK: + case EVENT_LOW_ENERGY_UPDATE: + date_time = watch_rtc_get_date_time(); + previous_date_time = state->previous_date_time; + state->previous_date_time = date_time.reg; + + // check the battery voltage once a day... + if (date_time.unit.day != state->last_battery_check) { + state->last_battery_check = date_time.unit.day; + watch_enable_adc(); + uint16_t voltage = watch_get_vcc_voltage(); + watch_disable_adc(); + // 2.2 volts will happen when the battery has maybe 5-10% remaining? + // we can refine this later. + state->battery_low = (voltage < 2200); + } + + // ...and set the LAP indicator if low. + if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + + if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { + // everything before seconds is the same, don't waste cycles setting those segments. + if ( state->next_required ) pc_next_prime_second ( state, &date_time ); + if ( date_time.unit.second == state->next_prime_second ) { + if ( state->this_minute_displayed ) { + watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); + watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + } else if ( state->this_hour_displayed ) { + pos = 6; + sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); + watch_display_string(buf, pos); + state->this_minute_displayed = true; + } else { + pos = 4; + if (!settings->bit.clock_mode_24h) { + date_time.unit.hour %= 12; + if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } + sprintf(buf, "%2d%02d%02d", date_time.unit.hour, date_time.unit.minute, date_time.unit.second); + watch_display_string(buf, pos); + state->this_minute_displayed = true; + state->this_hour_displayed = true; + watch_set_colon(); + } + state->next_required = true; // We used this one up. + } + break; + } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { + // everything before minutes is the same - new minute, nothing to display just yet + pc_read_bitfield ( state, &date_time ); + pc_next_prime_second ( state, &date_time ); + break; + } else { + // other stuff changed; let's do it all. + // We are forced to use 12 hour mode, do some cleanup. + if (date_time.unit.hour < 12) { + watch_clear_indicator(WATCH_INDICATOR_PM); + } else { + watch_set_indicator(WATCH_INDICATOR_PM); + } + date_time.unit.hour %= 12; + if (date_time.unit.hour == 0) date_time.unit.hour = 12; + pc_read_bitfield ( state, &date_time ); + state->this_hour_displayed = false; + pc_next_prime_second ( state, &date_time ); + pos = 0; + if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { + if (!watch_tick_animation_is_running()) watch_start_tick_animation(500); + sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute); + state->this_minute_displayed = true; + state->this_hour_displayed = true; + } else { + if ( date_time.unit.second == state->next_prime_second ) { + sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); + state->next_required = true; + state->this_minute_displayed = true; + state->this_hour_displayed = true; + } else if ( state->force_print ) { + sprintf(buf, "%s%2d%2d%02d--", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute); + state->force_print = false; + state->this_minute_displayed = true; + state->this_hour_displayed = true; + } + else break; + } + } + watch_display_string(buf, pos); + // handle alarm indicator + if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); + break; + case EVENT_ALARM_LONG_PRESS: + strcpy ( myName, "" ); // Or better, put your name here. +// strcpy ( myName, "rALPh " ); + if ( strlen ( myName ) > 0 ) { + watch_display_string ( myName, 4 ); + state->this_minute_displayed = false; + state->this_hour_displayed = false; + watch_clear_colon(); + } + break; + case EVENT_LIGHT_LONG_PRESS: + watch_display_string ( "2025AU", 4 ); // Version date. + state->this_minute_displayed = false; + state->this_hour_displayed = false; + watch_clear_colon(); + break; + case EVENT_BACKGROUND_TASK: + // uncomment this line to snap back to the clock face when the hour signal sounds: + // movement_move_to_face(state->watch_face_index); + #ifdef SIGNAL_TUNE_DEFAULT + movement_play_signal(); + #else + movement_play_tune(); + #endif + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void prime_12h_clock_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool prime_12h_clock_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; + prime_12h_clock_state_t *state = (prime_12h_clock_state_t *)context; + if (!state->signal_enabled) return false; + + watch_date_time date_time = watch_rtc_get_date_time(); + + return date_time.unit.minute == 0; +} diff --git a/prime_12h_clock_face.h b/prime_12h_clock_face.h new file mode 100644 index 000000000..a74d9801b --- /dev/null +++ b/prime_12h_clock_face.h @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2024 Klingon Jane, based on simple_clock_face by: + * Copyright (c) 2022 Joey Castillo + * + * 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. + */ + +/* +This algorithm employs wheel factorization to consider only 16 candidate numbers per +each block of 60 seconds. Details are here: + +https://en.wikipedia.org/wiki/Wheel_factorization + +This is the 12 hour, data-reduced, version of the 24 hour prime clock face. +*/ + +#ifndef PRIME_12H_CLOCK_FACE_H_ +#define PRIME_12H_CLOCK_FACE_H_ + +#include "movement.h" + +typedef struct { + uint32_t previous_date_time; + uint8_t last_battery_check; + uint8_t watch_face_index; + uint8_t indexer; // Value from 0 to 16 inclusive + uint16_t seconds_bitfield; // From master prime bitfield table + uint8_t next_prime_second; + bool signal_enabled; + bool battery_low; + bool alarm_enabled; + bool force_print; // Must print something this round. + bool next_required; // Must calculate next_prime_second + bool mode_24h; + bool this_minute_displayed; // Has this minute already been displayed? + bool this_hour_displayed; // Has this hour already been displayed? + uint8_t my_mod; // Modulus of current time ( 0, 1 or 2 ); there are 3 patterns +} prime_12h_clock_state_t; + +void prime_12h_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void prime_12h_clock_face_activate(movement_settings_t *settings, void *context); +bool prime_12h_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void prime_12h_clock_face_resign(movement_settings_t *settings, void *context); +bool prime_12h_clock_face_wants_background_task(movement_settings_t *settings, void *context); + +#define prime_12h_clock_face ((const watch_face_t){ \ + prime_12h_clock_face_setup, \ + prime_12h_clock_face_activate, \ + prime_12h_clock_face_loop, \ + prime_12h_clock_face_resign, \ + prime_12h_clock_face_wants_background_task, \ +}) + +#endif // PRIME_12H_CLOCK_FACE_H_ diff --git a/xyzzy_face.c b/xyzzy_face.c new file mode 100644 index 000000000..e7805b09f --- /dev/null +++ b/xyzzy_face.c @@ -0,0 +1,1204 @@ +/* + * MIT License + * + * Copyright (c) 2025 Klingon Jane + * + * 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. + */ + +// XYZZY Micro-Adventure +// SEE xyzzy_face.h FOR GAME PLAY DOCUMENTATION + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include +#include "xyzzy_face.h" +#include "watch.h" +#include "watch_utility.h" + +#define XY_TICK_FREQUENCY 32 +#define XY_LONG_PRESS 19 +#define XY_SHORT_PRESS 2 + +#define XY_MODE_WAITING_TO_START 0 +#define XY_MODE_LEVEL_SELECT 1 +#define XY_MODE_PLAYING 2 +#define XY_MODE_CODE_ENTRY 3 +#define XY_MODE_TIME_DISPLAY 4 + +#define XY_VERSION_NUMBER 17 +// Joey Castillo to oversee storage allocation row +#define XY_STORAGE_ROW 5 +#define XY_STORAGE_KEY_NUMBER 11 + +char xyzzy_select_names[XY_NUM_LEVELS+1][7] = + { " tIny ", "Stndrd", "COLOSL", "SELECT" }; +char xyzzy_threat_names[XY_MAX_THREATS][7] = + { " bEAr ", "CAnYON", " trOLL", " rivEr", " drA6n" }; +char xyzzy_item_names[XY_MAX_THREATS][7] = + { "HOnEY ", " rOPE ", " 6EMS ", " bOArd", " PhASr" }; +char xyzzy_action_names[XY_MAX_THREATS][7] = + { " fEEd ", "CLImb ", " bribE", "SUrF ", "Stun " }; + +#define XY_NUM_KEYWORDS 9 +char xyzzy_keywords[XY_NUM_KEYWORDS][7] = + { " --- ", "SUn ", " MOOn ", " tree ", "EArtH ", " HILL ", "CLOUd ", " POnd ", "Star " }; +#define XY_NUM_TELEPORT 9 +char teleportStrings[XY_NUM_TELEPORT][7] = + { "- - - ", " - -", " - ", " - ", "- - -", " -- - ", "- -- -", " -- --", "--- -" }; + +// Final items/countries rewards not relavent to game play. +#define XY_NUM_COUNTRIES 6 +char xyzzy_countries[XY_NUM_COUNTRIES][7] = + { " EntEr", "IdaHO ", "VULCAN", " MarS ", " ArUbA", " FIJI " }; +#define XY_NUM_REWARDS 7 +char xyzzy_rewards[XY_NUM_REWARDS][7] = + { "HUmAn ", "StICk ", " PEbbL", "rAnCH ", "CASTLE", " YaCHT", "CHatEU" }; + +// Some arbitrary branch value for main path +#define XY_MAIN_PATH 37 + +// -------------- +// Custom methods +// -------------- + + +static int gen_random_int (int32_t lower, int32_t upper) { + int32_t range; + int32_t retVal; + range = upper - lower + 1; + if ( range < 2 ) range = 2; + // Emulator: use rand. Hardware: use arc4random. + #if __EMSCRIPTEN__ + retVal = rand() % range; + #else + retVal = arc4random_uniform(range); + #endif + retVal += lower; + return retVal; +} + +static void xyzzy_display_location (xyzzy_state_t *state ) { + char buf[11]; + uint8_t i; + if ( state->mode == XY_MODE_PLAYING ) { + sprintf ( buf, " %2d ", state->loc ); + watch_display_string ( buf, 4 ); + if ( state->loc == 0 ) watch_display_string ( "EN", 8 ); + if ( state->onMainPath ) { + if ( state->loc == state->locXyzzy ) watch_display_string ( "2Y", 8 ); + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) { + if ( state->loc == state->locBranchPoint[i] ) watch_display_string ( "br", 8 ); + } + } + } +} + + +static void xyzzy_display_code_entry (xyzzy_state_t *state ) { + char buf[11]; + sprintf ( buf, "%2d%s", state->userCodeIndex+1, xyzzy_keywords[state->userCodes[state->userCodeIndex]] ); + watch_display_string ( buf, 2 ); +} + +static void write_to_xyzzy_EEPROM(xyzzy_state_t *state) { + uint8_t base = 13; // Array writes being here + // Do not exceed 64 bytes per write + uint8_t size; + uint8_t output_array [ 64 ]; // More than large enough for any one page + uint8_t offset = 0; + uint8_t i, j; + uint8_t index; + uint32_t bigNum; // Working copy of big number we are parsing into pieces + uint8_t piece; // The current 8-bit piece + + // 1st page + size = base + XY_MAX_PATH_LENGTH + ( XY_MAX_CODES * 3 ) + ( XY_MAX_BRANCHES * 2 ); // (55 bytes) + offset = 0; + output_array [ 0 ] = XY_STORAGE_KEY_NUMBER; + output_array [ 1 ] = state->mode; + output_array [ 2 ] = state->levelSelected; + output_array [ 3 ] = state->loc; + output_array [ 4 ] = state->locMainPathEnd; + output_array [ 5 ] = state->numCodes; + output_array [ 6 ] = state->totalCodesSeen; + output_array [ 7 ] = state->locXyzzy; + output_array [ 8 ] = state->xyzzyKnown; + output_array [ 9 ] = state->brIndex; + output_array [ 10 ] = state->onMainPath; + output_array [ 11 ] = state->secretDestination; + output_array [ 12 ] = state->secretItem; + index = base; // First array output start location + for ( i = 0; i < XY_MAX_PATH_LENGTH; i++ ) output_array [ index + i ] = state->mainPath[i]; + index += XY_MAX_PATH_LENGTH; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) output_array [ index + i ] = state->locCodes[i]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) output_array [ index + i ] = state->codeValues[i]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) output_array [ index + i ] = state->codesSeen[i]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) output_array [ index + i ] = state->locBranchPoint[i]; + index += XY_MAX_BRANCHES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) output_array [ index + i ] = state->locBranchEnd[i]; + index += XY_MAX_BRANCHES; // Advance to next array output start location + + // Erase full row before first write only + watch_storage_erase ( XY_STORAGE_ROW ); + watch_storage_sync ( ); + watch_storage_write ( XY_STORAGE_ROW, offset, output_array, size ); + watch_storage_sync ( ); + + + // 2nd page + size = ( XY_MAX_THREATS * 6 ) + ( XY_MAX_BRANCHES * XY_MAX_BRANCH_LENGTH ); // (54 bytes) + offset = 64; + index = 0; + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->locThreat[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->brThreat[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->inventory[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->locItem[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->brItem[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) output_array [ index + i ] = state->ri[i]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( j = 0; j < XY_MAX_BRANCHES; j++ ) { + for ( i = 0; i < XY_MAX_BRANCH_LENGTH; i++ ) output_array [ index + i ] = state->branch[j][i]; + index += XY_MAX_BRANCH_LENGTH; // Advance to next array output start location + } + watch_storage_write ( XY_STORAGE_ROW, offset, output_array, size ); + watch_storage_sync ( ); + + // 3rd page + size = ( 4 * 2 ) + ( 4 * XY_NUM_LEVELS ); // (20 bytes) + offset = 128; + index = 0; + // Parse the start time into four 8-bit integers + bigNum = state->startTime; + for ( i = 0; i < 4; i++ ) { + piece = bigNum % 256; + output_array [ index + 3 - i ] = piece; + bigNum = bigNum / 256; + } + index += 4; + // Parse the lastSolveTime into four 8-bit integers + bigNum = state->lastSolveTime; + for ( i = 0; i < 4; i++ ) { + piece = bigNum % 256; + output_array [ index + 3 - i ] = piece; + bigNum = bigNum / 256; + } + index += 4; + // Also all the bestTime array + for ( j = 0; j < XY_NUM_LEVELS; j++ ) { + bigNum = state->bestTime[j]; + for ( i = 0; i < 4; i++ ) { + piece = bigNum % 256; + output_array [ index + 3 - i ] = piece; + bigNum = bigNum / 256; + } + index += 4; + } + watch_storage_write ( XY_STORAGE_ROW, offset, output_array, size ); + watch_storage_sync ( ); +} + + +static void read_from_xyzzy_EEPROM(xyzzy_state_t *state) { + uint8_t base = 13; // Array reads being here + uint8_t size; + uint8_t stored_data [ 64 ]; // More than enough for largest read + uint8_t offset = 0; + uint8_t i, j; + uint8_t index; + uint32_t bigNum; + + // Read 1st page + size = base + XY_MAX_PATH_LENGTH + ( XY_MAX_CODES * 3 ) + ( XY_MAX_BRANCHES * 2 ); // (55 bytes) + offset = 0; + watch_storage_read (XY_STORAGE_ROW, offset, stored_data, size); + // See if data was ever written to EEPROM storage + if ( stored_data[0] == XY_STORAGE_KEY_NUMBER ) + { + state->mode = stored_data [ 1 ]; + state->loc = stored_data [ 2 ]; + state->levelSelected = stored_data [ 3 ]; + state->locMainPathEnd = stored_data [ 4 ]; + state->numCodes = stored_data [ 5 ]; + state->totalCodesSeen = stored_data [ 6 ]; + state->locXyzzy = stored_data [ 7 ]; + state->xyzzyKnown = stored_data [ 8 ]; + state->brIndex = stored_data [ 9 ]; + state->onMainPath = stored_data [ 10 ]; + state->secretDestination = stored_data [ 11 ]; + state->secretItem = stored_data [ 12 ]; + index = base; // First array output start location + for ( i = 0; i < XY_MAX_PATH_LENGTH; i++ ) state->mainPath[i] = stored_data [ index + i ]; + index += XY_MAX_PATH_LENGTH; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) state->locCodes[i] = stored_data [ index + i ]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) state->codeValues[i] = stored_data [ index + i ]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_CODES; i++ ) state->codesSeen[i] = stored_data [ index + i ]; + index += XY_MAX_CODES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) state->locBranchPoint[i] = stored_data [ index + i ]; + index += XY_MAX_BRANCHES; // Advance to next array output start location + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) state->locBranchEnd[i] = stored_data [ index + i ]; + index += XY_MAX_BRANCHES; // Advance to next array output start location + + // Read 2nd page + size = ( XY_MAX_THREATS * 6 ) + ( XY_MAX_BRANCHES * XY_MAX_BRANCH_LENGTH ); // (54 bytes) + offset = 64; + index = 0; + watch_storage_read (XY_STORAGE_ROW, offset, stored_data, size); + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->locThreat[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->brThreat[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->inventory[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->locItem[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->brItem[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->ri[i] = stored_data [ index + i ]; + index += XY_MAX_THREATS; // Advance to next array output start location + for ( j = 0; j < XY_MAX_BRANCHES; j++ ) { + for ( i = 0; i < XY_MAX_BRANCH_LENGTH; i++ ) state->branch[j][i] = stored_data [ index + i ]; + index += XY_MAX_BRANCH_LENGTH; // Advance to next array output start location + } + + // Read 3rd page + size = ( 4 * 2 ) + ( 4 * XY_NUM_LEVELS ); // (20 bytes) + offset = 128; + index = 0; + watch_storage_read (XY_STORAGE_ROW, offset, stored_data, size); + // Read 32-bit startTime + bigNum = 0; + for ( i = 0; i < 4; i++ ) { + bigNum *= 256; + bigNum += stored_data [ index + i ]; + } + state->startTime = bigNum; + index += 4; // Advance to next array output start location + // Read 32-bit lastSolveTime + bigNum = 0; + for ( i = 0; i < 4; i++ ) { + bigNum *= 256; + bigNum += stored_data [ index + i ]; + } + state->lastSolveTime = bigNum; + index += 4; // Advance to next array output start location + // Also the bestTime array + for ( j = 0; j < XY_NUM_LEVELS; j++ ) { + bigNum = 0; + for ( i = 0; i < 4; i++ ) { + bigNum *= 256; + bigNum += stored_data [ index + i ]; + } + state->bestTime[j] = bigNum; + index += 4; // Advance to next array output start location + } + } + else + { + state->mode = XY_MODE_WAITING_TO_START; + state->secretDestination = 0; + state->secretItem = 0; + for ( i = 0; i < XY_NUM_LEVELS; i++ ) { + state->bestTime[i] = 0; + } + } + state->writePending = false; +} + + +// Generate a new cave +static void xyzzy_gen_new_cave(xyzzy_state_t *state) { + uint8_t i; + bool done; + bool valOK; + uint8_t newLoc; + uint8_t newCode; + uint8_t j; + uint8_t temp; + uint8_t numBranches; + uint8_t numThreats; // Based on user selected difficulty + uint8_t maxBranchPoint; + + state->mode = XY_MODE_PLAYING; + state->loc = 0; // Entrance + state->totalCodesSeen = 0; + state->xyzzyKnown = 0; + state->onMainPath = true; + state->brIndex = 0; // Not meaningful yet + state->shortcutCounter = 0; + state->tickCounter = 0; + state->writePending = true; + state->secretDestination = gen_random_int ( 1, XY_NUM_COUNTRIES - 1 ); + state->secretItem = gen_random_int ( 1, XY_NUM_REWARDS - 1 ); + state->resetCounter = 0; + +// INITIALIZE CAVE + + // Remember when this cave was generated - I.E. right now. + state->startTime = watch_utility_date_time_to_unix_time ( watch_rtc_get_date_time(), 0 ); + // Generate all paths at full length + for ( i = 0; i < XY_MAX_PATH_LENGTH; i++ ) state->mainPath[i] = gen_random_int(0,1); // Main path + for ( j = 0; j < XY_MAX_BRANCHES; j++ ) { + for ( i = 0; i < XY_MAX_BRANCH_LENGTH; i++ ) state->branch[j][i] = gen_random_int(0,1); // Branches + } + // Initialize other arrays + for ( i = 0; i < XY_MAX_CODES; i++ ) { + state->locCodes[i] = 199; // Init as not applicable + state->codeValues[i] = 199; + state->codesSeen[i] = 0; + } + for ( i = 0; i < XY_MAX_CODES; i++ ) { // Unique code values for final room + done = false; + do { + valOK = true; + newCode = gen_random_int(1,XY_NUM_KEYWORDS-1); + for ( j = 0; j < XY_MAX_CODES; j++ ) if ( newCode == state->codeValues[j] ) valOK = false; + if ( valOK ) done = true; + } while ( !done ); + state->codeValues[i] = newCode; + } + for ( i = 0; i < XY_MAX_THREATS; i++ ) { + state->inventory[i] = 0; // Start out with nothing + state->locItem[i] = 199; // Init as not applicable + state->brItem[i] = 0; // Init only, will reassign later if used. + state->locThreat[i] = 199; // Init as not applicable + state->brThreat[i] = 0; // Init only, will reassign later if used. + } + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) { + state->locBranchPoint[i] = 199; // Init as not applicable + state->locBranchEnd[i] = 201; // Init as anot applicable + } + for ( i = 0; i < XY_MAX_THREATS; i++ ) state->ri[i] = 199; // Randomized index 0 to n-1, but scrambled + for ( i = 0; i < XY_MAX_THREATS; i++ ) { + done = false; + do { + valOK = true; + newCode = gen_random_int ( 0, XY_MAX_THREATS - 1 ); + for ( j = 0; j < XY_MAX_THREATS; j++ ) if ( newCode == state->ri[j] ) valOK = false; + if ( valOK ) done = true; + } while ( !done ); + state->ri[i] = newCode; + } + + if ( state->levelSelected == 0 ) { // Easy/demo level + state->locMainPathEnd = 8; + state->numCodes = 2; + numBranches = 0; + numThreats = 0; + } else if ( state->levelSelected == 1 ) { // Moderate game + state->locMainPathEnd = 21; + state->numCodes = 4; + numBranches = 1; + numThreats = 1; + } else { // The real game + state->locMainPathEnd = gen_random_int ( XY_MAX_PATH_LENGTH-2, XY_MAX_PATH_LENGTH ); + state->numCodes = 4; + numBranches = 3; + numThreats = 3; + } + if ( numThreats > numBranches ) numThreats = numBranches; // A limitation, for now.... + // ... could allow numThreats = numBranches + 1 with a code change to place last item + // on main path + + // Assign locXyzzy + // Place primary threat near final room + // Randomly assign all branch point locations and branch ends + // LOOP - assign all items and threats + // Assign all locCodes to be at unique locations, and not at any branch path start. + + // Assign locXyzzy + state->locXyzzy = 199; // Not used if very short cave + if ( state->locMainPathEnd > 15 ) state->locXyzzy = ( state->locMainPathEnd / 2 ) + 1; + // Place primary threat near final room + state->locThreat[0] = 199; // No primary threat + state->brThreat[0] = XY_MAIN_PATH; + if ( numThreats > 0 ) state->locThreat[0] = gen_random_int ( state->locMainPathEnd-4, state->locMainPathEnd-1 ); + + // Randomly assign all branch point locations and branch ends + // Add in any branches next, all unique and not at xyzzy + maxBranchPoint = min ( state->locThreat[0], state->locMainPathEnd ) - 1; + for ( i = 0; i < numBranches; i++ ) { + done = false; + while ( ! done ) { + valOK = true; + newLoc = gen_random_int ( 2, maxBranchPoint ); + if ( newLoc == state->locXyzzy ) valOK = false; + for ( j = 0; j < XY_MAX_BRANCHES; j++ ) { + if ( newLoc == state->locBranchPoint[j] ) valOK = false; + } + if ( valOK ) done = true; + } + state->locBranchPoint[i] = newLoc; + state->locBranchEnd[i] = state->locBranchPoint[i] + gen_random_int ( XY_MAX_BRANCH_LENGTH -3, XY_MAX_BRANCH_LENGTH ); + } + + // LOOP - assign all items and threats + for ( i = 0; i < numThreats; i++ ) { + state->brItem[i] = i; // Simply assign one item per branch + // Usually place item right at end of branch+1 ( discover item AND dead end ) + if ( gen_random_int ( 0, 2 ) != 1 ) { + state->locItem[i] = state->locBranchEnd[i] + 1; // Friendly + } else { + state->locItem[i] = state->locBranchEnd[i] - gen_random_int ( 0, 2 ); // Half friendly + state->locItem[i] = max ( state->locBranchPoint[i] + 2, state->locItem[i] ); // Safety + } + // Place a threat at the front of this branch, if applicable + if ( ( i + 1 ) < numThreats ) { + state->brThreat[i+1] = i; + state->locThreat[i+1] = gen_random_int ( state->locBranchPoint[i]+1, state->locItem[i]-1 ); + state->locThreat[i+1] = max ( state->locBranchPoint[i]+1, state->locThreat[i+1] ); // Safety + } + } + // Assign all locCodes to be at unique locations, and not at any branch path start. + for ( i = 0; i < state->numCodes; i++ ) { + done = false; + while ( ! done ) { + valOK = true; + newLoc = gen_random_int ( 2, state->locMainPathEnd - 2 ); + for ( j = 0; j < XY_MAX_BRANCHES; j++ ) { + if ( newLoc == state->locBranchPoint[j] ) valOK = false; + } + for ( j = 0; j < state->numCodes; j++ ) { + if ( newLoc == state->locCodes[j] ) valOK = false; + } + if ( valOK ) done = true; + } + state->locCodes[i] = newLoc; + } + // And locCodes must be in ascending order when done + for ( i = 0; i < state->numCodes - 1; i++ ) + { + for ( j = 0; j < state->numCodes - i - 1; j++ ) { + if ( state->locCodes[j] > state->locCodes[j+1] ) { + temp = state->locCodes[j]; + state->locCodes[j] = state->locCodes[j+1]; + state->locCodes[j+1] = temp; + } + } + } + // Small easement for tiny cave + if ( state->levelSelected == 0 ) { + state->locCodes[0] = 3; + state->locCodes[1] = 6; + } +} + + +// --------------------------- +// Standard watch face methods +// --------------------------- +void xyzzy_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(xyzzy_state_t)); + memset(*context_ptr, 0, sizeof(xyzzy_state_t)); + } + xyzzy_state_t *state = (xyzzy_state_t *)*context_ptr; + // Emulator only: Seed random number generator + #if __EMSCRIPTEN__ + srand(time(NULL)); + #endif + // Read in cave layout, secretDestination etc. from EEPROM. + read_from_xyzzy_EEPROM(state); + state->shortcutCounter = 0; +} + +void xyzzy_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + xyzzy_state_t *state = (xyzzy_state_t *)context; + char buf[16]; + uint8_t i; + movement_request_tick_frequency(XY_TICK_FREQUENCY); + // No fancy intro + watch_clear_all_indicators ( ); + watch_display_string("CA ", 0); // Cave + // What to display depends on what mode we re-enter the game in + if ( state->mode == XY_MODE_WAITING_TO_START ) { + sprintf ( buf, "%s", xyzzy_countries[state->secretDestination] ); + watch_display_string ( buf, 4 ); + } else if ( state->mode == XY_MODE_LEVEL_SELECT ) { + state->levelSelected = XY_NUM_LEVELS; // Shows 'Select' + sprintf ( buf, "%s", xyzzy_select_names[state->levelSelected] ); + watch_display_string ( buf, 4 ); + } else { + xyzzy_display_location ( state ); + for ( i = 0; i < XY_MAX_THREATS; i++ ) if (state->inventory[i]) watch_set_indicator (state->ri[i]); + } + state->tickCounter = 0; + state->buttonTimer = 0; + state->bothPressedTimer = 0; + state->ignoreNextRelease = false; +} + +bool xyzzy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + xyzzy_state_t *state = (xyzzy_state_t *)context; + char buf [ 20 ]; // [11] is more correct and works; compiler too helpful. + bool userPickedLeft = false; // During gameplay + bool userPickedRight = false; // During gameplay + bool displayLocation = false; + bool correctPick; // Did user make 'correct' choice - advance further into cave + bool branchMatch = false; // There is a path branch at this location + bool codeMatch = false; // There is a keyword at this location + bool valOK; + uint8_t codeMatchIndex; // Which keyword is at this location? + uint16_t i; + uint8_t j; + uint8_t branchInd; // Index when on a branch - starts at 0 + uint8_t oldVal; + uint32_t bigNum; + bool jumpToXyzzy; // Jump to xyzzy instead of entrance + int8_t itemIndex; // Index if item was found, or -1 + int8_t threatIndex; // Index if threat encountered, or -1 + // Debouncing of buttons + bool alarmPress = false; // Pressed right now + bool lightPress = false; // Pressed right now + bool alarmUpShort = false; // Short release + bool lightUpShort = false; // Short release + bool alarmDownLong = false; // Long press + bool lightDownLong = false; // Long press + uint32_t nowSeconds; // Unix time is in seconds + uint32_t deltaSeconds; // Total play time + uint8_t hours, minutes, seconds; + uint16_t days; + uint8_t codePending[XY_MAX_CODES]; // Check user code entries + bool codeEntryCompleted = false; // Did user just finish entering a code? + + switch (event.event_type) { + case EVENT_TICK: + // ticks are used for input debounce or the alternating the display updates + // if the game is over, or the best times display + state->tickCounter++; + if ( state->mode == XY_MODE_WAITING_TO_START ) { + // While waiting, we display the high score list and other items until + // the user starts the next game. + if ( state->tickCounter == 1 ) { + watch_display_string ( " tHE ", 4 ); + } else if ( state->tickCounter == (int) (XY_TICK_FREQUENCY * 1.6) ) { // Blank space + watch_display_string ( " ", 4 ); + } else if ( state->tickCounter == XY_TICK_FREQUENCY * 2 ) { + watch_display_string ( "CAVE ", 4 ); + } else if ( state->tickCounter == (int) (XY_TICK_FREQUENCY * 5.9) ) { // Blank space + watch_display_string ( " ", 4 ); + } else if ( state->tickCounter == XY_TICK_FREQUENCY * 7 ) { // Show 'Enter ' or reward destination + sprintf ( buf, "%s", xyzzy_countries[state->secretDestination] ); + watch_display_string ( buf, 4 ); + } else if ( state->tickCounter == (int) (XY_TICK_FREQUENCY * 9.6) ) { // Blank space + watch_display_string ( " ", 4 ); + } else if ( state->tickCounter == XY_TICK_FREQUENCY * 10 ) { // Show 'Human ' or reward item + sprintf ( buf, "%s", xyzzy_rewards[state->secretItem] ); + watch_display_string ( buf, 4 ); + } else if ( state->tickCounter == (int) (XY_TICK_FREQUENCY * 12.6) ) { // Blank space + watch_display_string ( " ", 4 ); + } else if ( state->tickCounter >= XY_TICK_FREQUENCY * 14 ) state->tickCounter = 0; + } else if ( state->mode == XY_MODE_TIME_DISPLAY ) { + if ( state->tickCounter == 1 ) { // Display the currently selected request + if ( state->tdSubLevel == 0 ) { // Beginning of a new level, ex: tiny level + // Will need to do the math + if ( state->tdLevel < XY_NUM_LEVELS ) { + bigNum = state->bestTime[state->tdLevel]; + sprintf ( buf, "%6s", xyzzy_select_names[state->tdLevel] ); + } else { + bigNum = state->lastSolveTime; + sprintf ( buf, " LaSt " ); + } + if ( bigNum == 0 ) { // Avoid normal handling if this cave never completed + watch_display_string ( buf, 4 ); + delay_ms ( 1000 ); + sprintf ( buf, "---- " ); + state->tdSubLevel = 4; + } + state->seconds = bigNum % 60; + bigNum = bigNum / 60; + state->minutes = bigNum % 60; + bigNum = bigNum / 60; + state->hours = bigNum % 24; + bigNum = bigNum / 24; + state->days = bigNum; + } else if ( state->tdSubLevel == 1 ) { // Days + sprintf ( buf, "%4ddY", state->days ); + } else if ( state->tdSubLevel == 2 ) { // Hours + sprintf ( buf, "%4dHr", state->hours ); + } else if ( state->tdSubLevel == 3 ) { // Minutes + sprintf ( buf, "%4dMI", state->minutes ); + } else if ( state->tdSubLevel == 4 ) { // Seconds + sprintf ( buf, "%4dSE", state->seconds ); + } else { // Blank + sprintf ( buf, " " ); + } + watch_display_string ( buf, 4 ); + } else if ( ( state->tickCounter >= XY_TICK_FREQUENCY * 1.0 ) || + ( ( state->tdSubLevel >= 5 ) && ( state->tickCounter >= XY_TICK_FREQUENCY * 0.3 ) ) ) { + // Time to advance to next item + state->tdSubLevel++; + // But skip days if they are zero + if ( ( state->tdSubLevel == 1 ) && ( state->days == 0 ) ) state->tdSubLevel = 2; + // Also skip hours if both days and hours are zero + if ( ( state->tdSubLevel == 2 ) && ( state->days == 0 ) && ( state->hours == 0 ) ) { + state->tdSubLevel = 3; + } + // Are we done displaying this level? + if ( state->tdSubLevel > 5 ) { + state->tdSubLevel = 0; // Advance to next level display + state->tdLevel++; + if ( state->tdLevel > XY_NUM_LEVELS ) { + state->mode = XY_MODE_WAITING_TO_START; // All done time display + sprintf ( buf, " vEr%2d", XY_VERSION_NUMBER ); + watch_display_string ( buf, 4 ); + delay_ms (2500); + } + } + state->tickCounter = 0; // Reset counter + } + } + + // We'll monitor both the light and alarm buttons + alarmPress = watch_get_pin_level(BTN_ALARM); + lightPress = watch_get_pin_level(BTN_LIGHT); + // If neither button pressed, we check for a button just being release + if ( !alarmPress && !lightPress ) { + // Possibly a button was just released + if ( state->buttonTimer > 0 ) { + if ( !state->ignoreNextRelease ) { + // Which button and for how long? + if ( state->alarmWasPressed ) { + if ( state->buttonTimer >= XY_SHORT_PRESS ) alarmUpShort = true; + // else nothing - it was noise + } + if ( state->lightWasPressed ) { + if ( state->buttonTimer >= XY_SHORT_PRESS ) lightUpShort = true; + // else nothing - it was noise + } + } else { // We just ignored a release after a long press + state->ignoreNextRelease = false; + } + state->buttonTimer = 0; + } + state->alarmWasPressed = false; + state->lightWasPressed = false; + state->bothPressedTimer = 0; + } + else if ( alarmPress && !lightPress ) { + state->alarmWasPressed = true; + state->buttonTimer++; + state->bothPressedTimer = 0; + if ( state->buttonTimer == XY_LONG_PRESS ) { + alarmDownLong = true; + state->ignoreNextRelease = true; + } + } + else if ( lightPress && !alarmPress) { + state->lightWasPressed = true; + state->buttonTimer++; + state->bothPressedTimer = 0; + if ( state->buttonTimer == XY_LONG_PRESS ) { + lightDownLong = true; + state->ignoreNextRelease = true; + } + } else { // Both pressed + state->ignoreNextRelease = true; + state->bothPressedTimer++; + if ( state->bothPressedTimer == XY_LONG_PRESS ) { + state->tickCounter = 0; + watch_clear_all_indicators(); + if ( state->mode != XY_MODE_WAITING_TO_START ) { + state->resetCounter = 0; + watch_display_string ( " rESEt ", 2 ); + delay_ms ( 700 ); + } else { + state->resetCounter++; + state->bothPressedTimer = XY_LONG_PRESS / 2; + if ( state->resetCounter < 7 ) { + sprintf ( buf, " rESET%d", state->resetCounter ); + watch_display_string ( buf, 2 ); + delay_ms ( 500 ); + state->tickCounter=2; // Avoid flashing intro while full reset + } else { + for ( i = 0; i < 5; i++ ) { + watch_display_string ( " ", 4 ); + delay_ms ( 75 ); + watch_display_string ( "rESET ", 4 ); + delay_ms ( 150 ); + } + watch_display_string ( "CLEAr ", 4 ); + delay_ms ( 1000 ); + state->resetCounter = 0; + for ( i = 0; i < XY_NUM_LEVELS; i++ ) state->bestTime[i] = 0; + state->lastSolveTime = 0; + } + } + state->mode = XY_MODE_WAITING_TO_START; + state->secretDestination = 0; + state->secretItem = 0; + } + } + break; // End case EVENT_TICK + case EVENT_LIGHT_LONG_UP: + case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_LONG_UP: + case EVENT_ALARM_BUTTON_UP: + case EVENT_LIGHT_BUTTON_DOWN: // Disable the backlight + case EVENT_ALARM_BUTTON_DOWN: +// state->tickCounter = 0; // Why? + break; + case EVENT_LOW_ENERGY_UPDATE: + if (state->writePending) { + write_to_xyzzy_EEPROM(state); + watch_storage_sync ( ); + state->writePending=false; + } + watch_display_string("SLEEP ", 4); + break; + default: + movement_default_loop_handler(event, settings); + break; + } // END event switch statement + + if ( lightDownLong ) { + if ( ( state->mode == XY_MODE_WAITING_TO_START ) || + ( state->mode == XY_MODE_TIME_DISPLAY ) ) { + // Advance to level select menu + state->mode = XY_MODE_LEVEL_SELECT; + state->levelSelected = XY_NUM_LEVELS; // Shows "SELECT" + sprintf ( buf, "%s", xyzzy_select_names[state->levelSelected] ); + watch_display_string ( buf, 4 ); + } else if ( state->mode == XY_MODE_PLAYING ) { + state->onMainPath = 1; + state->xyzzyCounter = 0; + displayLocation = true; + if ( ( state->loc != state->locXyzzy ) && state->xyzzyKnown ) { // Jump to xyzzy location + state->loc = state->locXyzzy; + } else { // Jump to entrance + state->loc = 0; + } + } else if ( state->mode == XY_MODE_CODE_ENTRY ) { // User is done entering code. + codeEntryCompleted = true; + } + } + + if ( lightUpShort ) { + if ( state->mode == XY_MODE_LEVEL_SELECT ) { + // User made a level selection + if ( state->levelSelected != XY_NUM_LEVELS ) { // Ignore when menu says 'Select' + // NEW CAVE - NEW GAME + xyzzy_gen_new_cave ( state ); + watch_display_string ( " NEW ", 4 ); + delay_ms ( 1000 ); + watch_display_string ( "CAvE ", 4 ); + delay_ms ( 1000 ); + watch_display_string ( " --- ", 4 ); + delay_ms (400); + // The game is starting + state->mode = XY_MODE_PLAYING; + displayLocation = true; + } + } else if ( state->mode == XY_MODE_PLAYING ) { + userPickedLeft = true; + } else if ( state->mode == XY_MODE_CODE_ENTRY ) { // Switch to next code value + state->userCodeIndex++; + if ( state->userCodeIndex >= state->totalCodesSeen ) state->userCodeIndex = 0; + xyzzy_display_code_entry ( state ); + } + } + + if ( alarmDownLong ) { + if ( state->mode == XY_MODE_PLAYING ) { + state->onMainPath = 1; + state->xyzzyCounter = 0; + displayLocation = true; + if ( state->loc != 0 ) { + // Jump to main entrance + state->loc = 0; + } else if ( state->xyzzyKnown ) { + // Already at entrance, jump to XYZZY + state->loc = state->locXyzzy; + } + } else if ( state->mode == XY_MODE_WAITING_TO_START ) { + // Start time display mode + state->mode = XY_MODE_TIME_DISPLAY; + state->tdLevel = 0; + state->tdSubLevel = 0; + state->tickCounter = 0; + } + } + + if ( alarmUpShort ) { + if ( state->mode == XY_MODE_PLAYING ) { + userPickedRight = true; + } else if ( state->mode == XY_MODE_LEVEL_SELECT ) { + state->levelSelected++; + if ( state->levelSelected >= XY_NUM_LEVELS ) state->levelSelected = 0; + sprintf ( buf, "%s", xyzzy_select_names[state->levelSelected] ); + watch_display_string ( buf, 4 ); + } else if ( state->mode == XY_MODE_CODE_ENTRY ) { // Increase current code value + state->userCodes[state->userCodeIndex]++; + if ( state->userCodes[state->userCodeIndex] >= XY_NUM_KEYWORDS ) state->userCodes[state->userCodeIndex] = 1; + xyzzy_display_code_entry ( state ); + } + } + + if ( userPickedLeft || userPickedRight ) { + state->writePending = true; + displayLocation = true; + // Did user pick 'correctly'? + correctPick = false; + if ( state->onMainPath ) { + if ( ( userPickedLeft && ( state->mainPath[state->loc] == 0 ) ) || + ( userPickedRight && ( state->mainPath[state->loc] == 1 ) ) ) { + correctPick = true; + } + } else { // On branches use relative index + branchInd = state->loc - state->locBranchPoint[state->brIndex] - 1; + if ( branchInd >= XY_MAX_BRANCH_LENGTH ) branchInd = 0; // Unnecessary safety ( I hope ) + if ( ( userPickedLeft && ( state->branch[state->brIndex][branchInd] == 0 ) ) || + ( userPickedRight && ( state->branch[state->brIndex][branchInd] == 1 ) ) ) { + correctPick = true; + } + } + + // Advance if correct pick + if ( correctPick ) { + state->loc++; + if ( ( state->loc >= state->locXyzzy ) && ( state->onMainPath ) ) state->xyzzyKnown = 1; + + // Check for: + // final room + // end of current branch ( dead end ) + // any threat + // any item + + bool bFinalRoom = false; + bool bDeadEnd = false; + bool bThreat = false; + bool bItem = false; + + if ( state->onMainPath ) { + if ( state->loc >= state->locMainPathEnd ) bFinalRoom = true; + // No need to check deadEnd + threatIndex = -1; + for ( i = 0; i < XY_MAX_THREATS; i++ ) { + if ( state->loc == state->locThreat[i] ) { + if ( state->brThreat[i] == XY_MAIN_PATH ) { + bThreat = true; + threatIndex = i; + } + } + } + // No need to check item on main branch ( yet ) + } else { + // No need to check finalRoom + // Check for dead end + if ( state->loc > ( state->locBranchEnd[state->brIndex] ) ) bDeadEnd = true; + threatIndex = -1; + for ( i = 0; i < XY_MAX_THREATS; i++ ) { + if ( ( state->loc == state->locThreat[i] ) && ( state->brIndex == state->brThreat[i] ) ) { + bThreat = true; + threatIndex = i; + } + } + itemIndex = -1; + for ( i = 0; i < XY_MAX_THREATS; i++ ) { // Item match? + if ( ( state->brIndex == state->brItem[i] ) && ( state->loc == state->locItem[i] ) ) { + // Found an item!! + itemIndex = i; + bItem = true; + } + } + } + if ( bFinalRoom ) { // Entering final room + watch_display_string ( " FINAL", 4 ); + delay_ms ( 1500 ); + watch_display_string ( " rooM ", 4 ); + delay_ms ( 1500 ); + if ( state->totalCodesSeen > 0 ) + { + state->mode = XY_MODE_CODE_ENTRY; + for ( i = 0; i < XY_MAX_CODES; i++ ) state->userCodes[i] = 0; + state->userCodeIndex = 0; + displayLocation = false; + xyzzy_display_code_entry ( state ); + } else { + codeEntryCompleted = true; + } + } + if ( bItem ) { + // Player discovered item! + state->inventory[itemIndex] = 1; + state->locItem[itemIndex] = 199; // Won't find a second time. + sprintf ( buf, "%s", xyzzy_item_names[state->ri[itemIndex]] ); + watch_display_string ( buf, 4 ); + delay_ms ( 800 ); + for ( i = 0; i < 6; i++ ) { + watch_clear_indicator ( state->ri[itemIndex] ); + delay_ms ( 100 ); + watch_set_indicator ( state->ri[itemIndex] ); + delay_ms ( 100 ); + } + delay_ms ( 800 ); + } + if ( bDeadEnd ) { + watch_display_string ( " dEAd ", 4 ); + delay_ms ( 1000 ); + watch_display_string ( " End ", 4 ); + delay_ms ( 1000 ); + state->onMainPath = 1; + state->xyzzyCounter = 0; + if ( ( state->xyzzyKnown ) && ( state->locBranchPoint[state->brIndex] > state->locXyzzy ) ) { + // Return to xyzzy + state->loc = state->locXyzzy; + } else { // Return to entrance + state->loc = 0; + } + } + if ( bThreat ) { + sprintf ( buf, "%s", xyzzy_threat_names[state->ri[threatIndex]] ); + watch_display_string ( buf, 4 ); + delay_ms ( 2000 ); + watch_display_string ( " ", 4 ); + delay_ms ( 500 ); + if ( state->inventory[threatIndex] ) { // OK - required item in inventory + sprintf ( buf, "%s", xyzzy_item_names[state->ri[threatIndex]] ); + watch_display_string ( buf, 4 ); + delay_ms ( 1000 ); + sprintf ( buf, "%s", xyzzy_action_names[state->ri[threatIndex]] ); + for ( i = 0; i < 4; i++ ) { + watch_display_string ( buf, 4 ); + delay_ms ( 500 ); + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + } + watch_display_string ( " dONE ", 4); + delay_ms ( 800 ); + state->inventory[threatIndex] = 0; + state->locThreat[threatIndex] = 199; // Removed from cave + } else { // Kicked back to entrance + state->loc = 0; + state->onMainPath = 1; + } + } + } else { // User picked 'incorrectly' + branchMatch = false; + codeMatch = false; + if ( state->onMainPath ) { + for ( i = 0; i < XY_MAX_BRANCHES; i++ ) { + if ( state->loc == state->locBranchPoint[i] ) { + branchMatch = true; + state->brIndex = i; + } + } + for ( i = 0; i < state->numCodes; i++ ) { + if ( ( state->loc == state->locCodes[i] ) && state->onMainPath ) { + codeMatch = true; + codeMatchIndex = i; + } + } + } + if ( branchMatch ) { + // Begin proceeding down other path + state->loc++; + state->onMainPath = false; + } else if ( codeMatch ) { + // Stay at this position, display code segment + if ( state->codesSeen[codeMatchIndex] == 0 ) { + state->totalCodesSeen++; + state->codesSeen[codeMatchIndex] = 1; + } + sprintf ( buf, "%s", xyzzy_keywords[state->codeValues[codeMatchIndex]] ); + watch_display_string ( buf, 4 ); + delay_ms ( 1000 ); + } else { // Incorrect move + // A quick blink if already at the entrance + if ( state->loc == 0 ) { + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + } + // Jump back to entrance, or xyzzy location if applicable + jumpToXyzzy = false; + if ( ( state->xyzzyKnown ) && ( state->xyzzyCounter < 6 ) ) { + if ( ( state->onMainPath ) && + ( state->loc > state->locXyzzy ) ) { + jumpToXyzzy = true; + } + if ( !state->onMainPath ) { + if ( state->locBranchPoint[state->brIndex] > state->locXyzzy ) { + jumpToXyzzy = true; + } + } + } + state->onMainPath = 1; + if ( jumpToXyzzy ) { + state->loc = state->locXyzzy; + state->xyzzyCounter++; + } else { + state->loc = 0; + state->xyzzyCounter = 0; + } + } + } + } // END If User picked left or right + + if ( codeEntryCompleted ) + { + // User is done entering code. Is it right? + // Only need to enter codes seen, don't care about order + for ( i = 0; i < XY_MAX_CODES; i++ ) codePending[i] = state->codesSeen[i]; + for ( i = 0; i < state->numCodes; i++ ) { + for ( j = 0; j < state->numCodes; j++ ) { + if ( state->userCodes[i] == state->codeValues[j] ) codePending[j] = 0; + } + } + // There should be no codes left pending + valOK = true; + for ( i = 0; i < state->numCodes; i++ ) if ( codePending[i] ) valOK = false; + if ( !valOK ) { // Nope, back to entrance. + state->mode = XY_MODE_PLAYING; + state->loc = 0; + watch_display_string ( " NO ", 2 ); + delay_ms ( 1000 ); + displayLocation = true; + } else { // They did it - winner! + nowSeconds = watch_utility_date_time_to_unix_time ( watch_rtc_get_date_time(), 0 ); + if ( state->totalCodesSeen > 0 ) { + watch_display_string ( " YES ", 2 ); + delay_ms ( 1100 ); + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + } + watch_display_string ( " 6AME ", 2 ); + delay_ms ( 1000 ); + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + watch_display_string ( "OvEr ", 4 ); + delay_ms ( 1000 ); + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + watch_clear_all_indicators ( ); + watch_display_string ( " TELE ", 4 ); + delay_ms ( 700 ); + watch_display_string ( " POrt ", 4 ); + delay_ms ( 700 ); + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + oldVal = 100; + for ( i = 0; i < 15; i++ ) { + do { + j = gen_random_int ( 0, XY_NUM_TELEPORT - 1 ); + } while ( j == oldVal ); + oldVal = j; + watch_display_string ( teleportStrings[j], 4 ); + delay_ms ( 100 ); + } + watch_display_string ( " ", 4 ); + delay_ms ( 350 ); + watch_display_string ( " dESt ", 4 ); + delay_ms ( 800 ); + watch_display_string ( " ", 4 ); + delay_ms ( 1500 ); + sprintf ( buf, "%s", xyzzy_countries[state->secretDestination] ); + watch_display_string ( buf, 4 ); + delay_ms ( 4000 ); + watch_display_string ( " ", 4 ); + delay_ms ( 300 ); + watch_display_string ( " YOU ", 4 ); + delay_ms ( 800 ); + watch_display_string ( " 6Et ", 4 ); + delay_ms ( 800 ); + watch_display_string ( " ", 4 ); + delay_ms ( 600 ); + sprintf ( buf, "%s", xyzzy_rewards[state->secretItem] ); + watch_display_string ( buf, 4 ); + delay_ms ( 2500 ); + watch_display_string ( " ", 4 ); + delay_ms ( 400 ); + deltaSeconds = nowSeconds - state->startTime; + state->lastSolveTime = deltaSeconds; + // Check for fastest run at this level + if ( ( deltaSeconds < state->bestTime[state->levelSelected] ) || ( state->bestTime[state->levelSelected]==0 ) ) { + // New fastest time for at this cave size + state->bestTime[state->levelSelected] = deltaSeconds; + watch_display_string ( " NEw ", 4 ); + delay_ms ( 700 ); + for ( i = 0; i < 5; i++ ) { + watch_display_string ( " ", 4 ); + delay_ms ( 200 ); + watch_display_string ( " bESt ", 4 ); + delay_ms ( 200 ); + } + watch_display_string ( " tIME ", 4 ); + delay_ms ( 700 ); + } + // Break this down in to days, hours, minutes and seconds + seconds = deltaSeconds % 60; + deltaSeconds = deltaSeconds / 60; + minutes = deltaSeconds % 60; + deltaSeconds = deltaSeconds / 60; + hours = deltaSeconds % 24; + days = deltaSeconds / 24; + if ( days > 0 ) { + sprintf ( buf, "%4ddy", days ); + watch_display_string ( buf, 4 ); + delay_ms ( 2500 ); + } + if ( ( hours > 0 ) || ( days > 0 ) ) { + sprintf ( buf, "%4dHr", hours ); + watch_display_string ( buf, 4 ); + delay_ms ( 2500 ); + } + sprintf ( buf, "%4dMI", minutes ); + watch_display_string ( buf, 4 ); + delay_ms ( 2500 ); + sprintf ( buf, "%4dSE", seconds ); + watch_display_string ( buf, 4 ); + delay_ms ( 2500 ); + state->mode = XY_MODE_WAITING_TO_START; + state->tickCounter = 0; + displayLocation = false; + } + } // ENDIF Code entry completed + + if ( ( displayLocation ) && ( state->mode == XY_MODE_PLAYING ) ) xyzzy_display_location ( state ); + + return true; +} + +void xyzzy_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + xyzzy_state_t *state = (xyzzy_state_t *)context; + if (state->writePending) { + write_to_xyzzy_EEPROM(state); + state->writePending=false; + } +} + diff --git a/xyzzy_face.h b/xyzzy_face.h new file mode 100644 index 000000000..9a964bdfa --- /dev/null +++ b/xyzzy_face.h @@ -0,0 +1,115 @@ +/* + * MIT License + * + * Copyright (c) 2025 Klingon Jane + * + * 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 thNe 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 XYZZY_FACE_H_ +#define XYZZY_FACE_H_ + +#include "movement.h" +#define XY_MAX_PATH_LENGTH 24 +#define XY_MAX_BRANCH_LENGTH 8 +#define XY_MAX_CODES 4 +#define XY_NUM_LEVELS 3 +#define XY_MAX_BRANCHES 3 +#define XY_MAX_THREATS 5 + +/* + +XYZZY Micro-adventure. + +A lightning-quick tribute to the original game. + +Full acknowledgements to Will Crowther 1976 and Don Woods 1977 for +"Colossal Cave Adventure", which started this genre of games. + +Tiny cave hint: The keywords always appear at locations 3 and 6. + +Thankfully; keywords only appear on the main path in all caves. + +*/ + + +typedef struct { + // An asterisk '*' means saved to EEPROM + uint8_t mode; // * 0 game over show country, 1 level select, 2 playing + uint8_t levelSelected; // * 0 = training, 1 = normal, 2 = select + uint8_t loc; // * Current progress into the cave + uint8_t mainPath[XY_MAX_PATH_LENGTH]; // * Main path defined by 0s and 1s + uint8_t locMainPathEnd; // * Final room - end of main path - end of game + uint8_t numCodes; // * Actual codes in use, up to XY_MAX_CODES + uint8_t locCodes[XY_MAX_CODES]; // * Code numbers on main path + uint8_t codeValues[XY_MAX_CODES]; // * Values from 0 thru N + uint8_t codesSeen[XY_MAX_CODES]; // Booleans + uint8_t totalCodesSeen; // How many codes has player seen? + uint8_t userCodes[XY_MAX_CODES]; // Codes enter by player + uint8_t userCodeIndex; // Code currently being entered + uint8_t locXyzzy; // * Return point short-cut when deeper in cave + uint8_t xyzzyKnown; // * Has user ever reached xyzzy location? + uint8_t xyzzyCounter; // Usually jump back to xyzzy + uint8_t locThreat[XY_MAX_THREATS]; // * Where is the threat? + uint8_t brThreat[XY_MAX_THREATS]; // * Which branch is threat on? + uint8_t inventory[XY_MAX_THREATS]; // * Do we have this item? + uint8_t locItem[XY_MAX_THREATS]; // * How deep is this item + uint8_t brItem[XY_MAX_THREATS]; // * Which branch is this item? + uint8_t ri[XY_MAX_THREATS]; // * Randomized index from 0 to n-1 for threat concealment + uint8_t locBranchPoint[XY_MAX_BRANCHES]; // * Where does branch occur? + uint8_t locBranchEnd[XY_MAX_BRANCHES]; // * Last valid place in this branch + uint8_t branch[XY_MAX_BRANCHES][XY_MAX_BRANCH_LENGTH]; // * Branch defined by 0s and 1s + uint8_t brIndex; // * Which branch you are on + bool onMainPath; // * You are on the main path ( not any branch ) + uint8_t shortcutCounter; // How many consec times we've jumped back to locXyzzy + uint16_t tickCounter; // For minimum delays and debounce + bool writePending; // Need to update EEPROM when resigning + uint8_t secretDestination; // * Your fixed secret country/planet destination + uint8_t secretItem; // * Your fixed item reward + uint32_t startTime; // * Seconds in UNIX time of cave generation + uint32_t lastSolveTime; // * Time of last solution + uint32_t bestTime[XY_NUM_LEVELS]; // * Best times ever + uint16_t days; // For display of solve times + uint8_t hours; // For display of solve times + uint8_t minutes; // For display of solve times + uint8_t seconds; // For display of solve times + uint8_t tdLevel; // For time display + uint8_t tdSubLevel; // For time display + bool alarmWasPressed; // Was the alarm button down previously? + bool lightWasPressed; // Was the light button down previously? + bool ignoreNextRelease; // Ignore release after long press + uint16_t buttonTimer; // How many ticks was button held down + uint8_t bothPressedTimer; // How many ticks both buttons held down + uint8_t resetCounter; // To clear all saved times +} xyzzy_state_t; + +void xyzzy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void xyzzy_face_activate(movement_settings_t *settings, void *context); +bool xyzzy_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void xyzzy_face_resign(movement_settings_t *settings, void *context); + +#define xyzzy_face ((const watch_face_t){ \ + xyzzy_face_setup, \ + xyzzy_face_activate, \ + xyzzy_face_loop, \ + xyzzy_face_resign, \ + NULL, \ +}) + +#endif // XYZZY_FACE_H_