]> git.earman.xyz Git - sensor-watch.git/commitdiff
simon_face: Simon game complication
authorMatt Greer <matt.e.greer@gmail.com>
Sun, 5 May 2024 13:47:47 +0000 (09:47 -0400)
committerMatt Greer <matt.e.greer@gmail.com>
Sun, 5 May 2024 17:43:33 +0000 (13:43 -0400)
movement/make/Makefile
movement/movement_faces.h
movement/watch_faces/complication/simon_face.c [new file with mode: 0644]
movement/watch_faces/complication/simon_face.h [new file with mode: 0644]

index da5486b0cf86b1a9b0afa28751b69fb4c5fa4bc7..09fdb3318b22fac77bdeaa9d7493bf366eed8a6e 100644 (file)
@@ -129,6 +129,7 @@ SRCS += \
   ../watch_faces/clock/minute_repeater_decimal_face.c \
   ../watch_faces/complication/tuning_tones_face.c \
   ../watch_faces/complication/kitchen_conversions_face.c \
+  ../watch_faces/complication/simon_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.
index 35571109af9ae1a891e4d2044ea39d68ae1dd73c..72aae1b11cf2e357385c703ba8e1791d16664c48 100644 (file)
 #include "minute_repeater_decimal_face.h"
 #include "tuning_tones_face.h"
 #include "kitchen_conversions_face.h"
+#include "simon_face.h"
 // New includes go above this line.
 
 #endif // MOVEMENT_FACES_H_
diff --git a/movement/watch_faces/complication/simon_face.c b/movement/watch_faces/complication/simon_face.c
new file mode 100644 (file)
index 0000000..99fc160
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 <#author_name#>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "simon_face.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Emulator only: need time() to seed the random number generator
+#if __EMSCRIPTEN__
+#include <time.h>
+#endif
+
+static char _simon_display_buf[12];
+
+static inline uint8_t _simon_get_rand_num(uint8_t num_values) {
+#if __EMSCRIPTEN__
+    return rand() % num_values;
+#else
+    return arc4random_uniform(num_values);
+#endif
+}
+
+static void _simon_clear_display(simon_state_t *state) {
+    if (state->playing_state == SIMON_NOT_PLAYING) {
+        watch_display_string("          ", 0);
+    } else {
+        sprintf(_simon_display_buf, "  %2d      ", state->sequence_length);
+        watch_display_string(_simon_display_buf, 0);
+    }
+}
+
+static void _simon_not_playing_display(simon_state_t *state) {
+    _simon_clear_display(state);
+
+    sprintf(_simon_display_buf, "SI  %d", state->best_score);
+    watch_display_string(_simon_display_buf, 0);
+}
+
+static void _simon_reset(simon_state_t *state) {
+    state->playing_state = SIMON_NOT_PLAYING;
+    state->listen_index = 0;
+    state->sequence_length = 0;
+    _simon_not_playing_display(state);
+}
+
+
+static void _simon_display_note(SimonNote note, simon_state_t *state) {
+    char *ndtemplate = NULL;
+
+    switch (note) {
+        case SIMON_LED_NOTE:
+            ndtemplate = "LI%2d      ";
+            break;
+        case SIMON_ALARM_NOTE:
+            ndtemplate = "  %2d    AL";
+            break;
+        case SIMON_MODE_NOTE:
+            ndtemplate = "  %2dDE    ";
+            break;
+        case SIMON_WRONG_NOTE:
+            ndtemplate = "OH  NOOOOO";
+    }
+
+    sprintf(_simon_display_buf, ndtemplate, state->sequence_length);
+    watch_display_string(_simon_display_buf, 0);
+}
+
+static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_rest) {
+    _simon_display_note(note, state);
+    switch (note) {
+        case SIMON_LED_NOTE:
+            watch_set_led_yellow();
+            watch_buzzer_play_note(BUZZER_NOTE_D3, 300);
+            break;
+        case SIMON_MODE_NOTE:
+            watch_set_led_red();
+            watch_buzzer_play_note(BUZZER_NOTE_E4, 300);
+            break;
+        case SIMON_ALARM_NOTE:
+            watch_set_led_green();
+            watch_buzzer_play_note(BUZZER_NOTE_C3, 300);
+            break;
+        case SIMON_WRONG_NOTE:
+            watch_buzzer_play_note(BUZZER_NOTE_A1, 800);
+            break;
+    }
+    watch_set_led_off();
+
+    if (note != SIMON_WRONG_NOTE) {
+        _simon_clear_display(state);
+        if (!skip_rest) {
+            watch_buzzer_play_note(BUZZER_NOTE_REST, 200);
+        }
+    }
+}
+
+
+static void _simon_setup_next_note(simon_state_t *state) {
+    if (state->sequence_length > state->best_score) {
+        state->best_score = state->sequence_length;
+    }
+
+    _simon_clear_display(state);
+    state->playing_state = SIMON_TEACHING;
+    state->sequence[state->sequence_length] = _simon_get_rand_num(3) + 1;
+    state->sequence_length = state->sequence_length + 1;
+    state->teaching_index = 0;
+    state->listen_index = 0;
+}
+
+static void _simon_listen(SimonNote note, simon_state_t *state) {
+    if (state->sequence[state->listen_index] == note) {
+        _simon_play_note(note, state, true);
+        state->listen_index++;
+
+        if (state->listen_index == state->sequence_length) {
+            state->playing_state = SIMON_READY_FOR_NEXT_NOTE;
+        }
+    } else {
+        _simon_play_note(SIMON_WRONG_NOTE, state, true);
+        _simon_reset(state);
+    }
+}
+
+static void _simon_begin_listening(simon_state_t *state) {
+    state->playing_state = SIMON_LISTENING_BACK;
+    state->listen_index = 0;
+}
+
+void simon_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(simon_state_t));
+        memset(*context_ptr, 0, sizeof(simon_state_t));
+        // Do any one-time tasks in here; the inside of this conditional happens
+        // only at boot.
+    }
+    // Do any pin or peripheral setup here; this will be called whenever the watch
+    // wakes from deep sleep.
+#if __EMSCRIPTEN__
+    // simulator only: seed the randon number generator
+    time_t t;
+    srand((unsigned)time(&t));
+#endif
+}
+
+void simon_face_activate(movement_settings_t *settings, void *context) {
+  (void) settings;
+  (void) context;
+}
+
+bool simon_face_loop(movement_event_t event, movement_settings_t *settings,
+        void *context) {
+    simon_state_t *state = (simon_state_t *)context;
+
+    switch (event.event_type) {
+        case EVENT_ACTIVATE:
+            // Show your initial UI here.
+            _simon_reset(state);
+            break;
+        case EVENT_TICK:
+            if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE) {
+                _simon_setup_next_note(state);
+            } else if (state->playing_state == SIMON_TEACHING) {
+                SimonNote note = state->sequence[state->teaching_index];
+                // if this is the final note in the sequence, don't play the rest to let
+                // the player jump in faster
+                _simon_play_note(note, state, state->teaching_index == (state->sequence_length - 1));
+                state->teaching_index++;
+
+                if (state->teaching_index == state->sequence_length) {
+                    _simon_begin_listening(state);
+                }
+            }
+            break;
+        case EVENT_LIGHT_BUTTON_DOWN:
+            break;
+        case EVENT_LIGHT_BUTTON_UP:
+            if (state->playing_state == SIMON_NOT_PLAYING) {
+                state->sequence_length = 0;
+                _simon_setup_next_note(state);
+            } else if (state->playing_state == SIMON_LISTENING_BACK) {
+                _simon_listen(SIMON_LED_NOTE, state);
+            }
+            break;
+        case EVENT_MODE_LONG_PRESS:
+            if (state->playing_state == SIMON_NOT_PLAYING) {
+                movement_move_to_face(0);
+            } else {
+                state->playing_state = SIMON_NOT_PLAYING;
+                _simon_reset(state);
+            }
+            break;
+        case EVENT_MODE_BUTTON_UP:
+            if (state->playing_state == SIMON_NOT_PLAYING) {
+                movement_move_to_next_face();
+            } else if (state->playing_state == SIMON_LISTENING_BACK) {
+                _simon_listen(SIMON_MODE_NOTE, state);
+            }
+            break;
+        case EVENT_ALARM_BUTTON_UP:
+            if (state->playing_state == SIMON_LISTENING_BACK) {
+                _simon_listen(SIMON_ALARM_NOTE, state);
+            }
+            break;
+        case EVENT_TIMEOUT:
+            movement_move_to_face(0);
+            break;
+        case EVENT_LOW_ENERGY_UPDATE:
+            break;
+        default:
+            return movement_default_loop_handler(event, settings);
+    }
+
+    return true;
+}
+
+void simon_face_resign(movement_settings_t *settings, void *context) {
+    (void)settings;
+    (void)context;
+    watch_set_led_off();
+    watch_set_buzzer_off();
+}
diff --git a/movement/watch_faces/complication/simon_face.h b/movement/watch_faces/complication/simon_face.h
new file mode 100644 (file)
index 0000000..b59d56c
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 <#author_name#>
+ *
+ * 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 SIMON_FACE_H_
+#define SIMON_FACE_H_
+
+#include "movement.h"
+
+/*
+ * simon_face
+ * -----------
+ * The classic electronic game, Simon, reduced to be played on a Sensor-Watch
+ *
+ * How to play:
+ *
+ * When first arriving at the face, it will show your best score.
+ *
+ * Press the light button to start the game.
+ *
+ * A sequence will be played, starting with length 1. The sequence can be
+ * made up of tones corresponding to any of the three buttons.
+ *
+ * light button: "LI" will display at the top of the screen, the LED will be yellow, and a high D will play
+ * mode button: "DE" will display at the left of the screen, the LED will be red, and a high E will play
+ * alarm button: "AL" will display on the right of the screen, the LED will be green, and a high C will play
+ *
+ * Once the sequence has finished, press the same buttons to recreate the sequence.
+ *
+ * If correct, the sequence will get one tone longer and play again. See how long of a sequence you can get.
+ *
+ * If you recreate the sequence incorrectly, a low note will play with "OH NOOOOO" displayed and the game is over.
+ * Press light to play again.
+ *
+ * Once playing, long press the mode button when it is your turn to exit the game early.
+ */
+
+#define MAX_SEQUENCE 99
+
+typedef enum SimonNote {
+    SIMON_LED_NOTE = 1,
+    SIMON_MODE_NOTE,
+    SIMON_ALARM_NOTE,
+    SIMON_WRONG_NOTE
+} SimonNote;
+
+typedef enum SimonPlayingState {
+    SIMON_NOT_PLAYING = 0,
+    SIMON_TEACHING,
+    SIMON_LISTENING_BACK,
+    SIMON_READY_FOR_NEXT_NOTE
+} SimonPlayingState;
+
+typedef struct {
+    uint8_t best_score;
+    SimonNote sequence[MAX_SEQUENCE];
+    uint8_t sequence_length;
+    uint8_t teaching_index;
+    uint8_t listen_index;
+    SimonPlayingState playing_state;
+} simon_state_t;
+
+void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr);
+void simon_face_activate(movement_settings_t *settings, void *context);
+bool simon_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void simon_face_resign(movement_settings_t *settings, void *context);
+
+#define simon_face                                                             \
+    ((const watch_face_t){                                                       \
+     simon_face_setup,                                                        \
+     simon_face_activate,                                                     \
+     simon_face_loop,                                                         \
+     simon_face_resign,                                                       \
+     NULL,                                                                    \
+     })
+
+#endif // SIMON_FACE_H_