| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629 |
- #include "minesweeper_game_screen.h"
- static const Icon* tile_icons[13] = {
- &I_tile_empty_8x8,
- &I_tile_0_8x8,
- &I_tile_1_8x8,
- &I_tile_2_8x8,
- &I_tile_3_8x8,
- &I_tile_4_8x8,
- &I_tile_5_8x8,
- &I_tile_6_8x8,
- &I_tile_7_8x8,
- &I_tile_8_8x8,
- &I_tile_mine_8x8,
- &I_tile_flag_8x8,
- &I_tile_uncleared_8x8,
- };
- // They way this enum is set up allows us to index the Icon* array above for some mine types
- typedef enum {
- MineSweeperGameScreenTileNone = 0,
- MineSweeperGameScreenTileZero,
- MineSweeperGameScreenTileOne,
- MineSweeperGameScreenTileTwo,
- MineSweeperGameScreenTileThree,
- MineSweeperGameScreenTileFour,
- MineSweeperGameScreenTileFive,
- MineSweeperGameScreenTileSix,
- MineSweeperGameScreenTileSeven,
- MineSweeperGameScreenTileEight,
- MineSweeperGameScreenTileMine,
- MineSweeperGameScreenTileTypeCount,
- } MineSweeperGameScreenTileType;
- typedef enum {
- MineSweeperGameScreenTileStateFlagged,
- MineSweeperGameScreenTileStateUncleared,
- MineSweeperGameScreenTileStateCleared,
- } MineSweeperGameScreenTileState;
- struct MineSweeperGameScreen {
- View* view;
- void* context;
- GameScreenInputCallback input_callback;
- };
- typedef struct {
- int16_t x_abs, y_abs;
- } CurrentPosition;
- typedef struct {
- uint16_t x_abs, y_abs;
- const Icon* icon;
- } IconElement;
- typedef struct {
- IconElement icon_element;
- MineSweeperGameScreenTileState tile_state;
- MineSweeperGameScreenTileType tile_type;
- } MineSweeperTile;
- typedef struct {
- MineSweeperTile board[MINESWEEPER_BOARD_MAX_TILES];
- CurrentPosition curr_pos;
- uint8_t right_boundary, bottom_boundary, board_width, board_height, board_difficulty;
- uint16_t mines_left;
- uint16_t flags_left;
- uint16_t tiles_left;
- uint32_t start_tick;
- FuriString* info_str;
- bool ensure_solvable_board;
- bool is_restart_triggered;
- bool is_holding_down_button;
- bool has_lost_game;
- } MineSweeperGameScreenModel;
- // Multipliers for ratio of mines to tiles
- static const float difficulty_multiplier[3] = {
- 0.15f,
- 0.17f,
- 0.19f,
- };
- // Offsets array used consistently when checking surrounding tiles
- static const int8_t offsets[8][2] = {
- {-1, 1},
- {0, 1},
- {1, 1},
- {1, 0},
- {1, -1},
- {0, -1},
- {-1, -1},
- {-1, 0},
- };
- static MineSweeperTile board_t[MINESWEEPER_BOARD_MAX_TILES];
- /****************************************************************
- * Function declarations
- *
- * Non public function declarations
- ***************************************************************/
- // Static helper functions
- static void setup_board(MineSweeperGameScreen* instance);
- static bool check_board_with_verifier(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- uint16_t total_mines);
- static inline void bfs_tile_clear_verifier(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- const uint16_t x,
- const uint16_t y,
- point_deq_t* edges,
- point_set_t* visited);
- static uint16_t bfs_tile_clear(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- const uint16_t x,
- const uint16_t y);
- static void mine_sweeper_game_screen_set_board_information(
- MineSweeperGameScreen* instance,
- const uint8_t width,
- const uint8_t height,
- const uint8_t difficulty,
- bool is_solvable);
- static bool try_clear_surrounding_tiles(MineSweeperGameScreenModel* model);
- static void
- bfs_to_closest_tile(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
- // Currently not using enter/exit callback
- static void mine_sweeper_game_screen_view_enter(void* context);
- static void mine_sweeper_game_screen_view_exit(void* context);
- // Different input/draw callbacks for play/win/lose state
- static void mine_sweeper_game_screen_view_end_draw_callback(Canvas* canvas, void* _model);
- static void mine_sweeper_game_screen_view_play_draw_callback(Canvas* canvas, void* _model);
- // These consolidate the function calls for led/haptic/sound for specific events
- static void mine_sweeper_long_ok_effect(void* context);
- static void mine_sweeper_short_ok_effect(void* context);
- static void mine_sweeper_flag_effect(void* context);
- static void mine_sweeper_move_effect(void* context);
- static void mine_sweeper_oob_effect(void* context);
- static void mine_sweeper_lose_effect(void* context);
- static void mine_sweeper_win_effect(void* context);
- static inline bool handle_player_move(
- MineSweeperGameScreen* instance,
- MineSweeperGameScreenModel* model,
- InputEvent* event);
- static int8_t
- handle_short_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
- static int8_t
- handle_long_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
- static bool
- handle_long_back_flag_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
- static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event, void* context);
- static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event, void* context);
- /**************************************************************
- * Function definitions
- *************************************************************/
- /**
- * This function is called on alloc, reset, and win/lose condition.
- * It sets up a random board to be checked by the verifier
- */
- static void setup_board(MineSweeperGameScreen* instance) {
- furi_assert(instance);
- uint16_t board_tile_count = 0;
- uint8_t board_width = 0, board_height = 0, board_difficulty = 0;
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- board_width = model->board_width;
- board_height = model->board_height;
- board_tile_count = (model->board_width * model->board_height);
- board_difficulty = model->board_difficulty;
- },
- false);
- uint16_t num_mines = board_tile_count * difficulty_multiplier[board_difficulty];
- /** We can use a temporary buffer to set the tile types initially
- * and manipulate then save to actual model
- */
- MineSweeperGameScreenTileType tiles[MINESWEEPER_BOARD_MAX_TILES];
- memset(&tiles, MineSweeperGameScreenTileZero, sizeof(tiles));
- // Randomly place tiles except in the corners to help guarantee solvability
- for(uint16_t i = 0; i < num_mines; i++) {
- uint16_t rand_pos;
- uint16_t x;
- uint16_t y;
- bool is_invalid_position;
- do {
- rand_pos = furi_hal_random_get() % board_tile_count;
- x = rand_pos / board_width;
- y = rand_pos % board_width;
- is_invalid_position =
- ((rand_pos == 0) || (x == 0 && y == 1) || (x == 1 && y == 0) ||
- rand_pos == board_tile_count - 1 || (x == 0 && y == board_width - 1) ||
- (x == board_height - 1 && y == 0));
- } while(tiles[rand_pos] == MineSweeperGameScreenTileMine || is_invalid_position);
- tiles[rand_pos] = MineSweeperGameScreenTileMine;
- }
- /** All mines are set so we look at each tile for surrounding mines */
- for(uint16_t i = 0; i < board_tile_count; i++) {
- MineSweeperGameScreenTileType tile_type = tiles[i];
- if(tile_type == MineSweeperGameScreenTileMine) {
- continue;
- }
- uint16_t mine_count = 0;
- uint16_t x = i / board_width;
- uint16_t y = i % board_width;
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = x + (int16_t)offsets[j][0];
- int16_t dy = y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(tiles[pos] == MineSweeperGameScreenTileMine) {
- mine_count++;
- }
- }
- tiles[i] = (MineSweeperGameScreenTileType)mine_count + 1;
- }
- // Save tiles to view model
- // Because of way tile enum and tile_icons array is set up we can
- // index tile_icons with the enum type to get the correct Icon*
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- for(uint16_t i = 0; i < board_tile_count; i++) {
- model->board[i].tile_type = tiles[i];
- model->board[i].tile_state = MineSweeperGameScreenTileStateUncleared;
- model->board[i].icon_element.icon = tile_icons[tiles[i]];
- model->board[i].icon_element.x_abs = (i / model->board_width);
- model->board[i].icon_element.y_abs = (i % model->board_width);
- }
- model->mines_left = num_mines;
- model->flags_left = num_mines;
- model->tiles_left = (model->board_width * model->board_height) - model->mines_left;
- model->curr_pos.x_abs = 0;
- model->curr_pos.y_abs = 0;
- model->right_boundary = MINESWEEPER_SCREEN_TILE_WIDTH;
- model->bottom_boundary = MINESWEEPER_SCREEN_TILE_HEIGHT;
- model->is_restart_triggered = false;
- model->has_lost_game = false;
- },
- true);
- }
- /**
- * This function serves as the verifier for a board to check whether it has to be solved ambiguously or not
- *
- * Returns true if it is unambiguously solvable.
- */
- static bool check_board_with_verifier(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- uint16_t total_mines) {
- furi_assert(board);
- // Double ended queue used to track edges.
- point_deq_t deq;
- point_set_t visited;
- // Ordered Set for visited points
- point_deq_init(deq);
- point_set_init(visited);
- bool is_solvable = false;
- // Point_t pos will be used to keep track of the current point
- Point_t pos;
- pointobj_init(pos);
- // Starting position is 0,0
- Point start_pos = (Point){.x = 0, .y = 0};
- pointobj_set_point(pos, start_pos);
- // Initially bfs clear from 0,0 as it is safe. We should push all 'edges' found
- // into the deq and this will be where we start off from
- bfs_tile_clear_verifier(board, board_width, board_height, 0, 0, &deq, &visited);
- //While we have valid edges to check and have not solved the board
- while(!is_solvable && point_deq_size(deq) > 0) {
- bool is_stuck =
- true; // This variable will track if any flag was placed for any edge to see if we are stuck
- uint16_t deq_size = point_deq_size(deq);
- // Iterate through all edge tiles and push new ones on
- while(deq_size-- > 0) {
- // Pop point and get 1d position in buffer
- point_deq_pop_front(&pos, deq);
- Point curr_pos = pointobj_get_point(pos);
- uint16_t curr_pos_1d = curr_pos.x * board_width + curr_pos.y;
- // Get tile at 1d position
- MineSweeperTile tile = board[curr_pos_1d];
- uint8_t tile_num = tile.tile_type - 1;
- // Track total surrounding tiles and flagged tiles
- uint8_t num_surrounding_tiles = 0;
- uint8_t num_flagged_tiles = 0;
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[j][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(board[pos].tile_state == MineSweeperGameScreenTileStateUncleared) {
- num_surrounding_tiles++;
- } else if(board[pos].tile_state == MineSweeperGameScreenTileStateFlagged) {
- num_surrounding_tiles++;
- num_flagged_tiles++;
- }
- }
- if(num_flagged_tiles == tile_num) {
- // If the tile has the same number of surrounding flags as its type we bfs clear the uncleared surrounding tiles
- // pushing new unvisited edges on deq
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[j][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(board[pos].tile_state == MineSweeperGameScreenTileStateUncleared) {
- bfs_tile_clear_verifier(
- board, board_width, board_height, dx, dy, &deq, &visited);
- }
- }
- is_stuck = false;
- } else if(num_surrounding_tiles == tile_num) {
- // If the number of surrounding tiles is the tile num it is unambiguous so we place a flag on those tiles,
- // decrement the mine count appropriately and check win condition, and then mark stuck as false
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[j][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(board[pos].tile_state == MineSweeperGameScreenTileStateUncleared) {
- board[pos].tile_state = MineSweeperGameScreenTileStateFlagged;
- }
- }
- total_mines -= (num_surrounding_tiles - num_flagged_tiles);
- if(total_mines == 0) is_solvable = true;
- is_stuck = false;
- } else if(num_surrounding_tiles != 0) {
- // If we have tiles around this position but the number of flagged tiles != tile num
- // and the surrounding tiles != tile num this means the tile is ambiguous. We can push
- // it back on the deq to be reprocessed with any other new edges
- point_deq_push_back(deq, pos);
- }
- }
- // If we are stuck we break as it is an ambiguous map generation
- if(is_stuck) {
- break;
- }
- }
- point_set_clear(visited);
- point_deq_clear(deq);
- return is_solvable;
- }
- /**
- * This is a bfs_tile clear used by the verifier which performs the normal tile clear
- * but also pushes new edges to the deq passed in. There is a separate function used
- * for the bfs_tile_clear used on the user click
- */
- static inline void bfs_tile_clear_verifier(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- const uint16_t x,
- const uint16_t y,
- point_deq_t* edges,
- point_set_t* visited) {
- furi_assert(board);
- furi_assert(edges);
- furi_assert(visited);
- // Init both the set and dequeue
- point_deq_t deq;
- point_set_t set;
- point_deq_init(deq);
- point_set_init(set);
- // Point_t pos will be used to keep track of the current point
- Point_t pos;
- pointobj_init(pos);
- // Starting position is current pos
- Point start_pos = (Point){.x = x, .y = y};
- pointobj_set_point(pos, start_pos);
- point_deq_push_back(deq, pos);
- while(point_deq_size(deq) > 0) {
- point_deq_pop_front(&pos, deq);
- Point curr_pos = pointobj_get_point(pos);
- uint16_t curr_pos_1d = curr_pos.x * board_width + curr_pos.y;
- // If in visited set
- if(point_set_cget(set, pos) != NULL || point_set_cget(*visited, pos) != NULL) {
- }
- // If it is cleared continue
- if(board[curr_pos_1d].tile_state == MineSweeperGameScreenTileStateCleared) {
- continue;
- }
- // Else set tile to cleared
- board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateCleared;
- // Add point to visited set
- point_set_push(set, pos);
- //When we hit a potential edge
- if(board[curr_pos_1d].tile_type != MineSweeperGameScreenTileZero) {
- // We can push this edge into edges if it is not in visited, as it is a new edge
- // and also add to the visited set
- if(point_set_cget(*visited, pos) == NULL) {
- point_deq_push_back(*edges, pos);
- point_set_push(*visited, pos);
- }
- // Continue processing next point for bfs tile clear
- continue;
- }
- // Process all surrounding neighbors and add valid to dequeue
- for(uint8_t i = 0; i < 8; i++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[i][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[i][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- Point neighbor = (Point){.x = dx, .y = dy};
- pointobj_set_point(pos, neighbor);
- if(point_set_cget(set, pos) != NULL || point_set_cget(*visited, pos) != NULL) continue;
- point_deq_push_back(deq, pos);
- }
- }
- point_set_clear(set);
- point_deq_clear(deq);
- }
- /**
- * This is a bfs_tile clear used in the input callbacks to clear the board on user input
- */
- static inline uint16_t bfs_tile_clear(
- MineSweeperTile* board,
- const uint8_t board_width,
- const uint8_t board_height,
- const uint16_t x,
- const uint16_t y) {
- furi_assert(board);
- // We will return this number as the number of tiles cleared
- uint16_t ret = 0;
- // Init both the set and dequeue
- point_deq_t deq;
- point_set_t set;
- point_deq_init(deq);
- point_set_init(set);
- // Point_t pos will be used to keep track of the current point
- Point_t pos;
- pointobj_init(pos);
- // Starting position is current pos
- Point start_pos = (Point){.x = x, .y = y};
- pointobj_set_point(pos, start_pos);
- point_deq_push_back(deq, pos);
- while(point_deq_size(deq) > 0) {
- point_deq_pop_front(&pos, deq);
- Point curr_pos = pointobj_get_point(pos);
- uint16_t curr_pos_1d = curr_pos.x * board_width + curr_pos.y;
- // If in visited set
- if(point_set_cget(set, pos) != NULL) {
- continue;
- }
- // If it is not uncleared continue
- if(board[curr_pos_1d].tile_state != MineSweeperGameScreenTileStateUncleared) {
- continue;
- }
- // Else set tile to cleared
- board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateCleared;
- // Increment total number of cleared tiles
- ret++;
- // Add point to visited set
- point_set_push(set, pos);
- // If it is not a zero tile continue
- if(board[curr_pos_1d].tile_type != MineSweeperGameScreenTileZero) {
- continue;
- }
- // Process all surrounding neighbors and add valid to dequeue
- for(uint8_t i = 0; i < 8; i++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[i][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[i][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- Point neighbor = (Point){.x = dx, .y = dy};
- pointobj_set_point(pos, neighbor);
- if(point_set_cget(set, pos) != NULL) continue;
- point_deq_push_back(deq, pos);
- }
- }
- point_set_clear(set);
- point_deq_clear(deq);
- return ret;
- }
- static void mine_sweeper_game_screen_set_board_information(
- MineSweeperGameScreen* instance,
- uint8_t width,
- uint8_t height,
- uint8_t difficulty,
- bool is_solvable) {
- furi_assert(instance);
- // These are the min/max values that can actually be set
- if(width > 146) {
- width = 146;
- }
- if(width < 16) {
- width = 16;
- }
- if(height > 64) {
- height = 64;
- }
- if(height < 7) {
- height = 7;
- }
- if(difficulty > 2) {
- difficulty = 2;
- }
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- model->board_width = width;
- model->board_height = height;
- model->board_difficulty = difficulty;
- model->ensure_solvable_board = is_solvable;
- },
- true);
- }
- // THIS FUNCTION CAN TRIGGER THE LOSE CONDITION
- static bool try_clear_surrounding_tiles(MineSweeperGameScreenModel* model) {
- furi_assert(model);
- uint8_t curr_x = model->curr_pos.x_abs;
- uint8_t curr_y = model->curr_pos.y_abs;
- uint8_t board_width = model->board_width;
- uint8_t board_height = model->board_height;
- uint16_t curr_pos_1d = curr_x * board_width + curr_y;
- MineSweeperTile tile = model->board[curr_pos_1d];
- // Return false if tile is zero tile or not cleared
- if(tile.tile_state != MineSweeperGameScreenTileStateCleared ||
- tile.tile_type == MineSweeperGameScreenTileZero) {
- return false;
- }
- uint8_t num_surrounding_flagged = 0;
- bool was_mine_found = false;
- bool is_lose_condition_triggered = false;
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = curr_x + (int16_t)offsets[j][0];
- int16_t dy = curr_y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(model->board[pos].tile_state == MineSweeperGameScreenTileStateFlagged) {
- num_surrounding_flagged++;
- } else if(
- !was_mine_found && model->board[pos].tile_type == MineSweeperGameScreenTileMine &&
- model->board[pos].tile_state != MineSweeperGameScreenTileStateFlagged) {
- was_mine_found = true;
- }
- }
- // We clear surrounding tile
- if(num_surrounding_flagged >= tile.tile_type - 1) {
- if(was_mine_found) is_lose_condition_triggered = true;
- for(uint8_t j = 0; j < 8; j++) {
- int16_t dx = curr_x + (int16_t)offsets[j][0];
- int16_t dy = curr_y + (int16_t)offsets[j][1];
- if(dx < 0 || dy < 0 || dx >= board_height || dy >= board_width) {
- continue;
- }
- uint16_t pos = dx * board_width + dy;
- if(model->board[pos].tile_state == MineSweeperGameScreenTileStateUncleared) {
- // Decrement tiles left by the amount cleared
- uint16_t tiles_cleared =
- bfs_tile_clear(model->board, model->board_width, model->board_height, dx, dy);
- model->tiles_left -= tiles_cleared;
- }
- }
- }
- return is_lose_condition_triggered;
- }
- /**
- * Function is used on a long backpress on a cleared tile and returns the position
- * of the first found uncleared tile using a bfs search
- */
- static void
- bfs_to_closest_tile(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
- furi_assert(model);
- // Init both the set and dequeue
- point_deq_t deq;
- point_set_t set;
- point_deq_init(deq);
- point_set_init(set);
- // Return the value in this point
- Point result = (Point){.x = 0, .y = 0};
- // Point_t pos will be used to keep track of the current point
- Point_t pos;
- pointobj_init(pos);
- // Starting position is current pos
- Point start_pos = (Point){.x = model->curr_pos.x_abs, .y = model->curr_pos.y_abs};
- pointobj_set_point(pos, start_pos);
- point_deq_push_back(deq, pos);
- while(point_deq_size(deq) > 0) {
- point_deq_pop_front(&pos, deq);
- Point curr_pos = pointobj_get_point(pos);
- uint16_t curr_pos_1d = curr_pos.x * model->board_width + curr_pos.y;
- // If the current tile is uncleared and not start pos we save result
- // to this position and break
- if(model->board[curr_pos_1d].tile_state == MineSweeperGameScreenTileStateUncleared &&
- !(start_pos.x == curr_pos.x && start_pos.y == curr_pos.y)) {
- result = curr_pos;
- break;
- }
- // If in visited set continue
- if(point_set_cget(set, pos) != NULL) {
- continue;
- }
- // Add point to visited set
- point_set_push(set, pos);
- // Process all surrounding neighbors and add valid to dequeue
- for(uint8_t i = 0; i < 8; i++) {
- int16_t dx = curr_pos.x + (int16_t)offsets[i][0];
- int16_t dy = curr_pos.y + (int16_t)offsets[i][1];
- if(dx < 0 || dy < 0 || dx >= model->board_height || dy >= model->board_width) {
- continue;
- }
- Point neighbor = (Point){.x = dx, .y = dy};
- pointobj_set_point(pos, neighbor);
- point_deq_push_back(deq, pos);
- }
- }
- point_set_clear(set);
- point_deq_clear(deq);
- // Save cursor to new closest tile position
- // If the cursor moves outisde of the model boundaries we need to
- // move the boundary appropriately
- model->curr_pos.x_abs = result.x;
- model->curr_pos.y_abs = result.y;
- bool is_outside_top_boundary = model->curr_pos.x_abs <
- (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT);
- bool is_outside_bottom_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
- bool is_outside_left_boundary = model->curr_pos.y_abs <
- (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
- bool is_outside_right_boundary = model->curr_pos.y_abs >= model->right_boundary;
- if(is_outside_top_boundary) {
- model->bottom_boundary = model->curr_pos.x_abs + MINESWEEPER_SCREEN_TILE_HEIGHT;
- } else if(is_outside_bottom_boundary) {
- model->bottom_boundary = model->curr_pos.x_abs + 1;
- }
- if(is_outside_right_boundary) {
- model->right_boundary = model->curr_pos.y_abs + 1;
- } else if(is_outside_left_boundary) {
- model->right_boundary = model->curr_pos.y_abs + MINESWEEPER_SCREEN_TILE_WIDTH;
- }
- mine_sweeper_play_happy_bump(instance->context);
- }
- static void mine_sweeper_short_ok_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_blink_magenta(instance->context);
- mine_sweeper_play_ok_sound(instance->context);
- mine_sweeper_play_happy_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static void mine_sweeper_long_ok_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_blink_magenta(instance->context);
- mine_sweeper_play_ok_sound(instance->context);
- mine_sweeper_play_long_ok_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static void mine_sweeper_flag_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_blink_cyan(instance->context);
- mine_sweeper_play_flag_sound(instance->context);
- mine_sweeper_play_happy_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static void mine_sweeper_move_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_play_happy_bump(instance->context);
- }
- static void mine_sweeper_oob_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_blink_red(instance->context);
- mine_sweeper_play_flag_sound(instance->context);
- mine_sweeper_play_oob_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static void mine_sweeper_lose_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_set_rgb(instance->context, 255, 0, 000);
- mine_sweeper_play_lose_sound(instance->context);
- mine_sweeper_play_lose_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static void mine_sweeper_win_effect(void* context) {
- furi_assert(context);
- MineSweeperGameScreen* instance = context;
- mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
- mine_sweeper_play_win_sound(instance->context);
- mine_sweeper_play_win_bump(instance->context);
- mine_sweeper_stop_all_sound(instance->context);
- }
- static inline bool handle_player_move(
- MineSweeperGameScreen* instance,
- MineSweeperGameScreenModel* model,
- InputEvent* event) {
- bool consumed = false;
- bool is_outside_boundary;
- switch(event->key) {
- case InputKeyUp:
- (model->curr_pos.x_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
- mine_sweeper_move_effect(instance);
- model->curr_pos.x_abs = (model->curr_pos.x_abs - 1 < 0) ? 0 : model->curr_pos.x_abs - 1;
- is_outside_boundary = model->curr_pos.x_abs <
- (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT);
- if(is_outside_boundary) {
- model->bottom_boundary--;
- }
- consumed = true;
- break;
- case InputKeyDown:
- (model->curr_pos.x_abs + 1 >= model->board_height) ? mine_sweeper_oob_effect(instance) :
- mine_sweeper_move_effect(instance);
- model->curr_pos.x_abs = (model->curr_pos.x_abs + 1 >= model->board_height) ?
- model->board_height - 1 :
- model->curr_pos.x_abs + 1;
- is_outside_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
- if(is_outside_boundary) {
- model->bottom_boundary++;
- }
- consumed = true;
- break;
- case InputKeyLeft:
- (model->curr_pos.y_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
- mine_sweeper_move_effect(instance);
- model->curr_pos.y_abs = (model->curr_pos.y_abs - 1 < 0) ? 0 : model->curr_pos.y_abs - 1;
- is_outside_boundary = model->curr_pos.y_abs <
- (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
- if(is_outside_boundary) {
- model->right_boundary--;
- }
- consumed = true;
- break;
- case InputKeyRight:
- (model->curr_pos.y_abs + 1 >= model->board_width) ? mine_sweeper_oob_effect(instance) :
- mine_sweeper_move_effect(instance);
- model->curr_pos.y_abs = (model->curr_pos.y_abs + 1 >= model->board_width) ?
- model->board_width - 1 :
- model->curr_pos.y_abs + 1;
- is_outside_boundary = model->curr_pos.y_abs >= model->right_boundary;
- if(is_outside_boundary) {
- model->right_boundary++;
- }
- consumed = true;
- break;
- default:
- consumed = true;
- break;
- }
- return consumed;
- }
- static int8_t
- handle_short_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
- furi_assert(instance);
- furi_assert(model);
- uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- bool is_win_condition_triggered = false;
- bool is_lose_condition_triggered = false;
- MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
- MineSweeperGameScreenTileType type = model->board[curr_pos_1d].tile_type;
- // LOSE/WIN CONDITION OR TILE CLEAR
- if(state == MineSweeperGameScreenTileStateUncleared && type == MineSweeperGameScreenTileMine) {
- is_lose_condition_triggered = true;
- model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateCleared;
- } else if(state == MineSweeperGameScreenTileStateUncleared) {
- uint16_t tiles_cleared = bfs_tile_clear(
- model->board,
- model->board_width,
- model->board_height,
- (uint16_t)model->curr_pos.x_abs,
- (uint16_t)model->curr_pos.y_abs);
- model->tiles_left -= tiles_cleared;
- // Check win condition
- if(model->mines_left == 0 && model->flags_left == 0 && model->tiles_left == 0) {
- is_win_condition_triggered = true;
- } else {
- // if not met play ok effect
- mine_sweeper_short_ok_effect(instance);
- }
- }
- if(is_lose_condition_triggered) {
- return -1;
- } else if(is_win_condition_triggered) {
- return 1;
- }
- return 0;
- }
- static int8_t
- handle_long_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
- furi_assert(instance);
- furi_assert(model);
- uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- bool is_win_condition_triggered = false;
- bool is_lose_condition_triggered = false;
- MineSweeperGameScreenTileType type = model->board[curr_pos_1d].tile_type;
- // Try to clear surrounding tiles if correct number is flagged.
- is_lose_condition_triggered = try_clear_surrounding_tiles(model);
- model->is_holding_down_button = true;
- // Check win condition
- if(model->mines_left == 0 && model->flags_left == 0 && model->tiles_left == 0) {
- is_win_condition_triggered = true;
- }
- // We need to check if it is ok to play this or else we conflict
- // with the lose effect and crash
- if(!is_win_condition_triggered && !is_lose_condition_triggered &&
- type != MineSweeperGameScreenTileZero) {
- mine_sweeper_long_ok_effect(instance);
- }
- if(is_lose_condition_triggered) {
- return -1;
- } else if(is_win_condition_triggered) {
- return 1;
- }
- return 0;
- }
- static bool handle_long_back_flag_input(
- MineSweeperGameScreen* instance,
- MineSweeperGameScreenModel* model) {
- furi_assert(instance);
- furi_assert(model);
- uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
- bool is_win_condition_triggered = false;
- if(state == MineSweeperGameScreenTileStateFlagged) {
- if(model->board[curr_pos_1d].tile_type == MineSweeperGameScreenTileMine)
- model->mines_left++;
- model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateUncleared;
- model->flags_left++;
- } else if(model->flags_left > 0) {
- if(model->board[curr_pos_1d].tile_type == MineSweeperGameScreenTileMine)
- model->mines_left--;
- model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateFlagged;
- model->flags_left--;
- }
- // WIN CONDITION
- // This can be a win condition where the non-mine tiles are cleared and they place the last flag
- if(model->flags_left == 0 && model->mines_left == 0 && model->tiles_left == 0) {
- is_win_condition_triggered = true;
- mine_sweeper_win_effect(instance);
- mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
- } else {
- mine_sweeper_flag_effect(instance);
- }
- return is_win_condition_triggered;
- }
- static void mine_sweeper_game_screen_view_enter(void* context) {
- furi_assert(context);
- UNUSED(context);
- }
- static void mine_sweeper_game_screen_view_exit(void* context) {
- furi_assert(context);
- UNUSED(context);
- }
- static void mine_sweeper_game_screen_view_end_draw_callback(Canvas* canvas, void* _model) {
- furi_assert(canvas);
- furi_assert(_model);
- MineSweeperGameScreenModel* model = _model;
- canvas_clear(canvas);
- uint16_t cursor_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- for(uint8_t x_rel = 0; x_rel < MINESWEEPER_SCREEN_TILE_HEIGHT; x_rel++) {
- uint16_t x_abs = (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) + x_rel;
- for(uint8_t y_rel = 0; y_rel < MINESWEEPER_SCREEN_TILE_WIDTH; y_rel++) {
- uint16_t y_abs = (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) + y_rel;
- uint16_t curr_rendering_tile_pos_1d = x_abs * model->board_width + y_abs;
- MineSweeperTile tile = model->board[curr_rendering_tile_pos_1d];
- if(cursor_pos_1d == curr_rendering_tile_pos_1d) {
- canvas_set_color(canvas, ColorWhite);
- } else {
- canvas_set_color(canvas, ColorBlack);
- }
- canvas_draw_icon(
- canvas,
- y_rel * icon_get_width(tile.icon_element.icon),
- x_rel * icon_get_height(tile.icon_element.icon),
- tile.icon_element.icon);
- }
- }
- canvas_set_color(canvas, ColorBlack);
- // If any borders are at the limits of the game board we draw a border line
- // Right border
- if(model->right_boundary == model->board_width) {
- canvas_draw_line(canvas, 127, 0, 127, 63 - 8);
- }
- // Left border
- if((model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) == 0) {
- canvas_draw_line(canvas, 0, 0, 0, 63 - 8);
- }
- // Bottom border
- if(model->bottom_boundary == model->board_height) {
- canvas_draw_line(canvas, 0, 63 - 8, 127, 63 - 8);
- }
- // Top border
- if((model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) == 0) {
- canvas_draw_line(canvas, 0, 0, 127, 0);
- }
- const char* end_status_str = "";
- if(model->has_lost_game) {
- end_status_str = "YOU LOSE! PRESS OK.\0";
- } else {
- end_status_str = "YOU WIN! PRESS OK.\0";
- }
- // Draw win/lose text
- furi_string_printf(model->info_str, "%s", end_status_str);
- canvas_draw_str_aligned(
- canvas, 0, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
- // Draw time text
- uint32_t ticks_elapsed = furi_get_tick() - model->start_tick;
- uint32_t sec = ticks_elapsed / furi_kernel_get_tick_frequency();
- uint32_t minutes = sec / 60;
- sec = sec % 60;
- furi_string_printf(model->info_str, "%02ld:%02ld", minutes, sec);
- canvas_draw_str_aligned(
- canvas,
- 126 - canvas_string_width(canvas, furi_string_get_cstr(model->info_str)),
- 64 - 7,
- AlignLeft,
- AlignTop,
- furi_string_get_cstr(model->info_str));
- }
- static void mine_sweeper_game_screen_view_play_draw_callback(Canvas* canvas, void* _model) {
- furi_assert(canvas);
- furi_assert(_model);
- MineSweeperGameScreenModel* model = _model;
- canvas_clear(canvas);
- uint16_t cursor_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- for(uint8_t x_rel = 0; x_rel < MINESWEEPER_SCREEN_TILE_HEIGHT; x_rel++) {
- uint16_t x_abs = (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) + x_rel;
- for(uint8_t y_rel = 0; y_rel < MINESWEEPER_SCREEN_TILE_WIDTH; y_rel++) {
- uint16_t y_abs = (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) + y_rel;
- uint16_t curr_rendering_tile_pos_1d = x_abs * model->board_width + y_abs;
- MineSweeperTile tile = model->board[curr_rendering_tile_pos_1d];
- if(cursor_pos_1d == curr_rendering_tile_pos_1d) {
- canvas_set_color(canvas, ColorWhite);
- } else {
- canvas_set_color(canvas, ColorBlack);
- }
- switch(tile.tile_state) {
- case MineSweeperGameScreenTileStateFlagged:
- canvas_draw_icon(
- canvas,
- y_rel * icon_get_width(tile.icon_element.icon),
- x_rel * icon_get_height(tile.icon_element.icon),
- tile_icons[11]);
- break;
- case MineSweeperGameScreenTileStateUncleared:
- canvas_draw_icon(
- canvas,
- y_rel * icon_get_width(tile.icon_element.icon),
- x_rel * icon_get_height(tile.icon_element.icon),
- tile_icons[12]);
- break;
- case MineSweeperGameScreenTileStateCleared:
- canvas_draw_icon(
- canvas,
- y_rel * icon_get_width(tile.icon_element.icon),
- x_rel * icon_get_height(tile.icon_element.icon),
- tile.icon_element.icon);
- break;
- default:
- break;
- }
- }
- }
- canvas_set_color(canvas, ColorBlack);
- // If any borders are at the limits of the game board we draw a border line
- // Right border
- if(model->right_boundary == model->board_width) {
- canvas_draw_line(canvas, 127, 0, 127, 63 - 8);
- }
- // Left border
- if((model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) == 0) {
- canvas_draw_line(canvas, 0, 0, 0, 63 - 8);
- }
- // Bottom border
- if(model->bottom_boundary == model->board_height) {
- canvas_draw_line(canvas, 0, 63 - 8, 127, 63 - 8);
- }
- // Top border
- if((model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) == 0) {
- canvas_draw_line(canvas, 0, 0, 127, 0);
- }
- // Draw X Position Text
- furi_string_printf(model->info_str, "X:%03hhd", model->curr_pos.x_abs);
- canvas_draw_str_aligned(
- canvas, 0, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
- // Draw Y Position Text
- furi_string_printf(model->info_str, "Y:%03hhd", model->curr_pos.y_abs);
- canvas_draw_str_aligned(
- canvas, 33, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
- // Draw flag text
- furi_string_printf(model->info_str, "F:%03hd", model->flags_left);
- canvas_draw_str_aligned(
- canvas, 66, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
- // Draw time text
- uint32_t ticks_elapsed = furi_get_tick() - model->start_tick;
- uint32_t sec = ticks_elapsed / furi_kernel_get_tick_frequency();
- uint32_t minutes = sec / 60;
- sec = sec % 60;
- furi_string_printf(model->info_str, "%02ld:%02ld", minutes, sec);
- canvas_draw_str_aligned(
- canvas,
- 126 - canvas_string_width(canvas, furi_string_get_cstr(model->info_str)),
- 64 - 7,
- AlignLeft,
- AlignTop,
- furi_string_get_cstr(model->info_str));
- }
- static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- furi_assert(event);
- MineSweeperGameScreen* instance = context;
- bool consumed = false;
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- if(model->is_holding_down_button && event->type == InputTypeRelease) {
- //When we lose we are holding the button down, record this release
- model->is_holding_down_button = false;
- consumed = true;
- }
- if(!model->is_holding_down_button && event->key == InputKeyOk &&
- event->type == InputTypeRelease) {
- // After release when user presses and releases ok we want to restart the next time this function is pressed
- model->is_restart_triggered = true;
- consumed = true;
- } else if(
- !model->is_holding_down_button && model->is_restart_triggered &&
- event->key == InputKeyOk) {
- // After restart flagged is triggered this should also trigger and restart the game
- mine_sweeper_led_reset(instance->context);
- mine_sweeper_game_screen_reset_clock(instance);
- view_set_draw_callback(
- instance->view, mine_sweeper_game_screen_view_play_draw_callback);
- view_set_input_callback(
- instance->view, mine_sweeper_game_screen_view_play_input_callback);
- // Here we are going to generate a valid map for the player
- bool is_valid_board = false;
- size_t memsz = sizeof(MineSweeperTile) * MINESWEEPER_BOARD_MAX_TILES;
- do {
- setup_board(instance);
- memset(board_t, 0, memsz);
- memcpy(
- board_t,
- model->board,
- sizeof(MineSweeperTile) * (model->board_width * model->board_height));
- is_valid_board = check_board_with_verifier(
- board_t, model->board_width, model->board_height, model->mines_left);
- } while(model->ensure_solvable_board && !is_valid_board);
- consumed = true;
- } else if((event->type == InputTypePress || event->type == InputTypeRepeat)) {
- // Any other input we consider generic player movement
- consumed = handle_player_move(instance, model, event);
- }
- },
- false);
- return consumed;
- }
- static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- furi_assert(event);
- MineSweeperGameScreen* instance = context;
- bool consumed = false;
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- // Checking button types
- if(event->type == InputTypeRelease) {
- model->is_holding_down_button = false;
- consumed = true;
- } else if(event->key == InputKeyOk) { // Attempt to Clear Space !! THIS CAN BE A LOSE CONDITION
- bool is_lose_condition_triggered = false;
- bool is_win_condition_triggered = false;
- if(!model->is_holding_down_button && event->type == InputTypePress) {
- //ret : -1 lose condition, 1 win condition, 0 neutral
- int8_t ret = handle_short_ok_input(instance, model);
- if(ret != 0) {
- (ret == -1) ? (is_lose_condition_triggered = true) :
- (is_win_condition_triggered = true);
- }
- // LOSE/WIN CONDITION OR CLEAR SURROUNDING
- } else if(!model->is_holding_down_button && event->type == InputTypeLong) {
- //ret : -1 lose condition, 1 win condition, 0 neutral
- int8_t ret = handle_long_ok_input(instance, model);
- if(ret != 0) {
- (ret == -1) ? (is_lose_condition_triggered = true) :
- (is_win_condition_triggered = true);
- }
- }
- // Check if win or lose condition was triggered on OK press
- if(is_lose_condition_triggered) {
- model->has_lost_game = true;
- mine_sweeper_lose_effect(instance);
- view_set_draw_callback(
- instance->view, mine_sweeper_game_screen_view_end_draw_callback);
- view_set_input_callback(
- instance->view, mine_sweeper_game_screen_view_end_input_callback);
- } else if(is_win_condition_triggered) {
- mine_sweeper_win_effect(instance);
- view_set_draw_callback(
- instance->view, mine_sweeper_game_screen_view_end_draw_callback);
- view_set_input_callback(
- instance->view, mine_sweeper_game_screen_view_end_input_callback);
- }
- consumed = true;
- } else if(event->key == InputKeyBack) { // We can use holding the back button for either
- // Setting a flag on a covered tile, or moving to
- // the next closest covered tile on when on a uncovered
- // tile
- if(event->type == InputTypeLong ||
- event->type == InputTypeRepeat) { // Only process longer back keys;
- // short presses should take
- // us to the menu
- bool is_win_condition_triggered = false;
- uint16_t curr_pos_1d =
- model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
- MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
- if(state == MineSweeperGameScreenTileStateCleared) {
- // BFS to closest uncovered position
- bfs_to_closest_tile(instance, model);
- model->is_holding_down_button = true;
- // Flag or Unflag tile and check win condition
- } else if(
- !model->is_holding_down_button &&
- state != MineSweeperGameScreenTileStateCleared) {
- is_win_condition_triggered = handle_long_back_flag_input(instance, model);
- model->is_holding_down_button = true;
- if(is_win_condition_triggered) {
- view_set_draw_callback(
- instance->view, mine_sweeper_game_screen_view_end_draw_callback);
- view_set_input_callback(
- instance->view, mine_sweeper_game_screen_view_end_input_callback);
- }
- }
- consumed = true;
- }
- } else if(
- event->type == InputTypePress ||
- event->type == InputTypeRepeat) { // Finally handle move
- consumed = handle_player_move(instance, model, event);
- }
- },
- true);
- if(!consumed && instance->input_callback != NULL) {
- consumed = instance->input_callback(event, instance->context);
- }
- return consumed;
- }
- MineSweeperGameScreen* mine_sweeper_game_screen_alloc(
- uint8_t width,
- uint8_t height,
- uint8_t difficulty,
- bool ensure_solvable) {
- MineSweeperGameScreen* mine_sweeper_game_screen =
- (MineSweeperGameScreen*)malloc(sizeof(MineSweeperGameScreen));
- mine_sweeper_game_screen->view = view_alloc();
- view_set_context(mine_sweeper_game_screen->view, mine_sweeper_game_screen);
- view_allocate_model(
- mine_sweeper_game_screen->view, ViewModelTypeLocking, sizeof(MineSweeperGameScreenModel));
- view_set_draw_callback(
- mine_sweeper_game_screen->view, mine_sweeper_game_screen_view_play_draw_callback);
- view_set_input_callback(
- mine_sweeper_game_screen->view, mine_sweeper_game_screen_view_play_input_callback);
- // This are currently unused
- view_set_enter_callback(mine_sweeper_game_screen->view, mine_sweeper_game_screen_view_enter);
- view_set_exit_callback(mine_sweeper_game_screen->view, mine_sweeper_game_screen_view_exit);
- // Not being used
- mine_sweeper_game_screen->input_callback = NULL;
- // Allocate strings in model
- with_view_model(
- mine_sweeper_game_screen->view,
- MineSweeperGameScreenModel * model,
- {
- model->info_str = furi_string_alloc();
- model->is_holding_down_button = false;
- },
- true);
- // Reset the clock - This will set the start time at the allocation of the game screen
- // but this is a public api as well and can be called in a scene for more accurate start times
- mine_sweeper_game_screen_reset_clock(mine_sweeper_game_screen);
- // We need to initize board width and height before setup
- mine_sweeper_game_screen_set_board_information(
- mine_sweeper_game_screen, width, height, difficulty, ensure_solvable);
- // Here we are going to generate a valid map for the player
- bool is_valid_board = false;
- size_t memsz = sizeof(MineSweeperTile) * MINESWEEPER_BOARD_MAX_TILES;
- do {
- setup_board(mine_sweeper_game_screen);
- uint16_t num_mines = 1;
- uint16_t board_width = 16; //default values
- uint16_t board_height = 7; //default values
- with_view_model(
- mine_sweeper_game_screen->view,
- MineSweeperGameScreenModel * model,
- {
- num_mines = model->mines_left;
- board_width = model->board_width;
- board_height = model->board_height;
- memset(board_t, 0, memsz);
- memcpy(
- board_t, model->board, sizeof(MineSweeperTile) * (board_width * board_height));
- },
- true);
- is_valid_board = check_board_with_verifier(board_t, board_width, board_height, num_mines);
- } while(ensure_solvable && !is_valid_board);
- return mine_sweeper_game_screen;
- }
- void mine_sweeper_game_screen_free(MineSweeperGameScreen* instance) {
- furi_assert(instance);
- // Dealloc strings in model
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- { furi_string_free(model->info_str); },
- false);
- // Free view and any dynamically allocated members in main struct
- view_free(instance->view);
- free(instance);
- }
- // This function should be called whenever you want to reset the game state
- // This should NOT be called in the on_exit in the game scene
- void mine_sweeper_game_screen_reset(
- MineSweeperGameScreen* instance,
- uint8_t width,
- uint8_t height,
- uint8_t difficulty,
- bool ensure_solvable) {
- furi_assert(instance);
- instance->input_callback = NULL;
- // Reset led
- mine_sweeper_led_reset(instance->context);
- // We need to initize board width and height before setup
- mine_sweeper_game_screen_set_board_information(
- instance, width, height, difficulty, ensure_solvable);
- mine_sweeper_game_screen_reset_clock(instance);
- // Here we are going to generate a valid map for the player
- bool is_valid_board = false;
- size_t memsz = sizeof(MineSweeperTile) * MINESWEEPER_BOARD_MAX_TILES;
- do {
- setup_board(instance);
- uint16_t num_mines = 1;
- uint16_t board_width = 16; //default values
- uint16_t board_height = 7; //default values
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- {
- num_mines = model->mines_left;
- board_width = model->board_width;
- board_height = model->board_height;
- memset(board_t, 0, memsz);
- memcpy(
- board_t, model->board, sizeof(MineSweeperTile) * (board_width * board_height));
- },
- true);
- is_valid_board = check_board_with_verifier(board_t, board_width, board_height, num_mines);
- } while(ensure_solvable && !is_valid_board);
- }
- // This function should be called when you want to reset the game clock
- // Already called in reset and alloc function for game, but can be called from
- // other scenes that need it like a start scene that plays after alloc
- void mine_sweeper_game_screen_reset_clock(MineSweeperGameScreen* instance) {
- furi_assert(instance);
- with_view_model(
- instance->view,
- MineSweeperGameScreenModel * model,
- { model->start_tick = furi_get_tick(); },
- true);
- }
- View* mine_sweeper_game_screen_get_view(MineSweeperGameScreen* instance) {
- furi_assert(instance);
- return instance->view;
- }
- void mine_sweeper_game_screen_set_context(MineSweeperGameScreen* instance, void* context) {
- furi_assert(instance);
- instance->context = context;
- }
|