minesweeper.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <stdlib.h>
  6. #include <notification/notification_messages.h>
  7. #include "assets.h"
  8. #define PLAYFIELD_WIDTH 16
  9. #define PLAYFIELD_HEIGHT 7
  10. #define TILE_WIDTH 8
  11. #define TILE_HEIGHT 8
  12. #define MINECOUNT 27
  13. typedef enum {
  14. EventTypeTick,
  15. EventTypeKey,
  16. } EventType;
  17. typedef struct {
  18. EventType type;
  19. InputEvent input;
  20. } PluginEvent;
  21. typedef enum {
  22. TileType0, // this HAS to be in order, then
  23. TileType1,
  24. TileType2,
  25. TileType3,
  26. TileType4,
  27. TileType5,
  28. TileType6,
  29. TileType7,
  30. TileType8,
  31. TileTypeUncleared,
  32. TileTypeFlag,
  33. TileTypeMine
  34. } TileType;
  35. typedef enum {
  36. FieldEmpty, // <-- same goes for this
  37. FieldMine
  38. } Field;
  39. typedef struct {
  40. Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
  41. TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
  42. int cursor_x;
  43. int cursor_y;
  44. bool game_started;
  45. } Minesweeper;
  46. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  47. furi_assert(event_queue);
  48. PluginEvent event = {.type = EventTypeKey, .input = *input_event};
  49. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  50. }
  51. static void render_callback(Canvas* const canvas, void* ctx) {
  52. const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25);
  53. if (minesweeper_state == NULL) {
  54. return;
  55. }
  56. canvas_set_font(canvas, FontPrimary);
  57. for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
  58. for (int x = 0; x < PLAYFIELD_WIDTH; x++) {
  59. if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
  60. canvas_invert_color(canvas);
  61. }
  62. switch (minesweeper_state->playfield[x][y]) {
  63. case TileType0:
  64. canvas_draw_xbm(
  65. canvas,
  66. x*TILE_HEIGHT, // x
  67. 8 + (y * TILE_WIDTH), // y
  68. TILE_WIDTH,
  69. TILE_HEIGHT,
  70. tile_0_bits);
  71. break;
  72. case TileType1:
  73. canvas_draw_xbm(
  74. canvas,
  75. x*TILE_HEIGHT, // x
  76. 8 + (y * TILE_WIDTH), // y
  77. TILE_WIDTH,
  78. TILE_HEIGHT,
  79. tile_1_bits);
  80. break;
  81. case TileType2:
  82. canvas_draw_xbm(
  83. canvas,
  84. x*TILE_HEIGHT, // x
  85. 8 + (y * TILE_WIDTH), // y
  86. TILE_WIDTH,
  87. TILE_HEIGHT,
  88. tile_2_bits);
  89. break;
  90. case TileType3:
  91. canvas_draw_xbm(
  92. canvas,
  93. x*TILE_HEIGHT, // x
  94. 8 + (y * TILE_WIDTH), // y
  95. TILE_WIDTH,
  96. TILE_HEIGHT,
  97. tile_3_bits);
  98. break;
  99. case TileType4:
  100. canvas_draw_xbm(
  101. canvas,
  102. x*TILE_HEIGHT, // x
  103. 8 + (y * TILE_WIDTH), // y
  104. TILE_WIDTH,
  105. TILE_HEIGHT,
  106. tile_4_bits);
  107. break;
  108. case TileType5:
  109. canvas_draw_xbm(
  110. canvas,
  111. x*TILE_HEIGHT, // x
  112. 8 + (y * TILE_WIDTH), // y
  113. TILE_WIDTH,
  114. TILE_HEIGHT,
  115. tile_5_bits);
  116. break;
  117. case TileType6:
  118. canvas_draw_xbm(
  119. canvas,
  120. x*TILE_HEIGHT, // x
  121. 8 + (y * TILE_WIDTH), // y
  122. TILE_WIDTH,
  123. TILE_HEIGHT,
  124. tile_6_bits);
  125. break;
  126. case TileType7:
  127. canvas_draw_xbm(
  128. canvas,
  129. x*TILE_HEIGHT, // x
  130. 8 + (y * TILE_WIDTH), // y
  131. TILE_WIDTH,
  132. TILE_HEIGHT,
  133. tile_7_bits);
  134. break;
  135. case TileType8:
  136. canvas_draw_xbm(
  137. canvas,
  138. x*TILE_HEIGHT, // x
  139. 8 + (y * TILE_WIDTH), // y
  140. TILE_WIDTH,
  141. TILE_HEIGHT,
  142. tile_8_bits);
  143. break;
  144. case TileTypeFlag:
  145. canvas_draw_xbm(
  146. canvas,
  147. x*TILE_HEIGHT, // x
  148. 8 + (y * TILE_WIDTH), // y
  149. TILE_WIDTH,
  150. TILE_HEIGHT,
  151. tile_flag_bits);
  152. break;
  153. case TileTypeUncleared:
  154. canvas_draw_xbm(
  155. canvas,
  156. x*TILE_HEIGHT, // x
  157. 8 + (y * TILE_WIDTH), // y
  158. TILE_WIDTH,
  159. TILE_HEIGHT,
  160. tile_uncleared_bits);
  161. break;
  162. case TileTypeMine:
  163. canvas_draw_xbm(
  164. canvas,
  165. x*TILE_HEIGHT, // x
  166. 8 + (y * TILE_WIDTH), // y
  167. TILE_WIDTH,
  168. TILE_HEIGHT,
  169. tile_mine_bits);
  170. break;
  171. }
  172. if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
  173. canvas_invert_color(canvas);
  174. }
  175. }
  176. }
  177. release_mutex((ValueMutex*)ctx, minesweeper_state);
  178. }
  179. static void setup_playfield(Minesweeper* minesweeper_state) {
  180. int mines_left = MINECOUNT;
  181. while(mines_left > 0) {
  182. int rand_x = rand() % PLAYFIELD_WIDTH;
  183. int rand_y = rand() % PLAYFIELD_HEIGHT;
  184. // make sure first guess isn't a mine
  185. if (minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty &&
  186. (minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y )) {
  187. minesweeper_state->minefield[rand_x][rand_y] = FieldMine;
  188. mines_left--;
  189. }
  190. }
  191. }
  192. static void place_flag(Minesweeper* minesweeper_state) {
  193. if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeUncleared) {
  194. minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeFlag;
  195. } else if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeFlag) {
  196. minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeUncleared;
  197. }
  198. }
  199. static void game_lost() {
  200. NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
  201. notification_message(notifications, &sequence_set_vibro_on);
  202. furi_delay_ms(200);
  203. notification_message(notifications, &sequence_reset_vibro);
  204. furi_record_close(RECORD_NOTIFICATION);
  205. }
  206. static void play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) {
  207. if (minesweeper_state->playfield[cursor_x][cursor_y] != TileTypeUncleared) {
  208. // we're on an already uncovered field
  209. return;
  210. }
  211. if (minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) {
  212. // TODO: player loses!
  213. minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine;
  214. game_lost();
  215. return;
  216. } else {
  217. // get number of surrounding mines.
  218. int hint = 0;
  219. for (int y = cursor_y-1; y <= cursor_y+1; y++) {
  220. for (int x = cursor_x-1; x <= cursor_x+1; x++) {
  221. if ( x == cursor_x && y == cursor_y ) {
  222. // we're on the cell the user selected, so ignore.
  223. continue;
  224. }
  225. // make sure we don't go OOB
  226. if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
  227. if(minesweeper_state->minefield[x][y] == FieldMine) {
  228. hint ++;
  229. }
  230. }
  231. }
  232. }
  233. // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜
  234. minesweeper_state->playfield[cursor_x][cursor_y] = hint;
  235. FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint);
  236. if (hint == 0) {
  237. // auto open surrounding fields.
  238. for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
  239. for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
  240. if ( auto_x == cursor_x && auto_y == cursor_y ) {
  241. continue;
  242. }
  243. if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
  244. if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
  245. play_move(minesweeper_state, auto_x, auto_y);
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. }
  253. static void minesweeper_state_init(Minesweeper* const plugin_state) {
  254. plugin_state->cursor_x = plugin_state->cursor_y = 0;
  255. plugin_state->game_started = false;
  256. for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
  257. for (int x = 0; x < PLAYFIELD_WIDTH; x++){
  258. plugin_state->minefield[x][y] = FieldEmpty;
  259. plugin_state->playfield[x][y] = TileTypeUncleared;
  260. }
  261. }
  262. }
  263. int32_t minesweeper_app(void* p) {
  264. UNUSED(p);
  265. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
  266. Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper));
  267. // setup
  268. minesweeper_state_init(minesweeper_state);
  269. ValueMutex state_mutex;
  270. if (!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) {
  271. FURI_LOG_E("Minesweeper", "cannot create mutex\r\n");
  272. free(minesweeper_state);
  273. return 255;
  274. }
  275. // BEGIN IMPLEMENTATION
  276. // Set system callbacks
  277. ViewPort* view_port = view_port_alloc();
  278. view_port_draw_callback_set(view_port, render_callback, &state_mutex);
  279. view_port_input_callback_set(view_port, input_callback, event_queue);
  280. // Open GUI and register view_port
  281. Gui* gui = furi_record_open("gui");
  282. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  283. PluginEvent event;
  284. for (bool processing = true; processing;) {
  285. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  286. Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex);
  287. if(event_status == FuriStatusOk) {
  288. // press events
  289. if(event.type == EventTypeKey) {
  290. if(event.input.type == InputTypePress) {
  291. switch(event.input.key) {
  292. case InputKeyUp:
  293. minesweeper_state->cursor_y--;
  294. if(minesweeper_state->cursor_y < 0) {
  295. minesweeper_state->cursor_y = 0;
  296. }
  297. break;
  298. case InputKeyDown:
  299. minesweeper_state->cursor_y++;
  300. if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) {
  301. minesweeper_state->cursor_y = PLAYFIELD_HEIGHT-1;
  302. }
  303. break;
  304. case InputKeyRight:
  305. minesweeper_state->cursor_x++;
  306. if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) {
  307. minesweeper_state->cursor_x = PLAYFIELD_WIDTH-1;
  308. }
  309. break;
  310. case InputKeyLeft:
  311. minesweeper_state->cursor_x--;
  312. if(minesweeper_state->cursor_x < 0) {
  313. minesweeper_state->cursor_x = 0;
  314. }
  315. break;
  316. case InputKeyOk:
  317. if (!minesweeper_state->game_started) {
  318. setup_playfield(minesweeper_state);
  319. minesweeper_state->game_started = true;
  320. }
  321. play_move(
  322. minesweeper_state,
  323. minesweeper_state->cursor_x,
  324. minesweeper_state->cursor_y);
  325. break;
  326. case InputKeyBack:
  327. // Exit the plugin
  328. processing = false;
  329. break;
  330. }
  331. } else if (event.input.type == InputTypeLong) {
  332. // hold events
  333. switch(event.input.key) {
  334. case InputKeyUp:
  335. case InputKeyDown:
  336. case InputKeyRight:
  337. case InputKeyLeft:
  338. break;
  339. case InputKeyOk:
  340. place_flag(minesweeper_state);
  341. break;
  342. case InputKeyBack:
  343. processing = false;
  344. break;
  345. }
  346. }
  347. }
  348. } else {
  349. FURI_LOG_D("Minesweeper", "FuriMessageQueue: event timeout");
  350. // event timeout
  351. }
  352. view_port_update(view_port);
  353. release_mutex(&state_mutex, minesweeper_state);
  354. }
  355. view_port_enabled_set(view_port, false);
  356. gui_remove_view_port(gui, view_port);
  357. furi_record_close("gui");
  358. view_port_free(view_port);
  359. furi_message_queue_free(event_queue);
  360. delete_mutex(&state_mutex);
  361. free(minesweeper_state);
  362. return 0;
  363. }