update minesweeper

https://github.com/panki27/minesweeper
This commit is contained in:
MX 2022-10-14 01:43:57 +03:00
parent a16542cda6
commit dad4772bec
No known key found for this signature in database
GPG Key ID: 6C4C311DFD4B4AB5
3 changed files with 446 additions and 507 deletions

View File

@ -11,6 +11,7 @@ This is a Minesweeper implementation for the Flipper Zero device.
- Arrow buttons to move - Arrow buttons to move
- Push center button to open field - Push center button to open field
- Hold center button to toggle flag - Hold center button to toggle flag
- Push center button on an already open field that has the correct amount of flags surrounding it to auto-open the remaining ones (thanks @gelin!)
## Compiling ## Compiling

View File

@ -1,144 +1,48 @@
#define tile_0_width 8 #define tile_0_width 8
#define tile_0_height 8 #define tile_0_height 8
static uint8_t tile_0_bits[] = { static uint8_t tile_0_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
#define tile_1_width 8 #define tile_1_width 8
#define tile_1_height 8 #define tile_1_height 8
static uint8_t tile_1_bits[] = { static uint8_t tile_1_bits[] = {
0x00, 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, };
0x10,
0x18,
0x10,
0x10,
0x10,
0x10,
0x00,
};
#define tile_2_width 8 #define tile_2_width 8
#define tile_2_height 8 #define tile_2_height 8
static uint8_t tile_2_bits[] = { static uint8_t tile_2_bits[] = {
0x00, 0x00, 0x1C, 0x20, 0x20, 0x18, 0x04, 0x3C, 0x00, };
0x1C,
0x20,
0x20,
0x18,
0x04,
0x3C,
0x00,
};
#define tile_3_width 8 #define tile_3_width 8
#define tile_3_height 8 #define tile_3_height 8
static uint8_t tile_3_bits[] = { static uint8_t tile_3_bits[] = {
0x00, 0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, };
0x1C,
0x20,
0x20,
0x18,
0x20,
0x1C,
0x00,
};
#define tile_4_width 8 #define tile_4_width 8
#define tile_4_height 8 #define tile_4_height 8
static uint8_t tile_4_bits[] = { static uint8_t tile_4_bits[] = {
0x00, 0x00, 0x04, 0x14, 0x14, 0x3C, 0x10, 0x10, 0x00, };
0x04,
0x14,
0x14,
0x3C,
0x10,
0x10,
0x00,
};
#define tile_5_width 8 #define tile_5_width 8
#define tile_5_height 8 #define tile_5_height 8
static uint8_t tile_5_bits[] = { static uint8_t tile_5_bits[] = {
0x00, 0x00, 0x3C, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, };
0x3C,
0x04,
0x1C,
0x20,
0x20,
0x1C,
0x00,
};
#define tile_6_width 8 #define tile_6_width 8
#define tile_6_height 8 #define tile_6_height 8
static uint8_t tile_6_bits[] = { static uint8_t tile_6_bits[] = {
0x00, 0x00, 0x18, 0x24, 0x04, 0x1C, 0x24, 0x18, 0x00, };
0x18,
0x24,
0x04,
0x1C,
0x24,
0x18,
0x00,
};
#define tile_7_width 8 #define tile_7_width 8
#define tile_7_height 8 #define tile_7_height 8
static uint8_t tile_7_bits[] = { static uint8_t tile_7_bits[] = {
0x00, 0x00, 0x3C, 0x20, 0x20, 0x10, 0x08, 0x08, 0x00, };
0x3C,
0x20,
0x20,
0x10,
0x08,
0x08,
0x00,
};
#define tile_8_width 8 #define tile_8_width 8
#define tile_8_height 8 #define tile_8_height 8
static uint8_t tile_8_bits[] = { static uint8_t tile_8_bits[] = {
0x00, 0x00, 0x18, 0x24, 0x18, 0x24, 0x24, 0x18, 0x00, };
0x18,
0x24,
0x18,
0x24,
0x24,
0x18,
0x00,
};
#define tile_flag_width 8 #define tile_flag_width 8
#define tile_flag_height 8 #define tile_flag_height 8
static uint8_t tile_flag_bits[] = { static uint8_t tile_flag_bits[] = {
0xFF, 0xFF, 0x81, 0xB9, 0x89, 0x89, 0x9D, 0x81, 0xFF, };
0x81,
0xB9,
0x89,
0x89,
0x9D,
0x81,
0xFF,
};
#define tile_mine_width 8 #define tile_mine_width 8
#define tile_mine_height 8 #define tile_mine_height 8
static uint8_t tile_mine_bits[] = { static uint8_t tile_mine_bits[] = {
0x55, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, };
0xAA,
0x55,
0xAA,
0x55,
0xAA,
0x55,
0xAA,
};
#define tile_uncleared_width 8 #define tile_uncleared_width 8
#define tile_uncleared_height 8 #define tile_uncleared_height 8
static uint8_t tile_uncleared_bits[] = { static uint8_t tile_uncleared_bits[] = {
0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, };
0x81,
0x81,
0x81,
0x81,
0x81,
0x81,
0xFF,
};

View File

@ -28,18 +28,18 @@ typedef struct {
} PluginEvent; } PluginEvent;
typedef enum { typedef enum {
TileType0, // this HAS to be in order, for hint assignment to be ez pz TileType0, // this HAS to be in order, for hint assignment to be ez pz
TileType1, TileType1,
TileType2, TileType2,
TileType3, TileType3,
TileType4, TileType4,
TileType5, TileType5,
TileType6, TileType6,
TileType7, TileType7,
TileType8, TileType8,
TileTypeUncleared, TileTypeUncleared,
TileTypeFlag, TileTypeFlag,
TileTypeMine TileTypeMine
} TileType; } TileType;
typedef enum { typedef enum {
@ -48,23 +48,23 @@ typedef enum {
} Field; } Field;
typedef struct { typedef struct {
Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
FuriTimer* timer; FuriTimer* timer;
int cursor_x; int cursor_x;
int cursor_y; int cursor_y;
int mines_left; int mines_left;
int fields_cleared; int fields_cleared;
int flags_set; int flags_set;
bool game_started; bool game_started;
uint32_t game_started_tick; uint32_t game_started_tick;
} Minesweeper; } Minesweeper;
static void timer_callback(void* ctx) { static void timer_callback(void* ctx) {
UNUSED(ctx); UNUSED(ctx);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_reset_vibro); notification_message(notification, &sequence_reset_vibro);
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
} }
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
@ -76,8 +76,8 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
static void render_callback(Canvas* const canvas, void* ctx) { static void render_callback(Canvas* const canvas, void* ctx) {
const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25); const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25);
if(minesweeper_state == NULL) { if (minesweeper_state == NULL) {
return; return;
} }
FuriString* mineStr; FuriString* mineStr;
FuriString* timeStr; FuriString* timeStr;
@ -90,134 +90,134 @@ static void render_callback(Canvas* const canvas, void* ctx) {
int seconds = 0; int seconds = 0;
int minutes = 0; int minutes = 0;
if(minesweeper_state->game_started) { if (minesweeper_state->game_started) {
uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); seconds = (int) ticks_elapsed / furi_kernel_get_tick_frequency();
minutes = (int)seconds / 60; minutes = (int) seconds / 60;
seconds = seconds % 60; seconds = seconds % 60;
} }
furi_string_printf(timeStr, "%01d:%02d", minutes, seconds); furi_string_printf(timeStr, "%01d:%02d", minutes, seconds);
canvas_draw_str_aligned(canvas, 128, 0, AlignRight, AlignTop, furi_string_get_cstr(timeStr)); canvas_draw_str_aligned(canvas, 128, 0, AlignRight, AlignTop, furi_string_get_cstr(timeStr));
for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
for(int x = 0; x < PLAYFIELD_WIDTH; x++) { for (int x = 0; x < PLAYFIELD_WIDTH; x++) {
if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) { if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
canvas_invert_color(canvas); canvas_invert_color(canvas);
}
switch(minesweeper_state->playfield[x][y]) {
case TileType0:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_0_bits);
break;
case TileType1:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_1_bits);
break;
case TileType2:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_2_bits);
break;
case TileType3:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_3_bits);
break;
case TileType4:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_4_bits);
break;
case TileType5:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_5_bits);
break;
case TileType6:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_6_bits);
break;
case TileType7:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_7_bits);
break;
case TileType8:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_8_bits);
break;
case TileTypeFlag:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_flag_bits);
break;
case TileTypeUncleared:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_uncleared_bits);
break;
case TileTypeMine:
canvas_draw_xbm(
canvas,
x * TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_mine_bits);
break;
}
if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
canvas_invert_color(canvas);
}
} }
switch (minesweeper_state->playfield[x][y]) {
case TileType0:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_0_bits);
break;
case TileType1:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_1_bits);
break;
case TileType2:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_2_bits);
break;
case TileType3:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_3_bits);
break;
case TileType4:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_4_bits);
break;
case TileType5:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_5_bits);
break;
case TileType6:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_6_bits);
break;
case TileType7:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_7_bits);
break;
case TileType8:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_8_bits);
break;
case TileTypeFlag:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_flag_bits);
break;
case TileTypeUncleared:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_uncleared_bits);
break;
case TileTypeMine:
canvas_draw_xbm(
canvas,
x*TILE_HEIGHT, // x
8 + (y * TILE_WIDTH), // y
TILE_WIDTH,
TILE_HEIGHT,
tile_mine_bits);
break;
}
if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
canvas_invert_color(canvas);
}
}
} }
furi_string_free(mineStr); furi_string_free(mineStr);
@ -226,302 +226,336 @@ static void render_callback(Canvas* const canvas, void* ctx) {
} }
static void setup_playfield(Minesweeper* minesweeper_state) { static void setup_playfield(Minesweeper* minesweeper_state) {
int mines_left = MINECOUNT; int mines_left = MINECOUNT;
for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
for(int x = 0; x < PLAYFIELD_WIDTH; x++) { for (int x = 0; x < PLAYFIELD_WIDTH; x++){
minesweeper_state->minefield[x][y] = FieldEmpty; minesweeper_state->minefield[x][y] = FieldEmpty;
minesweeper_state->playfield[x][y] = TileTypeUncleared; minesweeper_state->playfield[x][y] = TileTypeUncleared;
}
} }
while(mines_left > 0) { }
int rand_x = rand() % PLAYFIELD_WIDTH; while(mines_left > 0) {
int rand_y = rand() % PLAYFIELD_HEIGHT; int rand_x = rand() % PLAYFIELD_WIDTH;
// make sure first guess isn't a mine int rand_y = rand() % PLAYFIELD_HEIGHT;
if(minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty && // make sure first guess isn't a mine
(minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y)) { if (minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty &&
minesweeper_state->minefield[rand_x][rand_y] = FieldMine; (minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y )) {
mines_left--; minesweeper_state->minefield[rand_x][rand_y] = FieldMine;
} mines_left--;
} }
minesweeper_state->mines_left = MINECOUNT; }
minesweeper_state->fields_cleared = 0; minesweeper_state->mines_left = MINECOUNT;
minesweeper_state->flags_set = 0; minesweeper_state->fields_cleared = 0;
minesweeper_state->game_started_tick = furi_get_tick(); minesweeper_state->flags_set = 0;
minesweeper_state->game_started = false; minesweeper_state->game_started_tick = furi_get_tick();
minesweeper_state->game_started = false;
} }
static void place_flag(Minesweeper* minesweeper_state) { static void place_flag(Minesweeper* minesweeper_state) {
if(minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeUncleared) {
TileTypeUncleared) { minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeFlag;
minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = minesweeper_state->flags_set++;
TileTypeFlag; } else if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeFlag) {
minesweeper_state->flags_set++; minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeUncleared;
} else if( minesweeper_state->flags_set--;
minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == }
TileTypeFlag) {
minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] =
TileTypeUncleared;
minesweeper_state->flags_set--;
}
} }
static bool game_lost(Minesweeper* minesweeper_state) { static bool game_lost(Minesweeper* minesweeper_state) {
// returns true if the player wants to restart, otherwise false // returns true if the player wants to restart, otherwise false
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc(); DialogMessage* message = dialog_message_alloc();
const char* header_text = "Game Over"; const char* header_text = "Game Over";
const char* message_text = "You hit a mine!"; const char* message_text = "You hit a mine!";
dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter);
dialog_message_set_buttons(message, NULL, "Play again", NULL); dialog_message_set_buttons(message, NULL, "Play again", NULL);
dialog_message_set_icon(message, NULL, 0, 10); dialog_message_set_icon(message, NULL, 0, 10);
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
notification_message(notifications, &sequence_set_vibro_on); notification_message(notifications, &sequence_set_vibro_on);
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
furi_timer_start(minesweeper_state->timer, (uint32_t)furi_kernel_get_tick_frequency() * 0.2); furi_timer_start(minesweeper_state->timer, (uint32_t) furi_kernel_get_tick_frequency() * 0.2);
DialogMessageButton choice = dialog_message_show(dialogs, message); DialogMessageButton choice = dialog_message_show(dialogs, message);
dialog_message_free(message); dialog_message_free(message);
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
return choice == DialogMessageButtonCenter; return choice == DialogMessageButtonCenter;
} }
static bool game_won(Minesweeper* minesweeper_state) { static bool game_won(Minesweeper* minesweeper_state) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
FuriString* tempStr; FuriString* tempStr;
tempStr = furi_string_alloc(); tempStr = furi_string_alloc();
int seconds = 0; int seconds = 0;
int minutes = 0; int minutes = 0;
uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); seconds = (int) ticks_elapsed / furi_kernel_get_tick_frequency();
minutes = (int)seconds / 60; minutes = (int) seconds / 60;
seconds = seconds % 60; seconds = seconds % 60;
DialogMessage* message = dialog_message_alloc(); DialogMessage* message = dialog_message_alloc();
const char* header_text = "Game won!"; const char* header_text = "Game won!";
furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds); furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds);
dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
dialog_message_set_text( dialog_message_set_text(message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter);
message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter); dialog_message_set_buttons(message, NULL, "Play again", NULL);
dialog_message_set_buttons(message, NULL, "Play again", NULL); dialog_message_set_icon(message, NULL, 72, 17);
// TODO: create icon
dialog_message_set_icon(message, NULL, 72, 17);
DialogMessageButton choice = dialog_message_show(dialogs, message); DialogMessageButton choice = dialog_message_show(dialogs, message);
dialog_message_free(message); dialog_message_free(message);
furi_string_free(tempStr); furi_string_free(tempStr);
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
return choice == DialogMessageButtonCenter; return choice == DialogMessageButtonCenter;
} }
// returns false if the move loses the game - otherwise true
static bool play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) { static bool play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) {
if(minesweeper_state->playfield[cursor_x][cursor_y] != TileTypeUncleared) { if (minesweeper_state->playfield[cursor_x][cursor_y] == TileTypeFlag) {
// we're on an already uncovered field // we're on a flagged field, do nothing
return true; return true;
} }
if(minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) { if (minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) {
// TODO: player loses! // player loses - draw mine
minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine; minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine;
return false; return false;
} else { }
// get number of surrounding mines.
int hint = 0; if (minesweeper_state->playfield[cursor_x][cursor_y] >= TileType1 && minesweeper_state->playfield[cursor_x][cursor_y] <= TileType8) {
for(int y = cursor_y - 1; y <= cursor_y + 1; y++) { // click on a cleared cell with a number
for(int x = cursor_x - 1; x <= cursor_x + 1; x++) { // count the flags around
if(x == cursor_x && y == cursor_y) { int flags = 0;
// we're on the cell the user selected, so ignore. for (int y = cursor_y-1; y <= cursor_y+1; y++) {
continue; for (int x = cursor_x-1; x <= cursor_x+1; x++) {
} if ( x == cursor_x && y == cursor_y ) {
// make sure we don't go OOB // we're on the cell the user selected, so ignore.
if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) { continue;
if(minesweeper_state->minefield[x][y] == FieldMine) {
hint++;
}
}
}
} }
// 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜 // make sure we don't go OOB
minesweeper_state->playfield[cursor_x][cursor_y] = hint; if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
minesweeper_state->fields_cleared++; if (minesweeper_state->playfield[x][y] == TileTypeFlag) {
FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint); flags ++;
if(hint == 0) { }
// auto open surrounding fields.
for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) {
for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) {
if(auto_x == cursor_x && auto_y == cursor_y) {
continue;
}
if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 &&
auto_y < PLAYFIELD_HEIGHT) {
if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
play_move(minesweeper_state, auto_x, auto_y);
}
}
}
}
} }
return true; }
} }
int mines = minesweeper_state->playfield[cursor_x][cursor_y]; // ¯\_(ツ)_/¯
if (flags == mines) {
// auto uncover all non-flags around (to win faster ;)
for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
if ( auto_x == cursor_x && auto_y == cursor_y ) {
continue;
}
if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
if(!play_move(minesweeper_state, auto_x, auto_y)) {
// flags were wrong, we got a mine!
return false;
}
}
}
}
}
// we're done without hitting a mine - so return
return true;
}
}
// calculate number of surrounding mines.
int hint = 0;
for (int y = cursor_y-1; y <= cursor_y+1; y++) {
for (int x = cursor_x-1; x <= cursor_x+1; x++) {
if ( x == cursor_x && y == cursor_y ) {
// we're on the cell the user selected, so ignore.
continue;
}
// make sure we don't go OOB
if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
if(minesweeper_state->minefield[x][y] == FieldMine) {
hint ++;
}
}
}
}
// 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜
minesweeper_state->playfield[cursor_x][cursor_y] = hint;
minesweeper_state->fields_cleared++;
FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint);
if (hint == 0) {
// the field is "empty"
// auto open surrounding fields.
for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
if ( auto_x == cursor_x && auto_y == cursor_y ) {
continue;
}
if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
play_move(minesweeper_state, auto_x, auto_y);
}
}
}
}
}
return true;
} }
static void minesweeper_state_init(Minesweeper* const minesweeper_state) { static void minesweeper_state_init(Minesweeper* const minesweeper_state) {
minesweeper_state->cursor_x = minesweeper_state->cursor_y = 0; minesweeper_state->cursor_x = minesweeper_state->cursor_y = 0;
minesweeper_state->game_started = false; minesweeper_state->game_started = false;
for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
for(int x = 0; x < PLAYFIELD_WIDTH; x++) { for (int x = 0; x < PLAYFIELD_WIDTH; x++){
minesweeper_state->playfield[x][y] = TileTypeUncleared; minesweeper_state->playfield[x][y] = TileTypeUncleared;
} }
} }
} }
int32_t minesweeper_app(void* p) { int32_t minesweeper_app(void* p) {
UNUSED(p); UNUSED(p);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc(); DialogMessage* message = dialog_message_alloc();
const char* header_text = "Minesweeper"; const char* header_text = "Minesweeper";
const char* message_text = "Hold OK pressed to toggle flags.\ngithub.com/panki27"; const char* message_text = "Hold OK pressed to toggle flags.\ngithub.com/panki27";
dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter);
dialog_message_set_buttons(message, NULL, "Play", NULL); dialog_message_set_buttons(message, NULL, "Play", NULL);
dialog_message_set_icon(message, NULL, 0, 10); dialog_message_set_icon(message, NULL, 0, 10);
dialog_message_show(dialogs, message); dialog_message_show(dialogs, message);
dialog_message_free(message); dialog_message_free(message);
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper)); Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper));
// setup // setup
minesweeper_state_init(minesweeper_state); minesweeper_state_init(minesweeper_state);
ValueMutex state_mutex; ValueMutex state_mutex;
if(!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) { if (!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) {
FURI_LOG_E("Minesweeper", "cannot create mutex\r\n"); FURI_LOG_E("Minesweeper", "cannot create mutex\r\n");
free(minesweeper_state); free(minesweeper_state);
return 255; return 255;
} }
// BEGIN IMPLEMENTATION // BEGIN IMPLEMENTATION
// Set system callbacks // Set system callbacks
ViewPort* view_port = view_port_alloc(); ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex); view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue); view_port_input_callback_set(view_port, input_callback, event_queue);
minesweeper_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, &state_mutex); minesweeper_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, &state_mutex);
// Open GUI and register view_port // Open GUI and register view_port
Gui* gui = furi_record_open("gui"); Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen); gui_add_view_port(gui, view_port, GuiLayerFullscreen);
PluginEvent event; PluginEvent event;
for(bool processing = true; processing;) { for (bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex); Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) { if(event_status == FuriStatusOk) {
// press events // press events
if(event.type == EventTypeKey) { if(event.type == EventTypeKey) {
if(event.input.type == InputTypeShort) { if(event.input.type == InputTypeShort) {
switch(event.input.key) { switch(event.input.key) {
case InputKeyUp: case InputKeyUp:
minesweeper_state->cursor_y--; minesweeper_state->cursor_y--;
if(minesweeper_state->cursor_y < 0) { if(minesweeper_state->cursor_y < 0) {
minesweeper_state->cursor_y = 0; minesweeper_state->cursor_y = 0;
} }
break; break;
case InputKeyDown: case InputKeyDown:
minesweeper_state->cursor_y++; minesweeper_state->cursor_y++;
if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) { if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) {
minesweeper_state->cursor_y = PLAYFIELD_HEIGHT - 1; minesweeper_state->cursor_y = PLAYFIELD_HEIGHT-1;
} }
break; break;
case InputKeyRight: case InputKeyRight:
minesweeper_state->cursor_x++; minesweeper_state->cursor_x++;
if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) { if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) {
minesweeper_state->cursor_x = PLAYFIELD_WIDTH - 1; minesweeper_state->cursor_x = PLAYFIELD_WIDTH-1;
} }
break; break;
case InputKeyLeft: case InputKeyLeft:
minesweeper_state->cursor_x--; minesweeper_state->cursor_x--;
if(minesweeper_state->cursor_x < 0) { if(minesweeper_state->cursor_x < 0) {
minesweeper_state->cursor_x = 0; minesweeper_state->cursor_x = 0;
} }
break; break;
case InputKeyOk: case InputKeyOk:
if(!minesweeper_state->game_started) { if (!minesweeper_state->game_started) {
setup_playfield(minesweeper_state); setup_playfield(minesweeper_state);
minesweeper_state->game_started = true; minesweeper_state->game_started = true;
} }
if(!play_move( if (!play_move(minesweeper_state, minesweeper_state->cursor_x, minesweeper_state->cursor_y)) {
minesweeper_state, // ooops. looks like we hit a mine!
minesweeper_state->cursor_x, if (game_lost(minesweeper_state)) {
minesweeper_state->cursor_y)) { // player wants to restart.
// ooops. looks like we hit a mine! setup_playfield(minesweeper_state);
if(game_lost(minesweeper_state)) { } else {
// player wants to restart. // player wants to exit :(
setup_playfield(minesweeper_state); processing = false;
} else {
// player wants to exit :(
processing = false;
}
} else {
// check win condition.
if(minesweeper_state->fields_cleared ==
(PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH) - MINECOUNT) {
if(game_won(minesweeper_state)) {
//player wants to restart
setup_playfield(minesweeper_state);
} else {
processing = false;
}
}
}
break;
case InputKeyBack:
// Exit the plugin
processing = false;
break;
}
} else if(event.input.type == InputTypeLong) {
// hold events
FURI_LOG_D("Minesweeper", "Got a long press!");
switch(event.input.key) {
case InputKeyUp:
case InputKeyDown:
case InputKeyRight:
case InputKeyLeft:
break;
case InputKeyOk:
FURI_LOG_D("Minesweeper", "Toggling flag");
place_flag(minesweeper_state);
break;
case InputKeyBack:
processing = false;
break;
}
} }
} } else {
// check win condition.
if (minesweeper_state->fields_cleared == (PLAYFIELD_HEIGHT*PLAYFIELD_WIDTH) - MINECOUNT){
if (game_won(minesweeper_state)) {
//player wants to restart
setup_playfield(minesweeper_state);
} else {
processing = false;
}
}
}
break;
case InputKeyBack:
// Exit the plugin
processing = false;
break;
}
} else if (event.input.type == InputTypeLong) {
// hold events
FURI_LOG_D("Minesweeper", "Got a long press!");
switch(event.input.key) {
case InputKeyUp:
case InputKeyDown:
case InputKeyRight:
case InputKeyLeft:
break;
case InputKeyOk:
FURI_LOG_D("Minesweeper", "Toggling flag");
place_flag(minesweeper_state);
break;
case InputKeyBack:
processing = false;
break;
}
} }
view_port_update(view_port); }
release_mutex(&state_mutex, minesweeper_state); } else {
// event timeout
;
} }
view_port_enabled_set(view_port, false); view_port_update(view_port);
gui_remove_view_port(gui, view_port); release_mutex(&state_mutex, minesweeper_state);
furi_record_close("gui"); }
view_port_free(view_port); view_port_enabled_set(view_port, false);
furi_message_queue_free(event_queue); gui_remove_view_port(gui, view_port);
delete_mutex(&state_mutex); furi_record_close("gui");
furi_timer_free(minesweeper_state->timer); view_port_free(view_port);
free(minesweeper_state); furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_timer_free(minesweeper_state->timer);
free(minesweeper_state);
return 0; return 0;
} }