scene_game.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. #include "scene_game.h"
  2. #include "app.h"
  3. #include "game.h"
  4. #include "app_gameplay.h"
  5. #include "save_data_manager.h"
  6. #include "wave/scene_management.h"
  7. #include "wave/calc.h"
  8. #include "wave/data_structures/stack.h"
  9. #include "wave/data_structures/list.h"
  10. #include "wave/data_structures/string_writer.h"
  11. #include "wave/files/file_lines_reader.h"
  12. #include "wave/exception_manager.h"
  13. #include "racso_sokoban_icons.h"
  14. #include <gui/gui.h>
  15. #include <furi.h>
  16. #include <string.h>
  17. #include <stdlib.h>
  18. #include <stdio.h>
  19. #include <storage/storage.h>
  20. #define MAX_READ_BUFFER_SIZE 256
  21. #define MAX_FILENAME_LEN 256
  22. #define MAX_BOARD_SIZE 50
  23. #define CELL_SIZE 9
  24. #define MAX_UNDO_STATES 10
  25. typedef enum
  26. {
  27. CellType_Empty,
  28. CellType_Wall,
  29. CellType_Box,
  30. CellType_Target,
  31. CellType_Player,
  32. CellType_BoxOnTarget,
  33. CellType_PlayerOnTarget
  34. } CellType;
  35. typedef struct Level
  36. {
  37. int level_width, level_height;
  38. CellType board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
  39. } Level;
  40. typedef struct GameState
  41. {
  42. int playerX, playerY, pushesCount;
  43. CellType board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
  44. } GameState;
  45. typedef struct GameContext
  46. {
  47. Stack* states;
  48. Level* level;
  49. bool isCompleted;
  50. } GameContext;
  51. static GameContext game;
  52. // Victory Popup component
  53. void victory_popup_render_callback(Canvas* const canvas, AppContext* app)
  54. {
  55. const int ICON_SIDE = 9;
  56. AppGameplayState* gameplayState = app->gameplay;
  57. LevelsDatabase* database = app->database;
  58. LevelItem* levelItem = &database->collections[gameplayState->selectedCollection].levels[gameplayState->selectedLevel];
  59. GameState* state = stack_peek(game.states);
  60. canvas_clear(canvas);
  61. canvas_set_color(canvas, ColorBlack);
  62. canvas_set_font(canvas, FontPrimary);
  63. canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "COMPLETED!");
  64. canvas_set_font(canvas, FontSecondary);
  65. StringWriter* writer = string_writer_alloc(100);
  66. string_writer_add_str(writer, "Pushes: ");
  67. string_writer_add_int(writer, state->pushesCount);
  68. canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, string_writer_get(writer));
  69. canvas_draw_icon(canvas, 32 - ICON_SIDE / 2, 28, &I_checkbox_checked);
  70. string_writer_clear(writer);
  71. string_writer_add_str(writer, "Best: ");
  72. string_writer_add_int(writer, levelItem->playerBest);
  73. canvas_draw_str_aligned(canvas, 32, 42, AlignCenter, AlignCenter, string_writer_get(writer));
  74. canvas_draw_icon(canvas, 96 - ICON_SIDE / 2, 28, &I_star);
  75. string_writer_clear(writer);
  76. string_writer_add_str(writer, "World: ");
  77. string_writer_add_int(writer, levelItem->worldBest);
  78. canvas_draw_str_aligned(canvas, 96, 42, AlignCenter, AlignCenter, string_writer_get(writer));
  79. string_writer_free(writer);
  80. const int START_CENTER_X = 100, START_CENTER_Y = 59;
  81. canvas_draw_circle(canvas, START_CENTER_X, START_CENTER_Y, 4);
  82. canvas_draw_disc(canvas, START_CENTER_X, START_CENTER_Y, 2);
  83. canvas_draw_str_aligned(canvas, START_CENTER_X + 8, START_CENTER_Y + 4, AlignLeft, AlignBottom, "Next");
  84. }
  85. void victory_popup_handle_input(InputKey key, InputType type, AppContext* app)
  86. {
  87. AppGameplayState* gameplayState = app->gameplay;
  88. if (key == InputKeyOk && type == InputTypePress)
  89. {
  90. if (gameplayState->selectedLevel + 1 < app->database->collections[gameplayState->selectedCollection].levelsCount)
  91. {
  92. gameplayState->selectedLevel += 1;
  93. scene_manager_set_scene(app->sceneManager, SceneType_Game);
  94. return;
  95. }
  96. else
  97. {
  98. scene_manager_set_scene(app->sceneManager, SceneType_Menu);
  99. return;
  100. }
  101. }
  102. }
  103. bool level_reader_parse_symbol(char ch, CellType* cellType)
  104. {
  105. switch (ch)
  106. {
  107. case '#':
  108. *cellType = CellType_Wall;
  109. return true;
  110. case '*':
  111. *cellType = CellType_BoxOnTarget;
  112. return true;
  113. case '.':
  114. *cellType = CellType_Target;
  115. return true;
  116. case '@':
  117. *cellType = CellType_Player;
  118. return true;
  119. case '$':
  120. *cellType = CellType_Box;
  121. return true;
  122. case ' ':
  123. *cellType = CellType_Empty;
  124. return true;
  125. default:
  126. return false;
  127. }
  128. }
  129. bool level_reader_is_level_line(const char* line)
  130. {
  131. for (int i = 0; line[i] != '\0'; i++)
  132. {
  133. CellType cellType;
  134. if (!level_reader_parse_symbol(line[i], &cellType))
  135. return false;
  136. }
  137. return true;
  138. }
  139. bool level_reader_is_level_start_mark(const char* line)
  140. {
  141. bool hasNumber = false;
  142. for (int i = 0; line[i] != '\0'; i++)
  143. {
  144. if (line[i] >= '0' && line[i] <= '9')
  145. hasNumber = true;
  146. else if (line[i] != ' ')
  147. return false;
  148. }
  149. return hasNumber;
  150. }
  151. void level_reader_load_level(Level* ret_level, FileLinesReader* reader, int levelIndex)
  152. {
  153. ret_level->level_width = 0;
  154. ret_level->level_height = 0;
  155. for (int i = 0; i < MAX_BOARD_SIZE; i++)
  156. for (int j = 0; j < MAX_BOARD_SIZE; j++)
  157. ret_level->board[i][j] = CellType_Empty;
  158. for (int currentLevelFound = 0; currentLevelFound <= levelIndex; currentLevelFound++)
  159. {
  160. char line[MAX_READ_BUFFER_SIZE];
  161. while (!level_reader_is_level_start_mark(line))
  162. {
  163. file_lines_reader_readln(reader, line, MAX_READ_BUFFER_SIZE);
  164. if (file_lines_reader_is_eof(reader))
  165. return;
  166. };
  167. for (int i = 0; i < MAX_BOARD_SIZE; i++)
  168. {
  169. if (file_lines_reader_is_eof(reader))
  170. return;
  171. file_lines_reader_readln(reader, line, MAX_READ_BUFFER_SIZE);
  172. if (!level_reader_is_level_line(line))
  173. break;
  174. if (currentLevelFound < levelIndex)
  175. continue;
  176. ret_level->level_height += 1;
  177. int lineLen = strlen(line);
  178. if (lineLen > ret_level->level_width)
  179. ret_level->level_width = lineLen;
  180. for (int j = 0; j < lineLen; j++)
  181. {
  182. switch (line[j])
  183. {
  184. case '#':
  185. ret_level->board[i][j] = CellType_Wall;
  186. break;
  187. case '*':
  188. ret_level->board[i][j] = CellType_BoxOnTarget;
  189. break;
  190. case '.':
  191. ret_level->board[i][j] = CellType_Target;
  192. break;
  193. case '@':
  194. ret_level->board[i][j] = CellType_Player;
  195. break;
  196. case '$':
  197. ret_level->board[i][j] = CellType_Box;
  198. break;
  199. default:
  200. ret_level->board[i][j] = CellType_Empty;
  201. }
  202. }
  203. }
  204. if (currentLevelFound == levelIndex)
  205. return;
  206. }
  207. return;
  208. }
  209. const Icon* findIcon(CellType cellType, int size)
  210. {
  211. switch (cellType)
  212. {
  213. case CellType_Wall:
  214. switch (size)
  215. {
  216. case 5:
  217. return &I_cell_wall_5;
  218. case 7:
  219. return &I_cell_wall_7;
  220. case 9:
  221. return &I_cell_wall_9;
  222. }
  223. break;
  224. case CellType_Box:
  225. switch (size)
  226. {
  227. case 5:
  228. return &I_cell_box_5;
  229. case 7:
  230. return &I_cell_box_7;
  231. case 9:
  232. return &I_cell_box_9;
  233. }
  234. break;
  235. case CellType_Target:
  236. switch (size)
  237. {
  238. case 5:
  239. return &I_cell_target_5;
  240. case 7:
  241. return &I_cell_target_7;
  242. case 9:
  243. return &I_cell_target_9;
  244. }
  245. break;
  246. case CellType_Player:
  247. switch (size)
  248. {
  249. case 5:
  250. return &I_cell_player_5;
  251. case 7:
  252. return &I_cell_player_7;
  253. case 9:
  254. return &I_cell_player_9;
  255. }
  256. break;
  257. case CellType_BoxOnTarget:
  258. switch (size)
  259. {
  260. case 5:
  261. return &I_cell_box_target_5;
  262. case 7:
  263. return &I_cell_box_target_7;
  264. case 9:
  265. return &I_cell_box_target_9;
  266. }
  267. break;
  268. case CellType_PlayerOnTarget:
  269. switch (size)
  270. {
  271. case 5:
  272. return &I_cell_player_target_5;
  273. case 7:
  274. return &I_cell_player_target_7;
  275. case 9:
  276. return &I_cell_player_target_9;
  277. }
  278. break;
  279. default:
  280. return NULL;
  281. }
  282. return NULL;
  283. }
  284. void draw_game(Canvas* const canvas, GameContext* game, int cellSize)
  285. {
  286. GameState* state = stack_peek(game->states);
  287. int canvasSizeX = 128 / cellSize;
  288. int canvasSizeY = 64 / cellSize;
  289. int centerX = canvasSizeX / 2;
  290. int centerY = canvasSizeY / 2;
  291. int fromX, toX, fromY, toY;
  292. fromX = MAX(0, state->playerX - centerX);
  293. fromY = MAX(0, state->playerY - centerY);
  294. toX = fromX + canvasSizeX;
  295. toY = fromY + canvasSizeY;
  296. if (toX > game->level->level_width)
  297. {
  298. fromX -= toX - game->level->level_width;
  299. toX = game->level->level_width;
  300. }
  301. if (toY > game->level->level_height)
  302. {
  303. fromY -= toY - game->level->level_height;
  304. toY = game->level->level_height;
  305. }
  306. fromX = MAX(0, fromX);
  307. fromY = MAX(0, fromY);
  308. for (int y = fromY; y < toY; y++)
  309. {
  310. for (int x = fromX; x < toX; x++)
  311. {
  312. int cellX = (x - fromX) * cellSize, cellY = (y - fromY) * cellSize;
  313. const Icon* icon = findIcon(state->board[y][x], cellSize);
  314. if (icon)
  315. canvas_draw_icon(canvas, cellX, cellY, icon);
  316. }
  317. }
  318. }
  319. void game_render_callback(Canvas* const canvas, void* context)
  320. {
  321. AppContext* app = (AppContext*)context;
  322. canvas_clear(canvas);
  323. GameState* state = stack_peek(game.states);
  324. Level* level = game.level;
  325. if (state == NULL || level == NULL)
  326. return;
  327. int cellSize = 9;
  328. if (level->level_width * cellSize > 128 || level->level_height * cellSize > 64)
  329. cellSize = 7;
  330. if (level->level_width * cellSize > 128 || level->level_height * cellSize > 64)
  331. cellSize = 5;
  332. draw_game(canvas, &game, cellSize);
  333. if (game.isCompleted)
  334. victory_popup_render_callback(canvas, app);
  335. }
  336. void game_state_initialize(GameState* state, Level* level)
  337. {
  338. if (state == NULL)
  339. {
  340. throw_exception("state cannot be null");
  341. return;
  342. }
  343. if (level == NULL)
  344. {
  345. throw_exception("level cannot be null");
  346. return;
  347. }
  348. state->playerX = state->playerY = state->pushesCount = 0;
  349. memcpy(state->board, level->board, sizeof(CellType) * MAX_BOARD_SIZE * MAX_BOARD_SIZE);
  350. for (int y = 0; y < level->level_height; y++)
  351. {
  352. for (int x = 0; x < level->level_width; x++)
  353. {
  354. if (state->board[y][x] == CellType_Player || state->board[y][x] == CellType_PlayerOnTarget)
  355. {
  356. state->playerX = x;
  357. state->playerY = y;
  358. return;
  359. }
  360. }
  361. }
  362. }
  363. void load_selected_level(AppGameplayState* gameplayState, LevelsDatabase* database)
  364. {
  365. Storage* storage = furi_record_open(RECORD_STORAGE);
  366. File* file = storage_file_alloc(storage);
  367. char filename[MAX_FILENAME_LEN];
  368. snprintf(filename, MAX_FILENAME_LEN, "%s/%s.txt", STORAGE_APP_ASSETS_PATH_PREFIX, database->collections[gameplayState->selectedCollection].name);
  369. for (int i = 0; filename[i] != '\0'; i++)
  370. if (filename[i] >= 'A' && filename[i] <= 'Z')
  371. filename[i] += 'a' - 'A';
  372. FURI_LOG_D("GAME", "Opening file: %s", filename);
  373. storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING);
  374. FileLinesReader* reader = file_lines_reader_alloc(file, MAX_READ_BUFFER_SIZE);
  375. FURI_LOG_D("GAME", "Loading level %d", gameplayState->selectedLevel);
  376. level_reader_load_level(game.level, reader, gameplayState->selectedLevel);
  377. FURI_LOG_D("GAME", "Level size: %d x %d", game.level->level_width, game.level->level_height);
  378. GameState* initialState = malloc(sizeof(GameState));
  379. game_state_initialize(initialState, game.level);
  380. stack_push(game.states, initialState);
  381. file_lines_reader_free(reader);
  382. storage_file_free(file);
  383. furi_record_close(RECORD_STORAGE);
  384. }
  385. void game_transition_callback(int from, int to, void* context)
  386. {
  387. AppContext* app = (AppContext*)context;
  388. AppGameplayState* gameplayState = app->gameplay;
  389. LevelsDatabase* database = app->database;
  390. if (from == SceneType_Game)
  391. {
  392. while (stack_count(game.states) > 0)
  393. {
  394. GameState* state = stack_pop(game.states);
  395. free(state);
  396. }
  397. stack_free(game.states);
  398. free(game.level);
  399. }
  400. if (to == SceneType_Game)
  401. {
  402. game.level = malloc(sizeof(Level));
  403. game.states = stack_alloc();
  404. game.isCompleted = false;
  405. load_selected_level(gameplayState, database);
  406. }
  407. }
  408. void verify_level_completed(GameState* state)
  409. {
  410. for (int y = 0; y < MAX_BOARD_SIZE; y++)
  411. {
  412. for (int x = 0; x < MAX_BOARD_SIZE; x++)
  413. {
  414. if (state->board[y][x] == CellType_Box)
  415. return;
  416. }
  417. }
  418. game.isCompleted = true;
  419. }
  420. void apply_input(GameState* gameState, int dx, int dy, Level* level)
  421. {
  422. int newX = gameState->playerX + dx, newY = gameState->playerY + dy;
  423. if (newX < 0 || newX >= level->level_width || newY < 0 || newY >= level->level_height)
  424. return;
  425. CellType newCell = gameState->board[newY][newX];
  426. if (newCell == CellType_Wall)
  427. return;
  428. if (newCell == CellType_Box || newCell == CellType_BoxOnTarget)
  429. {
  430. int newBoxX = newX + dx, newBoxY = newY + dy;
  431. if (newBoxX < 0 || newBoxX >= level->level_width || newBoxY < 0 || newBoxY >= level->level_height)
  432. return;
  433. CellType newBoxCell = gameState->board[newBoxY][newBoxX];
  434. if (newBoxCell == CellType_Wall || newBoxCell == CellType_Box || newBoxCell == CellType_BoxOnTarget)
  435. return;
  436. gameState->board[newBoxY][newBoxX] = newBoxCell == CellType_Target ? CellType_BoxOnTarget : CellType_Box;
  437. newCell = newCell == CellType_BoxOnTarget ? CellType_Target : CellType_Empty;
  438. gameState->board[newY][newX] = newCell;
  439. gameState->pushesCount += 1;
  440. }
  441. gameState->board[newY][newX] = newCell == CellType_Target ? CellType_PlayerOnTarget : CellType_Player;
  442. gameState->board[gameState->playerY][gameState->playerX] = gameState->board[gameState->playerY][gameState->playerX] == CellType_PlayerOnTarget ? CellType_Target : CellType_Empty;
  443. gameState->playerX = newX;
  444. gameState->playerY = newY;
  445. }
  446. void game_handle_player_input(InputKey key, InputType type, GameContext* gameContext)
  447. {
  448. if (type != InputTypePress && type != InputTypeRepeat)
  449. return;
  450. int dx = 0, dy = 0;
  451. switch (key)
  452. {
  453. case InputKeyLeft:
  454. dx = -1;
  455. break;
  456. case InputKeyRight:
  457. dx = 1;
  458. break;
  459. case InputKeyUp:
  460. dy = -1;
  461. break;
  462. case InputKeyDown:
  463. dy = 1;
  464. break;
  465. default:
  466. return;
  467. }
  468. Level* level = gameContext->level;
  469. GameState* previousGameState = stack_peek(gameContext->states);
  470. GameState* gameState = malloc(sizeof(GameState));
  471. memcpy(gameState, previousGameState, sizeof(GameState));
  472. apply_input(gameState, dx, dy, level);
  473. if (gameState->playerX != previousGameState->playerX || gameState->playerY != previousGameState->playerY)
  474. {
  475. if (stack_count(gameContext->states) >= MAX_UNDO_STATES)
  476. {
  477. GameState* state = stack_discard_bottom(gameContext->states);
  478. free(state);
  479. }
  480. stack_push(gameContext->states, gameState);
  481. verify_level_completed(gameState);
  482. }
  483. else
  484. {
  485. free(gameState);
  486. }
  487. }
  488. void game_handle_input(InputKey key, InputType type, void* context)
  489. {
  490. AppContext* app = (AppContext*)context;
  491. GameState* gameState = stack_peek(game.states);
  492. if (key == InputKeyBack && type == InputTypePress)
  493. {
  494. scene_manager_set_scene(app->sceneManager, SceneType_Menu);
  495. return;
  496. }
  497. if (game.isCompleted)
  498. {
  499. victory_popup_handle_input(key, type, app);
  500. return;
  501. }
  502. if (key == InputKeyOk && (type == InputTypePress || type == InputTypeRepeat))
  503. {
  504. if (stack_count(game.states) > 1)
  505. {
  506. GameState* state = stack_pop(game.states);
  507. free(state);
  508. }
  509. return;
  510. }
  511. game_handle_player_input(key, type, &game);
  512. gameState = stack_peek(game.states);
  513. if (game.isCompleted)
  514. {
  515. FURI_LOG_D("GAME", "Level completed in %d pushes", gameState->pushesCount);
  516. AppGameplayState* gameplayState = app->gameplay;
  517. LevelsDatabase* database = app->database;
  518. LevelItem* levelItem = &database->collections[gameplayState->selectedCollection].levels[gameplayState->selectedLevel];
  519. if (levelItem->playerBest == 0 || gameState->pushesCount < levelItem->playerBest)
  520. {
  521. levelItem->playerBest = gameState->pushesCount;
  522. levels_database_save_player_progress(database);
  523. }
  524. }
  525. }
  526. void game_tick_callback(void* context)
  527. {
  528. AppContext* app = (AppContext*)context;
  529. UNUSED(app);
  530. }