GameLogic.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. #include <dolphin/helpers/dolphin_deed.h>
  2. #include <dolphin/dolphin.h>
  3. #include "GameLogic.h"
  4. #include "utils/Sprite.h"
  5. #include "assets.h"
  6. //#define SOLVE_TEST
  7. static Sprite logo = Sprite(sprite_logo, BlackOnly);
  8. static Sprite solve = Sprite(sprite_solve, BlackOnly);
  9. static Sprite main_image = Sprite(sprite_main_image, BlackOnly);
  10. static Sprite start = Sprite(sprite_start, BlackOnly);
  11. GameLogic::GameLogic(RenderBuffer *b, InputEventHandler *inputHandler) : buffer(b) {
  12. inputHandler->subscribe(this, [](void *ctx, int key, InputType type) {
  13. auto *inst = (GameLogic *) ctx;
  14. inst->Input(key, type);
  15. });
  16. }
  17. void GameLogic::Input(int key, InputType type) {
  18. if (type == InputTypeShort) {
  19. if (key == InputKeyBack && state == Play) {
  20. state = Logo;
  21. target[0] = 0;
  22. target[1] = 0;
  23. return;
  24. } else if (key == InputKeyOk) {
  25. if (state == Logo) {
  26. state = Intro;
  27. dolphin_deed(DolphinDeedPluginGameStart);
  28. return;
  29. }
  30. if (state == Solve) {
  31. end = furi_get_tick();
  32. QuickSolve();
  33. return;
  34. }
  35. if (state == Finish) {
  36. dolphin_deed(DolphinDeedPluginGameWin);
  37. state = Logo;
  38. return;
  39. } else if (state == Intro) {
  40. for (int i = 0; i < 7; i++) {
  41. while ((int) tableau[i].size() < (i + 1)) {
  42. tableau[i].push_back(stock.pop_back());
  43. }
  44. tableau[i].peek_back()->exposed = true;
  45. }
  46. tempCard = nullptr;
  47. state = Play;
  48. startTime = furi_get_tick();
  49. buffer->clear();
  50. DrawPlayScene();
  51. } else if (state == Play) {
  52. if (selection[0] == 0 && selection[1] == 0) {
  53. //Cycle waste and stock
  54. if (hand.size() == 0) {
  55. if (stock.size() > 0) {
  56. //move from stock to waste
  57. waste.push_back(stock.pop_back());
  58. waste.peek_back()->exposed = true;
  59. } else {
  60. //move back all the card
  61. while (waste.size() > 0) {
  62. waste.peek_back()->exposed = false;
  63. stock.push_back(waste.pop_back());
  64. }
  65. }
  66. return;
  67. }
  68. return;
  69. } else if (selection[1] == 1 || selection[0] > 0) {
  70. PickAndPlace();
  71. }
  72. return;
  73. }
  74. return;
  75. }
  76. if (state == Play)
  77. HandleNavigation(key);
  78. } else if (type == InputTypeLong) {
  79. switch (key) {
  80. case InputKeyLeft:
  81. selection[0] = 0;
  82. selectedCard = 1;
  83. break;
  84. case InputKeyRight:
  85. selection[0] = 6;
  86. selectedCard = 1;
  87. break;
  88. case InputKeyUp:
  89. selectedCard = 1;
  90. selection[1] = 0;
  91. break;
  92. case InputKeyOk:
  93. if (CanSolve()) {
  94. state = Solve;
  95. break;
  96. }
  97. //quick place
  98. if (state == Play && (selection[0] == 1 || selection[1] == 1) && selectedCard == 1) {
  99. Card *c = selection[1] == 0 ? waste.peek_back() : tableau[selection[0]].peek_back();
  100. if (!c->exposed) return;
  101. for (int i = 0; i < 4; i++) {
  102. if (c->CanPlaceFoundation(foundation[i].peek_back())) {
  103. if (selection[1] == 0)
  104. foundation[i].push_back(waste.pop_back());
  105. else
  106. foundation[i].push_back(tableau[selection[0]].pop_back());
  107. break;
  108. }
  109. }
  110. }
  111. break;
  112. default:
  113. break;
  114. }
  115. }
  116. if (selection[1] == 0 && selection[0] == 2) { //skip empty space
  117. selection[0] += key == InputKeyRight ? 1 : -1;
  118. }
  119. }
  120. void GameLogic::PickAndPlace() {
  121. //pick and place waste
  122. if (selection[0] == 1 && selection[1] == 0) {
  123. if (waste.size() > 0 && hand.size() == 0) {
  124. hand.push_back(waste.pop_back());
  125. target[0] = 1;
  126. target[1] = 0;
  127. } else if (hand.size() == 1 && target[0] == 1 && target[1] == 0) {
  128. waste.push_back(hand.pop_back());
  129. target[0] = 0;
  130. target[1] = 0;
  131. }
  132. }
  133. //place to foundation
  134. else if (selection[1] == 0 && selection[0] > 2 && hand.size() == 1) {
  135. uint8_t id = selection[0] - 3;
  136. if (hand.peek_back()->CanPlaceFoundation(foundation[id].peek_back())) {
  137. foundation[id].push_back(hand.pop_back());
  138. target[0] = selection[0];
  139. target[1] = selection[1];
  140. }
  141. }
  142. //pick and place columns
  143. else if (selection[1] == 1) {
  144. auto &tbl = tableau[selection[0]];
  145. if (hand.size() == 0) {
  146. if (tbl.peek_back() && !tbl.peek_back()->exposed) {
  147. tbl.peek_back()->exposed = true;
  148. } else {
  149. uint8_t count = selectedCard;
  150. while (count > 0) {
  151. hand.push_front(tbl.pop_back());
  152. count--;
  153. }
  154. selectedCard = 1;
  155. target[0] = selection[0];
  156. target[1] = selection[1];
  157. }
  158. } else if ((target[0] == selection[0] && target[1] == selection[1]) ||
  159. hand.peek_front()->CanPlaceColumn(tbl.peek_back())) {
  160. while (hand.size() > 0) {
  161. tbl.push_back(hand.pop_front());
  162. }
  163. target[0] = 0;
  164. target[1] = 0;
  165. }
  166. }
  167. for (uint8_t i = 0; i < 4; i++) {
  168. if(foundation[i].size()==0 || foundation[i].peek_back()->value!= KING)
  169. return;
  170. }
  171. buffer->clear();
  172. DrawPlayScene();
  173. selectedCard = 0;
  174. state = Finish;
  175. }
  176. void GameLogic::HandleNavigation(int key) {
  177. if (key == InputKeyLeft && selection[0] > 0) {
  178. selectedCard = 1;
  179. selection[0]--;
  180. } else if (key == InputKeyRight && selection[0] < 6) {
  181. selectedCard = 1;
  182. selection[0]++;
  183. } else if (key == InputKeyUp && selection[1] == 1) {
  184. auto &tbl = tableau[selection[0]];
  185. uint8_t first = FirstNonFlipped(tbl);
  186. if (tbl.size() > 0 && first < tbl.size() - selectedCard && hand.size() == 0) {
  187. selectedCard++;
  188. } else {
  189. selectedCard = 1;
  190. selection[1]--;
  191. }
  192. } else if (key == InputKeyDown && selection[1] == 0) {
  193. selectedCard = 1;
  194. selection[1]++;
  195. } else if (selection[1] == 1 && selectedCard > 1) {
  196. selectedCard--;
  197. }
  198. }
  199. void GameLogic::Update(float delta) {
  200. //keep the buffer for the falling animation to achieve the trailing effect
  201. if (state != Finish) {
  202. buffer->clear();
  203. }
  204. switch (state) {
  205. case Logo:
  206. Reset();
  207. buffer->draw(&logo, (Vector) {60, 30}, 0);
  208. buffer->draw(&main_image, (Vector) {115, 25}, 0);
  209. buffer->draw(&start, (Vector) {64, 55}, 0);
  210. break;
  211. case Intro:
  212. DoIntro(delta);
  213. dirty = true;
  214. end = 0;
  215. break;
  216. case Play:
  217. DrawPlayScene();
  218. if (CanSolve()) {
  219. buffer->draw_rbox(25, 53, 101, 64, Black);
  220. buffer->draw_rbox(26, 54, 100, 63, White);
  221. buffer->draw(&solve, (Vector) {64, 58}, 0);
  222. end = furi_get_tick();
  223. }
  224. break;
  225. case Solve:
  226. DrawPlayScene();
  227. HandleSolve(delta);
  228. dirty = true;
  229. break;
  230. case Finish:
  231. dirty = true;
  232. FallingCard(delta);
  233. break;
  234. default:
  235. break;
  236. }
  237. buffer->swap();
  238. readyToRender = true;
  239. }
  240. void GameLogic::Reset() {
  241. dolphin_deed(DolphinDeedPluginGameStart);
  242. delete tempCard;
  243. stock.deleteData();
  244. stock.clear();
  245. waste.deleteData();
  246. waste.clear();
  247. tempPos = {0, 0};
  248. selection[0] = 0;
  249. selection[1] = 0;
  250. target[0] = 0;
  251. selectedCard = 1;
  252. target[1] = -1;
  253. for (int i = 0; i < 7; i++) {
  254. tableau[i].deleteData();
  255. tableau[i].clear();
  256. if (i < 4) {
  257. foundation[i].deleteData();
  258. foundation[i].clear();
  259. }
  260. }
  261. GenerateDeck();
  262. }
  263. bool GameLogic::CanSolve() {
  264. for (uint8_t i = 0; i < 7; i++) {
  265. if (tableau[i].peek_front() && !tableau[i].peek_front()->exposed)
  266. return false;
  267. }
  268. return state == Play;
  269. }
  270. void GameLogic::GenerateDeck() {
  271. int cards_count = 52;
  272. uint8_t cards[cards_count];
  273. for (int i = 0; i < cards_count; i++) cards[i] = i % 52;
  274. srand(DWT->CYCCNT);
  275. #ifndef SOLVE_TEST
  276. //reorder
  277. for (int i = 0; i < cards_count; i++) {
  278. int r = i + (rand() % (cards_count - i));
  279. uint8_t card = cards[i];
  280. cards[i] = cards[r];
  281. cards[r] = card;
  282. }
  283. #endif
  284. //Init deck list
  285. for (int i = 0; i < cards_count; i++) {
  286. int letter = cards[i] % 13;
  287. int suit = cards[i] / 13;
  288. stock.push_back(new Card(suit, letter));
  289. FURI_LOG_I("Card check", "%i %i", letter, suit);
  290. }
  291. }
  292. GameLogic::~GameLogic() {
  293. stock.deleteData();
  294. stock.clear();
  295. waste.deleteData();
  296. waste.clear();
  297. hand.deleteData();
  298. hand.clear();
  299. delete tempCard;
  300. for (int i = 0; i < 7; i++) {
  301. tableau[i].deleteData();
  302. tableau[i].clear();
  303. if (i < 4) {
  304. foundation[i].deleteData();
  305. foundation[i].clear();
  306. }
  307. }
  308. }
  309. Vector pos, targetPos;
  310. void GameLogic::DoIntro(float delta) {
  311. //render next after finish
  312. #ifdef SOLVE_TEST
  313. startTime = furi_get_tick();
  314. state = Play;
  315. buffer->clear();
  316. DrawPlayScene();
  317. return;
  318. #endif
  319. if (!buffer) return;
  320. buffer->clear();
  321. DrawPlayScene();
  322. if (tempCard) {
  323. targetPos = {
  324. 2.0f + (float) target[0] * 18,
  325. MIN(25.0f + (float) target[1] * 4, 36)
  326. };
  327. tempTime += delta * 10;
  328. pos = Vector::Lerp(tempPos, targetPos, MIN(tempTime, 1));
  329. tempCard->Render((int) pos.x, (int) pos.y, false, buffer);
  330. float distance = targetPos.distance(pos);
  331. if (distance < 0.01) {
  332. tempCard = nullptr;
  333. tableau[target[0]].push_back(stock.pop_back());
  334. tableau[target[0]].peek_back()->exposed = target[0] == target[1];
  335. if (target[0] == 6 && target[1] == 6) {
  336. startTime = furi_get_tick();
  337. state = Play;
  338. buffer->clear();
  339. DrawPlayScene();
  340. }
  341. }
  342. } else {
  343. tempTime = 0;
  344. tempCard = stock.peek_back();
  345. tempPos.x = 2;
  346. tempPos.y = 1;
  347. if (target[0] == target[1]) {
  348. target[0]++;
  349. target[1] = 0;
  350. } else {
  351. target[1]++;
  352. }
  353. }
  354. }
  355. void GameLogic::DrawPlayScene() {
  356. if (stock.size() > 0) {
  357. if (stock.size() > 1) {
  358. stock.peek_back()->Render(2, 1, true, buffer);
  359. stock.peek_back()->Render(0, 0, selection[0] == 0 && selection[1] == 0, buffer);
  360. } else {
  361. stock.peek_back()->Render(2, 1, selection[0] == 0 && selection[1] == 0, buffer);
  362. }
  363. } else
  364. Card::TryRender(nullptr, 2, 1, selection[0] == 0 && selection[1] == 0, buffer);
  365. Card::TryRender(waste.peek_back(), 20, 1, selection[0] == 1 && selection[1] == 0, buffer);
  366. int i;
  367. for (i = 0; i < 4; i++) {
  368. Card::TryRender(foundation[i].peek_back(), 56 + i * 18, 1, selection[0] == i + 3 && selection[1] == 0, buffer);
  369. }
  370. for (i = 0; i < 7; i++) {
  371. DrawColumn(2 + i * 18, 25, (selection[0] == i && selection[1] == 1) ? selectedCard : 0, i);
  372. }
  373. if (hand.size() > 0) {
  374. if (selection[1] == 0)
  375. DrawColumn(10 + selection[0] * 18, 15 + selection[1] * 25, hand.size(), -1);
  376. else {
  377. int shift = MIN((int) tableau[selection[0]].size(), 4) * 4;
  378. DrawColumn(10 + selection[0] * 18, 30 + shift, hand.size(), -1);
  379. }
  380. }
  381. }
  382. void GameLogic::DrawColumn(uint8_t x, uint8_t y, uint8_t selected, int8_t column) {
  383. UNUSED(selected);
  384. auto &deck = column >= 0 ? tableau[column] : hand;
  385. if (deck.size() == 0 && column >= 0) {
  386. Card::RenderEmptyCard(x, y, buffer);
  387. if (selected == 1)
  388. buffer->draw_rbox(x + 1, y + 1, x + 16, y + 22, Flip);
  389. return;
  390. }
  391. uint8_t selection = deck.size() - selected;
  392. // if (selected != 0) selection--;
  393. uint8_t loop_end = deck.size();
  394. uint8_t loop_start = MAX(loop_end - 4, 0);
  395. uint8_t position = 0;
  396. uint8_t first_non_flipped = FirstNonFlipped(deck);
  397. bool had_top = false;
  398. bool showDark = column >= 0;
  399. // Draw the first flipped and non-flipped card with adjusted visibility
  400. if (first_non_flipped <= loop_start && selection != first_non_flipped) {
  401. // Draw a card back if it is not the first card
  402. if (first_non_flipped > 0) {
  403. Card::RenderBack(x, y + position, false, buffer, 5);
  404. // Increment loop start index and position
  405. position += 4;
  406. loop_start++;
  407. had_top = true;
  408. }
  409. // Draw the front side of the first non-flipped card
  410. deck[first_non_flipped]->Render(x, y + position, false, buffer, deck.size() == 1 ? 22 : 9);
  411. position += 8;
  412. loop_start++; // Increment loop start index
  413. }
  414. // Draw the selected card with adjusted visibility
  415. if (loop_start > selection) {
  416. if (!had_top && first_non_flipped > 0) {
  417. Card::RenderBack(x, y + position, false, buffer, 5);
  418. position += 4;
  419. loop_start++;
  420. }
  421. // Draw the front side of the selected card
  422. deck[selection]->Render(x, y + position, showDark, buffer, 9);
  423. position += 8;
  424. loop_start++; // Increment loop start index
  425. }
  426. int height = 5;
  427. uint8_t i = 0;
  428. for (auto *card: deck) {
  429. if (i >= loop_start && i < loop_end) {
  430. height = 5;
  431. if ((i + 1) == loop_end) height = 22;
  432. else if (i == selection || i == first_non_flipped) height = 9;
  433. card->Render(x, y + position, i == selection && showDark, buffer, height);
  434. if (i == selection || i == first_non_flipped)position += 4;
  435. position += 4;
  436. }
  437. i++;
  438. }
  439. }
  440. int8_t GameLogic::FirstNonFlipped(const List<Card> &deck) {
  441. int8_t index = 0;
  442. for (auto *card: deck) {
  443. if (card->exposed) return index;
  444. index++;
  445. }
  446. return -1;
  447. }
  448. void GameLogic::HandleSolve(float delta) {
  449. if (tempCard) {
  450. tempTime += delta * 8;
  451. Vector finalPos{56 + (float) target[0] * 18, 2};
  452. if(tempTime>1) tempTime=1;
  453. Vector localpos = Vector::Lerp(tempPos, finalPos, tempTime);
  454. tempCard->Render((uint8_t) localpos.x, (uint8_t) localpos.y, false, buffer);
  455. if (finalPos.distance(localpos) < 0.01) {
  456. foundation[target[0]].push_back(tempCard);
  457. tempCard = nullptr;
  458. }
  459. //check finish
  460. uint8_t size = 0;
  461. for (uint8_t i = 0; i < 4; i++) {
  462. if (foundation[i].size() > 0 && foundation[i].peek_back()->value == KING)
  463. size++;
  464. }
  465. if (size == 4) {
  466. buffer->clear();
  467. selectedCard = 0;
  468. DrawPlayScene();
  469. state = Finish;
  470. return;
  471. }
  472. } else {
  473. tempTime = 0;
  474. //search the lowest card
  475. int lowestSuit = -2, lowestValue = 13;
  476. for (int i = 0; i < 4; i++) {
  477. auto &fnd = foundation[i];
  478. if (foundation[i].size() == 0) {
  479. //find the missing suit
  480. int foundations[4] = {0, 0, 0, 0};
  481. for (int j = 0; j < 4; j++) {
  482. if (foundation[j].size() > 0) {
  483. foundations[foundation[j].peek_front()->suit] = 1;
  484. }
  485. }
  486. for (int j = 0; j < 4; j++) {
  487. if (foundations[j] == 0) {
  488. lowestSuit = j;
  489. lowestValue = -1;
  490. target[0] = (float) j;
  491. break;
  492. }
  493. }
  494. break;
  495. }
  496. if (i == 0 || (lowestValue + 1) % 13 > (fnd.peek_back()->value + 1) % 13) {
  497. lowestSuit = fnd.peek_back()->suit;
  498. lowestValue = fnd.peek_back()->value;
  499. target[0] = (float) i;
  500. }
  501. }
  502. Card lowest(lowestSuit, lowestValue);
  503. //try to find it in tableau
  504. for (int i = 0; i < 7; i++) {
  505. auto &tbl = tableau[i];
  506. if (tbl.peek_back() && tbl.peek_back()->CanPlaceFoundation(&lowest)) {
  507. tempCard = tbl.pop_back();
  508. int y = MIN((int) tableau[i].size(), 4) * 4 + 25;
  509. tempPos.x = 2 + (float) i * 18;
  510. tempPos.y = (float) y;
  511. return;
  512. }
  513. }
  514. //try to find in waste
  515. int index = -1;
  516. for (auto *w: waste) {
  517. index++;
  518. if (w->CanPlaceFoundation(&lowest)) {
  519. tempCard = waste.extract(index);
  520. tempCard->exposed = true;
  521. tempPos.x = 20;
  522. tempPos.y = 1;
  523. return;
  524. }
  525. }
  526. index = -1;
  527. //try to find in stock
  528. for (auto *w: stock) {
  529. index++;
  530. if (w->CanPlaceFoundation(&lowest)) {
  531. tempCard = stock.extract(index);
  532. tempCard->exposed = true;
  533. tempPos.x = 2;
  534. tempPos.y = 1;
  535. return;
  536. }
  537. }
  538. }
  539. }
  540. void GameLogic::QuickSolve() {
  541. if (tempCard) {
  542. delete tempCard;
  543. tempCard = nullptr;
  544. }
  545. waste.deleteData();
  546. waste.clear();
  547. stock.deleteData();
  548. stock.clear();
  549. for (uint8_t i = 0; i < 7; i++) {
  550. tableau[i].deleteData();
  551. tableau[i].clear();
  552. }
  553. int foundations[4] = {0, 0, 0, 0};
  554. for (int j = 0; j < 4; j++) {
  555. if (foundation[j].size() > 0) {
  556. foundations[foundation[j].peek_front()->suit] = 1;
  557. }
  558. }
  559. for (int j = 0; j < 4; j++) {
  560. if (foundations[j] == 0) {
  561. //seed the foundation
  562. foundation[j].push_back(new Card(j, ACE));
  563. foundation[j].peek_back()->exposed = true;
  564. }
  565. }
  566. for (uint8_t i = 0; i < 4; i++) {
  567. auto &fnd = foundation[i];
  568. if (fnd.peek_back()->value == ACE) {
  569. fnd.push_back(new Card(fnd.peek_back()->suit, TWO));
  570. fnd.peek_back()->exposed = true;
  571. }
  572. while (fnd.peek_back()->value != KING) {
  573. fnd.push_back(new Card(fnd.peek_back()->suit, fnd.peek_back()->value + 1));
  574. fnd.peek_back()->exposed = true;
  575. }
  576. }
  577. buffer->clear();
  578. DrawPlayScene();
  579. selectedCard = 0;
  580. state = Finish;
  581. }
  582. void GameLogic::FallingCard(float delta) {
  583. UNUSED(delta);
  584. if (tempCard) {
  585. if ((furi_get_tick() - tempTime) > 12) {
  586. tempTime = furi_get_tick();
  587. tempPos.x += velocity.x;
  588. tempPos.y -= velocity.y;
  589. if (tempPos.y > 41) {
  590. velocity.y *= -0.8;
  591. tempPos.y = 41;
  592. } else {
  593. velocity.y -= 1;
  594. if (velocity.y < -10) velocity.y = -10;
  595. }
  596. tempCard->Render((int8_t) tempPos.x, (int8_t) tempPos.y, false, buffer);
  597. if (tempPos.x < -18 || tempPos.x > 128) {
  598. delete tempCard;
  599. tempCard = nullptr;
  600. }
  601. }
  602. } else {
  603. float r1 = 2.0 * (float) (rand() % 2) - 1.0; // random number in range -1 to 1
  604. if (r1 == 0) r1 = 0.1;
  605. float r2 = inverse_tanh(r1);
  606. velocity.x = (float) (tanh(r2)) * (rand() % 3 + 1);
  607. if (velocity.x == 0) velocity.x = 1;
  608. velocity.y = (rand() % 3 + 1);
  609. if (foundation[selectedCard].size() > 0) {
  610. tempCard = foundation[selectedCard].pop_back();
  611. tempCard->exposed = true;
  612. tempPos.x = 56 + selectedCard * 18;
  613. tempPos.y = 2;
  614. selectedCard = (uint8_t) (selectedCard + 1) % 4;
  615. } else {
  616. state = Logo;
  617. return;
  618. }
  619. }
  620. }