|
@@ -1,10 +1,12 @@
|
|
|
#include <furi.h>
|
|
#include <furi.h>
|
|
|
|
|
+#include <furi_hal.h>
|
|
|
#include <gui/gui.h>
|
|
#include <gui/gui.h>
|
|
|
#include <input/input.h>
|
|
#include <input/input.h>
|
|
|
#include <stdlib.h>
|
|
#include <stdlib.h>
|
|
|
#include <dolphin/dolphin.h>
|
|
#include <dolphin/dolphin.h>
|
|
|
#include <notification/notification.h>
|
|
#include <notification/notification.h>
|
|
|
#include <notification/notification_messages.h>
|
|
#include <notification/notification_messages.h>
|
|
|
|
|
+#include <storage/storage.h>
|
|
|
|
|
|
|
|
typedef struct {
|
|
typedef struct {
|
|
|
// +-----x
|
|
// +-----x
|
|
@@ -18,21 +20,10 @@ typedef struct {
|
|
|
typedef enum {
|
|
typedef enum {
|
|
|
GameStateLife,
|
|
GameStateLife,
|
|
|
GameStatePause,
|
|
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,
|
|
GameStateLastChance,
|
|
|
GameStateGameOver,
|
|
GameStateGameOver,
|
|
|
} GameState;
|
|
} GameState;
|
|
|
|
|
|
|
|
-// Note: do not change without purpose. Current values are used in smart
|
|
|
|
|
-// orthogonality calculation in `snake_game_get_turn_snake`.
|
|
|
|
|
typedef enum {
|
|
typedef enum {
|
|
|
DirectionUp,
|
|
DirectionUp,
|
|
|
DirectionRight,
|
|
DirectionRight,
|
|
@@ -40,11 +31,19 @@ typedef enum {
|
|
|
DirectionLeft,
|
|
DirectionLeft,
|
|
|
} Direction;
|
|
} 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 x_back_symbol 50
|
|
|
#define y_back_symbol 9
|
|
#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 {
|
|
typedef struct {
|
|
|
FuriMutex* mutex;
|
|
FuriMutex* mutex;
|
|
|
Point points[MAX_SNAKE_LEN];
|
|
Point points[MAX_SNAKE_LEN];
|
|
@@ -53,6 +52,9 @@ typedef struct {
|
|
|
Direction nextMovement; // if backward of currentMovement, ignore
|
|
Direction nextMovement; // if backward of currentMovement, ignore
|
|
|
Point fruit;
|
|
Point fruit;
|
|
|
GameState state;
|
|
GameState state;
|
|
|
|
|
+ bool Endlessmode;
|
|
|
|
|
+ uint32_t timer_start_timestamp;
|
|
|
|
|
+ uint32_t timer_stopped_seconds;
|
|
|
} SnakeState;
|
|
} SnakeState;
|
|
|
|
|
|
|
|
typedef enum {
|
|
typedef enum {
|
|
@@ -114,7 +116,14 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
|
|
canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
|
|
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 + 3, f.y - 1);
|
|
|
canvas_draw_dot(canvas, f.x + 4, f.y - 2);
|
|
canvas_draw_dot(canvas, f.x + 4, f.y - 2);
|
|
|
- //canvas_draw_dot(canvas,f.x+4,f.y-3);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ //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
|
|
// Snake
|
|
|
for(uint16_t i = 0; i < snake_state->len; i++) {
|
|
for(uint16_t i = 0; i < snake_state->len; i++) {
|
|
@@ -133,34 +142,53 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
|
|
if(snake_state->state == GameStatePause || snake_state->state == GameStateGameOver) {
|
|
if(snake_state->state == GameStatePause || snake_state->state == GameStateGameOver) {
|
|
|
// Screen is 128x64 px
|
|
// Screen is 128x64 px
|
|
|
canvas_set_color(canvas, ColorWhite);
|
|
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_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);
|
|
canvas_set_font(canvas, FontPrimary);
|
|
|
if(snake_state->state == GameStateGameOver) {
|
|
if(snake_state->state == GameStateGameOver) {
|
|
|
- canvas_draw_str_aligned(canvas, 65, 31, AlignCenter, AlignBottom, "Game Over");
|
|
|
|
|
|
|
+ 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) {
|
|
if(snake_state->state == GameStatePause) {
|
|
|
- canvas_draw_str_aligned(canvas, 65, 31, AlignCenter, AlignBottom, "Pause");
|
|
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "Pause");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
- char buffer[20];
|
|
|
|
|
|
|
+ char buffer[40];
|
|
|
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
|
|
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_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_set_color(canvas, ColorBlack);
|
|
|
canvas_draw_str_aligned(
|
|
canvas_draw_str_aligned(
|
|
|
canvas, 65, 10, AlignCenter, AlignBottom, "Hold to Exit App");
|
|
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(
|
|
snprintf(
|
|
|
- buffer, sizeof(buffer), "Complete: %-5.1f%%", (double)((snake_state->len - 7U) / 4.58));
|
|
|
|
|
- canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer);
|
|
|
|
|
|
|
+ buffer,
|
|
|
|
|
+ sizeof(buffer),
|
|
|
|
|
+ "%-5.1f%% (%.2ld:%.2ld:%.2ld)",
|
|
|
|
|
+ (double)((snake_state->len - 7U) / 4.57),
|
|
|
|
|
+ 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 + 0, y_back_symbol);
|
|
|
canvas_draw_dot(canvas, x_back_symbol + 1, y_back_symbol);
|
|
canvas_draw_dot(canvas, x_back_symbol + 1, y_back_symbol);
|
|
@@ -186,11 +214,81 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
|
|
canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 7);
|
|
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_back_symbol - 1, y_back_symbol - 3);
|
|
|
}
|
|
}
|
|
|
|
|
+ //Left Arrow
|
|
|
|
|
+ canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer);
|
|
|
|
|
+ {
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
furi_mutex_release(snake_state->mutex);
|
|
furi_mutex_release(snake_state->mutex);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+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) {
|
|
static void snake_game_input_callback(InputEvent* input_event, void* ctx) {
|
|
|
furi_assert(ctx);
|
|
furi_assert(ctx);
|
|
|
FuriMessageQueue* event_queue = ctx;
|
|
FuriMessageQueue* event_queue = ctx;
|
|
@@ -220,49 +318,56 @@ static void snake_game_init_game(SnakeState* const snake_state) {
|
|
|
Point f = {18, 6};
|
|
Point f = {18, 6};
|
|
|
snake_state->fruit = f;
|
|
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;
|
|
snake_state->state = GameStateLife;
|
|
|
|
|
+
|
|
|
|
|
+ save_game(snake_state);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static Point snake_game_get_new_fruit(SnakeState const* const 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;
|
|
return p;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -334,7 +439,17 @@ static void
|
|
|
snake_state->state = GameStateLastChance;
|
|
snake_state->state = GameStateLastChance;
|
|
|
return;
|
|
return;
|
|
|
} else if(snake_state->state == GameStateLastChance) {
|
|
} 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);
|
|
notification_message_block(notification, &sequence_fail);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -346,7 +461,17 @@ static void
|
|
|
|
|
|
|
|
crush = snake_game_collision_with_tail(snake_state, next_step);
|
|
crush = snake_game_collision_with_tail(snake_state, next_step);
|
|
|
if(crush) {
|
|
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);
|
|
notification_message_block(notification, &sequence_fail);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -354,8 +479,23 @@ static void
|
|
|
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
|
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
|
|
if(eatFruit) {
|
|
if(eatFruit) {
|
|
|
snake_state->len++;
|
|
snake_state->len++;
|
|
|
- if(snake_state->len >= MAX_SNAKE_LEN) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if(snake_state->len >= MAX_SNAKE_LEN - 1) {
|
|
|
//You win!!!
|
|
//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;
|
|
snake_state->state = GameStateGameOver;
|
|
|
notification_message_block(notification, &sequence_fail);
|
|
notification_message_block(notification, &sequence_fail);
|
|
|
return;
|
|
return;
|
|
@@ -377,7 +517,16 @@ int32_t snake_20_app(void* p) {
|
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
|
|
|
|
|
|
|
|
SnakeState* snake_state = malloc(sizeof(SnakeState));
|
|
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;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
|
if(!snake_state->mutex) {
|
|
if(!snake_state->mutex) {
|
|
@@ -426,12 +575,18 @@ int32_t snake_20_app(void* p) {
|
|
|
}
|
|
}
|
|
|
break;
|
|
break;
|
|
|
case InputKeyRight:
|
|
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;
|
|
snake_state->nextMovement = DirectionRight;
|
|
|
}
|
|
}
|
|
|
break;
|
|
break;
|
|
|
case InputKeyLeft:
|
|
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;
|
|
snake_state->nextMovement = DirectionLeft;
|
|
|
}
|
|
}
|
|
|
break;
|
|
break;
|
|
@@ -440,6 +595,13 @@ int32_t snake_20_app(void* p) {
|
|
|
snake_game_init_game(snake_state);
|
|
snake_game_init_game(snake_state);
|
|
|
}
|
|
}
|
|
|
if(snake_state->state == GameStatePause) {
|
|
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);
|
|
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
|
|
snake_state->state = GameStateLife;
|
|
snake_state->state = GameStateLife;
|
|
|
}
|
|
}
|
|
@@ -448,16 +610,16 @@ int32_t snake_20_app(void* p) {
|
|
|
if(snake_state->state == GameStateLife) {
|
|
if(snake_state->state == GameStateLife) {
|
|
|
furi_timer_stop(timer);
|
|
furi_timer_stop(timer);
|
|
|
snake_state->state = GameStatePause;
|
|
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;
|
|
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:
|
|
default:
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -468,29 +630,63 @@ int32_t snake_20_app(void* p) {
|
|
|
case InputKeyUp:
|
|
case InputKeyUp:
|
|
|
if(snake_state->state != GameStatePause) {
|
|
if(snake_state->state != GameStatePause) {
|
|
|
snake_state->nextMovement = DirectionUp;
|
|
snake_state->nextMovement = DirectionUp;
|
|
|
- furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
|
|
+ //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;
|
|
break;
|
|
|
case InputKeyDown:
|
|
case InputKeyDown:
|
|
|
if(snake_state->state != GameStatePause) {
|
|
if(snake_state->state != GameStatePause) {
|
|
|
snake_state->nextMovement = DirectionDown;
|
|
snake_state->nextMovement = DirectionDown;
|
|
|
- furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
|
|
+ //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;
|
|
break;
|
|
|
case InputKeyRight:
|
|
case InputKeyRight:
|
|
|
if(snake_state->state != GameStatePause) {
|
|
if(snake_state->state != GameStatePause) {
|
|
|
snake_state->nextMovement = DirectionRight;
|
|
snake_state->nextMovement = DirectionRight;
|
|
|
- furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
|
|
+ //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;
|
|
break;
|
|
|
case InputKeyLeft:
|
|
case InputKeyLeft:
|
|
|
if(snake_state->state != GameStatePause) {
|
|
if(snake_state->state != GameStatePause) {
|
|
|
snake_state->nextMovement = DirectionLeft;
|
|
snake_state->nextMovement = DirectionLeft;
|
|
|
- furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
|
|
|
|
|
|
|
+ //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;
|
|
break;
|
|
|
case InputKeyBack:
|
|
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;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
break;
|
|
break;
|