diff --git a/ReadMe.md b/ReadMe.md index 39907875315..bc7ba7536c5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,28 +1,35 @@ # SNAKE 2.0 -A new version of Snake Game. +Advanced Snake Game (Remake of original Snake). ![Snake_2.0](https://github.com/Willzvul/Snake_2.0/blob/main/Snake%202.0.png) ## Features -1. Pause of game proccess -2. Possibility of speed Up by holding the arrows +### v2.2 +1. Pause of game proccess. +2. Possibility of speed Up by holding the arrows. 3. Snake has a head. -4. The target is an apple -5. Complete progress bar +4. The target is an apple. +5. Complete progress bar (%). -## Building FAP +### v2.3 +1. Saving progress when exiting and loading it when starting the game. +2. Endless mode switch (If turned ON -> the game continues even if the snake collides with the tail or the frame, so the player gets a chance to win the game). +3. Game timer. +4. Breaking by holding the back-direction arrow. +5. Brand-new system of the next random fruit positioning (it fixed the major bug when the fruit appeared in the left-upper corner of a screen despite the fact that the field (0,0) is already taken by the snake's body). -https://fap.playmean.xyz/Willzvul/Snake_2.0 +## Changelog -## Links - -Special thanks for *.c file in https://github.com/DarkFlippers/unleashed-firmware.git +v2.0 - Initial release, +v2.1 - Various important fixes, +v2.2 - Sync updates and latest API support +v2.3 - Latest API support and various important fixes (see 'Features') -## Free Beer +## Links -You can support me by using this link: -(only RU payments accepted) -https://yoomoney.ru/to/410018138145748/100 \ No newline at end of file +Special thanks for *.c files in: +https://github.com/DarkFlippers/unleashed-firmware.git +https://github.com/xMasterX/all-the-plugins/tree/dev \ No newline at end of file diff --git a/Snake 2.0.png b/Snake 2.0.png index a4cf48a28b0..340e71962ce 100644 Binary files a/Snake 2.0.png and b/Snake 2.0.png differ diff --git a/application.fam b/application.fam index 2ee25d04685..92066dd1293 100644 --- a/application.fam +++ b/application.fam @@ -1,12 +1,16 @@ App( - appid="Snake20", + appid="snake20", name="Snake 2.0", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="snake_20_app", cdefines=["APP_SNAKE_20"], requires=["gui"], stack_size=1 * 1024, order=30, fap_icon="snake_10px.png", - fap_category="Games_Extra", -) \ No newline at end of file + fap_category="Games", + fap_author="@Willzvul", + fap_weburl="https://github.com/Willzvul/Snake_2.0", + fap_version="2.3", + fap_description="Advanced Snake Game (Remake of original Snake)", +) diff --git a/changelog.md b/changelog.md new file mode 100644 index 00000000000..1ea34d596f0 --- /dev/null +++ b/changelog.md @@ -0,0 +1,13 @@ +v2.2: +1. Pause of game proccess. +2. Possibility of speed Up by holding the arrows. +3. Snake has a head. +4. The target is an apple. +5. Complete progress bar (%). + +v2.3: +1. Saving progress when exiting and loading it when starting the game. +2. Endless mode switch (If turned ON -> the game continues even if the snake collides with the tail or the frame, so the player gets a chance to win the game). +3. Game timer. +4. Breaking by holding the back-direction arrow. +5. Brand-new system of the next random fruit positioning (it fixed the major bug when the fruit appeared in the left-upper corner of a screen despite the fact that the field (0,0) is already taken by the snake's body). \ No newline at end of file diff --git a/img/1.png b/img/1.png new file mode 100644 index 00000000000..552cad3afa2 Binary files /dev/null and b/img/1.png differ diff --git a/img/2.png b/img/2.png new file mode 100644 index 00000000000..8fcadde62b1 Binary files /dev/null and b/img/2.png differ diff --git a/img/3.png b/img/3.png new file mode 100644 index 00000000000..340e71962ce Binary files /dev/null and b/img/3.png differ diff --git a/snake_20.c b/snake_20.c index f44f9ca9d05..32f43b6493b 100644 --- a/snake_20.c +++ b/snake_20.c @@ -1,10 +1,12 @@ #include +#include #include #include #include #include #include #include +#include typedef struct { // +-----x @@ -18,21 +20,10 @@ typedef struct { 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, @@ -40,18 +31,30 @@ typedef enum { DirectionLeft, } Direction; -#define MAX_SNAKE_LEN 15 * 31 //128 * 64 / 4 +#define MAX_SNAKE_LEN 15 * 31 //128 * 64 / 4 - 1px border line #define x_back_symbol 50 #define y_back_symbol 9 +#define x_arrow_left 81 +#define y_arrow_left 20 + +#define x_arrow_right 104 +#define y_arrow_right 20 + +#define SAVING_FILENAME APP_DATA_PATH("snake2.save") + typedef struct { + FuriMutex* mutex; Point points[MAX_SNAKE_LEN]; uint16_t len; Direction currentMovement; Direction nextMovement; // if backward of currentMovement, ignore Point fruit; GameState state; + bool Endlessmode; + uint32_t timer_start_timestamp; + uint32_t timer_stopped_seconds; } SnakeState; typedef enum { @@ -97,24 +100,30 @@ const NotificationSequence sequence_eat = { }; 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; - } + furi_assert(ctx); + const SnakeState* snake_state = ctx; + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); // 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); + canvas_draw_dot(canvas, f.x + 3, f.y - 1); + canvas_draw_dot(canvas, f.x + 4, f.y - 2); + + //Dot in the middle of an apple (just to know if the Endless mode is turn off) + if(!snake_state->Endlessmode) { + canvas_draw_dot(canvas, f.x + 2, f.y + 2); + canvas_draw_dot(canvas, f.x + 2, f.y + 3); + canvas_draw_dot(canvas, f.x + 3, f.y + 2); + canvas_draw_dot(canvas, f.x + 3, f.y + 3); + } // Snake for(uint16_t i = 0; i < snake_state->len; i++) { @@ -122,9 +131,9 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) { 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){ + if(i == 0) { canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas,p.x+1,p.y+1,2,2); + canvas_draw_box(canvas, p.x + 1, p.y + 1, 2, 2); canvas_set_color(canvas, ColorBlack); } } @@ -133,72 +142,164 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) { 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_draw_box(canvas, 33, 23, 64, 26); canvas_set_color(canvas, ColorBlack); - canvas_draw_frame(canvas, 34, 20, 62, 24); + canvas_draw_frame(canvas, 34, 24, 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 == GameStateGameOver) { + if(snake_state->len >= MAX_SNAKE_LEN-1) { + canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "You WON!"); + } else { + canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "Game Over"); + } } - if(snake_state->state == GameStatePause){ - canvas_draw_str_aligned(canvas, 65, 31, AlignCenter, AlignBottom, "Pause"); + if(snake_state->state == GameStatePause) { + canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "Pause"); } canvas_set_font(canvas, FontSecondary); - char buffer[20]; + char buffer[40]; snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U); - canvas_draw_str_aligned(canvas, 65, 41, AlignCenter, AlignBottom, buffer); + canvas_draw_str_aligned(canvas, 65, 45, AlignCenter, AlignBottom, buffer); - // Painting "back"-symbol, Help message for Exit App, ProgressBar + // Painting "back"-symbol, Help message for Exit App, ProgressBar (Complete %) canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 25, 2, 81, 11); - canvas_draw_box(canvas, 28, 54, 73, 9); + canvas_draw_box(canvas, 22, 1, 87, 22); + canvas_draw_box(canvas, 24, 54, 83, 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, 10, AlignCenter, AlignBottom, "Hold to Exit App"); + //Endless mode ON/OFF + if(snake_state->Endlessmode == false) { + canvas_draw_str_aligned(canvas, 24, 21, AlignLeft, AlignBottom, "Endless mode OFF"); + } else { + canvas_draw_str_aligned(canvas, 24, 21, AlignLeft, AlignBottom, "Endless mode"); + canvas_draw_str_aligned(canvas, 89, 21, AlignLeft, AlignBottom, "ON"); + } + + snprintf( + buffer, + sizeof(buffer), + "%-5.1f%% (%.2ld:%.2ld:%.2ld)", + (double)((snake_state->len - 7U) / 4.58), + snake_state->timer_stopped_seconds / 60 / 60, + snake_state->timer_stopped_seconds / 60 % 60, + snake_state->timer_stopped_seconds % 60); + + //Back symbol + { + 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); + } + //Left Arrow 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); + canvas_draw_dot(canvas, x_arrow_left + 0, y_arrow_left - 3); + canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 2); + canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 3); + canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 4); + canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 1); + canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 2); + canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 3); + canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 4); + canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 5); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 0); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 1); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 2); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 3); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 4); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 5); + canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 6); } + //Right Arrow + canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer); + { + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 0); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 1); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 2); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 3); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 4); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 5); + canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 6); + canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 1); + canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 2); + canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 3); + canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 4); + canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 5); + canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 2); + canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 3); + canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 4); + canvas_draw_dot(canvas, x_arrow_right + 3, y_arrow_right - 3); + } } - release_mutex((ValueMutex*)ctx, snake_state); + furi_mutex_release(snake_state->mutex); } -static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); +bool load_game(SnakeState* snake_state) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* file = storage_file_alloc(storage); + uint16_t bytes_readed = 0; + if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) { + bytes_readed = storage_file_read(file, snake_state, sizeof(SnakeState)); + } + storage_file_close(file); + storage_file_free(file); + + furi_record_close(RECORD_STORAGE); + + return bytes_readed == sizeof(SnakeState); +} + +void save_game(const SnakeState* snake_state) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, snake_state, sizeof(SnakeState)); + } + storage_file_close(file); + storage_file_free(file); + + furi_record_close(RECORD_STORAGE); +} + +static void snake_game_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; 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); +static void snake_game_update_timer_callback(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; SnakeEvent event = {.type = EventTypeTick}; furi_message_queue_put(event_queue, &event, 0); @@ -217,49 +318,56 @@ static void snake_game_init_game(SnakeState* const snake_state) { Point f = {18, 6}; snake_state->fruit = f; + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_stopped_seconds = 0; + snake_state->timer_start_timestamp = curr_ts; + snake_state->state = GameStateLife; + + save_game(snake_state); } 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; + // Max number of fruits on x axis = (16 * 2) - 1 = 31 (0<=x=>30) + // Max number of fruits on y axis = (8 * 2) - 1 = 15 (0<=y=>14) + // Total fields for fruits and snake body = 31 * 15 = 465 + // Empty fields for next random fruit = 465 - len(snake) + + bool* all_fields; + int* empty_fields; + all_fields = (bool*)malloc(MAX_SNAKE_LEN * sizeof(bool)); + empty_fields = (int*)malloc(MAX_SNAKE_LEN * sizeof(int)); + + for(uint16_t j = 0; j < MAX_SNAKE_LEN; j++) { + all_fields[j] = false; + } - buffer[p.y] |= 1 << p.x; - empty--; + for(uint16_t j = 0; j < snake_state->len; j++) { + Point p = snake_state->points[j]; + all_fields[p.x + 31 * p.y] = true; } - // 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--; - } + + int empty_counter = 0; + for(uint16_t j = 0; j < MAX_SNAKE_LEN; j++) { + if(!all_fields[j]) { + empty_fields[empty_counter] = j; + empty_counter++; } } - // We will never be here - Point p = {0, 0}; + + int newFruit = rand() % empty_counter; + + Point p = { + .x = empty_fields[newFruit] % 31, + .y = empty_fields[newFruit] / 31, + }; + + free(all_fields); + free(empty_fields); + return p; } @@ -331,7 +439,17 @@ static void snake_state->state = GameStateLastChance; return; } else if(snake_state->state == GameStateLastChance) { - snake_state->state = GameStateGameOver; + if(snake_state->Endlessmode) { + snake_state->state = GameStateLastChance; + } else { + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp; + + snake_state->state = GameStateGameOver; + } notification_message_block(notification, &sequence_fail); return; } @@ -343,7 +461,17 @@ static void crush = snake_game_collision_with_tail(snake_state, next_step); if(crush) { - snake_state->state = GameStateGameOver; + if(snake_state->Endlessmode) { + snake_state->state = GameStateLastChance; + } else { + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp; + + snake_state->state = GameStateGameOver; + } notification_message_block(notification, &sequence_fail); return; } @@ -351,8 +479,23 @@ static void 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) { + + if(snake_state->len >= MAX_SNAKE_LEN-1) { //You win!!! + //It's impossible to collect ALL fruits, because + //the number of rows is odd (15), + //the number of columnss is odd too (31). + //You just can't locate the snake's body + //on the odd number of cells. + //Because of it you win when you collect + //all but one fruits. + + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp; + snake_state->state = GameStateGameOver; notification_message_block(notification, &sequence_fail); return; @@ -374,10 +517,19 @@ int32_t snake_20_app(void* p) { FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); SnakeState* snake_state = malloc(sizeof(SnakeState)); - snake_game_init_game(snake_state); + if(!load_game(snake_state)) { + snake_game_init_game(snake_state); + } else { + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_start_timestamp = curr_ts - snake_state->timer_stopped_seconds; + snake_state->state = GameStateLife; + } - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) { + snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!snake_state->mutex) { FURI_LOG_E("SnakeGame", "cannot create mutex\r\n"); furi_message_queue_free(event_queue); free(snake_state); @@ -385,7 +537,7 @@ int32_t snake_20_app(void* p) { } ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex); + view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state); view_port_input_callback_set(view_port, snake_game_input_callback, event_queue); FuriTimer* timer = @@ -399,13 +551,13 @@ int32_t snake_20_app(void* p) { notification_message_block(notification, &sequence_display_backlight_enforce_on); - DOLPHIN_DEED(DolphinDeedPluginGameStart); + 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); + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); if(event_status == FuriStatusOk) { if(event.type == EventTypeKey) { @@ -423,12 +575,18 @@ int32_t snake_20_app(void* p) { } break; case InputKeyRight: - if(snake_state->state != GameStatePause) { + if(snake_state->state == GameStatePause || + snake_state->state == GameStateGameOver) { + snake_state->Endlessmode = !snake_state->Endlessmode; + } else { snake_state->nextMovement = DirectionRight; } break; case InputKeyLeft: - if(snake_state->state != GameStatePause) { + if(snake_state->state == GameStatePause || + snake_state->state == GameStateGameOver) { + snake_state->Endlessmode = !snake_state->Endlessmode; + } else { snake_state->nextMovement = DirectionLeft; } break; @@ -437,6 +595,13 @@ int32_t snake_20_app(void* p) { snake_game_init_game(snake_state); } if(snake_state->state == GameStatePause) { + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_start_timestamp = + curr_ts - snake_state->timer_stopped_seconds; + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); snake_state->state = GameStateLife; } @@ -445,61 +610,94 @@ int32_t snake_20_app(void* p) { if(snake_state->state == GameStateLife) { furi_timer_stop(timer); snake_state->state = GameStatePause; + + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt); + + snake_state->timer_stopped_seconds = + curr_ts - snake_state->timer_start_timestamp; + 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){ + 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); + snake_state->nextMovement = DirectionUp; + //Speed Up + if(snake_state->currentMovement == DirectionUp) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + } + //Breaking + if(snake_state->currentMovement == DirectionDown) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2); + } } break; case InputKeyDown: if(snake_state->state != GameStatePause) { - snake_state->nextMovement = DirectionDown; - furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + snake_state->nextMovement = DirectionDown; + //Speed Up + if(snake_state->currentMovement == DirectionDown) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + } + //Breaking + if(snake_state->currentMovement == DirectionUp) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2); + } } break; case InputKeyRight: if(snake_state->state != GameStatePause) { - snake_state->nextMovement = DirectionRight; - furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + snake_state->nextMovement = DirectionRight; + //Speed Up + if(snake_state->currentMovement == DirectionRight) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + } + //Breaking + if(snake_state->currentMovement == DirectionLeft) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2); + } } break; case InputKeyLeft: if(snake_state->state != GameStatePause) { - snake_state->nextMovement = DirectionLeft; - furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + snake_state->nextMovement = DirectionLeft; + //Speed Up + if(snake_state->currentMovement == DirectionLeft) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); + } + //Breaking + if(snake_state->currentMovement == DirectionRight) { + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2); + } } break; case InputKeyBack: - processing = false; + if(snake_state->state == GameStatePause || + snake_state->state == GameStateGameOver) { + save_game(snake_state); + processing = false; + } else { + snake_state->state = GameStateGameOver; + } 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); + //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); } @@ -507,8 +705,8 @@ int32_t snake_20_app(void* p) { // event timeout } + furi_mutex_release(snake_state->mutex); view_port_update(view_port); - release_mutex(&state_mutex, snake_state); } // Wait for all notifications to be played and return backlight to normal state @@ -521,8 +719,8 @@ int32_t snake_20_app(void* p) { furi_record_close(RECORD_NOTIFICATION); view_port_free(view_port); furi_message_queue_free(event_queue); - delete_mutex(&state_mutex); + furi_mutex_free(snake_state->mutex); free(snake_state); return 0; -} \ No newline at end of file +}