diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..143847c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: "FAP: Build for multiple SDK sources" +# This will build your app for dev and release channels on GitHub. +# It will also build your app every day to make sure it's up to date with the latest SDK changes. +# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information + +on: + push: + ## put your main branch name under "branches" + #branches: + # - master + pull_request: + schedule: + # do a build every day + - cron: "1 1 * * *" + +jobs: + ufbt-build: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: dev channel + sdk-channel: dev + - name: release channel + sdk-channel: release + # You can add unofficial channels here. See ufbt action docs for more info. + name: 'ufbt: Build for ${{ matrix.name }}' + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build with ufbt + uses: flipperdevices/flipperzero-ufbt-action@v0.1 + id: build-app + with: + sdk-channel: ${{ matrix.sdk-channel }} + - name: Upload app artifacts + uses: actions/upload-artifact@v3 + with: + # See ufbt action docs for other output variables + name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} + path: ${{ steps.build-app.outputs.fap-artifacts }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f36ac90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# 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 + +.vscode/ diff --git a/application.fam b/application.fam new file mode 100644 index 0000000..bfb1419 --- /dev/null +++ b/application.fam @@ -0,0 +1,18 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="minesweeper", # Must be unique + name="Mine Sweeper", # Displayed in menus + apptype=FlipperAppType.EXTERNAL, + entry_point="minesweeper_app", + stack_size=2 * 1024, + fap_category="Games", + + # Optional values + fap_version="0.1", + fap_icon="minesweeper.png", # 10x10 1-bit PNG + fap_description="Flipper Zero Minesweeper Implementation", + fap_author="Alexander Rodriguez", + # fap_weburl="https://github.com/user/minesweeper", + fap_icon_assets="images", # Image assets to compile for this application +) diff --git a/dist/minesweeper.fap b/dist/minesweeper.fap new file mode 100644 index 0000000..fdaa49a Binary files /dev/null and b/dist/minesweeper.fap differ diff --git a/game_screen.c b/game_screen.c new file mode 100644 index 0000000..b6193d1 --- /dev/null +++ b/game_screen.c @@ -0,0 +1,34 @@ +#include "minesweeper.h" + +typedef enum { + None, +} MineSweeperGameEvent; + +static void init_app_state(App* app) { + memset(app->app_state, 0, sizeof(*app->app_state)); +} + +void minesweeper_scenes_game_on_enter(void* context) { + App* app = (App*)context; + + view_dispatcher_switch_to_view(app->view_dispatcher, MineSweeperLoadingView); + + init_app_state(app); + + app->empty_screen_view = empty_screen_get_view(app->empty_screen); + + view_dispatcher_switch_to_view(app->view_dispatcher, MineSweeperEmptyScreenView); +} + +bool minesweeper_scenes_game_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + + bool consumed = false; + return consumed; + } + +void minesweeper_scenes_game_on_exit(void* context) { + UNUSED(context); +} + diff --git a/game_screen.h b/game_screen.h new file mode 100644 index 0000000..45f6502 --- /dev/null +++ b/game_screen.h @@ -0,0 +1,8 @@ +#ifndef GAME_SCREEN_H +#define GAME_SCREEN_H + +void minesweeper_scenes_game_on_enter(void* context); +bool minesweeper_scenes_game_on_event(void* context, SceneManagerEvent); +void minesweeper_scenes_game_on_exit(void* context); + +#endif diff --git a/images/.gitkeep b/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/images/MineSweeperIcon_36x36.png b/images/MineSweeperIcon_36x36.png new file mode 100644 index 0000000..86968a2 Binary files /dev/null and b/images/MineSweeperIcon_36x36.png differ diff --git a/minesweeper.c b/minesweeper.c new file mode 100644 index 0000000..9da2e25 --- /dev/null +++ b/minesweeper.c @@ -0,0 +1,111 @@ +#include + +#include "minesweeper.h" +#include "minesweeper_i.h" + +/* generated by fbt from .png files in images folder */ +#include + +static void (*const minesweeper_scene_on_enter_handlers[])(void*) = { + minesweeper_scenes_startup_scene_on_enter, + minesweeper_scenes_game_on_enter, +}; + +static bool (*const minesweeper_scene_on_event_handlers[])(void*, SceneManagerEvent) = { + minesweeper_scenes_startup_scene_on_event, + minesweeper_scenes_game_on_event, +}; + +static void (*const minesweeper_scene_on_exit_handlers[])(void*) = { + minesweeper_scenes_startup_scene_on_exit, + minesweeper_scenes_game_on_exit, +}; + +static const SceneManagerHandlers minesweeper_scene_manager_handlers = { + .on_enter_handlers = minesweeper_scene_on_enter_handlers, + .on_event_handlers = minesweeper_scene_on_event_handlers, + .on_exit_handlers = minesweeper_scene_on_exit_handlers, + .scene_num = MineSweeperSceneCount, +}; + +static bool minesweeper_custom_callback(void* context, uint32_t custom_event) { + furi_assert(context); + App* app = context; + return scene_manager_handle_custom_event(app->scene_manager, custom_event); +} + +static bool minesweeper_back_event_callback(void* context) { + furi_assert(context); + App* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static App* app_alloc() { + App* app = (App*)malloc(sizeof(App)); + furi_assert(app); + + app->app_state = (AppState*)malloc(sizeof(AppState)); + + app->scene_manager = scene_manager_alloc(&minesweeper_scene_manager_handlers, app); + furi_assert(app->scene_manager); + + app->view_dispatcher = view_dispatcher_alloc(); + furi_assert(app->view_dispatcher); + + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, minesweeper_custom_callback); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, minesweeper_back_event_callback); + + app->popup_view = NULL; + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, MineSweeperPopupView, popup_get_view(app->popup)); + + app->empty_screen_view = NULL; + app->empty_screen = empty_screen_alloc(); + view_dispatcher_add_view(app->view_dispatcher, MineSweeperEmptyScreenView, empty_screen_get_view(app->empty_screen)); + + app->loading = loading_alloc(); + view_dispatcher_add_view(app->view_dispatcher, MineSweeperLoadingView, loading_get_view(app->loading)); + + return app; + +} + +static void app_free(App* app) { + furi_assert(app); + + for (MineSweeperView minesweeper_view = MineSweeperPopupView; minesweeper_view < MineSweeperPopupCount; minesweeper_view++) { + view_dispatcher_remove_view(app->view_dispatcher, minesweeper_view); + } + + scene_manager_free(app->scene_manager); + + view_dispatcher_free(app->view_dispatcher); + + popup_free(app->popup); + empty_screen_free(app->empty_screen); + loading_free(app->loading); + + free(app->app_state); + free(app); + +} + +int32_t minesweeper_app(void* p) { + UNUSED(p); + + App* app = app_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + + view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene(app->scene_manager, MineSweeperStartupScene); + + view_dispatcher_run(app->view_dispatcher); + + app_free(app); + + return 0; +} diff --git a/minesweeper.h b/minesweeper.h new file mode 100644 index 0000000..b62abae --- /dev/null +++ b/minesweeper.h @@ -0,0 +1,87 @@ +#ifndef MINESWEEPER_H +#define MINESWEEPER_H + +#include +#include +#include +#include +#include +#include + +#include // memset +#include // sleep + +#include "minesweeper_icons.h" +//#include +//#include + +#define PLAY_WIDTH 16 +#define PLAY_HEIGHT 7 +#define TILE_WIDTH 8 + +#define MINE_COUNT 20 + +// Views +typedef enum { + MineSweeperPopupView, + MineSweeperLoadingView, + MineSweeperEmptyScreenView, + MineSweeperPopupCount, // Leave at end +} MineSweeperView; + +// Scenes +typedef enum { + MineSweeperStartupScene, + MineSweeperGameScene, + MineSweeperSceneCount, // Leave at end +} MineSweeperScene; + +typedef enum { + Tile0, + Tile1, + Tile2, + Tile3, + Tile4, + Tile5, + Tile6, + Tile7, + Tile8, + TileClear, + TileFlag, + TileMine, +} Tile; + +typedef enum { + TileStatusEmpty, + TileStatusMine, +} TileStatus; + +typedef struct CurPos { + uint8_t x; + uint8_t y; +} CurPos; + +// App state +typedef struct AppState { + TileStatus minefield[PLAY_HEIGHT][PLAY_WIDTH]; + Tile playfield[PLAY_HEIGHT][PLAY_WIDTH]; + //FuriTimer* timer; + CurPos curPos; + int fields_cleared; + int flags_used; +} AppState; + +// App +typedef struct App { + AppState* app_state; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + View* popup_view; + Popup* popup; + View* empty_screen_view; + EmptyScreen* empty_screen; + Loading* loading; +} App; + + +#endif diff --git a/minesweeper.png b/minesweeper.png new file mode 100644 index 0000000..3769e4f Binary files /dev/null and b/minesweeper.png differ diff --git a/minesweeper_i.h b/minesweeper_i.h new file mode 100644 index 0000000..2fab2c7 --- /dev/null +++ b/minesweeper_i.h @@ -0,0 +1,7 @@ +#ifndef MINESWEEPER_I_H +#define MINESWEEPER_I_H + +#include "startup_screen.h" +#include "game_screen.h" + +#endif diff --git a/startup_screen.c b/startup_screen.c new file mode 100644 index 0000000..ba226b9 --- /dev/null +++ b/startup_screen.c @@ -0,0 +1,73 @@ +#include "minesweeper.h" + +// Custom event enumeration +typedef enum { + MineSweeperStartEvent, + MineSweeperBackEvent, +} MineSweeperStartupEvent; + + +static void minesweeper_scenes_popup_callback(void* context) { + furi_assert(context); + App* app = context; + + scene_manager_handle_custom_event(app->scene_manager, MineSweeperStartEvent); +} + +void minesweeper_scenes_startup_scene_on_enter(void* context) { + App* app = (App*)context; + + furi_assert(app); + + // fake loading + view_dispatcher_switch_to_view(app->view_dispatcher, MineSweeperLoadingView); + + popup_reset(app->popup); + + popup_set_context(app->popup, app); + + popup_set_callback(app->popup, minesweeper_scenes_popup_callback); + + app->popup_view = popup_get_view(app->popup); + + popup_set_header(app->popup, "MINE\n Sweeper", 50, 25, AlignLeft, AlignCenter); + + //popup_set_text(app->popup, "Sweeper", 70, 35, AlignLeft, AlignCenter); + + popup_set_icon(app->popup, 5, 32-18, &I_MineSweeperIcon_36x36); + + for (int i = 0; i < 10000; ++i) {} + + view_dispatcher_switch_to_view(app->view_dispatcher, MineSweeperPopupView); +} + +bool minesweeper_scenes_startup_scene_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + App* app = (App*)context; + + bool consumed = false; + + switch (event.type) { + case SceneManagerEventTypeCustom : + switch (event.event) { + case MineSweeperStartEvent : + scene_manager_next_scene(app->scene_manager, MineSweeperGameScene); + consumed = true; + break; + default: + break; + } + break; + default : + break; + } + + return consumed; +} + +void minesweeper_scenes_startup_scene_on_exit(void* context) { + furi_assert(context); + App* app = context; + app->popup_view = NULL; +} diff --git a/startup_screen.h b/startup_screen.h new file mode 100644 index 0000000..f7e38e9 --- /dev/null +++ b/startup_screen.h @@ -0,0 +1,11 @@ +#ifndef STARTUP_SCREEN_H +#define STARTUP_SCREEN_H + +#include + + +void minesweeper_scenes_startup_scene_on_enter(void* context); +bool minesweeper_scenes_startup_scene_on_event(void* context, SceneManagerEvent event); +void minesweeper_scenes_startup_scene_on_exit(void* context); + +#endif