tictactoe_game.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <gui/view.h>
  6. #include <dolphin/dolphin.h>
  7. #define TAG "TicTacToe"
  8. typedef enum {
  9. EventTypeTick,
  10. EventTypeKey
  11. } EventType;
  12. typedef struct {
  13. FuriMutex* mutex;
  14. FuriTimer* timer;
  15. uint8_t selBoxX;
  16. uint8_t selBoxY;
  17. uint8_t selX;
  18. uint8_t selY;
  19. uint16_t scoreX;
  20. uint16_t scoreO;
  21. char player;
  22. char field[3][3];
  23. bool fieldx[3][3];
  24. uint8_t coords[3];
  25. bool button_state;
  26. } TicTacToeState;
  27. typedef struct {
  28. EventType type;
  29. InputEvent input;
  30. } GameEvent;
  31. void drawCross(Canvas* const canvas, uint8_t x, uint8_t y) {
  32. canvas_draw_line(canvas, x, y, x + 9, y + 9); // top left - bottom right slash
  33. canvas_draw_line(canvas, x + 9, y, x, y + 9); // down left - top right slash
  34. }
  35. void drawCircle(Canvas* const canvas, uint8_t x, uint8_t y) {
  36. canvas_draw_circle(canvas, x + 4, y + 5, 5);
  37. }
  38. void player_switch(TicTacToeState* ts) {
  39. if(ts->player == 'O') {
  40. ts->player = 'X';
  41. } else if(ts->player == 'X') {
  42. ts->player = 'O';
  43. }
  44. }
  45. void tictactoe_draw(Canvas* canvas, TicTacToeState* ts) {
  46. // Draws the game field
  47. canvas_draw_frame(canvas, 0, 0, 64, 64); // frame
  48. canvas_draw_line(canvas, 0, 21, 63, 21); // horizontal line
  49. canvas_draw_line(canvas, 0, 42, 63, 42); // horizontal line
  50. canvas_draw_line(canvas, 21, 0, 21, 63); // vertical line
  51. canvas_draw_line(canvas, 42, 0, 42, 63); // vertical line
  52. // Draws the game field elements (X or O)
  53. for(uint8_t i = 0; i <= 2; i++) {
  54. for(uint8_t j = 0; j <= 2; j++) {
  55. if(ts->field[i][j] == 'O') {
  56. drawCircle(canvas, ts->coords[i], ts->coords[j]);
  57. } else if(ts->field[i][j] == 'X') {
  58. drawCross(canvas, ts->coords[i], ts->coords[j]);
  59. }
  60. }
  61. }
  62. // Draws the selection box
  63. if(ts->selX == 1) {
  64. ts->selBoxX = 1;
  65. } else if(ts->selX == 2) {
  66. ts->selBoxX = 22;
  67. } else if(ts->selX == 3) {
  68. ts->selBoxX = 43;
  69. }
  70. if(ts->selY == 1) {
  71. ts->selBoxY = 1;
  72. } else if(ts->selY == 2) {
  73. ts->selBoxY = 22;
  74. } else if(ts->selY == 3) {
  75. ts->selBoxY = 43;
  76. }
  77. canvas_set_color(canvas, ColorBlack);
  78. canvas_draw_frame(canvas, ts->selBoxX, ts->selBoxY, 20, 20);
  79. canvas_draw_frame(canvas, ts->selBoxX + 1, ts->selBoxY + 1, 18, 18);
  80. // Draws the sidebar
  81. canvas_set_font(canvas, FontPrimary);
  82. canvas_draw_str(canvas, 81, 10, "SCORE");
  83. canvas_draw_str(canvas, 75, 24, "X:");
  84. char scoreXBuffer[10];
  85. snprintf(scoreXBuffer, sizeof(scoreXBuffer), "%d", ts->scoreX);
  86. canvas_draw_str(canvas, 88, 24, scoreXBuffer);
  87. canvas_draw_str(canvas, 75, 35, "O:");
  88. char scoreOBuffer[10];
  89. snprintf(scoreOBuffer, sizeof(scoreOBuffer), "%d", ts->scoreO);
  90. canvas_draw_str(canvas, 88, 35, scoreOBuffer);
  91. canvas_set_font(canvas, FontSecondary);
  92. canvas_draw_str(canvas, 75, 46, "Player:");
  93. if(ts->player == 'X') {
  94. drawCross(canvas, 93, 50);
  95. } else if(ts->player == 'O') {
  96. drawCircle(canvas, 93, 50);
  97. }
  98. }
  99. void clear_game_field(TicTacToeState* ts) {
  100. // Clears the game field arrays
  101. for(uint8_t i = 0; i <= 2; i++) {
  102. for(uint8_t j = 0; j <= 2; j++) {
  103. ts->field[i][j] = ' ';
  104. ts->fieldx[i][j] = false;
  105. }
  106. }
  107. ts->selX = 2; // Centers the selection box on X axis
  108. ts->selY = 2; // Centers the selection box on Y axis
  109. }
  110. void reset_game_data(TicTacToeState* ts) {
  111. ts->scoreO = 0;
  112. ts->scoreX = 0;
  113. ts->player = 'X';
  114. }
  115. void draw_win(Canvas* canvas, char player, TicTacToeState* ts) {
  116. // Handles the score table
  117. if(player == 'X') {
  118. ts->scoreX++;
  119. } else if(player == 'O') {
  120. ts->scoreO++;
  121. }
  122. // Switches the players
  123. player_switch(ts);
  124. // Draws the board with players switched
  125. tictactoe_draw(canvas, ts);
  126. // Clear the game field
  127. clear_game_field(ts);
  128. // Draw the new board
  129. tictactoe_draw(canvas, ts);
  130. }
  131. static void tictactoe_state_init(TicTacToeState* tictactoe_state) {
  132. // Set the initial game state
  133. tictactoe_state->selX = 2;
  134. tictactoe_state->selY = 2;
  135. tictactoe_state->player = 'X';
  136. tictactoe_state->coords[0] = 6;
  137. tictactoe_state->coords[1] = 27;
  138. tictactoe_state->coords[2] = 48;
  139. tictactoe_state->button_state = false;
  140. clear_game_field(tictactoe_state);
  141. reset_game_data(tictactoe_state);
  142. }
  143. static void tictactoe_draw_callback(Canvas* const canvas, void* ctx) {
  144. furi_assert(ctx);
  145. TicTacToeState* ticst = ctx;
  146. furi_mutex_acquire(ticst->mutex, FuriWaitForever);
  147. if(ticst->selX > 3) {
  148. ticst->selX = 3;
  149. } else if(ticst->selX < 1) {
  150. ticst->selX = 1;
  151. }
  152. if(ticst->selY > 3) {
  153. ticst->selY = 3;
  154. } else if(ticst->selY < 1) {
  155. ticst->selY = 1;
  156. }
  157. // Assigns the game field elements their value (X or O) when the OK button is pressed
  158. if(ticst->button_state) {
  159. ticst->button_state = false;
  160. for(uint8_t i = 0; i <= 2; i++) {
  161. for(uint8_t j = 0; j <= 2; j++) {
  162. if((ticst->selX == i + 1) && (ticst->selY == j + 1) &&
  163. (ticst->fieldx[i][j] == false)) {
  164. if(ticst->player == 'X') {
  165. ticst->field[i][j] = 'X';
  166. ticst->fieldx[i][j] = true;
  167. player_switch(ticst);
  168. } else if(ticst->player == 'O') {
  169. ticst->field[i][j] = 'O';
  170. ticst->fieldx[i][j] = true;
  171. player_switch(ticst);
  172. }
  173. }
  174. }
  175. }
  176. }
  177. // Checks the game field for winning combinations
  178. if((ticst->field[0][0] == 'X') && (ticst->field[1][0] == 'X') && (ticst->field[2][0] == 'X')) {
  179. draw_win(canvas, 'X', ticst);
  180. } else if(
  181. (ticst->field[0][1] == 'X') && (ticst->field[1][1] == 'X') &&
  182. (ticst->field[2][1] == 'X')) {
  183. draw_win(canvas, 'X', ticst);
  184. } else if(
  185. (ticst->field[0][2] == 'X') && (ticst->field[1][2] == 'X') &&
  186. (ticst->field[2][2] == 'X')) {
  187. draw_win(canvas, 'X', ticst);
  188. } else if(
  189. (ticst->field[0][0] == 'X') && (ticst->field[0][1] == 'X') &&
  190. (ticst->field[0][2] == 'X')) {
  191. draw_win(canvas, 'X', ticst);
  192. } else if(
  193. (ticst->field[1][0] == 'X') && (ticst->field[1][1] == 'X') &&
  194. (ticst->field[1][2] == 'X')) {
  195. draw_win(canvas, 'X', ticst);
  196. } else if(
  197. (ticst->field[2][0] == 'X') && (ticst->field[2][1] == 'X') &&
  198. (ticst->field[2][2] == 'X')) {
  199. draw_win(canvas, 'X', ticst);
  200. } else if(
  201. (ticst->field[0][0] == 'X') && (ticst->field[1][1] == 'X') &&
  202. (ticst->field[2][2] == 'X')) {
  203. draw_win(canvas, 'X', ticst);
  204. } else if(
  205. (ticst->field[2][0] == 'X') && (ticst->field[1][1] == 'X') &&
  206. (ticst->field[0][2] == 'X')) {
  207. draw_win(canvas, 'X', ticst);
  208. } else if(
  209. (ticst->field[0][0] == 'O') && (ticst->field[1][0] == 'O') &&
  210. (ticst->field[2][0] == 'O')) {
  211. draw_win(canvas, 'O', ticst);
  212. } else if(
  213. (ticst->field[0][1] == 'O') && (ticst->field[1][1] == 'O') &&
  214. (ticst->field[2][1] == 'O')) {
  215. draw_win(canvas, 'O', ticst);
  216. } else if(
  217. (ticst->field[0][2] == 'O') && (ticst->field[1][2] == 'O') &&
  218. (ticst->field[2][2] == 'O')) {
  219. draw_win(canvas, 'O', ticst);
  220. } else if(
  221. (ticst->field[0][0] == 'O') && (ticst->field[0][1] == 'O') &&
  222. (ticst->field[0][2] == 'O')) {
  223. draw_win(canvas, 'O', ticst);
  224. } else if(
  225. (ticst->field[1][0] == 'O') && (ticst->field[1][1] == 'O') &&
  226. (ticst->field[1][2] == 'O')) {
  227. draw_win(canvas, 'O', ticst);
  228. } else if(
  229. (ticst->field[2][0] == 'O') && (ticst->field[2][1] == 'O') &&
  230. (ticst->field[2][2] == 'O')) {
  231. draw_win(canvas, 'O', ticst);
  232. } else if(
  233. (ticst->field[0][0] == 'O') && (ticst->field[1][1] == 'O') &&
  234. (ticst->field[2][2] == 'O')) {
  235. draw_win(canvas, 'O', ticst);
  236. } else if(
  237. (ticst->field[2][0] == 'O') && (ticst->field[1][1] == 'O') &&
  238. (ticst->field[0][2] == 'O')) {
  239. draw_win(canvas, 'O', ticst);
  240. } else if(
  241. (ticst->fieldx[0][0] == true) && (ticst->fieldx[0][1] == true) &&
  242. (ticst->fieldx[0][2] == true) && (ticst->fieldx[1][0] == true) &&
  243. (ticst->fieldx[1][1] == true) && (ticst->fieldx[1][2] == true) &&
  244. (ticst->fieldx[2][0] == true) && (ticst->fieldx[2][1] == true) &&
  245. (ticst->fieldx[2][2] == true)) {
  246. draw_win(canvas, 'T', ticst);
  247. }
  248. tictactoe_draw(canvas, ticst);
  249. furi_mutex_release(ticst->mutex);
  250. }
  251. static void tictactoe_input_callback(InputEvent* input_event, void* ctx) {
  252. furi_assert(ctx);
  253. FuriMessageQueue* event_queue = ctx;
  254. GameEvent event = {.type = EventTypeKey, .input = *input_event};
  255. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  256. }
  257. static void tictactoe_update_timer_callback(void* ctx) {
  258. furi_assert(ctx);
  259. FuriMessageQueue* event_queue = ctx;
  260. GameEvent event = {.type = EventTypeTick};
  261. furi_message_queue_put(event_queue, &event, 0);
  262. }
  263. int32_t tictactoe_game_app(void* p) {
  264. UNUSED(p);
  265. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  266. TicTacToeState* tictactoe_state = malloc(sizeof(TicTacToeState));
  267. tictactoe_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  268. if(!tictactoe_state->mutex) {
  269. FURI_LOG_E(TAG, "Cannot create mutex\r\n");
  270. furi_message_queue_free(event_queue);
  271. free(tictactoe_state);
  272. return 255;
  273. }
  274. // Set system callbacks
  275. ViewPort* view_port = view_port_alloc();
  276. view_port_draw_callback_set(view_port, tictactoe_draw_callback, tictactoe_state);
  277. view_port_input_callback_set(view_port, tictactoe_input_callback, event_queue);
  278. tictactoe_state->timer =
  279. furi_timer_alloc(tictactoe_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  280. furi_timer_start(tictactoe_state->timer, furi_kernel_get_tick_frequency() / 22);
  281. tictactoe_state_init(tictactoe_state);
  282. // Open GUI and register view_port
  283. Gui* gui = furi_record_open(RECORD_GUI);
  284. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  285. // Call dolphin deed on game start
  286. dolphin_deed(DolphinDeedPluginGameStart);
  287. GameEvent event;
  288. for(bool processing = true; processing;) {
  289. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  290. furi_mutex_acquire(tictactoe_state->mutex, FuriWaitForever);
  291. if(event_status == FuriStatusOk) {
  292. // Key events
  293. if(event.type == EventTypeKey) {
  294. if(event.input.type == InputTypePress) {
  295. switch(event.input.key) {
  296. case InputKeyBack:
  297. processing = false;
  298. break;
  299. case InputKeyRight:
  300. tictactoe_state->selX++;
  301. break;
  302. case InputKeyLeft:
  303. tictactoe_state->selX--;
  304. break;
  305. case InputKeyUp:
  306. tictactoe_state->selY--;
  307. break;
  308. case InputKeyDown:
  309. tictactoe_state->selY++;
  310. break;
  311. case InputKeyOk:
  312. tictactoe_state->button_state = true;
  313. break;
  314. default:
  315. break;
  316. }
  317. }
  318. }
  319. }
  320. furi_mutex_release(tictactoe_state->mutex);
  321. view_port_update(view_port);
  322. }
  323. furi_timer_free(tictactoe_state->timer);
  324. view_port_enabled_set(view_port, false);
  325. gui_remove_view_port(gui, view_port);
  326. furi_record_close(RECORD_GUI);
  327. view_port_free(view_port);
  328. furi_message_queue_free(event_queue);
  329. furi_mutex_free(tictactoe_state->mutex);
  330. free(tictactoe_state);
  331. return 0;
  332. }