+++ /dev/null
-/*\r
- * MIT License\r
- *\r
- * Copyright (c) 2023 Chris Ellis\r
- *\r
- * Permission is hereby granted, free of charge, to any person obtaining a copy\r
- * of this software and associated documentation files (the "Software"), to deal\r
- * in the Software without restriction, including without limitation the rights\r
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
- * copies of the Software, and to permit persons to whom the Software is\r
- * furnished to do so, subject to the following conditions:\r
- *\r
- * The above copyright notice and this permission notice shall be included in all\r
- * copies or substantial portions of the Software.\r
- *\r
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
- * SOFTWARE.\r
- */\r
-\r
-// Emulator only: need time() to seed the random number generator.\r
-#if __EMSCRIPTEN__\r
-#include <time.h>\r
-#endif\r
-\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include "higher_lower_game_face.h"\r
-#include "watch_private_display.h"\r
-\r
-#define TITLE_TEXT "Hi-Lo"\r
-#define GAME_BOARD_SIZE 6\r
-#define MAX_BOARDS 40\r
-#define GUESSES_PER_SCREEN 5\r
-#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN)\r
-#define STATUS_DISPLAY_START 0\r
-#define BOARD_SCORE_DISPLAY_START 2\r
-#define BOARD_DISPLAY_START 4\r
-#define BOARD_DISPLAY_END 9\r
-#define MIN_CARD_VALUE 2\r
-#define MAX_CARD_VALUE 14\r
-#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)\r
-#define CARD_SUIT_COUNT 4\r
-#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)\r
-#define FLIP_BOARD_DIRECTION false\r
-\r
-typedef struct card_t {\r
- uint8_t value;\r
- bool revealed;\r
-} card_t;\r
-\r
-typedef enum {\r
- A, B, C, D, E, F, G\r
-} segment_t;\r
-\r
-typedef enum {\r
- HL_GUESS_EQUAL,\r
- HL_GUESS_HIGHER,\r
- HL_GUESS_LOWER\r
-} guess_t;\r
-\r
-typedef enum {\r
- HL_GS_TITLE_SCREEN,\r
- HL_GS_GUESSING,\r
- HL_GS_WIN,\r
- HL_GS_LOSE,\r
- HL_GS_SHOW_SCORE,\r
-} game_state_t;\r
-\r
-static game_state_t game_state = HL_GS_TITLE_SCREEN;\r
-static card_t game_board[GAME_BOARD_SIZE] = {0};\r
-static uint8_t guess_position = 0;\r
-static uint8_t score = 0;\r
-static uint8_t completed_board_count = 0;\r
-static uint8_t deck[DECK_SIZE] = {0};\r
-static uint8_t current_card = 0;\r
-\r
-static uint8_t generate_random_number(uint8_t num_values) {\r
- // Emulator: use rand. Hardware: use arc4random.\r
-#if __EMSCRIPTEN__\r
- return rand() % num_values;\r
-#else\r
- return arc4random_uniform(num_values);\r
-#endif\r
-}\r
-\r
-static void stack_deck(void) {\r
- for (size_t i = 0; i < CARD_RANK_COUNT; i++) {\r
- for (size_t j = 0; j < CARD_SUIT_COUNT; j++)\r
- deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i;\r
- }\r
-}\r
-\r
-static void shuffle_deck(void) {\r
- // Randomize shuffle with Fisher Yates\r
- size_t i;\r
- size_t j;\r
- uint8_t tmp;\r
-\r
- for (i = DECK_SIZE - 1; i > 0; i--) {\r
- j = generate_random_number(0xFF) % (i + 1);\r
- tmp = deck[j];\r
- deck[j] = deck[i];\r
- deck[i] = tmp;\r
- }\r
-}\r
-\r
-static void reset_deck(void) {\r
- current_card = 0;\r
- stack_deck();\r
- shuffle_deck();\r
-}\r
-\r
-static uint8_t get_next_card(void) {\r
- if (current_card >= DECK_SIZE)\r
- reset_deck();\r
- return deck[current_card++];\r
-}\r
-\r
-static void reset_board(bool first_round) {\r
- // First card is random on the first board, and carried over from the last position on subsequent boards\r
- const uint8_t first_card_value = first_round\r
- ? get_next_card()\r
- : game_board[GAME_BOARD_SIZE - 1].value;\r
-\r
- game_board[0].value = first_card_value;\r
- game_board[0].revealed = true; // Always reveal first card\r
-\r
- // Fill remainder of board\r
- for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) {\r
- game_board[i] = (card_t) {\r
- .value = get_next_card(),\r
- .revealed = false\r
- };\r
- }\r
-}\r
-\r
-static void init_game(void) {\r
- watch_clear_display();\r
- watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
- watch_display_string("GA", STATUS_DISPLAY_START);\r
- reset_deck();\r
- reset_board(true);\r
- score = 0;\r
- completed_board_count = 0;\r
- guess_position = 1;\r
-}\r
-\r
-static void set_segment_at_position(segment_t segment, uint8_t position) {\r
- const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF;\r
- const uint8_t com_pin = position_segment_data >> 6;\r
- const uint8_t seg = position_segment_data & 0x3F;\r
- watch_set_pixel(com_pin, seg);\r
-}\r
-\r
-static void render_board_position(size_t board_position) {\r
- const size_t display_position = FLIP_BOARD_DIRECTION\r
- ? BOARD_DISPLAY_START + board_position\r
- : BOARD_DISPLAY_END - board_position;\r
- const bool revealed = game_board[board_position].revealed;\r
-\r
- //// Current position indicator spot\r
- //if (board_position == guess_position) {\r
- // watch_display_character('-', display_position);\r
- // return;\r
- //}\r
-\r
- if (!revealed) {\r
- // Higher or lower indicator (currently just an empty space)\r
- watch_display_character(' ', display_position);\r
- //set_segment_at_position(F, display_position);\r
- return;\r
- }\r
-\r
- const uint8_t value = game_board[board_position].value;\r
- switch (value) {\r
- case 14: // A (≡)\r
- watch_display_character(' ', display_position);\r
- set_segment_at_position(A, display_position);\r
- set_segment_at_position(D, display_position);\r
- set_segment_at_position(G, display_position);\r
- break;\r
- case 13: // K (=)\r
- watch_display_character(' ', display_position);\r
- set_segment_at_position(A, display_position);\r
- set_segment_at_position(D, display_position);\r
- break;\r
- case 12: // Q (-)\r
- watch_display_character('-', display_position);\r
- break;\r
- default: {\r
- const char display_char = (value - MIN_CARD_VALUE) + '0';\r
- watch_display_character(display_char, display_position);\r
- }\r
- }\r
-}\r
-\r
-static void render_board(void) {\r
- for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) {\r
- render_board_position(i);\r
- }\r
-}\r
-\r
-static void render_board_count(void) {\r
- // Render completed boards (screens)\r
- char buf[3] = {0};\r
- snprintf(buf, sizeof(buf), "%2hhu", completed_board_count);\r
- watch_display_string(buf, BOARD_SCORE_DISPLAY_START);\r
-}\r
-\r
-static void render_final_score(void) {\r
- watch_display_string("SC", STATUS_DISPLAY_START);\r
- char buf[7] = {0};\r
- const uint8_t complete_boards = score / GUESSES_PER_SCREEN;\r
- snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score);\r
- watch_set_colon();\r
- watch_display_string(buf, BOARD_DISPLAY_START);\r
-}\r
-\r
-static guess_t get_answer(void) {\r
- if (guess_position < 1 || guess_position > GAME_BOARD_SIZE)\r
- return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this.\r
-\r
- game_board[guess_position].revealed = true;\r
- const uint8_t previous_value = game_board[guess_position - 1].value;\r
- const uint8_t current_value = game_board[guess_position].value;\r
-\r
- if (current_value > previous_value)\r
- return HL_GUESS_HIGHER;\r
- else if (current_value < previous_value)\r
- return HL_GUESS_LOWER;\r
- else\r
- return HL_GUESS_EQUAL;\r
-}\r
-\r
-static void do_game_loop(guess_t user_guess) {\r
- switch (game_state) {\r
- case HL_GS_TITLE_SCREEN:\r
- init_game();\r
- render_board();\r
- render_board_count();\r
- game_state = HL_GS_GUESSING;\r
- break;\r
- case HL_GS_GUESSING: {\r
- const guess_t answer = get_answer();\r
-\r
- // Render answer indicator\r
- switch (answer) {\r
- case HL_GUESS_EQUAL:\r
- watch_display_string("==", STATUS_DISPLAY_START);\r
- break;\r
- case HL_GUESS_HIGHER:\r
- watch_display_string("HI", STATUS_DISPLAY_START);\r
- break;\r
- case HL_GUESS_LOWER:\r
- watch_display_string("LO", STATUS_DISPLAY_START);\r
- break;\r
- }\r
-\r
- // Scoring\r
- if (answer == user_guess) {\r
- score++;\r
- } else if (answer == HL_GUESS_EQUAL) {\r
- // No score for two consecutive identical cards\r
- } else {\r
- // Incorrect guess, game over\r
- watch_display_string("GO", STATUS_DISPLAY_START);\r
- game_board[guess_position].revealed = true;\r
- render_board_position(guess_position);\r
- game_state = HL_GS_LOSE;\r
- return;\r
- }\r
-\r
- if (score >= WIN_SCORE) {\r
- // Win, perhaps some kind of animation sequence?\r
- watch_display_string("WI", STATUS_DISPLAY_START);\r
- watch_display_string(" ", BOARD_SCORE_DISPLAY_START);\r
- watch_display_string("------", BOARD_DISPLAY_START);\r
- game_state = HL_GS_WIN;\r
- return;\r
- }\r
-\r
- // Next guess position\r
- const bool final_board_guess = guess_position == GAME_BOARD_SIZE - 1;\r
- if (final_board_guess) {\r
- // Seed new board\r
- completed_board_count++;\r
- render_board_count();\r
- guess_position = 1;\r
- reset_board(false);\r
- render_board();\r
- } else {\r
- guess_position++;\r
- render_board_position(guess_position - 1);\r
- render_board_position(guess_position);\r
- }\r
- break;\r
- }\r
- case HL_GS_WIN:\r
- case HL_GS_LOSE:\r
- // Show score screen on button press from either state\r
- watch_clear_display();\r
- render_final_score();\r
- game_state = HL_GS_SHOW_SCORE;\r
- break;\r
- case HL_GS_SHOW_SCORE:\r
- watch_clear_display();\r
- watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
- watch_display_string("GA", STATUS_DISPLAY_START);\r
- game_state = HL_GS_TITLE_SCREEN;\r
- break;\r
- default:\r
- watch_display_string("ERROR", BOARD_DISPLAY_START);\r
- break;\r
- }\r
-}\r
-\r
-static void light_button_handler(void) {\r
- do_game_loop(HL_GUESS_HIGHER);\r
-}\r
-\r
-static void alarm_button_handler(void) {\r
- do_game_loop(HL_GUESS_LOWER);\r
-}\r
-\r
-void higher_lower_game_face_setup(uint8_t watch_face_index, void **context_ptr) {\r
- (void) watch_face_index;\r
-\r
- if (*context_ptr == NULL) {\r
- *context_ptr = malloc(sizeof(higher_lower_game_face_state_t));\r
- memset(*context_ptr, 0, sizeof(higher_lower_game_face_state_t));\r
- // Do any one-time tasks in here; the inside of this conditional happens only at boot.\r
- memset(game_board, 0, sizeof(game_board));\r
- }\r
- // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.\r
-}\r
-\r
-void higher_lower_game_face_activate(void *context) {\r
- higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;\r
- (void) state;\r
- // Handle any tasks related to your watch face coming on screen.\r
- game_state = HL_GS_TITLE_SCREEN;\r
-}\r
-\r
-bool higher_lower_game_face_loop(movement_event_t event, void *context) {\r
- higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;\r
- (void) state;\r
-\r
- switch (event.event_type) {\r
- case EVENT_ACTIVATE:\r
- // Show your initial UI here.\r
- watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
- watch_display_string("GA", STATUS_DISPLAY_START);\r
- break;\r
- case EVENT_TICK:\r
- // If needed, update your display here.\r
- break;\r
- case EVENT_LIGHT_BUTTON_UP:\r
- light_button_handler();\r
- break;\r
- case EVENT_LIGHT_BUTTON_DOWN:\r
- // Don't trigger light\r
- break;\r
- case EVENT_ALARM_BUTTON_UP:\r
- alarm_button_handler();\r
- break;\r
- case EVENT_TIMEOUT:\r
- // Your watch face will receive this event after a period of inactivity. If it makes sense to resign,\r
- // you may uncomment this line to move back to the first watch face in the list:\r
- // movement_move_to_face(0);\r
- break;\r
- default:\r
- return movement_default_loop_handler(event);\r
- }\r
-\r
- // return true if the watch can enter standby mode. Generally speaking, you should always return true.\r
- // Exceptions:\r
- // * If you are displaying a color using the low-level watch_set_led_color function, you should return false.\r
- // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false.\r
- // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or\r
- // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions.\r
- return true;\r
-}\r
-\r
-void higher_lower_game_face_resign(void *context) {\r
- (void) context;\r
-\r
- // handle any cleanup before your watch face goes off-screen.\r
-}\r
+++ /dev/null
-/*\r
- * MIT License\r
- *\r
- * Copyright (c) 2023 Chris Ellis\r
- *\r
- * Permission is hereby granted, free of charge, to any person obtaining a copy\r
- * of this software and associated documentation files (the "Software"), to deal\r
- * in the Software without restriction, including without limitation the rights\r
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
- * copies of the Software, and to permit persons to whom the Software is\r
- * furnished to do so, subject to the following conditions:\r
- *\r
- * The above copyright notice and this permission notice shall be included in all\r
- * copies or substantial portions of the Software.\r
- *\r
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
- * SOFTWARE.\r
- */\r
-\r
-#ifndef HIGHER_LOWER_GAME_FACE_H_\r
-#define HIGHER_LOWER_GAME_FACE_H_\r
-\r
-#include "movement.h"\r
-\r
-/*\r
- * Higher-Lower game face\r
- * ======================\r
- *\r
- * A game face based on the "higher-lower" card game where the objective is to correctly guess if the next card will\r
- * be higher or lower than the last revealed cards.\r
- *\r
- * Game Flow:\r
- * - When the face is selected, the "Hi-Lo" "Title" screen will be displayed, and the status indicator will display "GA" for game\r
- * - Pressing `ALARM` or `LIGHT` will start the game and proceed to the "Guessing" screen\r
- * - The first card will be revealed and the player must now make a guess\r
- * - A player can guess `Higher` by pressing the `LIGHT` button, and `Lower` by pressing the `ALARM` button\r
- * - The status indicator will show the result of the guess: HI (Higher), LO (Lower), or == (Equal)\r
- * - There are five guesses to make on each game screen, once the end of the screen is reached, a new screen\r
- * will be started, with the last revealed card carried over\r
- * - The number of completed screens is displayed in the top right (see Scoring)\r
- * - If the player has guessed correctly, the score is updated and play continues (see Scoring)\r
- * - If the player has guessed incorrectly, the status will change to GO (Game Over)\r
- * - The current card will be revealed\r
- * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen\r
- * - If the game is won, the status indicator will display "WI" and the "Win" screen will be displayed\r
- * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen\r
- * - The status indicator will change to "SC" when the final score is displayed\r
- * - The number of completed game screens will be displayed on using the first two digits\r
- * - The number of correct guesses will be displayed using the final three digits\r
- * - E.g. "13: 063" represents 13 completed screens, with 63 correct guesses\r
- * - Pressing `ALARM` or `LIGHT` while on the "Score" screen will transition to back to the "Title" screen\r
- *\r
- * Scoring:\r
- * - If the player guesses correctly (HI/LO) a point is gained\r
- * - If the player guesses incorrectly the game ends\r
- * - Unless the revealed card is equal (==) to the last card, in which case play continues, but no point is gained\r
- * - If the player completes 40 screens full of cards, the game ends and a win screen is displayed\r
- *\r
- * Misc:\r
- * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at\r
- * the following mapping of card values to screen display, but am open to better suggestions:\r
- *\r
- * Thanks to voloved for adding deck shuffling and drawing!\r
- *\r
- * | Cards | |\r
- * |---------|--------------------------|\r
- * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|\r
- * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡|\r
- *\r
- * A previous alternative can be found in the git history:\r
- * | Cards | |\r
- * |---------|--------------------------|\r
- * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|\r
- * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H|\r
- *\r
- *\r
- * Future Ideas:\r
- * - Add sounds\r
- * - Save/Display high score\r
- * - Add a "Win" animation\r
- * - Consider using lap indicator for larger score limit\r
- */\r
-\r
-typedef struct {\r
- // Anything you need to keep track of, put it here!\r
-} higher_lower_game_face_state_t;\r
-\r
-void higher_lower_game_face_setup(uint8_t watch_face_index, void ** context_ptr);\r
-void higher_lower_game_face_activate(void *context);\r
-bool higher_lower_game_face_loop(movement_event_t event, void *context);\r
-void higher_lower_game_face_resign(void *context);\r
-\r
-#define higher_lower_game_face ((const watch_face_t){ \\r
- higher_lower_game_face_setup, \\r
- higher_lower_game_face_activate, \\r
- higher_lower_game_face_loop, \\r
- higher_lower_game_face_resign, \\r
- NULL, \\r
-})\r
-\r
-#endif // HIGHER_LOWER_GAME_FACE_H_\r
--- /dev/null
+/*\r
+ * MIT License\r
+ *\r
+ * Copyright (c) 2023 Chris Ellis\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a copy\r
+ * of this software and associated documentation files (the "Software"), to deal\r
+ * in the Software without restriction, including without limitation the rights\r
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+ * copies of the Software, and to permit persons to whom the Software is\r
+ * furnished to do so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in all\r
+ * copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+ * SOFTWARE.\r
+ */\r
+\r
+// Emulator only: need time() to seed the random number generator.\r
+#if __EMSCRIPTEN__\r
+#include <time.h>\r
+#endif\r
+\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include "higher_lower_game_face.h"\r
+#include "watch_private_display.h"\r
+\r
+#define TITLE_TEXT "Hi-Lo"\r
+#define GAME_BOARD_SIZE 6\r
+#define MAX_BOARDS 40\r
+#define GUESSES_PER_SCREEN 5\r
+#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN)\r
+#define STATUS_DISPLAY_START 0\r
+#define BOARD_SCORE_DISPLAY_START 2\r
+#define BOARD_DISPLAY_START 4\r
+#define BOARD_DISPLAY_END 9\r
+#define MIN_CARD_VALUE 2\r
+#define MAX_CARD_VALUE 14\r
+#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)\r
+#define CARD_SUIT_COUNT 4\r
+#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)\r
+#define FLIP_BOARD_DIRECTION false\r
+\r
+typedef struct card_t {\r
+ uint8_t value;\r
+ bool revealed;\r
+} card_t;\r
+\r
+typedef enum {\r
+ A, B, C, D, E, F, G\r
+} segment_t;\r
+\r
+typedef enum {\r
+ HL_GUESS_EQUAL,\r
+ HL_GUESS_HIGHER,\r
+ HL_GUESS_LOWER\r
+} guess_t;\r
+\r
+typedef enum {\r
+ HL_GS_TITLE_SCREEN,\r
+ HL_GS_GUESSING,\r
+ HL_GS_WIN,\r
+ HL_GS_LOSE,\r
+ HL_GS_SHOW_SCORE,\r
+} game_state_t;\r
+\r
+static game_state_t game_state = HL_GS_TITLE_SCREEN;\r
+static card_t game_board[GAME_BOARD_SIZE] = {0};\r
+static uint8_t guess_position = 0;\r
+static uint8_t score = 0;\r
+static uint8_t completed_board_count = 0;\r
+static uint8_t deck[DECK_SIZE] = {0};\r
+static uint8_t current_card = 0;\r
+\r
+static uint8_t generate_random_number(uint8_t num_values) {\r
+ // Emulator: use rand. Hardware: use arc4random.\r
+#if __EMSCRIPTEN__\r
+ return rand() % num_values;\r
+#else\r
+ return arc4random_uniform(num_values);\r
+#endif\r
+}\r
+\r
+static void stack_deck(void) {\r
+ for (size_t i = 0; i < CARD_RANK_COUNT; i++) {\r
+ for (size_t j = 0; j < CARD_SUIT_COUNT; j++)\r
+ deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i;\r
+ }\r
+}\r
+\r
+static void shuffle_deck(void) {\r
+ // Randomize shuffle with Fisher Yates\r
+ size_t i;\r
+ size_t j;\r
+ uint8_t tmp;\r
+\r
+ for (i = DECK_SIZE - 1; i > 0; i--) {\r
+ j = generate_random_number(0xFF) % (i + 1);\r
+ tmp = deck[j];\r
+ deck[j] = deck[i];\r
+ deck[i] = tmp;\r
+ }\r
+}\r
+\r
+static void reset_deck(void) {\r
+ current_card = 0;\r
+ stack_deck();\r
+ shuffle_deck();\r
+}\r
+\r
+static uint8_t get_next_card(void) {\r
+ if (current_card >= DECK_SIZE)\r
+ reset_deck();\r
+ return deck[current_card++];\r
+}\r
+\r
+static void reset_board(bool first_round) {\r
+ // First card is random on the first board, and carried over from the last position on subsequent boards\r
+ const uint8_t first_card_value = first_round\r
+ ? get_next_card()\r
+ : game_board[GAME_BOARD_SIZE - 1].value;\r
+\r
+ game_board[0].value = first_card_value;\r
+ game_board[0].revealed = true; // Always reveal first card\r
+\r
+ // Fill remainder of board\r
+ for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) {\r
+ game_board[i] = (card_t) {\r
+ .value = get_next_card(),\r
+ .revealed = false\r
+ };\r
+ }\r
+}\r
+\r
+static void init_game(void) {\r
+ watch_clear_display();\r
+ watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
+ watch_display_string("GA", STATUS_DISPLAY_START);\r
+ reset_deck();\r
+ reset_board(true);\r
+ score = 0;\r
+ completed_board_count = 0;\r
+ guess_position = 1;\r
+}\r
+\r
+static void set_segment_at_position(segment_t segment, uint8_t position) {\r
+ const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF;\r
+ const uint8_t com_pin = position_segment_data >> 6;\r
+ const uint8_t seg = position_segment_data & 0x3F;\r
+ watch_set_pixel(com_pin, seg);\r
+}\r
+\r
+static void render_board_position(size_t board_position) {\r
+ const size_t display_position = FLIP_BOARD_DIRECTION\r
+ ? BOARD_DISPLAY_START + board_position\r
+ : BOARD_DISPLAY_END - board_position;\r
+ const bool revealed = game_board[board_position].revealed;\r
+\r
+ //// Current position indicator spot\r
+ //if (board_position == guess_position) {\r
+ // watch_display_character('-', display_position);\r
+ // return;\r
+ //}\r
+\r
+ if (!revealed) {\r
+ // Higher or lower indicator (currently just an empty space)\r
+ watch_display_character(' ', display_position);\r
+ //set_segment_at_position(F, display_position);\r
+ return;\r
+ }\r
+\r
+ const uint8_t value = game_board[board_position].value;\r
+ switch (value) {\r
+ case 14: // A (≡)\r
+ watch_display_character(' ', display_position);\r
+ set_segment_at_position(A, display_position);\r
+ set_segment_at_position(D, display_position);\r
+ set_segment_at_position(G, display_position);\r
+ break;\r
+ case 13: // K (=)\r
+ watch_display_character(' ', display_position);\r
+ set_segment_at_position(A, display_position);\r
+ set_segment_at_position(D, display_position);\r
+ break;\r
+ case 12: // Q (-)\r
+ watch_display_character('-', display_position);\r
+ break;\r
+ default: {\r
+ const char display_char = (value - MIN_CARD_VALUE) + '0';\r
+ watch_display_character(display_char, display_position);\r
+ }\r
+ }\r
+}\r
+\r
+static void render_board(void) {\r
+ for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) {\r
+ render_board_position(i);\r
+ }\r
+}\r
+\r
+static void render_board_count(void) {\r
+ // Render completed boards (screens)\r
+ char buf[3] = {0};\r
+ snprintf(buf, sizeof(buf), "%2hhu", completed_board_count);\r
+ watch_display_string(buf, BOARD_SCORE_DISPLAY_START);\r
+}\r
+\r
+static void render_final_score(void) {\r
+ watch_display_string("SC", STATUS_DISPLAY_START);\r
+ char buf[7] = {0};\r
+ const uint8_t complete_boards = score / GUESSES_PER_SCREEN;\r
+ snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score);\r
+ watch_set_colon();\r
+ watch_display_string(buf, BOARD_DISPLAY_START);\r
+}\r
+\r
+static guess_t get_answer(void) {\r
+ if (guess_position < 1 || guess_position > GAME_BOARD_SIZE)\r
+ return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this.\r
+\r
+ game_board[guess_position].revealed = true;\r
+ const uint8_t previous_value = game_board[guess_position - 1].value;\r
+ const uint8_t current_value = game_board[guess_position].value;\r
+\r
+ if (current_value > previous_value)\r
+ return HL_GUESS_HIGHER;\r
+ else if (current_value < previous_value)\r
+ return HL_GUESS_LOWER;\r
+ else\r
+ return HL_GUESS_EQUAL;\r
+}\r
+\r
+static void do_game_loop(guess_t user_guess) {\r
+ switch (game_state) {\r
+ case HL_GS_TITLE_SCREEN:\r
+ init_game();\r
+ render_board();\r
+ render_board_count();\r
+ game_state = HL_GS_GUESSING;\r
+ break;\r
+ case HL_GS_GUESSING: {\r
+ const guess_t answer = get_answer();\r
+\r
+ // Render answer indicator\r
+ switch (answer) {\r
+ case HL_GUESS_EQUAL:\r
+ watch_display_string("==", STATUS_DISPLAY_START);\r
+ break;\r
+ case HL_GUESS_HIGHER:\r
+ watch_display_string("HI", STATUS_DISPLAY_START);\r
+ break;\r
+ case HL_GUESS_LOWER:\r
+ watch_display_string("LO", STATUS_DISPLAY_START);\r
+ break;\r
+ }\r
+\r
+ // Scoring\r
+ if (answer == user_guess) {\r
+ score++;\r
+ } else if (answer == HL_GUESS_EQUAL) {\r
+ // No score for two consecutive identical cards\r
+ } else {\r
+ // Incorrect guess, game over\r
+ watch_display_string("GO", STATUS_DISPLAY_START);\r
+ game_board[guess_position].revealed = true;\r
+ render_board_position(guess_position);\r
+ game_state = HL_GS_LOSE;\r
+ return;\r
+ }\r
+\r
+ if (score >= WIN_SCORE) {\r
+ // Win, perhaps some kind of animation sequence?\r
+ watch_display_string("WI", STATUS_DISPLAY_START);\r
+ watch_display_string(" ", BOARD_SCORE_DISPLAY_START);\r
+ watch_display_string("------", BOARD_DISPLAY_START);\r
+ game_state = HL_GS_WIN;\r
+ return;\r
+ }\r
+\r
+ // Next guess position\r
+ const bool final_board_guess = guess_position == GAME_BOARD_SIZE - 1;\r
+ if (final_board_guess) {\r
+ // Seed new board\r
+ completed_board_count++;\r
+ render_board_count();\r
+ guess_position = 1;\r
+ reset_board(false);\r
+ render_board();\r
+ } else {\r
+ guess_position++;\r
+ render_board_position(guess_position - 1);\r
+ render_board_position(guess_position);\r
+ }\r
+ break;\r
+ }\r
+ case HL_GS_WIN:\r
+ case HL_GS_LOSE:\r
+ // Show score screen on button press from either state\r
+ watch_clear_display();\r
+ render_final_score();\r
+ game_state = HL_GS_SHOW_SCORE;\r
+ break;\r
+ case HL_GS_SHOW_SCORE:\r
+ watch_clear_display();\r
+ watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
+ watch_display_string("GA", STATUS_DISPLAY_START);\r
+ game_state = HL_GS_TITLE_SCREEN;\r
+ break;\r
+ default:\r
+ watch_display_string("ERROR", BOARD_DISPLAY_START);\r
+ break;\r
+ }\r
+}\r
+\r
+static void light_button_handler(void) {\r
+ do_game_loop(HL_GUESS_HIGHER);\r
+}\r
+\r
+static void alarm_button_handler(void) {\r
+ do_game_loop(HL_GUESS_LOWER);\r
+}\r
+\r
+void higher_lower_game_face_setup(uint8_t watch_face_index, void **context_ptr) {\r
+ (void) watch_face_index;\r
+\r
+ if (*context_ptr == NULL) {\r
+ *context_ptr = malloc(sizeof(higher_lower_game_face_state_t));\r
+ memset(*context_ptr, 0, sizeof(higher_lower_game_face_state_t));\r
+ // Do any one-time tasks in here; the inside of this conditional happens only at boot.\r
+ memset(game_board, 0, sizeof(game_board));\r
+ }\r
+ // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.\r
+}\r
+\r
+void higher_lower_game_face_activate(void *context) {\r
+ higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;\r
+ (void) state;\r
+ // Handle any tasks related to your watch face coming on screen.\r
+ game_state = HL_GS_TITLE_SCREEN;\r
+}\r
+\r
+bool higher_lower_game_face_loop(movement_event_t event, void *context) {\r
+ higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;\r
+ (void) state;\r
+\r
+ switch (event.event_type) {\r
+ case EVENT_ACTIVATE:\r
+ // Show your initial UI here.\r
+ watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);\r
+ watch_display_string("GA", STATUS_DISPLAY_START);\r
+ break;\r
+ case EVENT_TICK:\r
+ // If needed, update your display here.\r
+ break;\r
+ case EVENT_LIGHT_BUTTON_UP:\r
+ light_button_handler();\r
+ break;\r
+ case EVENT_LIGHT_BUTTON_DOWN:\r
+ // Don't trigger light\r
+ break;\r
+ case EVENT_ALARM_BUTTON_UP:\r
+ alarm_button_handler();\r
+ break;\r
+ case EVENT_TIMEOUT:\r
+ // Your watch face will receive this event after a period of inactivity. If it makes sense to resign,\r
+ // you may uncomment this line to move back to the first watch face in the list:\r
+ // movement_move_to_face(0);\r
+ break;\r
+ default:\r
+ return movement_default_loop_handler(event);\r
+ }\r
+\r
+ // return true if the watch can enter standby mode. Generally speaking, you should always return true.\r
+ // Exceptions:\r
+ // * If you are displaying a color using the low-level watch_set_led_color function, you should return false.\r
+ // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false.\r
+ // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or\r
+ // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions.\r
+ return true;\r
+}\r
+\r
+void higher_lower_game_face_resign(void *context) {\r
+ (void) context;\r
+\r
+ // handle any cleanup before your watch face goes off-screen.\r
+}\r
--- /dev/null
+/*\r
+ * MIT License\r
+ *\r
+ * Copyright (c) 2023 Chris Ellis\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a copy\r
+ * of this software and associated documentation files (the "Software"), to deal\r
+ * in the Software without restriction, including without limitation the rights\r
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+ * copies of the Software, and to permit persons to whom the Software is\r
+ * furnished to do so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in all\r
+ * copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+ * SOFTWARE.\r
+ */\r
+\r
+#ifndef HIGHER_LOWER_GAME_FACE_H_\r
+#define HIGHER_LOWER_GAME_FACE_H_\r
+\r
+#include "movement.h"\r
+\r
+/*\r
+ * Higher-Lower game face\r
+ * ======================\r
+ *\r
+ * A game face based on the "higher-lower" card game where the objective is to correctly guess if the next card will\r
+ * be higher or lower than the last revealed cards.\r
+ *\r
+ * Game Flow:\r
+ * - When the face is selected, the "Hi-Lo" "Title" screen will be displayed, and the status indicator will display "GA" for game\r
+ * - Pressing `ALARM` or `LIGHT` will start the game and proceed to the "Guessing" screen\r
+ * - The first card will be revealed and the player must now make a guess\r
+ * - A player can guess `Higher` by pressing the `LIGHT` button, and `Lower` by pressing the `ALARM` button\r
+ * - The status indicator will show the result of the guess: HI (Higher), LO (Lower), or == (Equal)\r
+ * - There are five guesses to make on each game screen, once the end of the screen is reached, a new screen\r
+ * will be started, with the last revealed card carried over\r
+ * - The number of completed screens is displayed in the top right (see Scoring)\r
+ * - If the player has guessed correctly, the score is updated and play continues (see Scoring)\r
+ * - If the player has guessed incorrectly, the status will change to GO (Game Over)\r
+ * - The current card will be revealed\r
+ * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen\r
+ * - If the game is won, the status indicator will display "WI" and the "Win" screen will be displayed\r
+ * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen\r
+ * - The status indicator will change to "SC" when the final score is displayed\r
+ * - The number of completed game screens will be displayed on using the first two digits\r
+ * - The number of correct guesses will be displayed using the final three digits\r
+ * - E.g. "13: 063" represents 13 completed screens, with 63 correct guesses\r
+ * - Pressing `ALARM` or `LIGHT` while on the "Score" screen will transition to back to the "Title" screen\r
+ *\r
+ * Scoring:\r
+ * - If the player guesses correctly (HI/LO) a point is gained\r
+ * - If the player guesses incorrectly the game ends\r
+ * - Unless the revealed card is equal (==) to the last card, in which case play continues, but no point is gained\r
+ * - If the player completes 40 screens full of cards, the game ends and a win screen is displayed\r
+ *\r
+ * Misc:\r
+ * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at\r
+ * the following mapping of card values to screen display, but am open to better suggestions:\r
+ *\r
+ * Thanks to voloved for adding deck shuffling and drawing!\r
+ *\r
+ * | Cards | |\r
+ * |---------|--------------------------|\r
+ * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|\r
+ * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡|\r
+ *\r
+ * A previous alternative can be found in the git history:\r
+ * | Cards | |\r
+ * |---------|--------------------------|\r
+ * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|\r
+ * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H|\r
+ *\r
+ *\r
+ * Future Ideas:\r
+ * - Add sounds\r
+ * - Save/Display high score\r
+ * - Add a "Win" animation\r
+ * - Consider using lap indicator for larger score limit\r
+ */\r
+\r
+typedef struct {\r
+ // Anything you need to keep track of, put it here!\r
+} higher_lower_game_face_state_t;\r
+\r
+void higher_lower_game_face_setup(uint8_t watch_face_index, void ** context_ptr);\r
+void higher_lower_game_face_activate(void *context);\r
+bool higher_lower_game_face_loop(movement_event_t event, void *context);\r
+void higher_lower_game_face_resign(void *context);\r
+\r
+#define higher_lower_game_face ((const watch_face_t){ \\r
+ higher_lower_game_face_setup, \\r
+ higher_lower_game_face_activate, \\r
+ higher_lower_game_face_loop, \\r
+ higher_lower_game_face_resign, \\r
+ NULL, \\r
+})\r
+\r
+#endif // HIGHER_LOWER_GAME_FACE_H_\r