| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691 |
- #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;
- }
|