scene_game.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  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_PlayerOnTarget;
  121. return true;
  122. case '$':
  123. *cellType = CellType_Box;
  124. return true;
  125. case ' ':
  126. *cellType = CellType_Empty;
  127. return true;
  128. default:
  129. return false;
  130. }
  131. }
  132. bool level_reader_is_level_line(const char* line)
  133. {
  134. for (int i = 0; line[i] != '\0'; i++)
  135. {
  136. CellType cellType;
  137. if (!level_reader_parse_symbol(line[i], &cellType))
  138. return false;
  139. }
  140. return true;
  141. }
  142. bool level_reader_is_level_start_mark(const char* line)
  143. {
  144. bool hasNumber = false;
  145. for (int i = 0; line[i] != '\0'; i++)
  146. {
  147. if (line[i] >= '0' && line[i] <= '9')
  148. hasNumber = true;
  149. else if (line[i] != ' ')
  150. return false;
  151. }
  152. return hasNumber;
  153. }
  154. void level_reader_load_level(Level* ret_level, FileLinesReader* reader, int levelIndex)
  155. {
  156. ret_level->level_width = 0;
  157. ret_level->level_height = 0;
  158. for (int i = 0; i < MAX_BOARD_SIZE; i++)
  159. for (int j = 0; j < MAX_BOARD_SIZE; j++)
  160. ret_level->board[i][j] = CellType_Empty;
  161. for (int currentLevelFound = 0; currentLevelFound <= levelIndex; currentLevelFound++)
  162. {
  163. char line[MAX_READ_BUFFER_SIZE];
  164. while (!level_reader_is_level_start_mark(line))
  165. {
  166. file_lines_reader_readln(reader, line, MAX_READ_BUFFER_SIZE);
  167. if (file_lines_reader_is_eof(reader))
  168. return;
  169. };
  170. for (int i = 0; i < MAX_BOARD_SIZE; i++)
  171. {
  172. if (file_lines_reader_is_eof(reader))
  173. return;
  174. file_lines_reader_readln(reader, line, MAX_READ_BUFFER_SIZE);
  175. if (!level_reader_is_level_line(line))
  176. break;
  177. if (currentLevelFound < levelIndex)
  178. continue;
  179. ret_level->level_height += 1;
  180. int lineLen = strlen(line);
  181. if (lineLen > ret_level->level_width)
  182. ret_level->level_width = lineLen;
  183. for (int j = 0; j < lineLen; j++)
  184. {
  185. CellType cellType;
  186. if (level_reader_parse_symbol(line[j], &cellType))
  187. ret_level->board[i][j] = cellType;
  188. }
  189. }
  190. if (currentLevelFound == levelIndex)
  191. return;
  192. }
  193. return;
  194. }
  195. const Icon* findIcon(CellType cellType, int size)
  196. {
  197. switch (cellType)
  198. {
  199. case CellType_Wall:
  200. switch (size)
  201. {
  202. case 5:
  203. return &I_cell_wall_5;
  204. case 7:
  205. return &I_cell_wall_7;
  206. case 9:
  207. return &I_cell_wall_9;
  208. }
  209. break;
  210. case CellType_Box:
  211. switch (size)
  212. {
  213. case 5:
  214. return &I_cell_box_5;
  215. case 7:
  216. return &I_cell_box_7;
  217. case 9:
  218. return &I_cell_box_9;
  219. }
  220. break;
  221. case CellType_Target:
  222. switch (size)
  223. {
  224. case 5:
  225. return &I_cell_target_5;
  226. case 7:
  227. return &I_cell_target_7;
  228. case 9:
  229. return &I_cell_target_9;
  230. }
  231. break;
  232. case CellType_Player:
  233. switch (size)
  234. {
  235. case 5:
  236. return &I_cell_player_5;
  237. case 7:
  238. return &I_cell_player_7;
  239. case 9:
  240. return &I_cell_player_9;
  241. }
  242. break;
  243. case CellType_BoxOnTarget:
  244. switch (size)
  245. {
  246. case 5:
  247. return &I_cell_box_target_5;
  248. case 7:
  249. return &I_cell_box_target_7;
  250. case 9:
  251. return &I_cell_box_target_9;
  252. }
  253. break;
  254. case CellType_PlayerOnTarget:
  255. switch (size)
  256. {
  257. case 5:
  258. return &I_cell_player_target_5;
  259. case 7:
  260. return &I_cell_player_target_7;
  261. case 9:
  262. return &I_cell_player_target_9;
  263. }
  264. break;
  265. default:
  266. return NULL;
  267. }
  268. return NULL;
  269. }
  270. void draw_game(Canvas* const canvas, GameContext* game, int cellSize)
  271. {
  272. GameState* state = stack_peek(game->states);
  273. int canvasSizeX = 128 / cellSize;
  274. int canvasSizeY = 64 / cellSize;
  275. int centerX = canvasSizeX / 2;
  276. int centerY = canvasSizeY / 2;
  277. int fromX, toX, fromY, toY;
  278. fromX = MAX(0, state->playerX - centerX);
  279. fromY = MAX(0, state->playerY - centerY);
  280. toX = fromX + canvasSizeX;
  281. toY = fromY + canvasSizeY;
  282. if (toX > game->level->level_width)
  283. {
  284. fromX -= toX - game->level->level_width;
  285. toX = game->level->level_width;
  286. }
  287. if (toY > game->level->level_height)
  288. {
  289. fromY -= toY - game->level->level_height;
  290. toY = game->level->level_height;
  291. }
  292. fromX = MAX(0, fromX);
  293. fromY = MAX(0, fromY);
  294. for (int y = fromY; y < toY; y++)
  295. {
  296. for (int x = fromX; x < toX; x++)
  297. {
  298. int cellX = (x - fromX) * cellSize, cellY = (y - fromY) * cellSize;
  299. const Icon* icon = findIcon(state->board[y][x], cellSize);
  300. if (icon)
  301. canvas_draw_icon(canvas, cellX, cellY, icon);
  302. }
  303. }
  304. }
  305. void game_render_callback(Canvas* const canvas, void* context)
  306. {
  307. AppContext* app = (AppContext*)context;
  308. canvas_clear(canvas);
  309. GameState* state = stack_peek(game.states);
  310. Level* level = game.level;
  311. if (state == NULL || level == NULL)
  312. return;
  313. int cellSize = 9;
  314. if (level->level_width * cellSize > 128 || level->level_height * cellSize > 64)
  315. cellSize = 7;
  316. if (level->level_width * cellSize > 128 || level->level_height * cellSize > 64)
  317. cellSize = 5;
  318. draw_game(canvas, &game, cellSize);
  319. if (game.isCompleted)
  320. victory_popup_render_callback(canvas, app);
  321. }
  322. void game_state_initialize(GameState* state, Level* level)
  323. {
  324. if (state == NULL)
  325. {
  326. throw_exception("state cannot be null");
  327. return;
  328. }
  329. if (level == NULL)
  330. {
  331. throw_exception("level cannot be null");
  332. return;
  333. }
  334. state->playerX = state->playerY = state->pushesCount = 0;
  335. memcpy(state->board, level->board, sizeof(CellType) * MAX_BOARD_SIZE * MAX_BOARD_SIZE);
  336. for (int y = 0; y < level->level_height; y++)
  337. {
  338. for (int x = 0; x < level->level_width; x++)
  339. {
  340. if (state->board[y][x] == CellType_Player || state->board[y][x] == CellType_PlayerOnTarget)
  341. {
  342. state->playerX = x;
  343. state->playerY = y;
  344. return;
  345. }
  346. }
  347. }
  348. }
  349. void load_selected_level(AppGameplayState* gameplayState, LevelsDatabase* database)
  350. {
  351. Storage* storage = furi_record_open(RECORD_STORAGE);
  352. File* file = storage_file_alloc(storage);
  353. char filename[MAX_FILENAME_LEN];
  354. snprintf(filename, MAX_FILENAME_LEN, "%s/%s.txt", STORAGE_APP_ASSETS_PATH_PREFIX, database->collections[gameplayState->selectedCollection].name);
  355. for (int i = 0; filename[i] != '\0'; i++)
  356. if (filename[i] >= 'A' && filename[i] <= 'Z')
  357. filename[i] += 'a' - 'A';
  358. FURI_LOG_D("GAME", "Opening file: %s", filename);
  359. storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING);
  360. FileLinesReader* reader = file_lines_reader_alloc(file, MAX_READ_BUFFER_SIZE);
  361. FURI_LOG_D("GAME", "Loading level %d", gameplayState->selectedLevel);
  362. level_reader_load_level(game.level, reader, gameplayState->selectedLevel);
  363. FURI_LOG_D("GAME", "Level size: %d x %d", game.level->level_width, game.level->level_height);
  364. GameState* initialState = malloc(sizeof(GameState));
  365. game_state_initialize(initialState, game.level);
  366. stack_push(game.states, initialState);
  367. file_lines_reader_free(reader);
  368. storage_file_free(file);
  369. furi_record_close(RECORD_STORAGE);
  370. }
  371. void game_transition_callback(int from, int to, void* context)
  372. {
  373. AppContext* app = (AppContext*)context;
  374. AppGameplayState* gameplayState = app->gameplay;
  375. LevelsDatabase* database = app->database;
  376. if (from == SceneType_Game)
  377. {
  378. while (stack_count(game.states) > 0)
  379. {
  380. GameState* state = stack_pop(game.states);
  381. free(state);
  382. }
  383. stack_free(game.states);
  384. free(game.level);
  385. }
  386. if (to == SceneType_Game)
  387. {
  388. game.level = malloc(sizeof(Level));
  389. game.states = stack_alloc();
  390. game.isCompleted = false;
  391. load_selected_level(gameplayState, database);
  392. }
  393. }
  394. void verify_level_completed(GameState* state)
  395. {
  396. for (int y = 0; y < MAX_BOARD_SIZE; y++)
  397. {
  398. for (int x = 0; x < MAX_BOARD_SIZE; x++)
  399. {
  400. if (state->board[y][x] == CellType_Box)
  401. return;
  402. }
  403. }
  404. game.isCompleted = true;
  405. }
  406. void apply_input(GameState* gameState, int dx, int dy, Level* level)
  407. {
  408. int newX = gameState->playerX + dx, newY = gameState->playerY + dy;
  409. if (newX < 0 || newX >= level->level_width || newY < 0 || newY >= level->level_height)
  410. return;
  411. CellType newCell = gameState->board[newY][newX];
  412. if (newCell == CellType_Wall)
  413. return;
  414. if (newCell == CellType_Box || newCell == CellType_BoxOnTarget)
  415. {
  416. int newBoxX = newX + dx, newBoxY = newY + dy;
  417. if (newBoxX < 0 || newBoxX >= level->level_width || newBoxY < 0 || newBoxY >= level->level_height)
  418. return;
  419. CellType newBoxCell = gameState->board[newBoxY][newBoxX];
  420. if (newBoxCell == CellType_Wall || newBoxCell == CellType_Box || newBoxCell == CellType_BoxOnTarget)
  421. return;
  422. gameState->board[newBoxY][newBoxX] = newBoxCell == CellType_Target ? CellType_BoxOnTarget : CellType_Box;
  423. newCell = newCell == CellType_BoxOnTarget ? CellType_Target : CellType_Empty;
  424. gameState->board[newY][newX] = newCell;
  425. gameState->pushesCount += 1;
  426. }
  427. gameState->board[newY][newX] = newCell == CellType_Target ? CellType_PlayerOnTarget : CellType_Player;
  428. gameState->board[gameState->playerY][gameState->playerX] = gameState->board[gameState->playerY][gameState->playerX] == CellType_PlayerOnTarget ? CellType_Target : CellType_Empty;
  429. gameState->playerX = newX;
  430. gameState->playerY = newY;
  431. }
  432. void game_handle_player_input(InputKey key, InputType type, GameContext* gameContext)
  433. {
  434. if (type != InputTypePress && type != InputTypeRepeat)
  435. return;
  436. int dx = 0, dy = 0;
  437. switch (key)
  438. {
  439. case InputKeyLeft:
  440. dx = -1;
  441. break;
  442. case InputKeyRight:
  443. dx = 1;
  444. break;
  445. case InputKeyUp:
  446. dy = -1;
  447. break;
  448. case InputKeyDown:
  449. dy = 1;
  450. break;
  451. default:
  452. return;
  453. }
  454. Level* level = gameContext->level;
  455. GameState* previousGameState = stack_peek(gameContext->states);
  456. GameState* gameState = malloc(sizeof(GameState));
  457. memcpy(gameState, previousGameState, sizeof(GameState));
  458. apply_input(gameState, dx, dy, level);
  459. if (gameState->playerX != previousGameState->playerX || gameState->playerY != previousGameState->playerY)
  460. {
  461. if (stack_count(gameContext->states) >= MAX_UNDO_STATES)
  462. {
  463. GameState* state = stack_discard_bottom(gameContext->states);
  464. free(state);
  465. }
  466. stack_push(gameContext->states, gameState);
  467. verify_level_completed(gameState);
  468. }
  469. else
  470. {
  471. free(gameState);
  472. }
  473. }
  474. void game_handle_input(InputKey key, InputType type, void* context)
  475. {
  476. AppContext* app = (AppContext*)context;
  477. GameState* gameState = stack_peek(game.states);
  478. if (key == InputKeyBack && type == InputTypePress)
  479. {
  480. scene_manager_set_scene(app->sceneManager, SceneType_Menu);
  481. return;
  482. }
  483. if (game.isCompleted)
  484. {
  485. victory_popup_handle_input(key, type, app);
  486. return;
  487. }
  488. if (key == InputKeyOk && (type == InputTypePress || type == InputTypeRepeat))
  489. {
  490. if (stack_count(game.states) > 1)
  491. {
  492. GameState* state = stack_pop(game.states);
  493. free(state);
  494. }
  495. return;
  496. }
  497. game_handle_player_input(key, type, &game);
  498. gameState = stack_peek(game.states);
  499. if (game.isCompleted)
  500. {
  501. FURI_LOG_D("GAME", "Level completed in %d pushes", gameState->pushesCount);
  502. AppGameplayState* gameplayState = app->gameplay;
  503. LevelsDatabase* database = app->database;
  504. LevelItem* levelItem = &database->collections[gameplayState->selectedCollection].levels[gameplayState->selectedLevel];
  505. if (levelItem->playerBest == 0 || gameState->pushesCount < levelItem->playerBest)
  506. {
  507. levelItem->playerBest = gameState->pushesCount;
  508. levels_database_save_player_progress(database);
  509. }
  510. }
  511. }
  512. void game_tick_callback(void* context)
  513. {
  514. AppContext* app = (AppContext*)context;
  515. UNUSED(app);
  516. }