simon_says.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <storage/storage.h>
  4. #include <gui/gui.h>
  5. #include <gui/elements.h>
  6. #include <gui/icon.h>
  7. #include <input/input.h>
  8. #include <notification/notification.h>
  9. #include <notification/notification_messages.h>
  10. #include <stdbool.h> // Header-file for boolean data-type.
  11. #include <stdio.h>
  12. #include <string.h>
  13. /* generated by fbt from .png files in images folder */
  14. #include <simon_says_icons.h>
  15. #include <assets_icons.h>
  16. #define TAG "Simon" // Used for logging
  17. #define DEBUG_MSG 1
  18. #define SCREEN_XRES 128
  19. #define SCREEN_YRES 64
  20. #define BOARD_X 72 // Used for board placement
  21. #define BOARD_Y 8
  22. #define GAME_START_LIVES 3
  23. #define SAVING_DIRECTORY STORAGE_APP_DATA_PATH_PREFIX
  24. #define SAVING_FILENAME SAVING_DIRECTORY "/game_simon_says.save"
  25. // Define Notes
  26. // Shamelessly stolen from Ocarina application
  27. // https://github.com/invalidna-me/flipperzero-ocarina
  28. #define NOTE_UP 587.33f
  29. #define NOTE_LEFT 493.88f
  30. #define NOTE_RIGHT 440.00f
  31. #define NOTE_DOWN 349.23
  32. #define NOTE_OK 293.66f
  33. /* ============================ Data structures ============================= */
  34. typedef enum game_state { preloading, mainMenu, inGame, gameOver, gameVictory } game_state;
  35. typedef enum difficulty_mode { normal, hard } difficulty_mode;
  36. typedef enum shape_names { up, down, left, right, number_of_shapes } Direction;
  37. typedef enum currently_playing { simon, player } currently_playing;
  38. typedef struct {
  39. /* Game state. */
  40. enum game_state gameState; // This is the current game state
  41. bool gameover; /* if true then switch to the game over state */
  42. bool is_wrong_direction; /* Is the last direction wrong? */
  43. enum currently_playing activePlayer; // This is used to track who is playing at the moment
  44. uint32_t lives; /* Number of lives in the current game. */
  45. enum difficulty_mode difficultyMode; // This is the difficulty mode for the current game
  46. bool sound_enabled; // This is the sound enabled flag for the current game
  47. float volume; // This is the volume for the current game
  48. /* Handle Score */
  49. int currentScore; // This is the score for the current
  50. int highScore; /* Highscore. Shown on Game Over Screen */
  51. bool is_new_highscore; /* Is the last score a new highscore? */
  52. /* Handle Shape Display */
  53. uint32_t numberOfMillisecondsBeforeShapeDisappears; // This defines the speed of the game
  54. enum shape_names simonMoves[1000]; // Store the sequence of shapes that Simon plays
  55. enum shape_names selectedShape; // This is used to track the shape that the player has selected
  56. bool set_board_neutral; // This is used to track if the board should be neutral or not
  57. int moveIndex; // This is used to track the current move in the sequence
  58. uint32_t last_button_press_tick;
  59. NotificationApp* notification;
  60. FuriMutex* mutex;
  61. } SimonData;
  62. /* ============================== Sequences ============================== */
  63. const NotificationSequence sequence_wrong_move = {
  64. &message_red_255,
  65. &message_vibro_on,
  66. // &message_note_g5, // Play sound but currently disabled
  67. &message_delay_25,
  68. // &message_note_e5,
  69. &message_vibro_off,
  70. &message_sound_off,
  71. NULL,
  72. };
  73. const NotificationSequence sequence_player_submit_move = {
  74. &message_vibro_on,
  75. // &message_note_g5, // Play sound but currently disabled. Need On/Off menu setting
  76. &message_delay_10,
  77. &message_delay_1,
  78. &message_delay_1,
  79. &message_delay_1,
  80. &message_delay_1,
  81. &message_delay_1,
  82. // &message_note_e5,
  83. &message_vibro_off,
  84. &message_sound_off,
  85. NULL,
  86. };
  87. const NotificationSequence sequence_up = {
  88. // &message_vibro_on,
  89. &message_note_g4,
  90. &message_delay_100,
  91. // &message_vibro_off,
  92. &message_sound_off,
  93. NULL,
  94. };
  95. const NotificationSequence sequence_down = {
  96. // &message_vibro_on,
  97. &message_note_c3,
  98. &message_delay_100,
  99. // &message_vibro_off,
  100. &message_sound_off,
  101. NULL,
  102. };
  103. const NotificationSequence sequence_left = {
  104. // &message_vibro_on,
  105. &message_note_e3,
  106. &message_delay_100,
  107. // &message_vibro_off,
  108. &message_sound_off,
  109. NULL,
  110. };
  111. const NotificationSequence sequence_right = {
  112. // &message_vibro_on,
  113. &message_note_g3,
  114. &message_delay_100,
  115. // &message_vibro_off,
  116. &message_sound_off,
  117. NULL,
  118. };
  119. // Indicate that it's Simon's turn
  120. const NotificationSequence sequence_simon_is_playing = {
  121. &message_red_255,
  122. &message_do_not_reset,
  123. NULL,
  124. };
  125. // Indicate that it's the Player's turn
  126. const NotificationSequence sequence_player_is_playing = {
  127. &message_red_0,
  128. &message_do_not_reset,
  129. NULL,
  130. };
  131. const NotificationSequence sequence_cleanup = {
  132. &message_red_0,
  133. &message_green_0,
  134. &message_blue_0,
  135. &message_sound_off,
  136. &message_vibro_off,
  137. NULL,
  138. };
  139. /* ============================ 2D drawing ================================== */
  140. /* Display remaining lives in the center of the board */
  141. void draw_remaining_lives(Canvas* canvas, const SimonData* simon_state) {
  142. // Convert score to string
  143. // int length = snprintf(NULL, 0, "%lu", simon_state->lives);
  144. // char* str_lives_remaining = malloc(length + 1);
  145. // snprintf(str_lives_remaining, length + 1, "%lu", simon_state->lives);
  146. // TODO: Make it a Simon Says icon on top right
  147. canvas_set_color(canvas, ColorBlack);
  148. canvas_set_font(canvas, FontSecondary);
  149. int x = SCREEN_XRES - 6;
  150. int lives = simon_state->lives;
  151. while(lives--) {
  152. canvas_draw_str(canvas, x, 8, "*");
  153. x -= 7;
  154. }
  155. }
  156. void draw_current_score(Canvas* canvas, const SimonData* simon_data) {
  157. /* Draw Game Score. */
  158. canvas_set_color(canvas, ColorXOR);
  159. canvas_set_font(canvas, FontSecondary);
  160. char str_score[32];
  161. snprintf(str_score, sizeof(str_score), "%i", simon_data->currentScore);
  162. canvas_draw_str_aligned(canvas, SCREEN_XRES / 2 + 4, 2, AlignCenter, AlignTop, str_score);
  163. }
  164. void play_sound_up(const SimonData* app) {
  165. if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
  166. furi_hal_speaker_start(NOTE_UP, app->volume);
  167. }
  168. }
  169. void play_sound_down(const SimonData* app) {
  170. if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
  171. furi_hal_speaker_start(NOTE_DOWN, app->volume);
  172. }
  173. }
  174. void play_sound_left(const SimonData* app) {
  175. if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
  176. furi_hal_speaker_start(NOTE_LEFT, app->volume);
  177. }
  178. }
  179. void play_sound_right(const SimonData* app) {
  180. if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
  181. furi_hal_speaker_start(NOTE_RIGHT, app->volume);
  182. }
  183. }
  184. void stop_sound() {
  185. if(furi_hal_speaker_is_mine()) {
  186. furi_hal_speaker_stop();
  187. furi_hal_speaker_release();
  188. }
  189. }
  190. /* Main Render Function */
  191. void simon_draw_callback(Canvas* canvas, void* ctx) {
  192. furi_assert(ctx);
  193. const SimonData* simon_state = ctx;
  194. furi_mutex_acquire(simon_state->mutex, FuriWaitForever);
  195. canvas_clear(canvas);
  196. // ######################### Main Menu #########################
  197. // Show Main Menu
  198. if(simon_state->gameState == mainMenu) {
  199. // Draw border frame
  200. canvas_draw_frame(canvas, 1, 1, SCREEN_XRES - 1, SCREEN_YRES - 1); // Border
  201. // Draw Simon text banner
  202. canvas_set_font(canvas, FontSecondary);
  203. canvas_set_color(canvas, ColorBlack);
  204. canvas_draw_str_aligned(
  205. canvas,
  206. SCREEN_XRES / 2,
  207. SCREEN_YRES / 2 - 4,
  208. AlignCenter,
  209. AlignCenter,
  210. "Welcome to Simon Says");
  211. // Display Press OK to start below title
  212. canvas_set_color(canvas, ColorXOR);
  213. canvas_draw_str_aligned(
  214. canvas,
  215. SCREEN_XRES / 2,
  216. SCREEN_YRES / 2 + 10,
  217. AlignCenter,
  218. AlignCenter,
  219. "Press OK to start");
  220. }
  221. // ######################### in Game #########################
  222. //@todo Render Callback
  223. // We're in an active game
  224. if(simon_state->gameState == inGame) {
  225. // Draw Current Score
  226. draw_current_score(canvas, simon_state);
  227. // Draw Lives
  228. draw_remaining_lives(canvas, simon_state);
  229. // Draw Simon Pose
  230. if(simon_state->activePlayer == player) {
  231. // Player's turn
  232. canvas_draw_icon(canvas, 0, 9, &I_DolphinWait_59x54);
  233. } else {
  234. // Simon's turn
  235. canvas_draw_icon(canvas, 0, 4, &I_DolphinTalking_59x63);
  236. }
  237. if(simon_state->set_board_neutral) {
  238. // Draw Neutral Board
  239. canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_board); // Draw Board
  240. // Stop Sound TODO: Move this to a better place
  241. //@todo Sound
  242. stop_sound();
  243. } else {
  244. switch(simon_state->selectedShape) {
  245. case up:
  246. canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_up); // Draw Up
  247. play_sound_up(simon_state);
  248. break;
  249. case down:
  250. canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_down); // Draw Down
  251. play_sound_down(simon_state);
  252. break;
  253. case left:
  254. canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_left); // Draw Left
  255. play_sound_left(simon_state);
  256. break;
  257. case right:
  258. canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_right); // Draw Right
  259. play_sound_right(simon_state);
  260. break;
  261. default:
  262. if(DEBUG_MSG)
  263. FURI_LOG_E(
  264. TAG, "Invalid shape: %d", simon_state->simonMoves[simon_state->moveIndex]);
  265. break;
  266. }
  267. }
  268. }
  269. // ######################### Game Over #########################
  270. if(simon_state->gameState == gameOver) {
  271. stop_sound(); //TODO: Make a game over sequence
  272. canvas_set_color(canvas, ColorXOR);
  273. canvas_set_font(canvas, FontPrimary);
  274. // TODO: if new highscore, display blinking "New High Score"
  275. // Display High Score Text
  276. if(simon_state->is_new_highscore) {
  277. canvas_draw_str_aligned(
  278. canvas, SCREEN_XRES / 2, 6, AlignCenter, AlignTop, "New High Score!");
  279. } else {
  280. canvas_draw_str_aligned(
  281. canvas, SCREEN_XRES / 2, 6, AlignCenter, AlignTop, "High Score");
  282. }
  283. // Convert highscore to string
  284. int length = snprintf(NULL, 0, "%i", simon_state->highScore);
  285. char* str_high_score = malloc(length + 1);
  286. snprintf(str_high_score, length + 1, "%i", simon_state->highScore);
  287. // Display High Score
  288. canvas_draw_str_aligned(
  289. canvas, SCREEN_XRES / 2, 22, AlignCenter, AlignCenter, str_high_score);
  290. free(str_high_score);
  291. // Display Game Over
  292. canvas_draw_str_aligned(
  293. canvas, SCREEN_XRES / 2, SCREEN_YRES / 2 + 2, AlignCenter, AlignCenter, "GAME OVER");
  294. // Display Press OK to restart below title
  295. canvas_set_font(canvas, FontSecondary);
  296. canvas_draw_str_aligned(
  297. canvas,
  298. SCREEN_XRES / 2,
  299. SCREEN_YRES / 2 + 15,
  300. AlignCenter,
  301. AlignCenter,
  302. "Press OK to restart");
  303. }
  304. // ######################### Victory #########################
  305. //Player Beat Simon beyond limit! A word record holder here!
  306. //TODO
  307. //release the mutex
  308. furi_mutex_release(simon_state->mutex);
  309. }
  310. /* ======================== Input Handling ============================== */
  311. void simon_input_callback(InputEvent* input_event, void* ctx) {
  312. furi_assert(ctx);
  313. FuriMessageQueue* event_queue = ctx;
  314. furi_message_queue_put(event_queue, input_event, FuriWaitForever);
  315. }
  316. /* ======================== Simon Game Engine ======================== */
  317. bool load_game(SimonData* app) {
  318. Storage* storage = furi_record_open(RECORD_STORAGE);
  319. storage_common_migrate(storage, EXT_PATH("apps/Games/game_simon_says.save"), SAVING_FILENAME);
  320. File* file = storage_file_alloc(storage);
  321. uint16_t bytes_readed = 0;
  322. if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
  323. if(storage_file_size(file) > sizeof(SimonData)) {
  324. storage_simply_remove(storage, SAVING_FILENAME);
  325. FURI_LOG_E(
  326. TAG, "Error: file is larger than the data structure! The file has been deleted.");
  327. } else {
  328. bytes_readed = storage_file_read(file, app, sizeof(SimonData));
  329. }
  330. storage_file_close(file);
  331. storage_file_free(file);
  332. }
  333. furi_record_close(RECORD_STORAGE);
  334. return bytes_readed == sizeof(SimonData);
  335. }
  336. void save_game(SimonData* app) {
  337. Storage* storage = furi_record_open(RECORD_STORAGE);
  338. File* file = storage_file_alloc(storage);
  339. if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  340. storage_file_write(file, app, sizeof(SimonData));
  341. }
  342. storage_file_close(file);
  343. storage_file_free(file);
  344. furi_record_close(RECORD_STORAGE);
  345. }
  346. int getRandomIntInRange(int lower, int upper) {
  347. return (rand() % (upper - lower + 1)) + lower;
  348. }
  349. void play_sound_sequence_correct() {
  350. notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_success);
  351. }
  352. void play_sound_wrong_move() {
  353. //TODO: play wrong sound: Try sequence_audiovisual_alert
  354. notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_error);
  355. }
  356. /* Restart game and give player a chance to try again on same sequence */
  357. // @todo restartGame
  358. void resetGame(SimonData* app) {
  359. app->moveIndex = 0;
  360. app->numberOfMillisecondsBeforeShapeDisappears = 500;
  361. app->activePlayer = simon;
  362. app->is_wrong_direction = false;
  363. app->last_button_press_tick = 0;
  364. app->set_board_neutral = true;
  365. app->activePlayer = simon;
  366. }
  367. /* Set gameover state */
  368. void game_over(SimonData* app) {
  369. if(app->is_new_highscore) save_game(app); // Save highscore but only on change
  370. app->gameover = true;
  371. app->lives = GAME_START_LIVES; // Show 3 lives in game over screen to match new game start
  372. app->gameState = gameOver;
  373. }
  374. /* Called after gameover to restart the game. This function
  375. * also calls restart_game(). */
  376. void restart_game_after_gameover(SimonData* app) {
  377. app->volume = 1.0f; //TODO: make this a setting
  378. app->gameState = inGame;
  379. app->gameover = false;
  380. app->currentScore = 0;
  381. app->is_new_highscore = false;
  382. app->lives = GAME_START_LIVES;
  383. app->simonMoves[0] = rand() % number_of_shapes;
  384. resetGame(app);
  385. }
  386. void addNewSimonMove(int addAtIndex, SimonData* app) {
  387. app->simonMoves[addAtIndex] = getRandomIntInRange(0, 3);
  388. }
  389. void startNewRound(SimonData* app) {
  390. addNewSimonMove(app->currentScore, app);
  391. app->moveIndex = 0;
  392. app->activePlayer = simon;
  393. }
  394. void onPlayerAnsweredCorrect(SimonData* app) {
  395. app->moveIndex++;
  396. }
  397. void onPlayerAnsweredWrong(SimonData* app) {
  398. if(app->lives > 0) {
  399. app->lives--;
  400. // Play the wrong sound
  401. if(app->sound_enabled) {
  402. play_sound_wrong_move();
  403. }
  404. resetGame(app);
  405. } else {
  406. // The player has no lives left
  407. // Game over
  408. game_over(app);
  409. //TODO: Play unique game over sound
  410. }
  411. }
  412. bool isRoundComplete(SimonData* app) {
  413. return app->moveIndex == app->currentScore;
  414. }
  415. enum shape_names getCurrentSimonMove(SimonData* app) {
  416. return app->simonMoves[app->moveIndex];
  417. }
  418. void onPlayerSelectedShapeCallback(enum shape_names shape, SimonData* app) {
  419. if(shape == getCurrentSimonMove(app)) {
  420. onPlayerAnsweredCorrect(app);
  421. } else {
  422. onPlayerAnsweredWrong(app);
  423. }
  424. }
  425. //@todo gametick
  426. void game_tick(SimonData* simon_state) {
  427. if(simon_state->gameState == inGame) {
  428. if(simon_state->activePlayer == simon) {
  429. // ############### Simon Turn ###############
  430. notification_message(simon_state->notification, &sequence_simon_is_playing);
  431. //@todo Gameplay
  432. if(simon_state->set_board_neutral) {
  433. if(simon_state->moveIndex < simon_state->currentScore) {
  434. simon_state->selectedShape = getCurrentSimonMove(simon_state);
  435. simon_state->set_board_neutral = false;
  436. simon_state->moveIndex++;
  437. } else {
  438. simon_state->activePlayer = player;
  439. simon_state->set_board_neutral = true;
  440. simon_state->moveIndex = 0;
  441. }
  442. } else {
  443. simon_state->set_board_neutral = true;
  444. }
  445. } else {
  446. // ############### Player Turn ###############
  447. notification_message(simon_state->notification, &sequence_player_is_playing);
  448. // It's Player's Turn
  449. if(isRoundComplete(simon_state)) {
  450. simon_state->activePlayer = simon;
  451. simon_state->currentScore++;
  452. // app->numberOfMillisecondsBeforeShapeDisappears -= 50;
  453. //TODO: Hacky way of handling highscore by subtracting 1 to account for the first move
  454. if(simon_state->currentScore - 1 > simon_state->highScore) {
  455. simon_state->highScore = simon_state->currentScore - 1;
  456. simon_state->is_new_highscore = true;
  457. }
  458. if(simon_state->sound_enabled) {
  459. play_sound_sequence_correct();
  460. }
  461. startNewRound(simon_state);
  462. }
  463. }
  464. }
  465. }
  466. /* ======================== Main Entry Point ============================== */
  467. int32_t simon_says_app_entry(void* p) {
  468. UNUSED(p);
  469. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  470. SimonData* simon_state = malloc(sizeof(SimonData));
  471. simon_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  472. if(!simon_state->mutex) {
  473. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  474. free(simon_state);
  475. return -1;
  476. }
  477. // Configure view port
  478. ViewPort* view_port = view_port_alloc();
  479. view_port_draw_callback_set(view_port, simon_draw_callback, simon_state);
  480. view_port_input_callback_set(view_port, simon_input_callback, event_queue);
  481. // Register view port in GUI
  482. Gui* gui = furi_record_open(RECORD_GUI);
  483. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  484. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  485. simon_state->notification = notification;
  486. InputEvent input;
  487. // Show Main Menu Screen
  488. //load_game(simon_state);
  489. restart_game_after_gameover(simon_state);
  490. simon_state->gameState = mainMenu;
  491. while(true) {
  492. game_tick(simon_state);
  493. FuriStatus q_status = furi_message_queue_get(
  494. event_queue, &input, simon_state->numberOfMillisecondsBeforeShapeDisappears);
  495. furi_mutex_acquire(simon_state->mutex, FuriWaitForever);
  496. if(q_status == FuriStatusOk) {
  497. //FURI_LOG_D(TAG, "Got input event: %d", input.key);
  498. //break out of the loop if the back key is pressed
  499. if(input.key == InputKeyBack && input.type == InputTypeLong) {
  500. // Save high score before quitting
  501. //if(simon_state->is_new_highscore) {
  502. // save_game(simon_state);
  503. //}
  504. break;
  505. }
  506. //@todo Set Game States
  507. if(input.key == InputKeyOk && simon_state->gameState != inGame) {
  508. restart_game_after_gameover(simon_state);
  509. // Set Simon Board state
  510. startNewRound(simon_state);
  511. view_port_update(view_port);
  512. }
  513. // Keep LED on if it is Simon's turn
  514. if(simon_state->activePlayer == player) {
  515. notification_message(notification, &sequence_player_is_playing);
  516. if(input.type == InputTypePress) {
  517. simon_state->set_board_neutral = false;
  518. switch(input.key) {
  519. case InputKeyUp:
  520. simon_state->selectedShape = up;
  521. onPlayerSelectedShapeCallback(up, simon_state);
  522. break;
  523. case InputKeyDown:
  524. simon_state->selectedShape = down;
  525. onPlayerSelectedShapeCallback(down, simon_state);
  526. break;
  527. case InputKeyLeft:
  528. simon_state->selectedShape = left;
  529. onPlayerSelectedShapeCallback(left, simon_state);
  530. break;
  531. case InputKeyRight:
  532. simon_state->selectedShape = right;
  533. onPlayerSelectedShapeCallback(right, simon_state);
  534. break;
  535. default:
  536. simon_state->set_board_neutral = true;
  537. break;
  538. }
  539. } else {
  540. //FURI_LOG_D(TAG, "Input type is not short");
  541. simon_state->set_board_neutral = true;
  542. }
  543. }
  544. }
  545. // @todo Animation Loop for debug
  546. // if(simon_state->gameState == inGame && simon_state->activePlayer == simon) {
  547. // simon_state->currentScore++;
  548. // simon_state->set_board_neutral = !simon_state->set_board_neutral;
  549. // }
  550. furi_mutex_release(simon_state->mutex);
  551. view_port_update(view_port);
  552. }
  553. stop_sound();
  554. notification_message(notification, &sequence_cleanup);
  555. gui_remove_view_port(gui, view_port);
  556. view_port_free(view_port);
  557. furi_message_queue_free(event_queue);
  558. furi_mutex_free(simon_state->mutex);
  559. furi_record_close(RECORD_NOTIFICATION);
  560. furi_record_close(RECORD_GUI);
  561. free(simon_state);
  562. return 0;
  563. }