Add Snake game (#829)
* Add snake game * Applications: Added a classic game https://en.wikipedia.org/wiki/Snake_(video_game_genre) * Snake Game: Making it impossible to lose button presses * Use more native press button event * Snake Game: use low level InputTypePress event instead of InputTypeShort high level unpredictable event Co-authored-by: LionZXY <nikita@kulikof.ru> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		@@ -42,6 +42,7 @@ extern int32_t vibro_test_app(void* p);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Plugins
 | 
					// Plugins
 | 
				
			||||||
extern int32_t music_player_app(void* p);
 | 
					extern int32_t music_player_app(void* p);
 | 
				
			||||||
 | 
					extern int32_t snake_game_app(void* p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// On system start hooks declaration
 | 
					// On system start hooks declaration
 | 
				
			||||||
extern void bt_cli_init();
 | 
					extern void bt_cli_init();
 | 
				
			||||||
@@ -203,6 +204,10 @@ const FlipperApplication FLIPPER_PLUGINS[] = {
 | 
				
			|||||||
#ifdef APP_MUSIC_PLAYER
 | 
					#ifdef APP_MUSIC_PLAYER
 | 
				
			||||||
    {.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14},
 | 
					    {.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14},
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef APP_SNAKE_GAME
 | 
				
			||||||
 | 
					    {.app = snake_game_app, .name = "Snake Game", .stack_size = 1024, .icon = &A_Plugins_14},
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const size_t FLIPPER_PLUGINS_COUNT = sizeof(FLIPPER_PLUGINS) / sizeof(FlipperApplication);
 | 
					const size_t FLIPPER_PLUGINS_COUNT = sizeof(FLIPPER_PLUGINS) / sizeof(FlipperApplication);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ APP_ABOUT	= 1
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Plugins
 | 
					# Plugins
 | 
				
			||||||
APP_MUSIC_PLAYER = 1
 | 
					APP_MUSIC_PLAYER = 1
 | 
				
			||||||
 | 
					APP_SNAKE_GAME = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Debug
 | 
					# Debug
 | 
				
			||||||
APP_ACCESSOR = 1
 | 
					APP_ACCESSOR = 1
 | 
				
			||||||
@@ -185,6 +186,11 @@ CFLAGS		+= -DAPP_MUSIC_PLAYER
 | 
				
			|||||||
SRV_GUI		= 1
 | 
					SRV_GUI		= 1
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					APP_SNAKE_GAME ?= 0
 | 
				
			||||||
 | 
					ifeq ($(APP_SNAKE_GAME), 1)
 | 
				
			||||||
 | 
					CFLAGS		+= -DAPP_SNAKE_GAME
 | 
				
			||||||
 | 
					SRV_GUI		= 1
 | 
				
			||||||
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APP_IBUTTON ?= 0
 | 
					APP_IBUTTON ?= 0
 | 
				
			||||||
ifeq ($(APP_IBUTTON), 1)
 | 
					ifeq ($(APP_IBUTTON), 1)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										419
									
								
								applications/snake-game/snake-game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								applications/snake-game/snake-game.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,419 @@
 | 
				
			|||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <gui/gui.h>
 | 
				
			||||||
 | 
					#include <input/input.h>
 | 
				
			||||||
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    //    +-----x
 | 
				
			||||||
 | 
					    //    |
 | 
				
			||||||
 | 
					    //    |
 | 
				
			||||||
 | 
					    //    y
 | 
				
			||||||
 | 
					    uint8_t x;
 | 
				
			||||||
 | 
					    uint8_t y;
 | 
				
			||||||
 | 
					} Point;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    GameStateLife,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    DirectionUp,
 | 
				
			||||||
 | 
					    DirectionRight,
 | 
				
			||||||
 | 
					    DirectionDown,
 | 
				
			||||||
 | 
					    DirectionLeft,
 | 
				
			||||||
 | 
					} Direction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_SNAKE_LEN 253
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Game Over banner
 | 
				
			||||||
 | 
					    if(snake_state->state == GameStateGameOver) {
 | 
				
			||||||
 | 
					        // Screen is 128x64 px
 | 
				
			||||||
 | 
					        canvas_set_color(canvas, ColorWhite);
 | 
				
			||||||
 | 
					        canvas_draw_box(canvas, 34, 20, 62, 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        canvas_set_color(canvas, ColorBlack);
 | 
				
			||||||
 | 
					        canvas_draw_frame(canvas, 34, 20, 62, 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        canvas_set_font(canvas, FontPrimary);
 | 
				
			||||||
 | 
					        canvas_draw_str(canvas, 37, 31, "Game Over");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					        char buffer[12];
 | 
				
			||||||
 | 
					        snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7);
 | 
				
			||||||
 | 
					        canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    release_mutex((ValueMutex*)ctx, snake_state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void snake_game_input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) {
 | 
				
			||||||
 | 
					    furi_assert(event_queue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
 | 
				
			||||||
 | 
					    osMessageQueuePut(event_queue, &event, 0, osWaitForever);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void snake_game_update_timer_callback(osMessageQueueId_t event_queue) {
 | 
				
			||||||
 | 
					    furi_assert(event_queue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SnakeEvent event = {.type = EventTypeTick};
 | 
				
			||||||
 | 
					    osMessageQueuePut(event_queue, &event, 0, 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));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
 | 
					    switch(snake_state->currentMovement) {
 | 
				
			||||||
 | 
					    case DirectionUp:
 | 
				
			||||||
 | 
					        switch(snake_state->nextMovement) {
 | 
				
			||||||
 | 
					        case DirectionRight:
 | 
				
			||||||
 | 
					            return DirectionRight;
 | 
				
			||||||
 | 
					        case DirectionLeft:
 | 
				
			||||||
 | 
					            return DirectionLeft;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return snake_state->currentMovement;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    case DirectionRight:
 | 
				
			||||||
 | 
					        switch(snake_state->nextMovement) {
 | 
				
			||||||
 | 
					        case DirectionUp:
 | 
				
			||||||
 | 
					            return DirectionUp;
 | 
				
			||||||
 | 
					        case DirectionDown:
 | 
				
			||||||
 | 
					            return DirectionDown;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return snake_state->currentMovement;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    case DirectionDown:
 | 
				
			||||||
 | 
					        switch(snake_state->nextMovement) {
 | 
				
			||||||
 | 
					        case DirectionRight:
 | 
				
			||||||
 | 
					            return DirectionRight;
 | 
				
			||||||
 | 
					        case DirectionLeft:
 | 
				
			||||||
 | 
					            return DirectionLeft;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return snake_state->currentMovement;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    default: // case DirectionLeft:
 | 
				
			||||||
 | 
					        switch(snake_state->nextMovement) {
 | 
				
			||||||
 | 
					        case DirectionUp:
 | 
				
			||||||
 | 
					            return DirectionUp;
 | 
				
			||||||
 | 
					        case DirectionDown:
 | 
				
			||||||
 | 
					            return DirectionDown;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return 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) {
 | 
				
			||||||
 | 
					    if(snake_state->state == GameStateGameOver) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0);
 | 
				
			||||||
 | 
					    if(can_turn) {
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					            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;
 | 
				
			||||||
 | 
					        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) {
 | 
				
			||||||
 | 
					            snake_state->state = GameStateGameOver;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    snake_game_move_snake(snake_state, next_step);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(eatFruit) {
 | 
				
			||||||
 | 
					        snake_state->fruit = snake_game_get_new_fruit(snake_state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32_t snake_game_app(void* p) {
 | 
				
			||||||
 | 
					    srand(DWT->CYCCNT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(SnakeEvent), NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SnakeState* snake_state = furi_alloc(sizeof(SnakeState));
 | 
				
			||||||
 | 
					    snake_game_init_game(snake_state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ValueMutex state_mutex;
 | 
				
			||||||
 | 
					    if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
 | 
				
			||||||
 | 
					        furi_log_print(FURI_LOG_ERROR, "cannot create mutex\r\n");
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osTimerId_t timer =
 | 
				
			||||||
 | 
					        osTimerNew(snake_game_update_timer_callback, osTimerPeriodic, event_queue, NULL);
 | 
				
			||||||
 | 
					    osTimerStart(timer, osKernelGetTickFreq() / 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Open GUI and register view_port
 | 
				
			||||||
 | 
					    Gui* gui = furi_record_open("gui");
 | 
				
			||||||
 | 
					    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SnakeEvent event;
 | 
				
			||||||
 | 
					    for(bool processing = true; processing;) {
 | 
				
			||||||
 | 
					        osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(event_status == osOK) {
 | 
				
			||||||
 | 
					            // press events
 | 
				
			||||||
 | 
					            if(event.type == EventTypeKey) {
 | 
				
			||||||
 | 
					                if(event.input.type == InputTypePress) {
 | 
				
			||||||
 | 
					                    switch(event.input.key) {
 | 
				
			||||||
 | 
					                    case InputKeyUp:
 | 
				
			||||||
 | 
					                        snake_state->nextMovement = DirectionUp;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case InputKeyDown:
 | 
				
			||||||
 | 
					                        snake_state->nextMovement = DirectionDown;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case InputKeyRight:
 | 
				
			||||||
 | 
					                        snake_state->nextMovement = DirectionRight;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case InputKeyLeft:
 | 
				
			||||||
 | 
					                        snake_state->nextMovement = DirectionLeft;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case InputKeyOk:
 | 
				
			||||||
 | 
					                        if(snake_state->state == GameStateGameOver) {
 | 
				
			||||||
 | 
					                            snake_game_init_game(snake_state);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case InputKeyBack:
 | 
				
			||||||
 | 
					                        processing = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if(event.type == EventTypeTick) {
 | 
				
			||||||
 | 
					                snake_game_process_game_step(snake_state);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // event timeout
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        view_port_update(view_port);
 | 
				
			||||||
 | 
					        release_mutex(&state_mutex, snake_state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osTimerDelete(timer);
 | 
				
			||||||
 | 
					    view_port_enabled_set(view_port, false);
 | 
				
			||||||
 | 
					    gui_remove_view_port(gui, view_port);
 | 
				
			||||||
 | 
					    furi_record_close("gui");
 | 
				
			||||||
 | 
					    view_port_free(view_port);
 | 
				
			||||||
 | 
					    osMessageQueueDelete(event_queue);
 | 
				
			||||||
 | 
					    delete_mutex(&state_mutex);
 | 
				
			||||||
 | 
					    free(snake_state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Screen is 128x64 px
 | 
				
			||||||
 | 
					// (4 + 4) * 16 - 4 + 2 + 2border == 128
 | 
				
			||||||
 | 
					// (4 + 4) * 8 - 4 + 2 + 2border == 64
 | 
				
			||||||
 | 
					// Game field from point{x:  0, y: 0} to point{x: 30, y: 14}.
 | 
				
			||||||
 | 
					// The snake turns only in even cells - intersections.
 | 
				
			||||||
 | 
					// ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪   ▪ ╎
 | 
				
			||||||
 | 
					// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
 | 
				
			||||||
 | 
					// └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
 | 
				
			||||||
		Reference in New Issue
	
	Block a user