minesweeper.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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 play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) {
  192. if (minesweeper_state->playfield[cursor_x][cursor_y] != TileTypeUncleared) {
  193. // we're on an already uncovered field
  194. return;
  195. }
  196. if (minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) {
  197. // TODO: player loses!
  198. return;
  199. } else {
  200. // get number of surrounding mines.
  201. int hint = 0;
  202. for (int y = cursor_y-1; y <= cursor_y+1; y++) {
  203. for (int x = cursor_x-1; x <= cursor_x+1; x++) {
  204. if ( x == cursor_x && y == cursor_y ) {
  205. // we're on the cell the user selected, so ignore.
  206. continue;
  207. }
  208. // make sure we don't go OOB
  209. if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
  210. if(minesweeper_state->minefield[x][y] == FieldMine) {
  211. hint ++;
  212. }
  213. }
  214. }
  215. }
  216. // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜
  217. minesweeper_state->playfield[cursor_x][cursor_y] = hint;
  218. FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint);
  219. if (hint == 0) {
  220. // auto open surrounding fields.
  221. for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
  222. for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
  223. if ( auto_x == cursor_x && auto_y == cursor_y ) {
  224. continue;
  225. }
  226. if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
  227. if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
  228. play_move(minesweeper_state, auto_x, auto_y);
  229. }
  230. }
  231. }
  232. }
  233. }
  234. }
  235. }
  236. static void minesweeper_state_init(Minesweeper* const plugin_state) {
  237. plugin_state->cursor_x = plugin_state->cursor_y = 0;
  238. plugin_state->game_started = false;
  239. for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
  240. for (int x = 0; x < PLAYFIELD_WIDTH; x++){
  241. plugin_state->minefield[x][y] = FieldEmpty;
  242. plugin_state->playfield[x][y] = TileTypeUncleared;
  243. }
  244. }
  245. }
  246. int32_t minesweeper_app(void* p) {
  247. UNUSED(p);
  248. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
  249. Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper));
  250. // setup
  251. minesweeper_state_init(minesweeper_state);
  252. ValueMutex state_mutex;
  253. if (!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) {
  254. FURI_LOG_E("Minesweeper", "cannot create mutex\r\n");
  255. free(minesweeper_state);
  256. return 255;
  257. }
  258. // BEGIN IMPLEMENTATION
  259. // Set system callbacks
  260. ViewPort* view_port = view_port_alloc();
  261. view_port_draw_callback_set(view_port, render_callback, &state_mutex);
  262. view_port_input_callback_set(view_port, input_callback, event_queue);
  263. // Open GUI and register view_port
  264. Gui* gui = furi_record_open("gui");
  265. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  266. PluginEvent event;
  267. for (bool processing = true; processing;) {
  268. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  269. Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex);
  270. if(event_status == FuriStatusOk) {
  271. // press events
  272. if(event.type == EventTypeKey) {
  273. if(event.input.type == InputTypePress) {
  274. switch(event.input.key) {
  275. case InputKeyUp:
  276. minesweeper_state->cursor_y--;
  277. if(minesweeper_state->cursor_y < 0) {
  278. minesweeper_state->cursor_y = 0;
  279. }
  280. break;
  281. case InputKeyDown:
  282. minesweeper_state->cursor_y++;
  283. if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) {
  284. minesweeper_state->cursor_y = PLAYFIELD_HEIGHT-1;
  285. }
  286. break;
  287. case InputKeyRight:
  288. minesweeper_state->cursor_x++;
  289. if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) {
  290. minesweeper_state->cursor_x = PLAYFIELD_WIDTH-1;
  291. }
  292. break;
  293. case InputKeyLeft:
  294. minesweeper_state->cursor_x--;
  295. if(minesweeper_state->cursor_x < 0) {
  296. minesweeper_state->cursor_x = 0;
  297. }
  298. break;
  299. case InputKeyOk:
  300. if (!minesweeper_state->game_started) {
  301. setup_playfield(minesweeper_state);
  302. minesweeper_state->game_started = true;
  303. }
  304. play_move(
  305. minesweeper_state,
  306. minesweeper_state->cursor_x,
  307. minesweeper_state->cursor_y);
  308. break;
  309. case InputKeyBack:
  310. // Exit the plugin
  311. processing = false;
  312. break;
  313. }
  314. }
  315. }
  316. } else {
  317. FURI_LOG_D("Minesweeper", "FuriMessageQueue: event timeout");
  318. // event timeout
  319. }
  320. view_port_update(view_port);
  321. release_mutex(&state_mutex, minesweeper_state);
  322. }
  323. view_port_enabled_set(view_port, false);
  324. gui_remove_view_port(gui, view_port);
  325. furi_record_close("gui");
  326. view_port_free(view_port);
  327. furi_message_queue_free(event_queue);
  328. delete_mutex(&state_mutex);
  329. free(minesweeper_state);
  330. return 0;
  331. }