solitaire.c 21 KB

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