solitaire.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. #include <stdlib.h>
  2. #include <dolphin/dolphin.h>
  3. #include <furi.h>
  4. #include <gui/canvas_i.h>
  5. #include "defines.h"
  6. #include "common/ui.h"
  7. #include "solitaire_icons.h"
  8. #include <notification/notification.h>
  9. #include <notification/notification_messages.h>
  10. void init(GameState* game_state);
  11. const NotificationSequence sequence_fail = {
  12. &message_vibro_on,
  13. &message_note_c4,
  14. &message_delay_10,
  15. &message_vibro_off,
  16. &message_sound_off,
  17. &message_delay_10,
  18. &message_vibro_on,
  19. &message_note_a3,
  20. &message_delay_10,
  21. &message_vibro_off,
  22. &message_sound_off,
  23. NULL,
  24. };
  25. int8_t columns[7][3] = {
  26. {1, 1, 25},
  27. {19, 1, 25},
  28. {37, 1, 25},
  29. {55, 1, 25},
  30. {73, 1, 25},
  31. {91, 1, 25},
  32. {109, 1, 25},
  33. };
  34. bool can_place_card(Card where, Card what) {
  35. bool a_black = where.pip == 0 || where.pip == 3;
  36. bool b_black = what.pip == 0 || what.pip == 3;
  37. if(a_black == b_black) return false;
  38. int8_t a_letter = (int8_t)where.character;
  39. int8_t b_letter = (int8_t)what.character;
  40. if(a_letter == 12) a_letter = -1;
  41. if(b_letter == 12) b_letter = -1;
  42. return (a_letter - 1) == b_letter;
  43. }
  44. static void draw_scene(Canvas* const canvas, const GameState* game_state) {
  45. if(game_state->had_change) {
  46. int deckIndex = game_state->deck.index;
  47. if(game_state->dragging_deck) deckIndex--;
  48. if((game_state->deck.index < (game_state->deck.card_count - 1) ||
  49. game_state->deck.index == -1) &&
  50. game_state->deck.card_count > 0) {
  51. draw_card_back_at(columns[0][0], columns[0][1], canvas);
  52. if(game_state->selectRow == 0 && game_state->selectColumn == 0) {
  53. draw_rounded_box(
  54. canvas,
  55. columns[0][0] + 1,
  56. columns[0][1] + 1,
  57. CARD_WIDTH - 2,
  58. CARD_HEIGHT - 2,
  59. Inverse);
  60. }
  61. } else
  62. draw_card_space(
  63. columns[0][0],
  64. columns[0][1],
  65. game_state->selectRow == 0 && game_state->selectColumn == 0,
  66. canvas);
  67. //deck side
  68. if(deckIndex >= 0) {
  69. Card c = game_state->deck.cards[deckIndex];
  70. draw_card_at_colored(
  71. columns[1][0],
  72. columns[1][1],
  73. c.pip,
  74. c.character,
  75. game_state->selectRow == 0 && game_state->selectColumn == 1,
  76. canvas);
  77. } else
  78. draw_card_space(
  79. columns[1][0],
  80. columns[1][1],
  81. game_state->selectRow == 0 && game_state->selectColumn == 1,
  82. canvas);
  83. for(uint8_t i = 0; i < 4; i++) {
  84. Card current = game_state->top_cards[i];
  85. bool selected = game_state->selectRow == 0 && game_state->selectColumn == (i + 3);
  86. if(current.disabled) {
  87. draw_card_space(columns[i + 3][0], columns[i + 3][1], selected, canvas);
  88. } else {
  89. draw_card_at(
  90. columns[i + 3][0], columns[i + 3][1], current.pip, current.character, canvas);
  91. if(selected) {
  92. draw_rounded_box(
  93. canvas,
  94. columns[i + 3][0],
  95. columns[i + 3][1],
  96. CARD_WIDTH,
  97. CARD_HEIGHT,
  98. Inverse);
  99. }
  100. }
  101. }
  102. for(uint8_t i = 0; i < 7; i++) {
  103. bool selected = game_state->selectRow == 1 && game_state->selectColumn == i;
  104. int8_t index = (game_state->bottom_columns[i].index - 1 - game_state->selected_card);
  105. if(index < 0) index = 0;
  106. draw_hand_column(
  107. game_state->bottom_columns[i],
  108. columns[i][0],
  109. columns[i][2],
  110. selected ? index : -1,
  111. canvas);
  112. }
  113. int8_t pos[2] = {
  114. columns[game_state->selectColumn][0],
  115. columns[game_state->selectColumn][game_state->selectRow + 1]};
  116. /* draw_icon_clip(canvas, &I_card_graphics, pos[0] + CARD_HALF_WIDTH, pos[1] + CARD_HALF_HEIGHT, 30, 5, 5, 5,
  117. Filled);*/
  118. if(game_state->dragging_hand.index > 0) {
  119. draw_hand_column(
  120. game_state->dragging_hand,
  121. pos[0] + CARD_HALF_WIDTH + 3,
  122. pos[1] + CARD_HALF_HEIGHT + 3,
  123. -1,
  124. canvas);
  125. }
  126. clone_buffer(get_buffer(canvas), game_state->animation.buffer);
  127. } else {
  128. clone_buffer(game_state->animation.buffer, get_buffer(canvas));
  129. }
  130. }
  131. static void draw_animation(Canvas* const canvas, const GameState* game_state) {
  132. if(!game_state->animation.started) {
  133. draw_scene(canvas, game_state);
  134. } else {
  135. clone_buffer(game_state->animation.buffer, get_buffer(canvas));
  136. draw_card_at(
  137. (int8_t)game_state->animation.x,
  138. (int8_t)game_state->animation.y,
  139. game_state->animation.card.pip,
  140. game_state->animation.card.character,
  141. canvas);
  142. }
  143. clone_buffer(get_buffer(canvas), game_state->animation.buffer);
  144. }
  145. static void render_callback(Canvas* const canvas, void* ctx) {
  146. const GameState* game_state = ctx;
  147. furi_mutex_acquire(game_state->mutex, 25);
  148. if(game_state == NULL) {
  149. return;
  150. }
  151. switch(game_state->state) {
  152. case GameStateAnimate:
  153. draw_animation(canvas, game_state);
  154. break;
  155. case GameStateStart:
  156. canvas_draw_icon(canvas, 0, 0, &I_solitaire_main);
  157. break;
  158. case GameStatePlay:
  159. draw_scene(canvas, game_state);
  160. break;
  161. default:
  162. break;
  163. }
  164. furi_mutex_release(game_state->mutex);
  165. }
  166. void remove_drag(GameState* game_state) {
  167. if(game_state->dragging_deck) {
  168. remove_from_deck(game_state->deck.index, &(game_state->deck));
  169. game_state->dragging_deck = false;
  170. } else if(game_state->dragging_column < 7) {
  171. game_state->dragging_column = 8;
  172. }
  173. game_state->dragging_hand.index = 0;
  174. }
  175. bool handleInput(GameState* game_state) {
  176. Hand currentHand = game_state->bottom_columns[game_state->selectColumn];
  177. switch(game_state->input) {
  178. case InputKeyUp:
  179. if(game_state->selectRow > 0) {
  180. int first = first_non_flipped_card(currentHand);
  181. first = currentHand.index - first;
  182. if((first - 1) > game_state->selected_card && game_state->dragging_hand.index == 0 &&
  183. !game_state->longPress) {
  184. game_state->selected_card++;
  185. } else {
  186. game_state->selectRow--;
  187. game_state->selected_card = 0;
  188. }
  189. }
  190. break;
  191. case InputKeyDown:
  192. if(game_state->selectRow < 1) {
  193. game_state->selectRow++;
  194. game_state->selected_card = 0;
  195. } else {
  196. if(game_state->selected_card > 0) {
  197. if(game_state->longPress)
  198. game_state->selected_card = 0;
  199. else
  200. game_state->selected_card--;
  201. }
  202. }
  203. break;
  204. case InputKeyRight:
  205. if(game_state->selectColumn < 6) {
  206. game_state->selectColumn++;
  207. game_state->selected_card = 0;
  208. }
  209. break;
  210. case InputKeyLeft:
  211. if(game_state->selectColumn > 0) {
  212. game_state->selectColumn--;
  213. game_state->selected_card = 0;
  214. }
  215. break;
  216. case InputKeyOk:
  217. return true;
  218. break;
  219. default:
  220. break;
  221. }
  222. if(game_state->selectRow == 0 && game_state->selectColumn == 2) {
  223. if(game_state->input == InputKeyRight)
  224. game_state->selectColumn++;
  225. else
  226. game_state->selectColumn--;
  227. }
  228. if(game_state->dragging_hand.index > 0) game_state->selected_card = 0;
  229. return false;
  230. }
  231. bool place_on_top(Card* where, Card what) {
  232. if(where->disabled && what.character == 12) {
  233. where->disabled = what.disabled;
  234. where->pip = what.pip;
  235. where->character = what.character;
  236. return true;
  237. } else if(where->pip == what.pip) {
  238. int8_t a_letter = (int8_t)where->character;
  239. int8_t b_letter = (int8_t)what.character;
  240. if(a_letter == 12) a_letter = -1;
  241. if(b_letter == 12) b_letter = -1;
  242. if(where->disabled && b_letter != -1) return false;
  243. if((a_letter + 1) == b_letter) {
  244. where->disabled = what.disabled;
  245. where->pip = what.pip;
  246. where->character = what.character;
  247. return true;
  248. }
  249. }
  250. return false;
  251. }
  252. void tick(GameState* game_state, NotificationApp* notification) {
  253. game_state->last_tick = furi_get_tick();
  254. uint8_t row = game_state->selectRow;
  255. uint8_t column = game_state->selectColumn;
  256. if(game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return;
  257. bool wasAction = false;
  258. if(game_state->state == GameStatePlay) {
  259. if(game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 &&
  260. game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) {
  261. game_state->state = GameStateAnimate;
  262. game_state->had_change = true;
  263. dolphin_deed(DolphinDeedPluginGameWin);
  264. return;
  265. }
  266. }
  267. if(handleInput(game_state)) {
  268. if(game_state->state == GameStatePlay) {
  269. if(game_state->longPress && game_state->dragging_hand.index == 1) {
  270. for(uint8_t i = 0; i < 4; i++) {
  271. if(place_on_top(
  272. &(game_state->top_cards[i]), game_state->dragging_hand.cards[0])) {
  273. remove_drag(game_state);
  274. wasAction = true;
  275. break;
  276. }
  277. }
  278. } else {
  279. if(row == 0 && column == 0 && game_state->dragging_hand.index == 0) {
  280. FURI_LOG_D(APP_NAME, "Drawing card");
  281. game_state->deck.index++;
  282. wasAction = true;
  283. if(game_state->deck.index >= (game_state->deck.card_count))
  284. game_state->deck.index = -1;
  285. }
  286. //pick/place from deck
  287. else if(row == 0 && column == 1) {
  288. //place
  289. if(game_state->dragging_deck) {
  290. wasAction = true;
  291. game_state->dragging_deck = false;
  292. game_state->dragging_hand.index = 0;
  293. }
  294. //pick
  295. else {
  296. if(game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) {
  297. wasAction = true;
  298. game_state->dragging_deck = true;
  299. add_to_hand(
  300. &(game_state->dragging_hand),
  301. game_state->deck.cards[game_state->deck.index]);
  302. }
  303. }
  304. }
  305. //place on top row
  306. else if(row == 0 && game_state->dragging_hand.index == 1) {
  307. column -= 3;
  308. Card currCard = game_state->dragging_hand.cards[0];
  309. wasAction = place_on_top(&(game_state->top_cards[column]), currCard);
  310. if(wasAction) remove_drag(game_state);
  311. }
  312. //pick/place from bottom
  313. else if(row == 1) {
  314. Hand* curr_hand = &(game_state->bottom_columns[column]);
  315. //pick up
  316. if(game_state->dragging_hand.index == 0) {
  317. Card curr_card = curr_hand->cards[curr_hand->index - 1];
  318. if(curr_card.flipped) {
  319. curr_hand->cards[curr_hand->index - 1].flipped = false;
  320. wasAction = true;
  321. } else {
  322. if(curr_hand->index > 0) {
  323. extract_hand_region(
  324. curr_hand,
  325. &(game_state->dragging_hand),
  326. curr_hand->index - game_state->selected_card - 1);
  327. game_state->selected_card = 0;
  328. game_state->dragging_column = column;
  329. wasAction = true;
  330. }
  331. }
  332. }
  333. //place
  334. else {
  335. Card first = game_state->dragging_hand.cards[0];
  336. if(game_state->dragging_column == column ||
  337. (curr_hand->index == 0 && first.character == 11) ||
  338. can_place_card(curr_hand->cards[curr_hand->index - 1], first)) {
  339. add_hand_region(curr_hand, &(game_state->dragging_hand));
  340. remove_drag(game_state);
  341. wasAction = true;
  342. }
  343. }
  344. }
  345. }
  346. if(!wasAction) {
  347. notification_message(notification, &sequence_fail);
  348. }
  349. }
  350. }
  351. if(game_state->state == GameStateAnimate) {
  352. if(game_state->animation.started && !game_state->longPress &&
  353. game_state->input == InputKeyOk) {
  354. init(game_state);
  355. game_state->state = GameStateStart;
  356. }
  357. game_state->animation.started = true;
  358. if(game_state->animation.x < -20 || game_state->animation.x > 128) {
  359. game_state->animation.deck++;
  360. if(game_state->animation.deck > 3) game_state->animation.deck = 0;
  361. int8_t cardIndex = 11 - game_state->animation.indexes[game_state->animation.deck];
  362. if(game_state->animation.indexes[0] == 13 && game_state->animation.indexes[1] == 13 &&
  363. game_state->animation.indexes[2] == 13 && game_state->animation.indexes[3] == 13) {
  364. init(game_state);
  365. game_state->state = GameStateStart;
  366. return;
  367. }
  368. if(cardIndex == -1) cardIndex = 12;
  369. game_state->animation.card = (Card){
  370. game_state->top_cards[game_state->animation.deck].pip, cardIndex, false, false};
  371. game_state->animation.indexes[game_state->animation.deck]++;
  372. game_state->animation.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1);
  373. game_state->animation.vy = (rand() % 3 + 1);
  374. game_state->animation.x = columns[game_state->animation.deck + 3][0];
  375. game_state->animation.y = columns[game_state->animation.deck + 3][1];
  376. }
  377. game_state->animation.x += game_state->animation.vx;
  378. game_state->animation.y -= game_state->animation.vy;
  379. game_state->animation.vy -= 1;
  380. if(game_state->animation.vy < -10) game_state->animation.vy = -10;
  381. if(game_state->animation.y > 41) {
  382. game_state->animation.y = 41;
  383. game_state->animation.vy = -(game_state->animation.vy * 0.7f);
  384. }
  385. }
  386. }
  387. void init(GameState* game_state) {
  388. dolphin_deed(DolphinDeedPluginGameStart);
  389. game_state->selectColumn = 0;
  390. game_state->selected_card = 0;
  391. game_state->selectRow = 0;
  392. generate_deck(&(game_state->deck), 1);
  393. shuffle_deck(&(game_state->deck));
  394. game_state->dragging_deck = false;
  395. game_state->animation.started = false;
  396. game_state->animation.deck = -1;
  397. game_state->animation.x = -21;
  398. game_state->state = GameStatePlay;
  399. game_state->dragging_column = 8;
  400. for(uint8_t i = 0; i < 7; i++) {
  401. free_hand(&(game_state->bottom_columns[i]));
  402. init_hand(&(game_state->bottom_columns[i]), 21);
  403. game_state->bottom_columns[i].index = 0;
  404. for(uint8_t j = 0; j <= i; j++) {
  405. Card cur = remove_from_deck(0, &(game_state->deck));
  406. cur.flipped = i != j;
  407. add_to_hand(&(game_state->bottom_columns[i]), cur);
  408. }
  409. }
  410. for(uint8_t i = 0; i < 4; i++) {
  411. game_state->animation.indexes[i] = 0;
  412. game_state->top_cards[i] = (Card){0, 0, true, false};
  413. }
  414. game_state->deck.index = -1;
  415. }
  416. void init_start(GameState* game_state) {
  417. game_state->input = InputKeyMAX;
  418. for(uint8_t i = 0; i < 7; i++) init_hand(&(game_state->bottom_columns[i]), 21);
  419. init_hand(&(game_state->dragging_hand), 13);
  420. game_state->animation.buffer = make_buffer();
  421. }
  422. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  423. furi_assert(event_queue);
  424. AppEvent event = {.type = EventTypeKey, .input = *input_event};
  425. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  426. }
  427. static void update_timer_callback(FuriMessageQueue* event_queue) {
  428. furi_assert(event_queue);
  429. AppEvent event = {.type = EventTypeTick};
  430. furi_message_queue_put(event_queue, &event, 0);
  431. }
  432. int32_t solitaire_app(void* p) {
  433. UNUSED(p);
  434. int32_t return_code = 0;
  435. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
  436. GameState* game_state = malloc(sizeof(GameState));
  437. init_start(game_state);
  438. set_card_graphics(&I_card_graphics);
  439. game_state->state = GameStateStart;
  440. game_state->processing = true;
  441. game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  442. if(!game_state->mutex) {
  443. FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
  444. return_code = 255;
  445. goto free_and_exit;
  446. }
  447. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  448. notification_message_block(notification, &sequence_display_backlight_enforce_on);
  449. ViewPort* view_port = view_port_alloc();
  450. view_port_draw_callback_set(view_port, render_callback, game_state);
  451. view_port_input_callback_set(view_port, input_callback, event_queue);
  452. FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
  453. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
  454. Gui* gui = furi_record_open("gui");
  455. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  456. AppEvent event;
  457. for(bool processing = true; processing;) {
  458. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
  459. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  460. game_state->had_change = false;
  461. if(event_status == FuriStatusOk) {
  462. if(event.type == EventTypeKey) {
  463. game_state->had_change = true;
  464. if(event.input.type == InputTypeLong) {
  465. game_state->longPress = true;
  466. switch(event.input.key) {
  467. case InputKeyUp:
  468. case InputKeyDown:
  469. case InputKeyRight:
  470. case InputKeyLeft:
  471. case InputKeyOk:
  472. game_state->input = event.input.key;
  473. break;
  474. case InputKeyBack:
  475. processing = false;
  476. return_code = 1;
  477. default:
  478. break;
  479. }
  480. } else if(event.input.type == InputTypePress) {
  481. game_state->longPress = false;
  482. switch(event.input.key) {
  483. case InputKeyUp:
  484. case InputKeyDown:
  485. case InputKeyRight:
  486. case InputKeyLeft:
  487. case InputKeyOk:
  488. if(event.input.key == InputKeyOk && game_state->state == GameStateStart) {
  489. game_state->state = GameStatePlay;
  490. init(game_state);
  491. } else {
  492. game_state->input = event.input.key;
  493. }
  494. break;
  495. case InputKeyBack:
  496. init(game_state);
  497. processing = false;
  498. return_code = 1;
  499. break;
  500. default:
  501. break;
  502. }
  503. }
  504. } else if(event.type == EventTypeTick) {
  505. tick(game_state, notification);
  506. processing = game_state->processing;
  507. game_state->input = InputKeyMAX;
  508. }
  509. }
  510. furi_mutex_release(game_state->mutex);
  511. view_port_update(view_port);
  512. }
  513. notification_message_block(notification, &sequence_display_backlight_enforce_auto);
  514. furi_timer_free(timer);
  515. view_port_enabled_set(view_port, false);
  516. gui_remove_view_port(gui, view_port);
  517. furi_record_close(RECORD_GUI);
  518. furi_record_close(RECORD_NOTIFICATION);
  519. view_port_free(view_port);
  520. furi_mutex_free(game_state->mutex);
  521. free_and_exit:
  522. free(game_state->animation.buffer);
  523. ui_cleanup();
  524. for(uint8_t i = 0; i < 7; i++) free_hand(&(game_state->bottom_columns[i]));
  525. free(game_state->deck.cards);
  526. free(game_state);
  527. furi_message_queue_free(event_queue);
  528. return return_code;
  529. }