minesweeper.c 12 KB

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