From a831ed33364edb9b94f0ce43365a3956fa45c903 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 25 Jun 2023 20:43:01 +0100 Subject: [PATCH 1/6] Hi-lo: Initial game face commit --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/higher_lower_game_face.c | 371 ++++++++++++++++++ .../complication/higher_lower_game_face.h | 55 +++ 4 files changed, 428 insertions(+) create mode 100755 movement/watch_faces/complication/higher_lower_game_face.c create mode 100755 movement/watch_faces/complication/higher_lower_game_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 8b056a02..c761c8fc 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -116,6 +116,7 @@ SRCS += \ ../watch_faces/complication/geomancy_face.c \ ../watch_faces/clock/simple_clock_bin_led_face.c \ ../watch_faces/complication/flashlight_face.c \ + ../watch_faces/complication/higher_lower_game_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index bf63732e..4b510da4 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -93,6 +93,7 @@ #include "dual_timer_face.h" #include "simple_clock_bin_led_face.h" #include "flashlight_face.h" +#include "higher_lower_game_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c new file mode 100755 index 00000000..3c35bc25 --- /dev/null +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -0,0 +1,371 @@ +/* + * MIT License + * + * Copyright (c) 2023 Chris Ellis + * + * 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. + */ + +// TODO: Win animation? +// TODO: Save highscore? +// TODO: Add sounds? +// Add sound option +// TODO: Flip board direction? + +// Future Ideas: +// - Use lap indicator for larger score improvement? + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include "higher_lower_game_face.h" +#include "watch_private_display.h" + +#define TITLE_TEXT "Hi-Lo" +#define GAME_BOARD_SIZE 6 +#define MAX_BOARDS 3 //40 +#define GUESSES_PER_SCREEN 5 +#define WIN_SCORE MAX_BOARDS * GUESSES_PER_SCREEN +#define STATUS_DISPLAY_START 0 +#define BOARD_SCORE_DISPLAY_START 2 +#define BOARD_DISPLAY_START 4 +#define BOARD_DISPLAY_END 9 +#define MIN_CARD_VALUE 2 +#define MAX_CARD_VALUE 14 + +typedef struct card_t { + uint8_t value; + bool revealed; +} card_t; + +typedef enum { + A, B, C, D, E, F, G +} segment_t; + +typedef enum { + HL_GUESS_EQUAL, + HL_GUESS_HIGHER, + HL_GUESS_LOWER +} guess_t; + +typedef enum { + HL_GS_TITLE_SCREEN, + HL_GS_GUESSING, + HL_GS_WIN, + HL_GS_LOSE, + HL_GS_SHOW_SCORE, +} game_state_t; + +static game_state_t game_state = HL_GS_TITLE_SCREEN; +static card_t game_board[GAME_BOARD_SIZE] = {0}; +static uint8_t guess_position = 0; +static uint8_t score = 0; +static uint8_t completed_board_count = 0; + +static uint8_t generate_random_number(uint8_t num_values) { + // Emulator: use rand. Hardware: use arc4random. +#if __EMSCRIPTEN__ + return rand() % num_values; +#else + return arc4random_uniform(num_values); +#endif +} + +static void reset_board(bool first_round) { + // First card is random on the first board, and carried over from the last position on subsequent boards + const uint8_t first_card_value = first_round + ? generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE + : game_board[GAME_BOARD_SIZE - 1].value; + + game_board[0].value = first_card_value; + game_board[0].revealed = true; // Always reveal first card + + // Fill remainder of board + for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { + game_board[i] = (card_t) { + //.value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, + .value = i + MIN_CARD_VALUE, + .revealed = false + }; + } +} + +static void init_game(void) { + watch_clear_display(); + watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); + watch_display_string("GA", STATUS_DISPLAY_START); + reset_board(true); + score = 0; + completed_board_count = 0; + guess_position = 1; +} + +static void set_segment_at_position(segment_t segment, uint8_t position) { + const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF; + const uint8_t com_pin = position_segment_data >> 6; + const uint8_t seg = position_segment_data & 0x3F; + watch_set_pixel(com_pin, seg); +} + +static void render_board_position(size_t board_position) { + const size_t display_position = BOARD_DISPLAY_END - board_position; + const bool revealed = game_board[board_position].revealed; + + //if (board_position == guess_position) { + // // Current spot + // watch_display_character('-', display_position); + // return; + //} + + if (!revealed) { + // Higher or lower indicator (currently just an empty space) + watch_display_character(' ', display_position); + //set_segment_at_position(F, display_position); + return; + } + + const uint8_t value = game_board[board_position].value; + switch (value) { + case 14: // A + watch_display_character('H', display_position); + break; + case 13: // K (≡) + watch_display_character(' ', display_position); + set_segment_at_position(A, display_position); + set_segment_at_position(D, display_position); + set_segment_at_position(G, display_position); + break; + case 12: // Q (=) + watch_display_character(' ', display_position); + set_segment_at_position(A, display_position); + set_segment_at_position(D, display_position); + break; + case 11: // J (-) + watch_display_character('-', display_position); + break; + case 10: // 10 (0) + watch_display_character('0', display_position); + break; + default: { + const char display_char = value + '0'; + watch_display_character(display_char, display_position); + } + } +} + +static void render_board() { + for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) { + render_board_position(i); + } +} + +static void render_board_count(void) { + // Render completed boards (screens) + char buf[3] = {0}; + snprintf(buf, sizeof(buf), "%2hhu", completed_board_count); + watch_display_string(buf, BOARD_SCORE_DISPLAY_START); +} + +static void render_final_score(void) { + watch_display_string("SC", STATUS_DISPLAY_START); + char buf[7] = {0}; + const uint8_t complete_boards = score / GUESSES_PER_SCREEN; + snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score); + watch_set_colon(); + watch_display_string(buf, BOARD_DISPLAY_START); +} + +static guess_t get_answer() { + if (guess_position < 1 || guess_position > GAME_BOARD_SIZE) + return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this. + + game_board[guess_position].revealed = true; + const uint8_t previous_value = game_board[guess_position - 1].value; + const uint8_t current_value = game_board[guess_position].value; + + if (current_value > previous_value) + return HL_GUESS_HIGHER; + else if (current_value < previous_value) + return HL_GUESS_LOWER; + else + return HL_GUESS_EQUAL; +} + +static void do_game_loop(guess_t user_guess) { + switch (game_state) { + case HL_GS_TITLE_SCREEN: + init_game(); + render_board(); + render_board_count(); + game_state = HL_GS_GUESSING; + break; + case HL_GS_GUESSING: { + const guess_t answer = get_answer(); + + // Render answer indicator + switch (answer) { + case HL_GUESS_EQUAL: + watch_display_string("==", STATUS_DISPLAY_START); + break; + case HL_GUESS_HIGHER: + watch_display_string("HI", STATUS_DISPLAY_START); + break; + case HL_GUESS_LOWER: + watch_display_string("LO", STATUS_DISPLAY_START); + break; + } + + // Scoring + if (answer == user_guess) { + score++; + } else if (answer == HL_GUESS_EQUAL) { + // No score for two consecutive identical cards + } else { + // Incorrect guess, game over + watch_display_string("GO", STATUS_DISPLAY_START); + game_board[guess_position].revealed = true; + render_board_position(guess_position); + game_state = HL_GS_LOSE; + return; + } + + if (score >= WIN_SCORE) { + // Win, perhaps some kind of animation sequence? + watch_display_string("WI", STATUS_DISPLAY_START); + watch_display_string(" ", BOARD_SCORE_DISPLAY_START); + watch_display_string("------", BOARD_DISPLAY_START); + game_state = HL_GS_WIN; + return; + } + + // Next guess position + const bool final_board_guess = guess_position == GAME_BOARD_SIZE - 1; + if (final_board_guess) { + // Seed new board + completed_board_count++; + render_board_count(); + guess_position = 1; + reset_board(false); + render_board(); + } else { + guess_position++; + render_board_position(guess_position - 1); + render_board_position(guess_position); + } + break; + } + case HL_GS_WIN: + case HL_GS_LOSE: + // Show score screen on button press from either state + watch_clear_display(); + render_final_score(); + game_state = HL_GS_SHOW_SCORE; + break; + case HL_GS_SHOW_SCORE: + watch_clear_display(); + watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); + watch_display_string("GA", STATUS_DISPLAY_START); + game_state = HL_GS_TITLE_SCREEN; + break; + default: + watch_display_string("ERROR", BOARD_DISPLAY_START); + break; + } +} + +static void light_button_handler(void) { + do_game_loop(HL_GUESS_HIGHER); +} + +static void alarm_button_handler(void) { + do_game_loop(HL_GUESS_LOWER); +} + +void higher_lower_game_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(higher_lower_game_face_state_t)); + memset(*context_ptr, 0, sizeof(higher_lower_game_face_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + memset(game_board, 0, sizeof(game_board)); + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void higher_lower_game_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context; + (void) state; + // Handle any tasks related to your watch face coming on screen. + game_state = HL_GS_TITLE_SCREEN; +} + +bool higher_lower_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context; + (void) state; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); + watch_display_string("GA", STATUS_DISPLAY_START); + break; + case EVENT_TICK: + // If needed, update your display here. + break; + case EVENT_LIGHT_BUTTON_UP: + light_button_handler(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + // Don't trigger light + break; + case EVENT_ALARM_BUTTON_UP: + alarm_button_handler(); + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, + // you may uncomment this line to move back to the first watch face in the list: + // movement_move_to_face(0); + break; + default: + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void higher_lower_game_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h new file mode 100755 index 00000000..1cef05dc --- /dev/null +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2023 Chris Ellis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef HIGHER_LOWER_GAME_FACE_H_ +#define HIGHER_LOWER_GAME_FACE_H_ + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +typedef struct { + // Anything you need to keep track of, put it here! +} higher_lower_game_face_state_t; + +void higher_lower_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void higher_lower_game_face_activate(movement_settings_t *settings, void *context); +bool higher_lower_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void higher_lower_game_face_resign(movement_settings_t *settings, void *context); + +#define higher_lower_game_face ((const watch_face_t){ \ + higher_lower_game_face_setup, \ + higher_lower_game_face_activate, \ + higher_lower_game_face_loop, \ + higher_lower_game_face_resign, \ + NULL, \ +}) + +#endif // HIGHER_LOWER_GAME_FACE_H_ + From 739ad64cc10d8889c0564e75cf3faa378726d3ba Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 2 Jul 2023 11:40:11 +0100 Subject: [PATCH 2/6] Hi-lo: Allow flipped board rendering option --- movement/watch_faces/complication/higher_lower_game_face.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index 3c35bc25..1edcaa02 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -52,6 +52,7 @@ #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE 14 +#define FLIP_BOARD_DIRECTION false typedef struct card_t { uint8_t value; @@ -128,11 +129,13 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { } static void render_board_position(size_t board_position) { - const size_t display_position = BOARD_DISPLAY_END - board_position; + size_t display_position = FLIP_BOARD_DIRECTION + ? BOARD_DISPLAY_START + board_position + : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; + //// Current position indicator spot //if (board_position == guess_position) { - // // Current spot // watch_display_character('-', display_position); // return; //} From b147ac0c672f95a04a72fd1254b4e686d47e43f0 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Jul 2023 15:52:54 +0100 Subject: [PATCH 3/6] Hi-lo: Update face description - Remove test code --- .../complication/higher_lower_game_face.c | 17 ++---- .../complication/higher_lower_game_face.h | 54 +++++++++++++++++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index 1edcaa02..aed6eeef 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -22,15 +22,6 @@ * SOFTWARE. */ -// TODO: Win animation? -// TODO: Save highscore? -// TODO: Add sounds? -// Add sound option -// TODO: Flip board direction? - -// Future Ideas: -// - Use lap indicator for larger score improvement? - // Emulator only: need time() to seed the random number generator. #if __EMSCRIPTEN__ #include @@ -43,7 +34,7 @@ #define TITLE_TEXT "Hi-Lo" #define GAME_BOARD_SIZE 6 -#define MAX_BOARDS 3 //40 +#define MAX_BOARDS 40 #define GUESSES_PER_SCREEN 5 #define WIN_SCORE MAX_BOARDS * GUESSES_PER_SCREEN #define STATUS_DISPLAY_START 0 @@ -104,8 +95,7 @@ static void reset_board(bool first_round) { // Fill remainder of board for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { game_board[i] = (card_t) { - //.value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, - .value = i + MIN_CARD_VALUE, + .value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, .revealed = false }; } @@ -129,7 +119,7 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { } static void render_board_position(size_t board_position) { - size_t display_position = FLIP_BOARD_DIRECTION + const size_t display_position = FLIP_BOARD_DIRECTION ? BOARD_DISPLAY_START + board_position : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; @@ -371,4 +361,3 @@ void higher_lower_game_face_resign(movement_settings_t *settings, void *context) // handle any cleanup before your watch face goes off-screen. } - diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index 1cef05dc..d0936808 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -28,10 +28,59 @@ #include "movement.h" /* - * A DESCRIPTION OF YOUR WATCH FACE + * Higher-Lower game face + * ====================== * - * and a description of how use it + * A game face based on the "higher-lower" card game where the objective is to correctly guess if the next card will + * be higher or lower than the last revealed cards. * + * Game Flow: + * - When the face is selected, the "Hi-Lo" "Title" screen will be displayed, and the status indicator will display "GA" for game + * - Pressing `ALARM` or `LIGHT` will start the game and proceed to the "Guessing" screen + * - The first card will be revealed and the player must now make a guess + * - A player can guess `Higher` by pressing the `LIGHT` button, and `Lower` by pressing the `ALARM` button + * - The status indicator will show the result of the guess: HI (Higher), LO (Lower), or == (Equal) + * - There are five guesses to make on each game screen, once the end of the screen is reached, a new screen + * will be started, with the last revealed card carried over + * - The number of completed screens is displayed in the top right (see Scoring) + * - If the player has guessed correctly, the score is updated and play continues (see Scoring) + * - If the player has guessed incorrectly, the status will change to GO (Game Over) + * - The current card will be revealed + * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen + * - If the game is won, the status indicator will display "WI" and the "Win" screen will be displayed + * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen + * - The status indicator will change to "SC" when the final score is displayed + * - The number of completed game screens will be displayed on using the first two digits + * - The number of correct guesses will be displayed using the final three digits + * - E.g. "13: 063" represents 13 completed screens, with 63 correct guesses + * - Pressing `ALARM` or `LIGHT` while on the "Score" screen will transition to back to the "Title" screen + * + * Scoring: + * - If the player guesses correctly (HI/LO) a point is gained + * - If the player guesses incorrectly the game ends + * - Unless the revealed card is equal (==) to the last card, in which case play continues, but no point is gained + * - If the player completes 40 screens full of cards, the game ends and a win screen is displayed + * + * Misc: + * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at + * the following mapping of card values to screen display, but am open to better suggestions: + * + * | Cards | | + * |---------|--------------------------| + * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| + * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H| + * + * The following may more legible choice: + * | Cards | | + * |---------|--------------------------| + * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| + * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| + * + * Future Ideas: + * - Add sounds + * - Save/Display high score + * - Add a "Win" animation + * - Consider using lap indicator for larger score limit */ typedef struct { @@ -52,4 +101,3 @@ void higher_lower_game_face_resign(movement_settings_t *settings, void *context) }) #endif // HIGHER_LOWER_GAME_FACE_H_ - From 93b9ca634168ba2c5bf1c8e16f11f58b51b4e4bf Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 8 Aug 2024 22:45:07 -0400 Subject: [PATCH 4/6] Made cards go through a deck format. --- .../complication/higher_lower_game_face.c | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index cf5205cd..bbf456bf 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -43,6 +43,8 @@ #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE 14 +#define DUPLICATES_OF_CARD 4 +#define DECK_COUNT (DUPLICATES_OF_CARD * (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)) #define FLIP_BOARD_DIRECTION false typedef struct card_t { @@ -73,6 +75,8 @@ static card_t game_board[GAME_BOARD_SIZE] = {0}; static uint8_t guess_position = 0; static uint8_t score = 0; static uint8_t completed_board_count = 0; +static uint8_t _deck[DECK_COUNT]; +static uint8_t _curr_card; static uint8_t generate_random_number(uint8_t num_values) { // Emulator: use rand. Hardware: use arc4random. @@ -83,10 +87,41 @@ static uint8_t generate_random_number(uint8_t num_values) { #endif } +static void stack_deck(uint8_t *array) { + const uint8_t unique_cards = MAX_CARD_VALUE - MIN_CARD_VALUE + 1; + for (uint8_t i = 0; i < unique_cards; i++) + { + for (uint8_t j = 0; j < DUPLICATES_OF_CARD; j++) + array[(i * DUPLICATES_OF_CARD) + j] = MIN_CARD_VALUE + i; + } +} + +static void shuffle_deck(uint8_t *array, uint8_t n) { + // Randomize shuffle with Fisher Yates + uint8_t i, j, tmp; + for (i = n - 1; i > 0; i--) { + j = generate_random_number(0xFF) % (i + 1); + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + } +} + +static void reset_deck(void) { + _curr_card = 0; + stack_deck(_deck); + shuffle_deck(_deck, DECK_COUNT); +} + +static uint8_t get_next_card(void) { + if (_curr_card >= DECK_COUNT) reset_deck(); + return _deck[_curr_card++]; +} + static void reset_board(bool first_round) { // First card is random on the first board, and carried over from the last position on subsequent boards const uint8_t first_card_value = first_round - ? generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE + ? get_next_card() : game_board[GAME_BOARD_SIZE - 1].value; game_board[0].value = first_card_value; @@ -95,7 +130,7 @@ static void reset_board(bool first_round) { // Fill remainder of board for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { game_board[i] = (card_t) { - .value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, + .value = get_next_card(), .revealed = false }; } @@ -105,6 +140,7 @@ static void init_game(void) { watch_clear_display(); watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); watch_display_string("GA", STATUS_DISPLAY_START); + reset_deck(); reset_board(true); score = 0; completed_board_count = 0; From 6db0a62bbf6e6de686ea8879f503671064d1840a Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 29 Jul 2023 17:44:54 +0100 Subject: [PATCH 5/6] Hi-lo: Use alternate card faces --- .../complication/higher_lower_game_face.c | 14 ++++---------- .../complication/higher_lower_game_face.h | 7 ++++--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index aed6eeef..cf5205cd 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -139,28 +139,22 @@ static void render_board_position(size_t board_position) { const uint8_t value = game_board[board_position].value; switch (value) { - case 14: // A - watch_display_character('H', display_position); - break; - case 13: // K (≡) + case 14: // A (≡) watch_display_character(' ', display_position); set_segment_at_position(A, display_position); set_segment_at_position(D, display_position); set_segment_at_position(G, display_position); break; - case 12: // Q (=) + case 13: // K (=) watch_display_character(' ', display_position); set_segment_at_position(A, display_position); set_segment_at_position(D, display_position); break; - case 11: // J (-) + case 12: // Q (-) watch_display_character('-', display_position); break; - case 10: // 10 (0) - watch_display_character('0', display_position); - break; default: { - const char display_char = value + '0'; + const char display_char = (value - MIN_CARD_VALUE) + '0'; watch_display_character(display_char, display_position); } } diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index d0936808..efbd394b 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -68,13 +68,14 @@ * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| - * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H| + * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| * - * The following may more legible choice: + * A previous alternative can be found in the git history: * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| - * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| + * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H| + * * * Future Ideas: * - Add sounds From 9e1e692511011e9bb105dbab0c2d5426dd7cbb94 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 1 Sep 2024 19:30:31 +0100 Subject: [PATCH 6/6] Hi-lo: Additional code tweaks --- .../complication/higher_lower_game_face.c | 59 ++++++++++--------- .../complication/higher_lower_game_face.h | 2 + 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index bbf456bf..f9dbcd09 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -36,15 +36,16 @@ #define GAME_BOARD_SIZE 6 #define MAX_BOARDS 40 #define GUESSES_PER_SCREEN 5 -#define WIN_SCORE MAX_BOARDS * GUESSES_PER_SCREEN +#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN) #define STATUS_DISPLAY_START 0 #define BOARD_SCORE_DISPLAY_START 2 #define BOARD_DISPLAY_START 4 #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE 14 -#define DUPLICATES_OF_CARD 4 -#define DECK_COUNT (DUPLICATES_OF_CARD * (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)) +#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1) +#define CARD_SUIT_COUNT 4 +#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT) #define FLIP_BOARD_DIRECTION false typedef struct card_t { @@ -75,8 +76,8 @@ static card_t game_board[GAME_BOARD_SIZE] = {0}; static uint8_t guess_position = 0; static uint8_t score = 0; static uint8_t completed_board_count = 0; -static uint8_t _deck[DECK_COUNT]; -static uint8_t _curr_card; +static uint8_t deck[DECK_SIZE] = {0}; +static uint8_t current_card = 0; static uint8_t generate_random_number(uint8_t num_values) { // Emulator: use rand. Hardware: use arc4random. @@ -87,35 +88,37 @@ static uint8_t generate_random_number(uint8_t num_values) { #endif } -static void stack_deck(uint8_t *array) { - const uint8_t unique_cards = MAX_CARD_VALUE - MIN_CARD_VALUE + 1; - for (uint8_t i = 0; i < unique_cards; i++) - { - for (uint8_t j = 0; j < DUPLICATES_OF_CARD; j++) - array[(i * DUPLICATES_OF_CARD) + j] = MIN_CARD_VALUE + i; +static void stack_deck(void) { + for (size_t i = 0; i < CARD_RANK_COUNT; i++) { + for (size_t j = 0; j < CARD_SUIT_COUNT; j++) + deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i; } } -static void shuffle_deck(uint8_t *array, uint8_t n) { +static void shuffle_deck(void) { // Randomize shuffle with Fisher Yates - uint8_t i, j, tmp; - for (i = n - 1; i > 0; i--) { - j = generate_random_number(0xFF) % (i + 1); - tmp = array[j]; - array[j] = array[i]; - array[i] = tmp; - } + size_t i; + size_t j; + uint8_t tmp; + + for (i = DECK_SIZE - 1; i > 0; i--) { + j = generate_random_number(0xFF) % (i + 1); + tmp = deck[j]; + deck[j] = deck[i]; + deck[i] = tmp; + } } static void reset_deck(void) { - _curr_card = 0; - stack_deck(_deck); - shuffle_deck(_deck, DECK_COUNT); + current_card = 0; + stack_deck(); + shuffle_deck(); } static uint8_t get_next_card(void) { - if (_curr_card >= DECK_COUNT) reset_deck(); - return _deck[_curr_card++]; + if (current_card >= DECK_SIZE) + reset_deck(); + return deck[current_card++]; } static void reset_board(bool first_round) { @@ -156,8 +159,8 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { static void render_board_position(size_t board_position) { const size_t display_position = FLIP_BOARD_DIRECTION - ? BOARD_DISPLAY_START + board_position - : BOARD_DISPLAY_END - board_position; + ? BOARD_DISPLAY_START + board_position + : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; //// Current position indicator spot @@ -196,7 +199,7 @@ static void render_board_position(size_t board_position) { } } -static void render_board() { +static void render_board(void) { for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) { render_board_position(i); } @@ -218,7 +221,7 @@ static void render_final_score(void) { watch_display_string(buf, BOARD_DISPLAY_START); } -static guess_t get_answer() { +static guess_t get_answer(void) { if (guess_position < 1 || guess_position > GAME_BOARD_SIZE) return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this. diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index efbd394b..13da5869 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -65,6 +65,8 @@ * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at * the following mapping of card values to screen display, but am open to better suggestions: * + * Thanks to voloved for adding deck shuffling and drawing! + * * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|