|
@@ -0,0 +1,528 @@
|
|
|
|
|
+#include <furi.h>
|
|
|
|
|
+#include <gui/gui.h>
|
|
|
|
|
+#include <input/input.h>
|
|
|
|
|
+#include <stdlib.h>
|
|
|
|
|
+#include <dolphin/dolphin.h>
|
|
|
|
|
+#include <notification/notification.h>
|
|
|
|
|
+#include <notification/notification_messages.h>
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ // +-----x
|
|
|
|
|
+ // |
|
|
|
|
|
+ // |
|
|
|
|
|
+ // y
|
|
|
|
|
+ uint8_t x;
|
|
|
|
|
+ uint8_t y;
|
|
|
|
|
+} Point;
|
|
|
|
|
+
|
|
|
|
|
+typedef enum {
|
|
|
|
|
+ GameStateLife,
|
|
|
|
|
+ GameStatePause,
|
|
|
|
|
+ // https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
|
|
|
|
|
+ // Armanto: While testing the early versions of the game, I noticed it was hard
|
|
|
|
|
+ // to control the snake upon getting close to and edge but not crashing — especially
|
|
|
|
|
+ // in the highest speed levels. I wanted the highest level to be as fast as I could
|
|
|
|
|
+ // possibly make the device "run," but on the other hand, I wanted to be friendly
|
|
|
|
|
+ // and help the player manage that level. Otherwise it might not be fun to play. So
|
|
|
|
|
+ // I implemented a little delay. A few milliseconds of extra time right before
|
|
|
|
|
+ // the player crashes, during which she can still change the directions. And if
|
|
|
|
|
+ // she does, the game continues.
|
|
|
|
|
+ GameStateLastChance,
|
|
|
|
|
+ GameStateGameOver,
|
|
|
|
|
+} GameState;
|
|
|
|
|
+
|
|
|
|
|
+// Note: do not change without purpose. Current values are used in smart
|
|
|
|
|
+// orthogonality calculation in `snake_game_get_turn_snake`.
|
|
|
|
|
+typedef enum {
|
|
|
|
|
+ DirectionUp,
|
|
|
|
|
+ DirectionRight,
|
|
|
|
|
+ DirectionDown,
|
|
|
|
|
+ DirectionLeft,
|
|
|
|
|
+} Direction;
|
|
|
|
|
+
|
|
|
|
|
+#define MAX_SNAKE_LEN 15 * 31 //128 * 64 / 4
|
|
|
|
|
+
|
|
|
|
|
+#define x_back_symbol 50
|
|
|
|
|
+#define y_back_symbol 9
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ Point points[MAX_SNAKE_LEN];
|
|
|
|
|
+ uint16_t len;
|
|
|
|
|
+ Direction currentMovement;
|
|
|
|
|
+ Direction nextMovement; // if backward of currentMovement, ignore
|
|
|
|
|
+ Point fruit;
|
|
|
|
|
+ GameState state;
|
|
|
|
|
+} SnakeState;
|
|
|
|
|
+
|
|
|
|
|
+typedef enum {
|
|
|
|
|
+ EventTypeTick,
|
|
|
|
|
+ EventTypeKey,
|
|
|
|
|
+} EventType;
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ EventType type;
|
|
|
|
|
+ InputEvent input;
|
|
|
|
|
+} SnakeEvent;
|
|
|
|
|
+
|
|
|
|
|
+const NotificationSequence sequence_fail = {
|
|
|
|
|
+ &message_vibro_on,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_vibro_off,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const NotificationSequence sequence_eat = {
|
|
|
|
|
+
|
|
|
|
|
+ &message_vibro_on,
|
|
|
|
|
+ &message_note_c7,
|
|
|
|
|
+ &message_delay_50,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_vibro_off,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
|
|
|
|
+ const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
|
|
|
|
|
+ if(snake_state == NULL) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Before the function is called, the state is set with the canvas_reset(canvas)
|
|
|
|
|
+
|
|
|
|
|
+ // Frame
|
|
|
|
|
+ canvas_draw_frame(canvas, 0, 0, 128, 64);
|
|
|
|
|
+
|
|
|
|
|
+ // Fruit
|
|
|
|
|
+ Point f = snake_state->fruit;
|
|
|
|
|
+ f.x = f.x * 4 + 1;
|
|
|
|
|
+ f.y = f.y * 4 + 1;
|
|
|
|
|
+ canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
|
|
|
|
|
+ canvas_draw_dot(canvas,f.x+3,f.y-1);
|
|
|
|
|
+ canvas_draw_dot(canvas,f.x+4,f.y-2);
|
|
|
|
|
+ //canvas_draw_dot(canvas,f.x+4,f.y-3);
|
|
|
|
|
+
|
|
|
|
|
+ // Snake
|
|
|
|
|
+ for(uint16_t i = 0; i < snake_state->len; i++) {
|
|
|
|
|
+ Point p = snake_state->points[i];
|
|
|
|
|
+ p.x = p.x * 4 + 2;
|
|
|
|
|
+ p.y = p.y * 4 + 2;
|
|
|
|
|
+ canvas_draw_box(canvas, p.x, p.y, 4, 4);
|
|
|
|
|
+ if(i==0){
|
|
|
|
|
+ canvas_set_color(canvas, ColorWhite);
|
|
|
|
|
+ canvas_draw_box(canvas,p.x+1,p.y+1,2,2);
|
|
|
|
|
+ canvas_set_color(canvas, ColorBlack);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Pause and GameOver banner
|
|
|
|
|
+ if(snake_state->state == GameStatePause || snake_state->state == GameStateGameOver) {
|
|
|
|
|
+ // Screen is 128x64 px
|
|
|
|
|
+ canvas_set_color(canvas, ColorWhite);
|
|
|
|
|
+ canvas_draw_box(canvas, 33, 19, 64, 26);
|
|
|
|
|
+
|
|
|
|
|
+ canvas_set_color(canvas, ColorBlack);
|
|
|
|
|
+ canvas_draw_frame(canvas, 34, 20, 62, 24);
|
|
|
|
|
+
|
|
|
|
|
+ canvas_set_font(canvas, FontPrimary);
|
|
|
|
|
+ if(snake_state->state == GameStateGameOver){
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 31, AlignCenter, AlignBottom, "Game Over");
|
|
|
|
|
+ }
|
|
|
|
|
+ if(snake_state->state == GameStatePause){
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 31, AlignCenter, AlignBottom, "Pause");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ canvas_set_font(canvas, FontSecondary);
|
|
|
|
|
+ char buffer[20];
|
|
|
|
|
+ snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 41, AlignCenter, AlignBottom, buffer);
|
|
|
|
|
+
|
|
|
|
|
+ // Painting "back"-symbol, Help message for Exit App, ProgressBar
|
|
|
|
|
+ canvas_set_color(canvas, ColorWhite);
|
|
|
|
|
+ canvas_draw_box(canvas, 25, 2, 81, 11);
|
|
|
|
|
+ canvas_draw_box(canvas, 28, 54, 73, 9);
|
|
|
|
|
+ canvas_set_color(canvas, ColorBlack);
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 10, AlignCenter, AlignBottom, "Hold to Exit App");
|
|
|
|
|
+ snprintf(buffer, sizeof(buffer), "Complete: %-5.1f%%", (double)((snake_state->len - 7U)/4.58));
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer);
|
|
|
|
|
+ {
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+0,y_back_symbol);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+1,y_back_symbol);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+2,y_back_symbol);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+3,y_back_symbol);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+4,y_back_symbol);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+5,y_back_symbol-1);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+6,y_back_symbol-2);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+6,y_back_symbol-3);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+5,y_back_symbol-4);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+4,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+3,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+2,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+1,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol+0,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-1,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-2,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-3,y_back_symbol-5);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-2,y_back_symbol-6);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-2,y_back_symbol-4);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-1,y_back_symbol-6);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-1,y_back_symbol-4);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-1,y_back_symbol-7);
|
|
|
|
|
+ canvas_draw_dot(canvas,x_back_symbol-1,y_back_symbol-3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ release_mutex((ValueMutex*)ctx, snake_state);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
|
|
|
+ furi_assert(event_queue);
|
|
|
|
|
+
|
|
|
|
|
+ SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
|
|
|
+ furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) {
|
|
|
|
|
+ furi_assert(event_queue);
|
|
|
|
|
+
|
|
|
|
|
+ SnakeEvent event = {.type = EventTypeTick};
|
|
|
|
|
+ furi_message_queue_put(event_queue, &event, 0);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void snake_game_init_game(SnakeState* const snake_state) {
|
|
|
|
|
+ Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
|
|
|
|
|
+ memcpy(snake_state->points, p, sizeof(p)); //-V1086
|
|
|
|
|
+
|
|
|
|
|
+ snake_state->len = 7;
|
|
|
|
|
+
|
|
|
|
|
+ snake_state->currentMovement = DirectionRight;
|
|
|
|
|
+
|
|
|
|
|
+ snake_state->nextMovement = DirectionRight;
|
|
|
|
|
+
|
|
|
|
|
+ Point f = {18, 6};
|
|
|
|
|
+ snake_state->fruit = f;
|
|
|
|
|
+
|
|
|
|
|
+ snake_state->state = GameStateLife;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
|
|
|
|
|
+ // 1 bit for each point on the playing field where the snake can turn
|
|
|
|
|
+ // and where the fruit can appear
|
|
|
|
|
+ uint16_t buffer[8];
|
|
|
|
|
+ memset(buffer, 0, sizeof(buffer));
|
|
|
|
|
+ uint8_t empty = 8 * 16;
|
|
|
|
|
+
|
|
|
|
|
+ for(uint16_t i = 0; i < snake_state->len; i++) {
|
|
|
|
|
+ Point p = snake_state->points[i];
|
|
|
|
|
+
|
|
|
|
|
+ if(p.x % 2 != 0 || p.y % 2 != 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ p.x /= 2;
|
|
|
|
|
+ p.y /= 2;
|
|
|
|
|
+
|
|
|
|
|
+ buffer[p.y] |= 1 << p.x;
|
|
|
|
|
+ empty--;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Bit set if snake use that playing field
|
|
|
|
|
+
|
|
|
|
|
+ uint16_t newFruit = rand() % empty;
|
|
|
|
|
+
|
|
|
|
|
+ // Skip random number of _empty_ fields
|
|
|
|
|
+ for(uint8_t y = 0; y < 8; y++) {
|
|
|
|
|
+ for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) {
|
|
|
|
|
+ if((buffer[y] & mask) == 0) {
|
|
|
|
|
+ if(newFruit == 0) {
|
|
|
|
|
+ Point p = {
|
|
|
|
|
+ .x = x * 2,
|
|
|
|
|
+ .y = y * 2,
|
|
|
|
|
+ };
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+ newFruit--;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // We will never be here
|
|
|
|
|
+ Point p = {0, 0};
|
|
|
|
|
+ return p;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool snake_game_collision_with_frame(Point const next_step) {
|
|
|
|
|
+ // if x == 0 && currentMovement == left then x - 1 == 255 ,
|
|
|
|
|
+ // so check only x > right border
|
|
|
|
|
+ return next_step.x > 30 || next_step.y > 14;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool
|
|
|
|
|
+ snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
|
|
|
|
|
+ for(uint16_t i = 0; i < snake_state->len; i++) {
|
|
|
|
|
+ Point p = snake_state->points[i];
|
|
|
|
|
+ if(p.x == next_step.x && p.y == next_step.y) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
|
|
|
|
|
+ // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
|
|
|
|
|
+ bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
|
|
|
|
|
+ return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static Point snake_game_get_next_step(SnakeState const* const snake_state) {
|
|
|
|
|
+ Point next_step = snake_state->points[0];
|
|
|
|
|
+ switch(snake_state->currentMovement) {
|
|
|
|
|
+ // +-----x
|
|
|
|
|
+ // |
|
|
|
|
|
+ // |
|
|
|
|
|
+ // y
|
|
|
|
|
+ case DirectionUp:
|
|
|
|
|
+ next_step.y--;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case DirectionRight:
|
|
|
|
|
+ next_step.x++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case DirectionDown:
|
|
|
|
|
+ next_step.y++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case DirectionLeft:
|
|
|
|
|
+ next_step.x--;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return next_step;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
|
|
|
|
|
+ memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
|
|
|
|
|
+ snake_state->points[0] = next_step;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void
|
|
|
|
|
+ snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) {
|
|
|
|
|
+ if(snake_state->state == GameStateGameOver) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
|
|
|
|
|
+
|
|
|
|
|
+ Point next_step = snake_game_get_next_step(snake_state);
|
|
|
|
|
+
|
|
|
|
|
+ bool crush = snake_game_collision_with_frame(next_step);
|
|
|
|
|
+ if(crush) {
|
|
|
|
|
+ if(snake_state->state == GameStateLife) {
|
|
|
|
|
+ snake_state->state = GameStateLastChance;
|
|
|
|
|
+ return;
|
|
|
|
|
+ } else if(snake_state->state == GameStateLastChance) {
|
|
|
|
|
+ snake_state->state = GameStateGameOver;
|
|
|
|
|
+ notification_message_block(notification, &sequence_fail);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if(snake_state->state == GameStateLastChance) {
|
|
|
|
|
+ snake_state->state = GameStateLife;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ crush = snake_game_collision_with_tail(snake_state, next_step);
|
|
|
|
|
+ if(crush) {
|
|
|
|
|
+ snake_state->state = GameStateGameOver;
|
|
|
|
|
+ notification_message_block(notification, &sequence_fail);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
|
|
|
|
+ if(eatFruit) {
|
|
|
|
|
+ snake_state->len++;
|
|
|
|
|
+ if(snake_state->len >= MAX_SNAKE_LEN) {
|
|
|
|
|
+ //You win!!!
|
|
|
|
|
+ snake_state->state = GameStateGameOver;
|
|
|
|
|
+ notification_message_block(notification, &sequence_fail);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ snake_game_move_snake(snake_state, next_step);
|
|
|
|
|
+
|
|
|
|
|
+ if(eatFruit) {
|
|
|
|
|
+ snake_state->fruit = snake_game_get_new_fruit(snake_state);
|
|
|
|
|
+ notification_message(notification, &sequence_eat);
|
|
|
|
|
+ notification_message(notification, &sequence_blink_red_100);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int32_t snake_20_app(void* p) {
|
|
|
|
|
+ UNUSED(p);
|
|
|
|
|
+
|
|
|
|
|
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
|
|
|
|
|
+
|
|
|
|
|
+ SnakeState* snake_state = malloc(sizeof(SnakeState));
|
|
|
|
|
+ snake_game_init_game(snake_state);
|
|
|
|
|
+
|
|
|
|
|
+ ValueMutex state_mutex;
|
|
|
|
|
+ if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
|
|
|
|
|
+ FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
|
|
|
|
|
+ furi_message_queue_free(event_queue);
|
|
|
|
|
+ free(snake_state);
|
|
|
|
|
+ return 255;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ViewPort* view_port = view_port_alloc();
|
|
|
|
|
+ view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex);
|
|
|
|
|
+ view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
|
|
|
|
|
+
|
|
|
|
|
+ FuriTimer* timer =
|
|
|
|
|
+ furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
|
|
|
|
+
|
|
|
|
|
+ // Open GUI and register view_port
|
|
|
|
|
+ Gui* gui = furi_record_open(RECORD_GUI);
|
|
|
|
|
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
|
+ NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
|
+
|
|
|
|
|
+ notification_message_block(notification, &sequence_display_backlight_enforce_on);
|
|
|
|
|
+
|
|
|
|
|
+ DOLPHIN_DEED(DolphinDeedPluginGameStart);
|
|
|
|
|
+
|
|
|
|
|
+ SnakeEvent event;
|
|
|
|
|
+ for(bool processing = true; processing;) {
|
|
|
|
|
+ FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
|
|
|
+
|
|
|
|
|
+ SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex);
|
|
|
|
|
+
|
|
|
|
|
+ if(event_status == FuriStatusOk) {
|
|
|
|
|
+ if(event.type == EventTypeKey) {
|
|
|
|
|
+ // press events
|
|
|
|
|
+ if(event.input.type == InputTypePress) {
|
|
|
|
|
+ switch(event.input.key) {
|
|
|
|
|
+ case InputKeyUp:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionUp;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyDown:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionDown;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyRight:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionRight;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyLeft:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionLeft;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyOk:
|
|
|
|
|
+ if(snake_state->state == GameStateGameOver) {
|
|
|
|
|
+ snake_game_init_game(snake_state);
|
|
|
|
|
+ }
|
|
|
|
|
+ if(snake_state->state == GameStatePause) {
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
|
|
|
|
+ snake_state->state = GameStateLife;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyBack:
|
|
|
|
|
+ if(snake_state->state == GameStateLife) {
|
|
|
|
|
+ furi_timer_stop(timer);
|
|
|
|
|
+ snake_state->state = GameStatePause;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(snake_state->state == GameStatePause) {
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
|
|
|
|
+ snake_state->state = GameStateLife;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(snake_state->state == GameStateGameOver) {
|
|
|
|
|
+ snake_game_init_game(snake_state);
|
|
|
|
|
+ }
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //LongPress Events
|
|
|
|
|
+ if(event.input.type == InputTypeLong){
|
|
|
|
|
+ switch(event.input.key) {
|
|
|
|
|
+ case InputKeyUp:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionUp;
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyDown:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionDown;
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyRight:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionRight;
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyLeft:
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ snake_state->nextMovement = DirectionLeft;
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyBack:
|
|
|
|
|
+ processing = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ //ReleaseKey Event
|
|
|
|
|
+ if(event.input.type == InputTypeRelease){
|
|
|
|
|
+ if(snake_state->state != GameStatePause) {
|
|
|
|
|
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if(event.type == EventTypeTick) {
|
|
|
|
|
+ snake_game_process_game_step(snake_state, notification);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // event timeout
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ view_port_update(view_port);
|
|
|
|
|
+ release_mutex(&state_mutex, snake_state);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for all notifications to be played and return backlight to normal state
|
|
|
|
|
+ notification_message_block(notification, &sequence_display_backlight_enforce_auto);
|
|
|
|
|
+
|
|
|
|
|
+ furi_timer_free(timer);
|
|
|
|
|
+ view_port_enabled_set(view_port, false);
|
|
|
|
|
+ gui_remove_view_port(gui, view_port);
|
|
|
|
|
+ furi_record_close(RECORD_GUI);
|
|
|
|
|
+ furi_record_close(RECORD_NOTIFICATION);
|
|
|
|
|
+ view_port_free(view_port);
|
|
|
|
|
+ furi_message_queue_free(event_queue);
|
|
|
|
|
+ delete_mutex(&state_mutex);
|
|
|
|
|
+ free(snake_state);
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|