Skip to content

Commit b9dbc4e

Browse files
Merge PR #398 - add simon game watch face
Adds a watch face that allows playing the classic Simon game with the watch's buzzer and RGB LED. Reviewed-by: Matheus Afonso Martins Moreira <matheus@matheusmoreira.com> GitHub-Pull-Request: #398
2 parents 543788b + 3eaf807 commit b9dbc4e

File tree

5 files changed

+462
-0
lines changed

5 files changed

+462
-0
lines changed

make.mk

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,20 @@ SRCS += \
215215

216216
endif
217217

218+
ifeq ($(LED), BLUE)
219+
CFLAGS += -DWATCH_IS_BLUE_BOARD
220+
endif
221+
222+
ifndef COLOR
223+
$(error Set the COLOR variable to RED, BLUE, or GREEN depending on what board you have.)
224+
endif
225+
226+
COLOR_VALID := $(filter $(COLOR),RED BLUE GREEN)
227+
228+
ifeq ($(COLOR_VALID),)
229+
$(error COLOR must be RED, BLUE, or GREEN)
230+
endif
231+
218232
ifeq ($(COLOR), BLUE)
219233
CFLAGS += -DWATCH_IS_BLUE_BOARD
220234
endif

movement/make/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ SRCS += \
136136
../watch_faces/complication/higher_lower_game_face.c \
137137
../watch_faces/clock/french_revolutionary_face.c \
138138
../watch_faces/clock/minimal_clock_face.c \
139+
../watch_faces/complication/simon_face.c \
139140
# New watch faces go above this line.
140141

141142
# Leave this line at the bottom of the file; it has all the targets for making your project.

movement/movement_faces.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
#include "higher_lower_game_face.h"
112112
#include "french_revolutionary_face.h"
113113
#include "minimal_clock_face.h"
114+
#include "simon_face.h"
114115
// New includes go above this line.
115116

116117
#endif // MOVEMENT_FACES_H_
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2024 <#author_name#>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
#include "simon_face.h"
26+
#include <stdio.h>
27+
#include <stdlib.h>
28+
#include <string.h>
29+
30+
// Emulator only: need time() to seed the random number generator
31+
#if __EMSCRIPTEN__
32+
#include <time.h>
33+
#endif
34+
35+
static char _simon_display_buf[12];
36+
static uint8_t _timer;
37+
static uint16_t _delay_beep;
38+
static uint16_t _timeout;
39+
static uint8_t _secSub;
40+
41+
static inline uint8_t _simon_get_rand_num(uint8_t num_values) {
42+
#if __EMSCRIPTEN__
43+
return rand() % num_values;
44+
#else
45+
return arc4random_uniform(num_values);
46+
#endif
47+
}
48+
49+
static void _simon_clear_display(simon_state_t *state) {
50+
if (state->playing_state == SIMON_NOT_PLAYING) {
51+
watch_display_string(" ", 0);
52+
} else {
53+
sprintf(_simon_display_buf, " %2d ", state->sequence_length);
54+
watch_display_string(_simon_display_buf, 0);
55+
}
56+
}
57+
58+
static void _simon_not_playing_display(simon_state_t *state) {
59+
_simon_clear_display(state);
60+
61+
sprintf(_simon_display_buf, "SI %d", state->best_score);
62+
if (!state->soundOff)
63+
watch_set_indicator(WATCH_INDICATOR_BELL);
64+
else
65+
watch_clear_indicator(WATCH_INDICATOR_BELL);
66+
if (!state->lightOff)
67+
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
68+
else
69+
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
70+
watch_display_string(_simon_display_buf, 0);
71+
switch (state->mode)
72+
{
73+
case SIMON_MODE_EASY:
74+
watch_display_string("E", 9);
75+
break;
76+
case SIMON_MODE_HARD:
77+
watch_display_string("H", 9);
78+
break;
79+
default:
80+
break;
81+
}
82+
}
83+
84+
static void _simon_reset(simon_state_t *state) {
85+
state->playing_state = SIMON_NOT_PLAYING;
86+
state->listen_index = 0;
87+
state->sequence_length = 0;
88+
_simon_not_playing_display(state);
89+
}
90+
91+
92+
static void _simon_display_note(SimonNote note, simon_state_t *state) {
93+
char *ndtemplate = NULL;
94+
95+
switch (note) {
96+
case SIMON_LED_NOTE:
97+
ndtemplate = "LI%2d ";
98+
break;
99+
case SIMON_ALARM_NOTE:
100+
ndtemplate = " %2d AL";
101+
break;
102+
case SIMON_MODE_NOTE:
103+
ndtemplate = " %2dDE ";
104+
break;
105+
case SIMON_WRONG_NOTE:
106+
ndtemplate = "OH NOOOOO";
107+
}
108+
109+
sprintf(_simon_display_buf, ndtemplate, state->sequence_length);
110+
watch_display_string(_simon_display_buf, 0);
111+
}
112+
113+
static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_rest) {
114+
_simon_display_note(note, state);
115+
switch (note) {
116+
case SIMON_LED_NOTE:
117+
if (!state->lightOff) watch_set_led_yellow();
118+
if (state->soundOff)
119+
delay_ms(_delay_beep);
120+
else
121+
watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep);
122+
break;
123+
case SIMON_MODE_NOTE:
124+
if (!state->lightOff) watch_set_led_red();
125+
if (state->soundOff)
126+
delay_ms(_delay_beep);
127+
else
128+
watch_buzzer_play_note(BUZZER_NOTE_E4, _delay_beep);
129+
break;
130+
case SIMON_ALARM_NOTE:
131+
if (!state->lightOff) watch_set_led_green();
132+
if (state->soundOff)
133+
delay_ms(_delay_beep);
134+
else
135+
watch_buzzer_play_note(BUZZER_NOTE_C3, _delay_beep);
136+
break;
137+
case SIMON_WRONG_NOTE:
138+
if (state->soundOff)
139+
delay_ms(800);
140+
else
141+
watch_buzzer_play_note(BUZZER_NOTE_A1, 800);
142+
break;
143+
}
144+
watch_set_led_off();
145+
146+
if (note != SIMON_WRONG_NOTE) {
147+
_simon_clear_display(state);
148+
if (!skip_rest) {
149+
watch_buzzer_play_note(BUZZER_NOTE_REST, (_delay_beep * 2)/3);
150+
}
151+
}
152+
}
153+
154+
155+
static void _simon_setup_next_note(simon_state_t *state) {
156+
if (state->sequence_length > state->best_score) {
157+
state->best_score = state->sequence_length;
158+
}
159+
160+
_simon_clear_display(state);
161+
state->playing_state = SIMON_TEACHING;
162+
state->sequence[state->sequence_length] = _simon_get_rand_num(3) + 1;
163+
state->sequence_length = state->sequence_length + 1;
164+
state->teaching_index = 0;
165+
state->listen_index = 0;
166+
}
167+
168+
static void _simon_listen(SimonNote note, simon_state_t *state) {
169+
if (state->sequence[state->listen_index] == note) {
170+
_simon_play_note(note, state, true);
171+
state->listen_index++;
172+
_timer = 0;
173+
174+
if (state->listen_index == state->sequence_length) {
175+
state->playing_state = SIMON_READY_FOR_NEXT_NOTE;
176+
}
177+
} else {
178+
_simon_play_note(SIMON_WRONG_NOTE, state, true);
179+
_simon_reset(state);
180+
}
181+
}
182+
183+
static void _simon_begin_listening(simon_state_t *state) {
184+
state->playing_state = SIMON_LISTENING_BACK;
185+
state->listen_index = 0;
186+
}
187+
188+
static void _simon_change_speed(simon_state_t *state){
189+
switch (state->mode)
190+
{
191+
case SIMON_MODE_HARD:
192+
_delay_beep = DELAY_FOR_TONE_MS / 2;
193+
_secSub = SIMON_FACE_FREQUENCY / 2;
194+
_timeout = (TIMER_MAX * SIMON_FACE_FREQUENCY) / 2;
195+
break;
196+
default:
197+
_delay_beep = DELAY_FOR_TONE_MS;
198+
_secSub = SIMON_FACE_FREQUENCY;
199+
_timeout = TIMER_MAX * SIMON_FACE_FREQUENCY;
200+
break;
201+
}
202+
}
203+
204+
void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
205+
void **context_ptr) {
206+
(void)settings;
207+
(void)watch_face_index;
208+
if (*context_ptr == NULL) {
209+
*context_ptr = malloc(sizeof(simon_state_t));
210+
memset(*context_ptr, 0, sizeof(simon_state_t));
211+
// Do any one-time tasks in here; the inside of this conditional happens
212+
// only at boot.
213+
}
214+
// Do any pin or peripheral setup here; this will be called whenever the watch
215+
// wakes from deep sleep.
216+
#if __EMSCRIPTEN__
217+
// simulator only: seed the randon number generator
218+
time_t t;
219+
srand((unsigned)time(&t));
220+
#endif
221+
}
222+
223+
void simon_face_activate(movement_settings_t *settings, void *context) {
224+
(void) settings;
225+
(void) context;
226+
simon_state_t *state = (simon_state_t *)context;
227+
_simon_change_speed(state);
228+
movement_request_tick_frequency(SIMON_FACE_FREQUENCY);
229+
_timer = 0;
230+
}
231+
232+
bool simon_face_loop(movement_event_t event, movement_settings_t *settings,
233+
void *context) {
234+
simon_state_t *state = (simon_state_t *)context;
235+
236+
switch (event.event_type) {
237+
case EVENT_ACTIVATE:
238+
// Show your initial UI here.
239+
_simon_reset(state);
240+
break;
241+
case EVENT_TICK:
242+
if (state->playing_state == SIMON_LISTENING_BACK && state->mode != SIMON_MODE_EASY)
243+
{
244+
_timer++;
245+
if(_timer >= (_timeout)){
246+
_timer = 0;
247+
_simon_play_note(SIMON_WRONG_NOTE, state, true);
248+
_simon_reset(state);
249+
}
250+
}
251+
else if (state->playing_state == SIMON_TEACHING && event.subsecond == 0) {
252+
SimonNote note = state->sequence[state->teaching_index];
253+
// if this is the final note in the sequence, don't play the rest to let
254+
// the player jump in faster
255+
_simon_play_note(note, state, state->teaching_index == (state->sequence_length - 1));
256+
state->teaching_index++;
257+
258+
if (state->teaching_index == state->sequence_length) {
259+
_simon_begin_listening(state);
260+
}
261+
}
262+
else if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE && (event.subsecond % _secSub) == 0) {
263+
_timer = 0;
264+
_simon_setup_next_note(state);
265+
}
266+
break;
267+
case EVENT_LIGHT_BUTTON_DOWN:
268+
break;
269+
case EVENT_LIGHT_LONG_PRESS:
270+
if (state->playing_state == SIMON_NOT_PLAYING) {
271+
state->lightOff = !state->lightOff;
272+
_simon_not_playing_display(state);
273+
}
274+
break;
275+
case EVENT_ALARM_LONG_PRESS:
276+
if (state->playing_state == SIMON_NOT_PLAYING) {
277+
state->soundOff = !state->soundOff;
278+
_simon_not_playing_display(state);
279+
if (!state->soundOff)
280+
watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep);
281+
}
282+
break;
283+
case EVENT_LIGHT_BUTTON_UP:
284+
if (state->playing_state == SIMON_NOT_PLAYING) {
285+
state->sequence_length = 0;
286+
watch_clear_indicator(WATCH_INDICATOR_BELL);
287+
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
288+
_simon_setup_next_note(state);
289+
} else if (state->playing_state == SIMON_LISTENING_BACK) {
290+
_simon_listen(SIMON_LED_NOTE, state);
291+
}
292+
break;
293+
case EVENT_MODE_LONG_PRESS:
294+
if (state->playing_state == SIMON_NOT_PLAYING) {
295+
movement_move_to_face(0);
296+
} else {
297+
state->playing_state = SIMON_NOT_PLAYING;
298+
_simon_reset(state);
299+
}
300+
break;
301+
case EVENT_MODE_BUTTON_UP:
302+
if (state->playing_state == SIMON_NOT_PLAYING) {
303+
movement_move_to_next_face();
304+
} else if (state->playing_state == SIMON_LISTENING_BACK) {
305+
_simon_listen(SIMON_MODE_NOTE, state);
306+
}
307+
break;
308+
case EVENT_ALARM_BUTTON_UP:
309+
if (state->playing_state == SIMON_LISTENING_BACK) {
310+
_simon_listen(SIMON_ALARM_NOTE, state);
311+
}
312+
else if (state->playing_state == SIMON_NOT_PLAYING){
313+
state->mode = (state->mode + 1) % SIMON_MODE_TOTAL;
314+
_simon_change_speed(state);
315+
_simon_not_playing_display(state);
316+
}
317+
break;
318+
case EVENT_TIMEOUT:
319+
movement_move_to_face(0);
320+
break;
321+
case EVENT_LOW_ENERGY_UPDATE:
322+
break;
323+
default:
324+
return movement_default_loop_handler(event, settings);
325+
}
326+
327+
return true;
328+
}
329+
330+
void simon_face_resign(movement_settings_t *settings, void *context) {
331+
(void)settings;
332+
(void)context;
333+
watch_set_led_off();
334+
watch_set_buzzer_off();
335+
}

0 commit comments

Comments
 (0)