tictactoe_game.c 12 KB

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