From 9794f86430d8d824b30af3e7e348d9573d8cbb03 Mon Sep 17 00:00:00 2001 From: Matt Greer Date: Sun, 5 May 2024 09:47:47 -0400 Subject: [PATCH 1/2] simon_face: Simon game complication --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/simon_face.c | 247 ++++++++++++++++++ .../watch_faces/complication/simon_face.h | 97 +++++++ 4 files changed, 346 insertions(+) create mode 100644 movement/watch_faces/complication/simon_face.c create mode 100644 movement/watch_faces/complication/simon_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..09fdb33 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -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. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..72aae1b 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #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 index 0000000..99fc160 --- /dev/null +++ b/movement/watch_faces/complication/simon_face.c @@ -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 +#include +#include + +// Emulator only: need time() to seed the random number generator +#if __EMSCRIPTEN__ +#include +#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 index 0000000..b59d56c --- /dev/null +++ b/movement/watch_faces/complication/simon_face.h @@ -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_ From 3eaf807590539e6f182bac9204b46f5b6681fccc Mon Sep 17 00:00:00 2001 From: voloved <36523934+voloved@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:23:31 -0400 Subject: [PATCH 2/2] Added Timeout; Ability to turn off LED and Sound; Added doublespeed mode. (#1) * Check that color is valid Instead of merely checking that COLOR is set, check that it is one of RED, BLUE or GREEN * Added ability to turn off sound and timer with modes * Added enum for mode --------- Co-authored-by: Wesley Ellis --- make.mk | 6 + .../watch_faces/complication/simon_face.c | 110 ++++++++++++++++-- .../watch_faces/complication/simon_face.h | 14 +++ 3 files changed, 119 insertions(+), 11 deletions(-) diff --git a/make.mk b/make.mk index bb2d153..00933d0 100644 --- a/make.mk +++ b/make.mk @@ -213,6 +213,12 @@ ifndef COLOR $(error Set the COLOR variable to RED, BLUE, or GREEN depending on what board you have.) endif +COLOR_VALID := $(filter $(COLOR),RED BLUE GREEN) + +ifeq ($(COLOR_VALID),) +$(error COLOR must be RED, BLUE, or GREEN) +endif + ifeq ($(COLOR), BLUE) CFLAGS += -DWATCH_IS_BLUE_BOARD endif diff --git a/movement/watch_faces/complication/simon_face.c b/movement/watch_faces/complication/simon_face.c index 99fc160..eb08f17 100644 --- a/movement/watch_faces/complication/simon_face.c +++ b/movement/watch_faces/complication/simon_face.c @@ -33,6 +33,10 @@ #endif static char _simon_display_buf[12]; +static uint8_t _timer; +static uint16_t _delay_beep; +static uint16_t _timeout; +static uint8_t _secSub; static inline uint8_t _simon_get_rand_num(uint8_t num_values) { #if __EMSCRIPTEN__ @@ -55,7 +59,26 @@ static void _simon_not_playing_display(simon_state_t *state) { _simon_clear_display(state); sprintf(_simon_display_buf, "SI %d", state->best_score); + if (!state->soundOff) + watch_set_indicator(WATCH_INDICATOR_BELL); + else + watch_clear_indicator(WATCH_INDICATOR_BELL); + if (!state->lightOff) + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); watch_display_string(_simon_display_buf, 0); + switch (state->mode) + { + case SIMON_MODE_EASY: + watch_display_string("E", 9); + break; + case SIMON_MODE_HARD: + watch_display_string("H", 9); + break; + default: + break; + } } static void _simon_reset(simon_state_t *state) { @@ -91,19 +114,31 @@ static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_res _simon_display_note(note, state); switch (note) { case SIMON_LED_NOTE: - watch_set_led_yellow(); - watch_buzzer_play_note(BUZZER_NOTE_D3, 300); + if (!state->lightOff) watch_set_led_yellow(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep); break; case SIMON_MODE_NOTE: - watch_set_led_red(); - watch_buzzer_play_note(BUZZER_NOTE_E4, 300); + if (!state->lightOff) watch_set_led_red(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_E4, _delay_beep); break; case SIMON_ALARM_NOTE: - watch_set_led_green(); - watch_buzzer_play_note(BUZZER_NOTE_C3, 300); + if (!state->lightOff) watch_set_led_green(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, _delay_beep); break; case SIMON_WRONG_NOTE: - watch_buzzer_play_note(BUZZER_NOTE_A1, 800); + if (state->soundOff) + delay_ms(800); + else + watch_buzzer_play_note(BUZZER_NOTE_A1, 800); break; } watch_set_led_off(); @@ -111,7 +146,7 @@ static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_res if (note != SIMON_WRONG_NOTE) { _simon_clear_display(state); if (!skip_rest) { - watch_buzzer_play_note(BUZZER_NOTE_REST, 200); + watch_buzzer_play_note(BUZZER_NOTE_REST, (_delay_beep * 2)/3); } } } @@ -134,6 +169,7 @@ 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++; + _timer = 0; if (state->listen_index == state->sequence_length) { state->playing_state = SIMON_READY_FOR_NEXT_NOTE; @@ -149,6 +185,22 @@ static void _simon_begin_listening(simon_state_t *state) { state->listen_index = 0; } +static void _simon_change_speed(simon_state_t *state){ + switch (state->mode) + { + case SIMON_MODE_HARD: + _delay_beep = DELAY_FOR_TONE_MS / 2; + _secSub = SIMON_FACE_FREQUENCY / 2; + _timeout = (TIMER_MAX * SIMON_FACE_FREQUENCY) / 2; + break; + default: + _delay_beep = DELAY_FOR_TONE_MS; + _secSub = SIMON_FACE_FREQUENCY; + _timeout = TIMER_MAX * SIMON_FACE_FREQUENCY; + break; + } +} + void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { (void)settings; @@ -171,6 +223,10 @@ void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void simon_face_activate(movement_settings_t *settings, void *context) { (void) settings; (void) context; + simon_state_t *state = (simon_state_t *)context; + _simon_change_speed(state); + movement_request_tick_frequency(SIMON_FACE_FREQUENCY); + _timer = 0; } bool simon_face_loop(movement_event_t event, movement_settings_t *settings, @@ -183,9 +239,16 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, _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) { + if (state->playing_state == SIMON_LISTENING_BACK && state->mode != SIMON_MODE_EASY) + { + _timer++; + if(_timer >= (_timeout)){ + _timer = 0; + _simon_play_note(SIMON_WRONG_NOTE, state, true); + _simon_reset(state); + } + } + else if (state->playing_state == SIMON_TEACHING && event.subsecond == 0) { 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 @@ -196,12 +259,32 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, _simon_begin_listening(state); } } + else if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE && (event.subsecond % _secSub) == 0) { + _timer = 0; + _simon_setup_next_note(state); + } break; case EVENT_LIGHT_BUTTON_DOWN: break; + case EVENT_LIGHT_LONG_PRESS: + if (state->playing_state == SIMON_NOT_PLAYING) { + state->lightOff = !state->lightOff; + _simon_not_playing_display(state); + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state->playing_state == SIMON_NOT_PLAYING) { + state->soundOff = !state->soundOff; + _simon_not_playing_display(state); + if (!state->soundOff) + watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep); + } + break; case EVENT_LIGHT_BUTTON_UP: if (state->playing_state == SIMON_NOT_PLAYING) { state->sequence_length = 0; + watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); _simon_setup_next_note(state); } else if (state->playing_state == SIMON_LISTENING_BACK) { _simon_listen(SIMON_LED_NOTE, state); @@ -226,6 +309,11 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, if (state->playing_state == SIMON_LISTENING_BACK) { _simon_listen(SIMON_ALARM_NOTE, state); } + else if (state->playing_state == SIMON_NOT_PLAYING){ + state->mode = (state->mode + 1) % SIMON_MODE_TOTAL; + _simon_change_speed(state); + _simon_not_playing_display(state); + } break; case EVENT_TIMEOUT: movement_move_to_face(0); diff --git a/movement/watch_faces/complication/simon_face.h b/movement/watch_faces/complication/simon_face.h index b59d56c..44bc9f3 100644 --- a/movement/watch_faces/complication/simon_face.h +++ b/movement/watch_faces/complication/simon_face.h @@ -71,12 +71,22 @@ typedef enum SimonPlayingState { SIMON_READY_FOR_NEXT_NOTE } SimonPlayingState; +typedef enum SimonMode { + SIMON_MODE_NORMAL = 0, // 5 Second timeout if nothing is input + SIMON_MODE_EASY, // There is no timeout in this mode + SIMON_MODE_HARD, // The speed of the teaching is doubled and th etimeout is halved + SIMON_MODE_TOTAL +} SimonMode; + typedef struct { uint8_t best_score; SimonNote sequence[MAX_SEQUENCE]; uint8_t sequence_length; uint8_t teaching_index; uint8_t listen_index; + bool soundOff; + bool lightOff; + uint8_t mode:6; SimonPlayingState playing_state; } simon_state_t; @@ -94,4 +104,8 @@ void simon_face_resign(movement_settings_t *settings, void *context); NULL, \ }) +#define TIMER_MAX 5 +#define SIMON_FACE_FREQUENCY 8 +#define DELAY_FOR_TONE_MS 300 + #endif // SIMON_FACE_H_