--- /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
+// TODO: Win animation?\r
+// TODO: Save highscore?\r
+// TODO: Add sounds?\r
+// Add sound option\r
+// TODO: Flip board direction?\r
+\r
+// Future Ideas:\r
+// - Use lap indicator for larger score improvement?\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 3 //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
+\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
+\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 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
+ ? generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE\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 = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE,\r
+ .value = i + MIN_CARD_VALUE,\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_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 = BOARD_DISPLAY_END - board_position;\r
+ const bool revealed = game_board[board_position].revealed;\r
+\r
+ //if (board_position == guess_position) {\r
+ // // Current spot\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('H', 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
+ set_segment_at_position(G, display_position);\r
+ break;\r
+ case 12: // Q (=)\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 11: // J (-)\r
+ watch_display_character('-', display_position);\r
+ break;\r
+ case 10: // 10 (0)\r
+ watch_display_character('0', display_position);\r
+ break;\r
+ default: {\r
+ const char display_char = value + '0';\r
+ watch_display_character(display_char, display_position);\r
+ }\r
+ }\r
+}\r
+\r
+static void render_board() {\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() {\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(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) {\r
+ (void) settings;\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(movement_settings_t *settings, void *context) {\r
+ (void) settings;\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, movement_settings_t *settings, 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, settings);\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(movement_settings_t *settings, void *context) {\r
+ (void) settings;\r
+ (void) context;\r
+\r
+ // handle any cleanup before your watch face goes off-screen.\r
+}\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
+ * A DESCRIPTION OF YOUR WATCH FACE\r
+ *\r
+ * and a description of how use it\r
+ *\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(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);\r
+void higher_lower_game_face_activate(movement_settings_t *settings, void *context);\r
+bool higher_lower_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context);\r
+void higher_lower_game_face_resign(movement_settings_t *settings, 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
+\r