GameLogic.cpp 19 KB

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