draw.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. #include "draw.h"
  2. #include <gui/gui.h>
  3. #include <gui/icon.h>
  4. #include <gui/elements.h>
  5. #include <gui/icon_i.h>
  6. #include "fonts.h"
  7. #include "game_vexed_icons.h"
  8. #include "ui.h"
  9. #include "move.h"
  10. #include "utils.h"
  11. //-----------------------------------------------------------------------------
  12. u_int32_t frameNo = 0;
  13. int r1 = 0, r2 = 0;
  14. //-----------------------------------------------------------------------------
  15. void draw_app(Canvas* canvas, Game* game) {
  16. canvas_clear(canvas);
  17. if((game->state == MAIN_MENU) || (game->state == RESET_PROMPT)) {
  18. draw_main_menu(canvas, game);
  19. }
  20. if(game->state == ABOUT) {
  21. draw_about(canvas, game, frameNo);
  22. }
  23. if(game->state == INTRO) {
  24. draw_intro(canvas, game, frameNo);
  25. }
  26. if(game->state == RESET_PROMPT) {
  27. draw_reset_prompt(canvas, game);
  28. }
  29. if(game->state == INVALID_PROMPT) {
  30. draw_invalid_prompt(canvas, game);
  31. }
  32. if(game->state >= SELECT_BRICK) {
  33. draw_playground(canvas, game);
  34. switch(game->state) {
  35. case SELECT_BRICK:
  36. draw_movable(canvas, game, frameNo);
  37. break;
  38. case SOLUTION_SELECT:
  39. draw_direction_solution(canvas, game, frameNo);
  40. break;
  41. case SELECT_DIRECTION:
  42. draw_direction(canvas, game, frameNo);
  43. break;
  44. case MOVE_SIDES:
  45. draw_ani_sides(canvas, game);
  46. break;
  47. case MOVE_GRAVITY:
  48. draw_ani_gravity(canvas, game);
  49. break;
  50. case EXPLODE:
  51. draw_ani_explode(canvas, game);
  52. break;
  53. default:
  54. break;
  55. }
  56. draw_scores(canvas, game, frameNo);
  57. draw_playfield_hint(canvas, game);
  58. switch(game->state) {
  59. case PAUSED:
  60. draw_paused(canvas, game);
  61. break;
  62. case HISTOGRAM:
  63. draw_histogram(canvas, game->stats);
  64. break;
  65. case SOLUTION_PROMPT:
  66. draw_solution_prompt(canvas, game);
  67. break;
  68. case GAME_OVER:
  69. draw_game_over(canvas, game->gameOverReason);
  70. break;
  71. case LEVEL_FINISHED:
  72. draw_level_finished(canvas, game);
  73. break;
  74. default:
  75. break;
  76. }
  77. }
  78. frameNo++;
  79. }
  80. //-----------------------------------------------------------------------------
  81. void draw_intro(Canvas* canvas, Game* game, uint32_t frameNo) {
  82. if(frameNo % 2 == 1) {
  83. if(game->move.frameNo < 100) game->move.frameNo++;
  84. }
  85. canvas_set_color(canvas, ColorBlack);
  86. if((game->move.frameNo < 12)) {
  87. uint8_t x, y;
  88. for(y = 0; y < SIZE_Y_BG; y++) {
  89. for(x = 0; x < SIZE_X_BG; x++) {
  90. canvas_draw_icon(
  91. canvas, x * TILE_SIZE, y * TILE_SIZE, tile_to_icon(game->bg[y][x], false));
  92. }
  93. }
  94. }
  95. if((game->move.frameNo < 4)) {
  96. gray_canvas(canvas);
  97. }
  98. if(game->move.frameNo > 7) {
  99. canvas_set_color(canvas, ColorXOR);
  100. canvas_draw_box(canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT);
  101. canvas_draw_icon(canvas, 0, 0, &I_logo_vexed_big);
  102. }
  103. if(game->move.frameNo > 11) {
  104. canvas_set_color(canvas, ColorBlack);
  105. canvas_draw_icon(canvas, 0, 0, &I_logo_vexed_big);
  106. }
  107. if(game->move.frameNo == 24) {
  108. game->state = MAIN_MENU;
  109. }
  110. }
  111. void draw_about(Canvas* canvas, Game* game, uint32_t frameNo) {
  112. if(frameNo % 10 == 9) {
  113. randomize_bg(&game->bg);
  114. }
  115. if(frameNo % 50 == 49) {
  116. r1 = rand();
  117. r2 = rand();
  118. randomize_bg(&game->bg);
  119. }
  120. uint8_t sx, sy;
  121. for(sy = 0; sy < SIZE_Y_BG; sy++) {
  122. for(sx = 0; sx < SIZE_X_BG; sx++) {
  123. canvas_draw_icon(
  124. canvas,
  125. (sx * TILE_SIZE) - (r1 % 7),
  126. sy * TILE_SIZE - (r2 % 7),
  127. tile_to_icon(game->bg[sy][sx], false));
  128. }
  129. }
  130. //gray_canvas(canvas);
  131. // back for os buttons
  132. canvas_set_color(canvas, ColorWhite);
  133. canvas_draw_rbox(canvas, 44, 50, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT, 3);
  134. const uint8_t y = dialog_frame(canvas, 92, 43, false, true, "Exit game?");
  135. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
  136. canvas_set_color(canvas, ColorBlack);
  137. canvas_draw_str_aligned(
  138. canvas, GUI_DISPLAY_WIDTH / 2, y + 2, AlignCenter, AlignTop, "(c) 2024 Dominik Dzienia");
  139. canvas_draw_str_aligned(
  140. canvas, GUI_DISPLAY_WIDTH / 2, y + 13, AlignCenter, AlignTop, "based on Vexed 2.2");
  141. canvas_draw_str_aligned(
  142. canvas, GUI_DISPLAY_WIDTH / 2, y + 21, AlignCenter, AlignTop, "(c) 2006 Vexed Dev Team");
  143. canvas_set_color(canvas, ColorBlack);
  144. canvas_set_font(canvas, FontSecondary);
  145. elements_button_center(canvas, "Exit");
  146. elements_button_right_back(canvas, "Back");
  147. }
  148. void draw_set_info(Canvas* canvas, Game* game) {
  149. BoundingBox box;
  150. const uint8_t w = 118;
  151. const uint8_t h = 46;
  152. const uint8_t x = (GUI_DISPLAY_WIDTH - w) / 2;
  153. const uint8_t y =
  154. dialog_frame(canvas, w, h, false, false, furi_string_get_cstr(game->levelSet->title));
  155. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
  156. canvas_set_color(canvas, ColorBlack);
  157. canvas_draw_str_aligned(
  158. canvas,
  159. GUI_DISPLAY_WIDTH / 2,
  160. y,
  161. AlignCenter,
  162. AlignTop,
  163. furi_string_get_cstr(game->levelSet->author));
  164. canvas_draw_str_aligned(
  165. canvas,
  166. GUI_DISPLAY_WIDTH / 2,
  167. y + 8,
  168. AlignCenter,
  169. AlignTop,
  170. furi_string_get_cstr(game->levelSet->url));
  171. canvas_draw_hline_dotted(canvas, x, y + 16, w);
  172. set_bounding_box(&box, x + 3, y + 16, w - 6, 16);
  173. elements_multiline_text_aligned_limited(
  174. canvas,
  175. &box,
  176. box.width / 2,
  177. box.height / 2,
  178. 2,
  179. AlignCenter,
  180. AlignCenter,
  181. furi_string_get_cstr(game->levelSet->description));
  182. }
  183. //-----------------------------------------------------------------------------
  184. void draw_level_info(Canvas* canvas, Game* game) {
  185. int bufSize = 80;
  186. char buf[bufSize];
  187. memset(buf, 0, bufSize);
  188. snprintf(
  189. buf,
  190. sizeof(buf),
  191. "%s #%u",
  192. furi_string_get_cstr(game->levelSet->title),
  193. game->selectedLevel + 1);
  194. const uint8_t x = (GUI_DISPLAY_WIDTH - 100) / 2;
  195. const uint8_t y = dialog_frame(canvas, 100, 40, false, false, buf);
  196. canvas_set_color(canvas, ColorBlack);
  197. canvas_draw_vline_dotted(canvas, GUI_DISPLAY_CENTER_X, y, 30);
  198. canvas_set_font(canvas, FontSecondary);
  199. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  200. canvas_draw_str_aligned(canvas, x + 25, y + 4, AlignCenter, AlignTop, "Moves/Par");
  201. memset(buf, 0, bufSize);
  202. if(game->levelSet->scores[game->selectedLevel].moves == 0) {
  203. snprintf(
  204. buf,
  205. sizeof(buf),
  206. "??? / %u",
  207. game->levelSet->pars[game->selectedLevel]);
  208. } else {
  209. snprintf(
  210. buf,
  211. sizeof(buf),
  212. "%u / %u",
  213. game->levelSet->scores[game->selectedLevel].moves,
  214. game->levelSet->pars[game->selectedLevel]);
  215. }
  216. canvas_set_font(canvas, FontPrimary);
  217. canvas_draw_str_aligned(canvas, x + 25, y + 22, AlignCenter, AlignBottom, buf);
  218. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  219. canvas_draw_str_aligned(canvas, x + 75, y + 4, AlignCenter, AlignTop, "Score");
  220. memset(buf, 0, bufSize);
  221. if(game->score == 0) {
  222. snprintf(buf, sizeof(buf), "on par");
  223. } else {
  224. snprintf(buf, sizeof(buf), "%+d", game->score);
  225. }
  226. canvas_set_font(canvas, FontPrimary);
  227. canvas_draw_str_aligned(canvas, x + 75, y + 22, AlignCenter, AlignBottom, buf);
  228. }
  229. //-----------------------------------------------------------------------------
  230. void draw_main_menu_new_game(Canvas* canvas, Game* game) {
  231. canvas_set_color(canvas, ColorBlack);
  232. canvas_set_font(canvas, FontSecondary);
  233. elements_button_center(canvas, "Start");
  234. if(game->hasContinue) {
  235. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  236. elements_button_right(canvas, "Continue");
  237. canvas_draw_str_aligned(
  238. canvas,
  239. GUI_DISPLAY_CENTER_X,
  240. 37,
  241. AlignCenter,
  242. AlignTop,
  243. "!!! Forgets all progress and scores !!!");
  244. }
  245. }
  246. //-----------------------------------------------------------------------------
  247. void draw_main_menu_continue(Canvas* canvas, Game* game) {
  248. int bufSize = 80;
  249. char buf[bufSize];
  250. int scorebufSize = 10;
  251. char scorebufSet[scorebufSize];
  252. bool hasNext = (game->continueLevel + 1) < game->levelSet->maxLevel;
  253. memset(scorebufSet, 0, scorebufSize);
  254. if(game->score == 0) {
  255. snprintf(scorebufSet, sizeof(scorebufSet), "par");
  256. } else {
  257. snprintf(scorebufSet, sizeof(scorebufSet), "%+d", game->score);
  258. }
  259. canvas_set_color(canvas, ColorBlack);
  260. canvas_set_font(canvas, FontSecondary);
  261. elements_button_left(canvas, "New");
  262. elements_button_center(canvas, "Start");
  263. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  264. elements_button_right(canvas, "Custom");
  265. memset(buf, 0, bufSize);
  266. if(hasNext) {
  267. snprintf(
  268. buf,
  269. sizeof(buf),
  270. "%s (%s), #%d",
  271. furi_string_get_cstr(game->continueSet),
  272. scorebufSet,
  273. game->continueLevel + 2);
  274. } else {
  275. snprintf(buf, sizeof(buf), "%s finished!", furi_string_get_cstr(game->continueSet));
  276. }
  277. canvas_draw_str_aligned(canvas, GUI_DISPLAY_CENTER_X, 37, AlignCenter, AlignTop, buf);
  278. }
  279. //-----------------------------------------------------------------------------
  280. void draw_main_menu_custom(Canvas* canvas, Game* game) {
  281. int bufSize = 80;
  282. char buf[bufSize];
  283. int scorebufSize = 10;
  284. char scorebuf[scorebufSize];
  285. canvas_set_color(canvas, ColorBlack);
  286. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  287. main_menu_pill(
  288. canvas,
  289. 35,
  290. 90,
  291. game->mainMenuBtn == LEVELSET_BTN,
  292. game->setPos > 0,
  293. game->setPos < game->setCount - 1,
  294. furi_string_get_cstr(game->selectedSet));
  295. memset(scorebuf, 0, scorebufSize);
  296. score_for_level(game, game->selectedLevel, scorebuf, scorebufSize);
  297. canvas_set_font(canvas, FontSecondary);
  298. memset(buf, 0, bufSize);
  299. snprintf(
  300. buf,
  301. sizeof(buf),
  302. "%u of %u (%s)",
  303. game->selectedLevel + 1,
  304. game->levelSet->maxLevel,
  305. scorebuf);
  306. main_menu_pill(
  307. canvas,
  308. 50,
  309. 90,
  310. game->mainMenuBtn == LEVELNO_BTN,
  311. game->selectedLevel > 0,
  312. game->selectedLevel < game->levelSet->maxLevel - 1,
  313. buf);
  314. }
  315. //-----------------------------------------------------------------------------
  316. void draw_main_menu(Canvas* canvas, Game* game) {
  317. canvas_set_color(canvas, ColorBlack);
  318. canvas_draw_line(canvas, 0, 6, GUI_DISPLAY_WIDTH, 6);
  319. canvas_draw_line(canvas, 0, 9, GUI_DISPLAY_WIDTH, 9);
  320. canvas_set_color(canvas, ColorBlack);
  321. canvas_draw_rbox(canvas, GUI_DISPLAY_CENTER_X - 16 - 6, 1, 32 + 12, 14, 3);
  322. canvas_set_color(canvas, ColorWhite);
  323. canvas_draw_icon(canvas, GUI_DISPLAY_CENTER_X - 16, 2, &I_logo_vexed_mini);
  324. canvas_set_font(canvas, FontSecondary);
  325. main_menu_pill(
  326. canvas,
  327. 20,
  328. 90,
  329. game->mainMenuBtn == MODE_BTN,
  330. game->mainMenuMode != NEW_GAME,
  331. game->mainMenuMode != CUSTOM,
  332. game_mode_label(game->mainMenuMode));
  333. switch(game->mainMenuMode) {
  334. case CONTINUE:
  335. draw_main_menu_continue(canvas, game);
  336. break;
  337. case CUSTOM:
  338. draw_main_menu_custom(canvas, game);
  339. break;
  340. case NEW_GAME:
  341. default:
  342. draw_main_menu_new_game(canvas, game);
  343. break;
  344. }
  345. if(game->mainMenuInfo) {
  346. gray_canvas(canvas);
  347. if(game->mainMenuBtn == LEVELSET_BTN) {
  348. draw_set_info(canvas, game);
  349. } else if(game->mainMenuBtn == LEVELNO_BTN) {
  350. draw_level_info(canvas, game);
  351. }
  352. canvas_set_color(canvas, ColorBlack);
  353. canvas_set_font(canvas, FontSecondary);
  354. elements_button_center(canvas, "Start");
  355. elements_button_right_back(canvas, "Back");
  356. }
  357. }
  358. //-----------------------------------------------------------------------------
  359. void draw_playground(Canvas* canvas, Game* game) {
  360. Neighbors tiles;
  361. uint8_t tile, x, y, sx, sy, ex, ey;
  362. bool whiteB = (game->state == LEVEL_FINISHED) || (game->solutionMode);
  363. for(y = 0; y < SIZE_Y; y++) {
  364. for(x = 0; x < SIZE_X; x++) {
  365. tile = game->board[y][x];
  366. sx = x * TILE_SIZE;
  367. sy = y * TILE_SIZE;
  368. ex = ((x + 1) * TILE_SIZE) - 1;
  369. ey = ((y + 1) * TILE_SIZE) - 1;
  370. if(tile > 0) {
  371. if((game->state == MOVE_SIDES) && (x == game->move.x) && (y == game->move.y))
  372. continue;
  373. if(((game->state == MOVE_GRAVITY) || (game->state == EXPLODE)) &&
  374. (game->toAnimate[y][x] == 1))
  375. continue;
  376. canvas_set_color(canvas, ColorBlack);
  377. canvas_draw_icon(canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
  378. }
  379. if(tile == WALL_TILE) {
  380. tiles = find_neighbors(&game->board, x, y);
  381. // UP
  382. if(tiles.u != WALL_TILE) {
  383. canvas_set_color(canvas, ColorBlack);
  384. canvas_draw_line(canvas, sx, sy + 1, ex, sy + 1);
  385. canvas_set_color(canvas, ColorWhite);
  386. canvas_draw_line(canvas, sx, sy, ex, sy);
  387. if(whiteB) canvas_draw_line(canvas, sx, sy + 2, ex, sy + 2);
  388. }
  389. // DOWN
  390. if(tiles.d != WALL_TILE) {
  391. canvas_set_color(canvas, ColorBlack);
  392. canvas_draw_line(canvas, sx, ey, ex, ey);
  393. canvas_set_color(canvas, ColorWhite);
  394. if(whiteB) canvas_draw_line(canvas, sx, ey - 1, ex, ey - 1);
  395. }
  396. // LEFT
  397. if(tiles.l != WALL_TILE) {
  398. canvas_set_color(canvas, ColorBlack);
  399. canvas_draw_line(
  400. canvas, sx + 1, sy + ((tiles.u != WALL_TILE) ? 1 : 0), sx + 1, ey);
  401. canvas_set_color(canvas, ColorWhite);
  402. canvas_draw_line(canvas, sx, sy, sx, ey);
  403. if(whiteB)
  404. canvas_draw_line(
  405. canvas,
  406. sx + 2,
  407. sy + ((tiles.u != WALL_TILE) ? 2 : 0),
  408. sx + 2,
  409. ey - ((tiles.d != WALL_TILE) ? 2 : 0));
  410. }
  411. // RIGHT
  412. if(tiles.r != WALL_TILE) {
  413. canvas_set_color(canvas, ColorBlack);
  414. canvas_draw_line(canvas, ex, (sy) + ((tiles.u != WALL_TILE) ? 1 : 0), ex, ey);
  415. canvas_set_color(canvas, ColorWhite);
  416. if(whiteB)
  417. canvas_draw_line(
  418. canvas,
  419. ex - 1,
  420. sy + ((tiles.u != WALL_TILE) ? 2 : 0),
  421. ex - 1,
  422. ey - ((tiles.d != WALL_TILE) ? 2 : 0));
  423. }
  424. if((tiles.dl != WALL_TILE) && (tiles.l == WALL_TILE)) {
  425. canvas_set_color(canvas, ColorBlack);
  426. canvas_draw_line(canvas, sx, ey, sx + 1, ey);
  427. canvas_set_color(canvas, ColorWhite);
  428. if(whiteB) canvas_draw_line(canvas, sx, ey - 1, sx + 2, ey - 1);
  429. }
  430. if((tiles.ur != WALL_TILE) && (tiles.u == WALL_TILE)) {
  431. canvas_set_color(canvas, ColorBlack);
  432. canvas_draw_line(canvas, ex, sy, ex, sy + 1);
  433. canvas_set_color(canvas, ColorWhite);
  434. if(whiteB) canvas_draw_line(canvas, ex - 1, sy, ex - 1, sy + 2);
  435. }
  436. if(tiles.ul != WALL_TILE) {
  437. canvas_set_color(canvas, ColorWhite);
  438. canvas_draw_dot(canvas, sx, sy);
  439. if(whiteB) canvas_draw_dot(canvas, sx + 2, sy + 2);
  440. if(tiles.l == WALL_TILE) {
  441. canvas_set_color(canvas, ColorBlack);
  442. canvas_draw_line(canvas, sx, sy + 1, sx + 1, sy + 1);
  443. canvas_set_color(canvas, ColorWhite);
  444. if(whiteB) canvas_draw_line(canvas, sx, sy + 2, sx + 1, sy + 2);
  445. }
  446. if(tiles.u == WALL_TILE) {
  447. canvas_set_color(canvas, ColorBlack);
  448. canvas_draw_line(canvas, sx + 1, sy, sx + 1, sy + 1);
  449. canvas_set_color(canvas, ColorWhite);
  450. if(whiteB) canvas_draw_line(canvas, sx + 2, sy, sx + 2, sy + 1);
  451. }
  452. }
  453. if((tiles.dr != WALL_TILE) && (tiles.r == WALL_TILE) && (tiles.d == WALL_TILE)) {
  454. canvas_set_color(canvas, ColorWhite);
  455. if(whiteB) canvas_draw_line(canvas, ex - 1, ey - 1, ex - 1, ey);
  456. }
  457. }
  458. }
  459. }
  460. }
  461. //-----------------------------------------------------------------------------
  462. void draw_movable(Canvas* canvas, Game* game, uint32_t frameNo) {
  463. bool oddFrame = (frameNo % 20 < 10);
  464. if(game->currentMovable != MOVABLE_NOT_FOUND) {
  465. canvas_set_color(canvas, ColorBlack);
  466. uint8_t x = coord_x(game->currentMovable);
  467. uint8_t y = coord_y(game->currentMovable);
  468. uint8_t how_movable = game->movables[y][x];
  469. if((how_movable & MOVABLE_LEFT) != 0) {
  470. canvas_draw_icon(
  471. canvas, (x - 1) * TILE_SIZE + (oddFrame ? 0 : 1), y * TILE_SIZE, &I_arr_l);
  472. }
  473. if((how_movable & MOVABLE_RIGHT) != 0) {
  474. canvas_draw_icon(
  475. canvas, (x + 1) * TILE_SIZE + (oddFrame ? 1 : 0), y * TILE_SIZE, &I_arr_r);
  476. }
  477. canvas_draw_frame(
  478. canvas, x * TILE_SIZE - 1, y * TILE_SIZE - 1, TILE_SIZE + 3, TILE_SIZE + 3);
  479. if(oddFrame) {
  480. canvas_draw_frame(
  481. canvas, x * TILE_SIZE - 2, y * TILE_SIZE - 2, TILE_SIZE + 5, TILE_SIZE + 5);
  482. } else {
  483. canvas_draw_frame(canvas, x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE + 1, TILE_SIZE + 1);
  484. }
  485. }
  486. }
  487. //-----------------------------------------------------------------------------
  488. void draw_direction(Canvas* canvas, Game* game, uint32_t frameNo) {
  489. bool oddFrame = (frameNo % 20 < 10);
  490. if(game->currentMovable != MOVABLE_NOT_FOUND) {
  491. canvas_set_color(canvas, ColorBlack);
  492. uint8_t x = coord_x(game->currentMovable);
  493. uint8_t y = coord_y(game->currentMovable);
  494. if(oddFrame) {
  495. canvas_draw_icon(canvas, (x - 1) * TILE_SIZE, y * TILE_SIZE, &I_mov_l);
  496. canvas_draw_icon(canvas, ((x + 1) * TILE_SIZE) + 1, y * TILE_SIZE, &I_mov_r);
  497. }
  498. }
  499. }
  500. //-----------------------------------------------------------------------------
  501. void draw_direction_solution(Canvas* canvas, Game* game, uint32_t frameNo) {
  502. bool oddFrame = (frameNo % 20 < 10);
  503. if(game->currentMovable != MOVABLE_NOT_FOUND) {
  504. canvas_set_color(canvas, ColorBlack);
  505. uint8_t x = coord_x(game->currentMovable);
  506. uint8_t y = coord_y(game->currentMovable);
  507. uint8_t how_movable = game->movables[y][x];
  508. if((how_movable & MOVABLE_LEFT) != 0) {
  509. canvas_draw_icon(
  510. canvas, (x - 1) * TILE_SIZE + (oddFrame ? 0 : 1), y * TILE_SIZE, &I_arr_l);
  511. }
  512. if((how_movable & MOVABLE_RIGHT) != 0) {
  513. canvas_draw_icon(
  514. canvas, (x + 1) * TILE_SIZE + (oddFrame ? 1 : 0), y * TILE_SIZE, &I_mov_r);
  515. }
  516. canvas_draw_frame(
  517. canvas, x * TILE_SIZE - 1, y * TILE_SIZE - 1, TILE_SIZE + 3, TILE_SIZE + 3);
  518. if(oddFrame) {
  519. canvas_draw_frame(
  520. canvas, x * TILE_SIZE - 2, y * TILE_SIZE - 2, TILE_SIZE + 5, TILE_SIZE + 5);
  521. } else {
  522. canvas_draw_frame(canvas, x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE + 1, TILE_SIZE + 1);
  523. }
  524. }
  525. game->move.frameNo--;
  526. if(game->move.frameNo == 0) {
  527. solution_move(game);
  528. }
  529. }
  530. //-----------------------------------------------------------------------------
  531. void draw_ani_sides(Canvas* canvas, Game* game) {
  532. uint8_t tile, sx, sy, deltaX;
  533. if(game->state == MOVE_SIDES) {
  534. tile = game->board[game->move.y][game->move.x];
  535. deltaX = ((game->move.dir & MOVABLE_LEFT) != 0) ? -1 : 1;
  536. sx = (game->move.x * TILE_SIZE) + (deltaX * game->move.frameNo);
  537. sy = game->move.y * TILE_SIZE;
  538. canvas_set_color(canvas, ColorBlack);
  539. canvas_draw_icon(canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
  540. game->move.frameNo++;
  541. if(game->move.frameNo > TILE_SIZE) {
  542. stop_move(game);
  543. }
  544. }
  545. }
  546. //-----------------------------------------------------------------------------
  547. void draw_ani_gravity(Canvas* canvas, Game* game) {
  548. uint8_t tile, x, y, sx, sy;
  549. if(game->state == MOVE_GRAVITY) {
  550. for(y = 0; y < SIZE_Y; y++) {
  551. for(x = 0; x < SIZE_X; x++) {
  552. tile = game->board[y][x];
  553. sx = x * TILE_SIZE;
  554. sy = y * TILE_SIZE;
  555. if((tile > 0) && (game->toAnimate[y][x] == 1)) {
  556. canvas_set_color(canvas, ColorBlack);
  557. canvas_draw_icon(
  558. canvas,
  559. sx,
  560. sy + game->move.frameNo,
  561. tile_to_icon(tile, game->state == GAME_OVER));
  562. }
  563. }
  564. }
  565. if(game->move.delay > 0) {
  566. game->move.delay--;
  567. return;
  568. }
  569. game->move.frameNo++;
  570. if(game->move.frameNo > TILE_SIZE) {
  571. stop_gravity(game);
  572. }
  573. }
  574. }
  575. //-----------------------------------------------------------------------------
  576. void draw_ani_explode(Canvas* canvas, Game* game) {
  577. uint8_t tile, x, y, sx, sy, cx, cy, s, o;
  578. if(game->state == EXPLODE) {
  579. for(y = 0; y < SIZE_Y; y++) {
  580. for(x = 0; x < SIZE_X; x++) {
  581. tile = game->board[y][x];
  582. if((tile > 0) && (game->toAnimate[y][x] == 1)) {
  583. sx = x * TILE_SIZE;
  584. sy = y * TILE_SIZE;
  585. cx = sx + 4;
  586. cy = sy + 4;
  587. if((game->move.delay % 4 < 2) || (game->move.delay > 8)) {
  588. canvas_set_color(canvas, ColorBlack);
  589. canvas_draw_icon(
  590. canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
  591. }
  592. if(game->move.frameNo > 0) {
  593. canvas_set_color(canvas, ColorXOR);
  594. o = MIN(((game->move.frameNo - 1) / 2), (uint8_t)4);
  595. s = (o * 2) + 1;
  596. canvas_draw_box(canvas, cx - o, cy - o, s, s);
  597. }
  598. }
  599. }
  600. }
  601. if(game->move.delay > 0) {
  602. game->move.delay--;
  603. return;
  604. }
  605. game->move.frameNo++;
  606. if(game->move.frameNo > 10) {
  607. stop_explosion(game);
  608. }
  609. }
  610. }
  611. //-----------------------------------------------------------------------------
  612. void draw_scores(Canvas* canvas, Game* game, uint32_t frameNo) {
  613. BoundingBox box;
  614. int bufSize = 80;
  615. char buf[bufSize];
  616. bool showScore = (frameNo % 200) < 100;
  617. canvas_set_color(canvas, ColorBlack);
  618. canvas_draw_rbox(canvas, 82, 1, 46, 17, 2);
  619. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
  620. canvas_set_color(canvas, ColorWhite);
  621. set_bounding_box(&box, 82, 1, 46, 17);
  622. elements_multiline_text_aligned_limited(
  623. canvas,
  624. &box,
  625. box.width / 2,
  626. box.height / 2,
  627. 2,
  628. AlignCenter,
  629. AlignCenter,
  630. furi_string_get_cstr(game->levelData->title));
  631. if(game->solutionMode) {
  632. canvas_set_color(canvas, ColorBlack);
  633. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
  634. canvas_draw_str_aligned(canvas, 104, 27, AlignCenter, AlignTop, "Solution");
  635. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
  636. memset(buf, 0, bufSize);
  637. snprintf(buf, sizeof(buf), "%d of %d", game->solutionStep + 1, game->solutionTotal);
  638. canvas_draw_str_aligned(canvas, 104, 34, AlignCenter, AlignTop, buf);
  639. } else {
  640. canvas_set_color(canvas, ColorBlack);
  641. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
  642. canvas_draw_str_aligned(
  643. canvas, 104, 20, AlignCenter, AlignTop, showScore ? "Score" : "Level");
  644. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
  645. memset(buf, 0, bufSize);
  646. if(showScore) {
  647. if(game->score == 0) {
  648. snprintf(buf, sizeof(buf), "on par");
  649. } else {
  650. snprintf(buf, sizeof(buf), "%+d", game->score);
  651. }
  652. } else {
  653. snprintf(buf, sizeof(buf), "%u/%u", game->currentLevel + 1, game->levelSet->maxLevel);
  654. }
  655. canvas_draw_str_aligned(canvas, 104, 27, AlignCenter, AlignTop, buf);
  656. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
  657. canvas_draw_str_aligned(
  658. canvas, 104, 34, AlignCenter, AlignTop, showScore ? "Best" : "Moves");
  659. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
  660. memset(buf, 0, bufSize);
  661. if(showScore) {
  662. snprintf(buf, sizeof(buf), "%s", game->parLabel);
  663. } else {
  664. snprintf(buf, sizeof(buf), "%u/%u", game->gameMoves, game->levelData->gamePar);
  665. }
  666. canvas_draw_str_aligned(canvas, 104, 41, AlignCenter, AlignTop, buf);
  667. }
  668. }
  669. //-----------------------------------------------------------------------------
  670. void draw_paused(Canvas* canvas, Game* game) {
  671. gray_canvas(canvas);
  672. menu_pill(
  673. canvas,
  674. 0,
  675. MENU_PAUSED_COUNT,
  676. ((game->menuPausedPos == 0) && (game->undoMovable != MOVABLE_NOT_FOUND)),
  677. game->undoMovable == MOVABLE_NOT_FOUND,
  678. "Undo",
  679. &I_ico_undo);
  680. menu_pill(
  681. canvas, 1, MENU_PAUSED_COUNT, game->menuPausedPos == 1, false, "Restart", &I_ico_restart);
  682. menu_pill(canvas, 2, MENU_PAUSED_COUNT, game->menuPausedPos == 2, false, "Menu", &I_ico_home);
  683. menu_pill(canvas, 3, MENU_PAUSED_COUNT, game->menuPausedPos == 3, false, "Skip", &I_ico_skip);
  684. menu_pill(canvas, 4, MENU_PAUSED_COUNT, game->menuPausedPos == 4, false, "Count", &I_ico_hist);
  685. menu_pill(
  686. canvas, 5, MENU_PAUSED_COUNT, game->menuPausedPos == 5, false, "Solve", &I_ico_check);
  687. canvas_set_color(canvas, ColorBlack);
  688. canvas_set_font(canvas, FontSecondary);
  689. elements_button_right_back(canvas, "Back to game");
  690. }
  691. //-----------------------------------------------------------------------------
  692. void draw_histogram(Canvas* canvas, Stats* stats) {
  693. gray_canvas(canvas);
  694. panel_histogram(canvas, furi_string_get_cstr(stats->bricksNonZero), stats->statsNonZero);
  695. }
  696. void draw_playfield_hint(Canvas* canvas, Game* game) {
  697. if(game->state == SELECT_BRICK) {
  698. if((game->currentMovable != MOVABLE_NOT_FOUND) &&
  699. (movable_dir(&game->movables, game->currentMovable) == MOVABLE_BOTH)) {
  700. hint_pill_double(canvas, "Select", "Choose", &I_hint_2);
  701. } else {
  702. hint_pill_double(canvas, "Select", "Move", &I_hint_1);
  703. }
  704. }
  705. if(game->state == SELECT_DIRECTION) {
  706. hint_pill_double(canvas, "Move", "Cancel", &I_hint_3);
  707. }
  708. if(game->state == SOLUTION_SELECT || game->solutionMode) {
  709. hint_pill_double(canvas, "ANY", "CANCEL", &I_hint_4);
  710. } else {
  711. if(game->state == MOVE_SIDES) {
  712. hint_pill_single(canvas, "moving..");
  713. }
  714. if(game->state == MOVE_GRAVITY) {
  715. hint_pill_single(canvas, "falling..");
  716. }
  717. if(game->state == EXPLODE) {
  718. hint_pill_single(canvas, "BOOM!");
  719. }
  720. }
  721. }
  722. //-----------------------------------------------------------------------------
  723. void draw_game_over(Canvas* canvas, GameOver gameOverReason) {
  724. gray_canvas(canvas);
  725. const uint8_t y = dialog_frame(canvas, 100, 38, true, false, "Game Over!");
  726. canvas_set_color(canvas, ColorBlack);
  727. canvas_set_font(canvas, FontSecondary);
  728. if(gameOverReason == CANNOT_MOVE) {
  729. canvas_draw_str_aligned(
  730. canvas, GUI_DISPLAY_CENTER_X, y + 8, AlignCenter, AlignTop, "Cannot move");
  731. } else if(gameOverReason == BRICKS_LEFT) {
  732. canvas_draw_str_aligned(
  733. canvas, GUI_DISPLAY_CENTER_X, y + 8, AlignCenter, AlignTop, "Unpaired bricks left");
  734. }
  735. elements_button_left(canvas, "Retry");
  736. elements_button_center(canvas, "Menu");
  737. elements_button_right_back(canvas, "Undo");
  738. }
  739. //-----------------------------------------------------------------------------
  740. void draw_level_finished(Canvas* canvas, Game* game) {
  741. int bufSize = 80;
  742. char buf[bufSize];
  743. bool hasNext = game->currentLevel < game->levelSet->maxLevel - 1;
  744. gray_canvas(canvas);
  745. const uint8_t x = (GUI_DISPLAY_WIDTH - 100) / 2;
  746. const uint8_t y =
  747. dialog_frame(canvas, 100, 40, true, false, hasNext ? "Level finished!" : "Pack finished!");
  748. canvas_set_color(canvas, ColorBlack);
  749. canvas_draw_vline_dotted(canvas, GUI_DISPLAY_CENTER_X, y, 30);
  750. canvas_set_font(canvas, FontSecondary);
  751. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  752. canvas_draw_str_aligned(canvas, x + 25, y + 4, AlignCenter, AlignTop, "Moves/Par");
  753. memset(buf, 0, bufSize);
  754. snprintf(buf, sizeof(buf), "%u / %u", game->gameMoves, game->levelData->gamePar);
  755. canvas_set_font(canvas, FontPrimary);
  756. canvas_draw_str_aligned(canvas, x + 25, y + 22, AlignCenter, AlignBottom, buf);
  757. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  758. canvas_draw_str_aligned(canvas, x + 75, y + 4, AlignCenter, AlignTop, "Score");
  759. memset(buf, 0, bufSize);
  760. if(game->score == 0) {
  761. snprintf(buf, sizeof(buf), "on par");
  762. } else {
  763. snprintf(buf, sizeof(buf), "%+d", game->score);
  764. }
  765. canvas_set_font(canvas, FontPrimary);
  766. canvas_draw_str_aligned(canvas, x + 75, y + 22, AlignCenter, AlignBottom, buf);
  767. canvas_set_font(canvas, FontSecondary);
  768. if(hasNext) {
  769. elements_button_center(canvas, "Next");
  770. elements_button_right_back(canvas, "Menu");
  771. } else {
  772. elements_button_center(canvas, "Menu");
  773. }
  774. }
  775. //-----------------------------------------------------------------------------
  776. void draw_solution_prompt(Canvas* canvas, Game* game) {
  777. gray_canvas(canvas);
  778. const uint8_t y = dialog_frame(canvas, 100, 40, true, false, "Show solution?");
  779. const bool penalty = solution_will_have_penalty(game);
  780. canvas_set_color(canvas, ColorBlack);
  781. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  782. if(penalty) {
  783. canvas_draw_str_aligned(
  784. canvas, GUI_DISPLAY_CENTER_X, y + 4, AlignCenter, AlignTop, "It has one-time penalty");
  785. canvas_draw_str_aligned(
  786. canvas, GUI_DISPLAY_CENTER_X, y + 15, AlignCenter, AlignTop, "of additional 5 point");
  787. } else {
  788. canvas_draw_str_aligned(
  789. canvas, GUI_DISPLAY_CENTER_X, y + 4, AlignCenter, AlignTop, "Show solution?");
  790. }
  791. elements_button_center(canvas, "Show");
  792. elements_button_right_back(canvas, "Resign");
  793. }
  794. //-----------------------------------------------------------------------------
  795. void draw_reset_prompt(Canvas* canvas, Game* game) {
  796. UNUSED(game);
  797. gray_canvas(canvas);
  798. const uint8_t y = dialog_frame(canvas, 110, 45, true, false, "Reset game?");
  799. canvas_set_color(canvas, ColorBlack);
  800. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  801. canvas_draw_str_aligned(
  802. canvas, GUI_DISPLAY_CENTER_X, y + 2, AlignCenter, AlignTop, "Starting new game will reset");
  803. canvas_draw_str_aligned(
  804. canvas, GUI_DISPLAY_CENTER_X, y + 10, AlignCenter, AlignTop, "all progress and scores!");
  805. canvas_draw_str_aligned(
  806. canvas, GUI_DISPLAY_CENTER_X, y + 21, AlignCenter, AlignTop, "Are you sure?");
  807. canvas_set_font(canvas, FontSecondary);
  808. elements_button_center(canvas, "Confirm");
  809. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  810. elements_button_right_back(canvas, "Back");
  811. }
  812. //-----------------------------------------------------------------------------
  813. void draw_invalid_prompt(Canvas* canvas, Game* game) {
  814. UNUSED(game);
  815. gray_canvas(canvas);
  816. const uint8_t y = dialog_frame(canvas, 110, 45, true, false, "Invalid level");
  817. canvas_set_color(canvas, ColorBlack);
  818. canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
  819. canvas_draw_str_aligned(
  820. canvas, GUI_DISPLAY_CENTER_X, y + 2, AlignCenter, AlignTop, "Cannot load/parse level!");
  821. canvas_draw_str_aligned(
  822. canvas,
  823. GUI_DISPLAY_CENTER_X,
  824. y + 10,
  825. AlignCenter,
  826. AlignTop,
  827. furi_string_get_cstr(game->errorMsg));
  828. canvas_draw_str_aligned(
  829. canvas, GUI_DISPLAY_CENTER_X, y + 21, AlignCenter, AlignTop, "Repair or remove file!");
  830. canvas_set_font(canvas, FontSecondary);
  831. elements_button_center(canvas, "Understood");
  832. }