Sfoglia il codice sorgente

Add snake_2 from https://github.com/Willzvul/Snake_2.0

git-subtree-dir: snake_2
git-subtree-mainline: cdb0813192da2d6649c45043d06e482f6cb715b1
git-subtree-split: 22bc45867f4b8a1c5e6544f9392d051a1c986b75
Willy-JL 2 anni fa
parent
commit
6b6cc70dcc

+ 52 - 0
snake_2/.gitignore

@@ -0,0 +1,52 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf

+ 1 - 0
snake_2/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/Willzvul/Snake_2.0 main

+ 28 - 0
snake_2/ReadMe.md

@@ -0,0 +1,28 @@
+# SNAKE 2.0
+
+A new version of Snake Game.
+
+![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
+3. Snake has a head.
+4. The target is an apple
+5. Complete progress bar
+
+## Building FAP
+
+https://fap.playmean.xyz/Willzvul/Snake_2.0
+
+## Links
+
+Special thanks for *.c file in https://github.com/DarkFlippers/unleashed-firmware.git
+
+## Free Beer
+
+You can support me by using this link:
+(only RU payments accepted) 
+https://yoomoney.ru/to/410018138145748/100

BIN
snake_2/Snake 2.0.png


+ 12 - 0
snake_2/application.fam

@@ -0,0 +1,12 @@
+App(
+    appid="Snake20",
+    name="Snake 2.0",
+    apptype=FlipperAppType.PLUGIN,
+    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",
+)

BIN
snake_2/snake_10px.png


+ 528 - 0
snake_2/snake_20.c

@@ -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;
+}