Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FL-1632] Dolphin: feed mini game #627

Merged
merged 12 commits into from
Aug 11, 2021
3 changes: 2 additions & 1 deletion applications/applications.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern int32_t lfrfid_app(void* p);
extern int32_t lfrfid_debug_app(void* p);
extern int32_t nfc_app(void* p);
extern int32_t passport_app(void* p);
extern int32_t food_minigame_app(void* p);
extern int32_t scene_app(void* p);
extern int32_t scened_app(void* p);
extern int32_t storage_test_app(void* p);
Expand Down Expand Up @@ -253,7 +254,7 @@ const FlipperApplication FLIPPER_SCENE =

const FlipperApplication FLIPPER_SCENE_APPS[] = {
{.app = passport_app, .name = "Passport", .stack_size = 1024, .icon = &A_Games_14},
{.app = music_player_app, .name = "Music player", .stack_size = 1024, .icon = &A_Plugins_14},
{.app = food_minigame_app, .name = "Food minigame", .stack_size = 1024, .icon = &A_Games_14},
};

const size_t FLIPPER_SCENE_APPS_COUNT = sizeof(FLIPPER_SCENE_APPS) / sizeof(FlipperApplication);
Expand Down
321 changes: 321 additions & 0 deletions applications/dolphin/games/food.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
#include <furi.h>
#include <gui/gui.h>
#include "dolphin/dolphin_state.h"

#define MAX_TRIES 3
#define DISHES_TOTAL 3
#define LID_POS_MAX 20
#define TRY_TIMEOUT 10

typedef enum {
EventTypeTick,
EventTypeKey,
EventTypeDeed,
} EventType;

typedef struct {
union {
InputEvent input;
} value;
EventType type;
} AppEvent;

typedef enum {
PlayerChoiceEvent,
OpenLootEvent,
WinEvent,
LooseEvent,
FinishedEvent,
ExitGameEvent,
GameEventTotal,
} GameEventType;

typedef enum {
LootSkeleton,
LootFish,
LootShit,
LootTotalNum,
} LootIdEnum;

typedef struct {
GameEventType current_event;
osMessageQueueId_t event_queue;
LootIdEnum loot_list[DISHES_TOTAL];

uint8_t cursor_pos;
uint8_t lid_pos;
uint8_t timeout;
uint8_t try;

bool selected;
bool deed;

} GameState;

typedef struct {
const Icon* f;
const Icon* b;
} LootGfx;

static const Icon* letters[DISHES_TOTAL] = {&I_letterA_10x10, &I_letterB_10x10, &I_letterC_10x10};

static const LootGfx loot[LootTotalNum] = {
[LootSkeleton] =
{
.f = &I_skeleton_25x17,
.b = &I_blackskeleton_25x17,
},
[LootFish] =
{
.f = &I_fish_25x17,
.b = &I_blackfish_25x17,
},
[LootShit] =
{
.f = &I_shit_25x17,
.b = &I_blackshit_25x17,
},
};

static void input_callback(InputEvent* input_event, void* ctx) {
osMessageQueueId_t event_queue = ctx;
AppEvent event;
event.type = EventTypeKey;
event.value.input = *input_event;
osMessageQueuePut(event_queue, &event, 0, osWaitForever);
}

static void draw_dish(Canvas* canvas, GameState* state, uint8_t x, uint8_t y, uint8_t id) {
bool active = state->cursor_pos == id;
bool opened = state->current_event == OpenLootEvent && active;

canvas_set_bitmap_mode(canvas, true);
canvas_set_color(canvas, ColorBlack);

if(active) {
canvas_draw_icon(canvas, x, y, &I_active_plate_48x18);
}

if(opened) {
state->lid_pos = CLAMP(state->lid_pos + 1, LID_POS_MAX, 0);
}

uint8_t lid_pos = (y - 17) - (opened ? state->lid_pos : 0);

canvas_draw_icon(canvas, x + 3, y - 6, &I_plate_42x19);

canvas_set_color(canvas, ColorWhite);
canvas_draw_icon(canvas, x + 11, y - 10, loot[state->loot_list[id]].b);
canvas_set_color(canvas, ColorBlack);
canvas_draw_icon(canvas, x + 11, y - 10, loot[state->loot_list[id]].f);

canvas_set_color(canvas, ColorWhite);
canvas_draw_icon(canvas, x + 6, lid_pos, &I_blacklid_36x27);
canvas_set_color(canvas, ColorBlack);
canvas_draw_icon(canvas, x + 6, lid_pos, &I_lid_36x27);
canvas_set_bitmap_mode(canvas, false);

canvas_draw_icon(canvas, x + 19, y + 8, letters[id]);
}

static void draw_dishes_scene(Canvas* canvas, GameState* state) {
uint8_t tries_left = MAX_TRIES - state->try;
for(size_t i = 0; i < MAX_TRIES; i++) {
if(i < tries_left) {
canvas_draw_disc(canvas, 5 + i * 8, 5, 2);
} else {
canvas_draw_circle(canvas, 5 + i * 8, 5, 2);
}
}

for(size_t i = 0; i < DISHES_TOTAL; i++) {
draw_dish(canvas, state, i * 40, i % 2 ? 26 : 44, i);
}
}

static void render_callback(Canvas* canvas, void* ctx) {
GameState* state = (GameState*)acquire_mutex((ValueMutex*)ctx, 25);
canvas_clear(canvas);

switch(state->current_event) {
case WinEvent:
canvas_draw_str(canvas, 30, 30, "Dolphin_happy.png");
break;
case LooseEvent:
canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, "Try again!");
break;
case ExitGameEvent:
break;
case FinishedEvent:
break;
default:
draw_dishes_scene(canvas, state);
break;
}

release_mutex((ValueMutex*)ctx, state);
}
static void reset_lid_pos(GameState* state) {
state->selected = false;
state->lid_pos = 0;
}

void dolphin_food_deed(GameState* state) {
furi_assert(state);
AppEvent event;
event.type = EventTypeDeed;
furi_check(osMessageQueuePut(state->event_queue, &event, 0, osWaitForever) == osOK);
}

static void reset_loot_array(GameState* state) {
for(size_t i = 0; i < LootTotalNum; i++) {
state->loot_list[i] = i;
}

for(size_t i = 0; i < LootTotalNum; i++) {
int temp = state->loot_list[i];
int r_idx = rand() % LootTotalNum;

state->loot_list[i] = state->loot_list[r_idx];
state->loot_list[r_idx] = temp;
}
}

static bool selected_is_food(GameState* state) {
return state->loot_list[state->cursor_pos] == LootFish;
}

static bool tries_exceed(GameState* state) {
return state->try == MAX_TRIES;
}

static bool timeout_exceed(GameState* state) {
return state->timeout == TRY_TIMEOUT;
}

static void gamestate_update(GameState* state, DolphinState* dolphin_state) {
switch(state->current_event) {
case PlayerChoiceEvent:
if(state->selected) {
state->current_event = OpenLootEvent;
}
break;
case OpenLootEvent:
state->timeout = CLAMP(state->timeout + 1, TRY_TIMEOUT, 0);
if(timeout_exceed(state)) {
state->timeout = 0;
state->current_event = selected_is_food(state) ? WinEvent : LooseEvent;
state->deed = selected_is_food(state);
}
break;
case LooseEvent:
state->timeout = CLAMP(state->timeout + 1, TRY_TIMEOUT, 0);
if(timeout_exceed(state)) {
state->timeout = 0;
state->current_event = FinishedEvent;
}
break;
case WinEvent:
if(state->deed) {
dolphin_food_deed(state);
}
break;
case FinishedEvent:
reset_lid_pos(state);
reset_loot_array(state);

state->try++;
state->current_event = tries_exceed(state) ? ExitGameEvent : PlayerChoiceEvent;
break;

default:
break;
}
}

static void food_minigame_controls(GameState* state, AppEvent* event) {
furi_assert(state);
furi_assert(event);

if(event->value.input.key == InputKeyRight) {
if(state->current_event == PlayerChoiceEvent) {
state->cursor_pos = CLAMP(state->cursor_pos + 1, DISHES_TOTAL - 1, 0);
}
} else if(event->value.input.key == InputKeyLeft) {
if(state->current_event == PlayerChoiceEvent) {
state->cursor_pos = CLAMP(state->cursor_pos - 1, DISHES_TOTAL - 1, 0);
}
} else if(event->value.input.key == InputKeyOk) {
switch(state->current_event) {
case PlayerChoiceEvent:
state->selected = true;
break;
case WinEvent:
state->current_event = FinishedEvent;
break;
default:
break;
}
}
}

int32_t food_minigame_app(void* p) {
GameState* state = furi_alloc(sizeof(GameState));
DolphinState* dolphin_state = dolphin_state_alloc();
dolphin_state_load(dolphin_state);

ValueMutex state_mutex;

state->event_queue = osMessageQueueNew(2, sizeof(AppEvent), NULL);

furi_check(state->event_queue);

if(!init_mutex(&state_mutex, state, sizeof(GameState*))) {
printf("[Food minigame] cannot create mutex\r\n");
return 0;
}

ViewPort* view_port = view_port_alloc();

view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, state->event_queue);

Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);

reset_loot_array(state);

AppEvent event;
while(1) {
osStatus_t event_status = osMessageQueueGet(state->event_queue, &event, NULL, 100);
if(event_status == osOK) {
if(event.type == EventTypeKey && event.value.input.type == InputTypeShort) {
food_minigame_controls(state, &event);

if(event.value.input.key == InputKeyBack) {
break;
}
} else if(event.type == EventTypeDeed) {
dolphin_state_on_deed(dolphin_state, DolphinDeedIButtonRead);
dolphin_state_save(dolphin_state);
state->deed = false;
}
}

if(state->current_event == ExitGameEvent) {
break;
}
gamestate_update(state, dolphin_state);
view_port_update(view_port);
}

gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close("gui");
delete_mutex(&state_mutex);
osMessageQueueDelete(state->event_queue);
dolphin_state_free(dolphin_state);
free(state);

return 0;
}
2 changes: 1 addition & 1 deletion applications/dolphin/scenes/assets/items.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,6 @@ void console_callback(Canvas* canvas, void* s) {
furi_assert(s);
SceneState* state = s;
if(state->use_pending) {
dolphin_scene_start_app(state, &FLIPPER_SCENE_APPS[1]);
dolphin_scene_start_app(state, &FLIPPER_SCENE_APPS[0]);
}
}