game.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. #include "game.h"
  2. #include "utils.h"
  3. #include "move.h"
  4. Game* alloc_game_state(int* error) {
  5. *error = 0;
  6. Game* game = malloc(sizeof(Game));
  7. game->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  8. if(!game->mutex) {
  9. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  10. free(game);
  11. *error = 255;
  12. return NULL;
  13. }
  14. game->levelData = alloc_level_data();
  15. game->levelSet = alloc_level_set();
  16. game->stats = alloc_stats();
  17. game->currentLevel = 0;
  18. game->gameMoves = 0;
  19. game->score = 0;
  20. game->currentMovableBackup = MOVABLE_NOT_FOUND;
  21. game->solutionMode = false;
  22. game->solutionStep = 0;
  23. game->solutionTotal = 0;
  24. game->undoMovable = MOVABLE_NOT_FOUND;
  25. game->currentMovable = MOVABLE_NOT_FOUND;
  26. game->nextMovable = MOVABLE_NOT_FOUND;
  27. game->menuPausedPos = 0;
  28. game->mainMenuBtn = MODE_BTN;
  29. game->mainMenuMode = NEW_GAME;
  30. game->mainMenuInfo = false;
  31. game->hasContinue = false;
  32. game->selectedSet = furi_string_alloc_set(assetLevels[0]);
  33. game->selectedLevel = 0;
  34. game->continueSet = furi_string_alloc_set(assetLevels[0]);
  35. game->continueLevel = 0;
  36. game->setPos = 0;
  37. game->setCount = 1;
  38. game->state = INTRO;
  39. game->gameOverReason = NOT_GAME_OVER;
  40. game->move.frameNo = 0;
  41. memset(game->parLabel, 0, PAR_LABEL_SIZE);
  42. game->errorMsg = furi_string_alloc();
  43. return game;
  44. }
  45. //-----------------------------------------------------------------------------
  46. void load_game_board(Game* g) {
  47. bool levelLoadable = false;
  48. // Open storage
  49. Storage* storage = furi_record_open(RECORD_STORAGE);
  50. if(load_level(storage, g->levelSet->id, g->currentLevel, g->levelData, g->errorMsg)) {
  51. levelLoadable = parse_level_notation(furi_string_get_cstr(g->levelData->board), &g->board);
  52. }
  53. // Close storage
  54. if(!levelLoadable) {
  55. handle_ivalid_set(g, storage, g->levelSet->id, g->errorMsg);
  56. }
  57. furi_record_close(RECORD_STORAGE);
  58. }
  59. //-----------------------------------------------------------------------------
  60. void free_game_state(Game* game) {
  61. view_port_free(game->viewPort);
  62. furi_mutex_free(game->mutex);
  63. free_level_data(game->levelData);
  64. free_level_set(game->levelSet);
  65. free_stats(game->stats);
  66. furi_string_free(game->selectedSet);
  67. furi_string_free(game->continueSet);
  68. furi_string_free(game->errorMsg);
  69. free_level_list(&game->levelList);
  70. free(game);
  71. }
  72. //-----------------------------------------------------------------------------
  73. GameOver is_game_over(PlayGround* mv, Stats* stats) {
  74. uint8_t sumMov = 0;
  75. uint8_t sum = 0;
  76. uint8_t x, y;
  77. for(uint8_t i = 0; i < WALL_TILE; i++) {
  78. sum += stats->ofBrick[i];
  79. }
  80. for(y = 0; y < SIZE_Y; y++) {
  81. for(x = 0; x < SIZE_X; x++) {
  82. sumMov += (*mv)[y][x];
  83. }
  84. }
  85. if((sum > 0) && (sumMov == 0)) {
  86. return CANNOT_MOVE;
  87. }
  88. for(uint8_t i = 0; i < WALL_TILE; i++) {
  89. if(stats->ofBrick[i] == 1) return BRICKS_LEFT;
  90. }
  91. return NOT_GAME_OVER;
  92. }
  93. //-----------------------------------------------------------------------------
  94. bool is_level_finished(Stats* stats) {
  95. uint8_t sum = 0;
  96. for(uint8_t i = 0; i < WALL_TILE; i++) {
  97. sum += stats->ofBrick[i];
  98. }
  99. return (sum == 0);
  100. }
  101. //-----------------------------------------------------------------------------
  102. Neighbors find_neighbors(PlayGround* pg, uint8_t x, uint8_t y) {
  103. Neighbors ne;
  104. ne.u = (y > 0) ? (*pg)[y - 1][x] : EMPTY_TILE;
  105. ne.l = (x > 0) ? (*pg)[y][x - 1] : EMPTY_TILE;
  106. ne.d = (y < SIZE_Y - 1) ? (*pg)[y + 1][x] : EMPTY_TILE;
  107. ne.r = (x < SIZE_X - 1) ? (*pg)[y][x + 1] : EMPTY_TILE;
  108. ne.dl = ((y < SIZE_Y - 1) && (x > 0)) ? (*pg)[y + 1][x - 1] : EMPTY_TILE;
  109. ne.ur = ((y > 0) && (x < SIZE_X - 1)) ? (*pg)[y - 1][x + 1] : EMPTY_TILE;
  110. ne.ul = ((y > 0) && (x > 0)) ? (*pg)[y - 1][x - 1] : EMPTY_TILE;
  111. ne.dr = ((x < SIZE_X - 1) && (y < SIZE_Y - 1)) ? (*pg)[y + 1][x + 1] : EMPTY_TILE;
  112. return ne;
  113. }
  114. //-----------------------------------------------------------------------------
  115. void index_set(Game* game) {
  116. const char* findSetId = furi_string_get_cstr(game->levelSet->id);
  117. game->setCount = level_count(game);
  118. game->setPos = 0;
  119. for(uint8_t i = 0; i < ASSETS_LEVELS_COUNT; i++) {
  120. if(strcmp(findSetId, assetLevels[i]) == 0) {
  121. game->setPos = i;
  122. return;
  123. }
  124. }
  125. if(game->levelList.ids != NULL) {
  126. for(uint8_t j = 0; j < game->levelList.count; j++) {
  127. if(strcmp(findSetId, furi_string_get_cstr(game->levelList.ids[j])) == 0) {
  128. game->setPos = ASSETS_LEVELS_COUNT + j;
  129. return;
  130. }
  131. }
  132. }
  133. }
  134. //-----------------------------------------------------------------------------
  135. void recalc_score(Game* g) {
  136. g->score = 0;
  137. for(uint8_t i = 0; i < g->levelSet->maxLevel; i++) {
  138. if(g->levelSet->scores[i].moves > 0) {
  139. g->score += g->levelSet->scores[i].moves - g->levelSet->pars[i];
  140. }
  141. if(g->levelSet->scores[i].spoiled) {
  142. g->score += 5;
  143. }
  144. }
  145. }
  146. //-----------------------------------------------------------------------------
  147. void handle_ivalid_set(Game* game, Storage* storage, FuriString* setId, FuriString* errorMsg) {
  148. mark_set_invalid(storage, setId, errorMsg);
  149. list_extra_levels(storage, &game->levelList);
  150. furi_string_set(game->errorMsg, "Invalid level: ");
  151. furi_string_cat(game->errorMsg, setId);
  152. furi_string_set(game->selectedSet, assetLevels[0]);
  153. game->mainMenuMode = CUSTOM;
  154. game->selectedLevel = 0;
  155. game->mainMenuBtn = LEVELSET_BTN;
  156. load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg);
  157. game->state = INVALID_PROMPT;
  158. }
  159. //-----------------------------------------------------------------------------
  160. void initial_load_game(Game* game) {
  161. Storage* storage = furi_record_open(RECORD_STORAGE);
  162. list_extra_levels(storage, &game->levelList);
  163. game->hasContinue = load_last_level(game->continueSet, &game->continueLevel);
  164. if(game->hasContinue) {
  165. furi_string_set(game->selectedSet, game->continueSet);
  166. game->selectedLevel = game->continueLevel + 1;
  167. game->mainMenuMode = CONTINUE;
  168. } else {
  169. furi_string_set(game->selectedSet, assetLevels[0]);
  170. game->selectedLevel = 0;
  171. game->mainMenuMode = NEW_GAME;
  172. }
  173. if(!load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg)) {
  174. handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
  175. }
  176. furi_record_close(RECORD_STORAGE);
  177. index_set(game);
  178. recalc_score(game);
  179. if(game->selectedLevel > game->levelSet->maxLevel - 1) {
  180. game->selectedLevel = game->levelSet->maxLevel - 1;
  181. }
  182. randomize_bg(&game->bg);
  183. }
  184. //-----------------------------------------------------------------------------
  185. void new_game(Game* game) {
  186. forget_continue(game);
  187. FuriString* setName = furi_string_alloc_set(assetLevels[0]);
  188. load_gameset_if_needed(game, setName);
  189. furi_string_free(setName);
  190. start_game_at_level(game, 0);
  191. }
  192. //-----------------------------------------------------------------------------
  193. void load_gameset_if_needed(Game* game, FuriString* expectedSet) {
  194. if(furi_string_cmp(expectedSet, game->levelSet->id) != 0) {
  195. Storage* storage = furi_record_open(RECORD_STORAGE);
  196. if(!load_level_set(storage, expectedSet, game->levelSet, game->errorMsg)) {
  197. handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
  198. }
  199. furi_record_close(RECORD_STORAGE);
  200. }
  201. index_set(game);
  202. recalc_score(game);
  203. }
  204. //-----------------------------------------------------------------------------
  205. const char* level_on_pos(Game* game, int pos) {
  206. if(pos < ASSETS_LEVELS_COUNT) {
  207. return assetLevels[pos];
  208. } else {
  209. int adjPos = pos - ASSETS_LEVELS_COUNT;
  210. FURI_LOG_D(TAG, "Level for exra %d, %d", pos, adjPos);
  211. if((game->levelList.ids != NULL) && (adjPos < game->levelList.count)) {
  212. if(game->levelList.ids[adjPos] != NULL) {
  213. return furi_string_get_cstr(game->levelList.ids[adjPos]);
  214. } else {
  215. return assetLevels[ASSETS_LEVELS_COUNT - 1];
  216. }
  217. } else {
  218. return assetLevels[ASSETS_LEVELS_COUNT - 1];
  219. }
  220. }
  221. return assetLevels[0];
  222. }
  223. //-----------------------------------------------------------------------------
  224. int level_count(Game* game) {
  225. return ASSETS_LEVELS_COUNT + ((game->levelList.ids != NULL) ? game->levelList.count : 0);
  226. }
  227. //-----------------------------------------------------------------------------
  228. void start_game_at_level(Game* game, uint8_t levelNo) {
  229. if(levelNo < game->levelSet->maxLevel) {
  230. game->currentLevel = levelNo;
  231. refresh_level(game);
  232. } else {
  233. game->mainMenuBtn = LEVELSET_BTN;
  234. game->mainMenuMode = CUSTOM;
  235. game->setPos = (game->setPos < game->setCount - 1) ? game->setPos + 1 : 0;
  236. furi_string_set(game->selectedSet, level_on_pos(game, game->setPos));
  237. load_gameset_if_needed(game, game->selectedSet);
  238. game->selectedLevel = 0;
  239. game->state = MAIN_MENU;
  240. }
  241. }
  242. //-----------------------------------------------------------------------------
  243. void score_for_level(Game* g, uint8_t levelNo, char* buf, size_t max) {
  244. if(g->levelSet->scores[levelNo].moves == 0) {
  245. snprintf(buf, max, "???");
  246. } else {
  247. if(g->levelSet->scores[levelNo].moves == g->levelSet->pars[levelNo]) {
  248. snprintf(buf, max, "par");
  249. } else {
  250. snprintf(
  251. buf, max, "%+d", g->levelSet->scores[levelNo].moves - g->levelSet->pars[levelNo]);
  252. }
  253. }
  254. }
  255. //-----------------------------------------------------------------------------
  256. void refresh_level(Game* g) {
  257. clear_board(&g->board);
  258. clear_board(&g->boardUndo);
  259. clear_board(&g->toAnimate);
  260. furi_string_set(g->selectedSet, g->levelSet->id);
  261. furi_string_set(g->continueSet, g->levelSet->id);
  262. g->selectedLevel = g->currentLevel;
  263. load_game_board(g);
  264. map_movability(&g->board, &g->movables);
  265. update_board_stats(&g->board, g->stats);
  266. g->currentMovable = find_movable(&g->movables);
  267. g->undoMovable = MOVABLE_NOT_FOUND;
  268. g->gameMoves = 0;
  269. g->state = SELECT_BRICK;
  270. memset(g->parLabel, 0, PAR_LABEL_SIZE);
  271. score_for_level(g, g->selectedLevel, g->parLabel, PAR_LABEL_SIZE);
  272. }
  273. //-----------------------------------------------------------------------------
  274. void level_finished(Game* g) {
  275. g->hasContinue = true;
  276. furi_string_set(g->selectedSet, g->levelSet->id);
  277. furi_string_set(g->continueSet, g->levelSet->id);
  278. g->continueLevel = g->currentLevel;
  279. uint16_t moves = (uint16_t)g->gameMoves;
  280. if((moves < g->levelSet->scores[g->currentLevel].moves) ||
  281. (g->levelSet->scores[g->currentLevel].moves == 0)) {
  282. g->levelSet->scores[g->currentLevel].moves = moves;
  283. }
  284. save_last_level(g->levelSet->id, g->currentLevel);
  285. save_set_scores(g->levelSet->id, g->levelSet->scores);
  286. recalc_score(g);
  287. }
  288. //-----------------------------------------------------------------------------
  289. void forget_continue(Game* game) {
  290. game->hasContinue = false;
  291. furi_string_set(game->selectedSet, assetLevels[0]);
  292. furi_string_set(game->continueSet, assetLevels[0]);
  293. game->selectedLevel = 0;
  294. game->continueLevel = 0;
  295. delete_progress(game->levelSet->scores);
  296. recalc_score(game);
  297. }
  298. //-----------------------------------------------------------------------------
  299. void click_selected(Game* game) {
  300. const uint8_t dir = movable_dir(&game->movables, game->currentMovable);
  301. switch(dir) {
  302. case MOVABLE_LEFT:
  303. case MOVABLE_RIGHT:
  304. start_move(game, dir);
  305. break;
  306. case MOVABLE_BOTH:
  307. game->state = SELECT_DIRECTION;
  308. break;
  309. default:
  310. break;
  311. }
  312. }
  313. //-----------------------------------------------------------------------------
  314. void start_gravity(Game* g) {
  315. uint8_t x, y;
  316. bool change = false;
  317. clear_board(&g->toAnimate);
  318. // go through it bottom to top so as all the blocks tumble down on top of each other
  319. for(y = (SIZE_Y - 2); y > 0; y--) {
  320. for(x = (SIZE_X - 1); x > 0; x--) {
  321. if((is_block(g->board[y][x])) && (g->board[y + 1][x] == EMPTY_TILE)) {
  322. change = true;
  323. g->toAnimate[y][x] = 1;
  324. }
  325. }
  326. }
  327. if(change) {
  328. g->move.frameNo = 0;
  329. g->move.delay = 5;
  330. g->state = MOVE_GRAVITY;
  331. } else {
  332. g->state = SELECT_BRICK;
  333. start_explosion(g);
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. void stop_gravity(Game* g) {
  338. uint8_t x, y;
  339. for(y = 0; y < SIZE_Y - 1; y++) {
  340. for(x = 0; x < SIZE_X; x++) {
  341. if(g->toAnimate[y][x] == 1) {
  342. g->board[y + 1][x] = g->board[y][x];
  343. g->board[y][x] = EMPTY_TILE;
  344. }
  345. }
  346. }
  347. start_gravity(g);
  348. }
  349. //-----------------------------------------------------------------------------
  350. void start_explosion(Game* g) {
  351. uint8_t x, y;
  352. bool change = false;
  353. clear_board(&g->toAnimate);
  354. // go through it bottom to top so as all the blocks tumble down on top of each other
  355. for(y = 0; y < SIZE_Y; y++) {
  356. for(x = 0; x < SIZE_X; x++) {
  357. if(is_block(g->board[y][x])) {
  358. if(((y > 0) && (g->board[y][x] == g->board[y - 1][x])) ||
  359. ((x > 0) && (g->board[y][x] == g->board[y][x - 1])) ||
  360. ((y < SIZE_Y - 1) && (g->board[y][x] == g->board[y + 1][x])) ||
  361. ((x < SIZE_X - 1) && (g->board[y][x] == g->board[y][x + 1]))) {
  362. change = true;
  363. g->toAnimate[y][x] = 1;
  364. }
  365. }
  366. }
  367. }
  368. if(change) {
  369. g->move.frameNo = 0;
  370. g->move.delay = 12;
  371. g->state = EXPLODE;
  372. } else {
  373. g->state = SELECT_BRICK;
  374. movement_stoped(g);
  375. }
  376. }
  377. //-----------------------------------------------------------------------------
  378. void stop_explosion(Game* g) {
  379. uint8_t x, y;
  380. for(y = 0; y < SIZE_Y - 1; y++) {
  381. for(x = 0; x < SIZE_X; x++) {
  382. if(g->toAnimate[y][x] == 1) {
  383. g->board[y][x] = EMPTY_TILE;
  384. }
  385. }
  386. }
  387. start_gravity(g);
  388. }
  389. //-----------------------------------------------------------------------------
  390. void start_move(Game* g, uint8_t direction) {
  391. if(!g->solutionMode) {
  392. g->undoMovable = g->currentMovable;
  393. copy_level(g->boardUndo, g->board);
  394. g->gameMoves++;
  395. }
  396. g->move.dir = direction;
  397. g->move.x = coord_x(g->currentMovable);
  398. g->move.y = coord_y(g->currentMovable);
  399. g->move.frameNo = 0;
  400. if(!g->solutionMode) {
  401. g->nextMovable =
  402. coord_from((g->move.x + ((direction == MOVABLE_LEFT) ? -1 : 1)), g->move.y);
  403. }
  404. g->state = MOVE_SIDES;
  405. }
  406. //-----------------------------------------------------------------------------
  407. void stop_move(Game* g) {
  408. uint8_t deltaX = ((g->move.dir & MOVABLE_LEFT) != 0) ? -1 : 1;
  409. uint8_t tile = g->board[g->move.y][g->move.x];
  410. g->board[g->move.y][g->move.x] = EMPTY_TILE;
  411. g->board[g->move.y][cap_x(g->move.x + deltaX)] = tile;
  412. start_gravity(g);
  413. }
  414. //-----------------------------------------------------------------------------
  415. void movement_stoped(Game* g) {
  416. if(g->solutionMode) {
  417. solution_next(g);
  418. } else {
  419. map_movability(&g->board, &g->movables);
  420. update_board_stats(&g->board, g->stats);
  421. g->currentMovable = g->nextMovable;
  422. g->nextMovable = MOVABLE_NOT_FOUND;
  423. if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
  424. find_movable_down(&g->movables, &g->currentMovable);
  425. }
  426. if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
  427. find_movable_right(&g->movables, &g->currentMovable);
  428. }
  429. if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
  430. g->currentMovable = MOVABLE_NOT_FOUND;
  431. }
  432. g->gameOverReason = is_game_over(&g->movables, g->stats);
  433. if(g->gameOverReason > NOT_GAME_OVER) {
  434. g->state = GAME_OVER;
  435. } else if(is_level_finished(g->stats)) {
  436. g->state = LEVEL_FINISHED;
  437. level_finished(g);
  438. } else {
  439. g->state = SELECT_BRICK;
  440. }
  441. }
  442. }
  443. //-----------------------------------------------------------------------------
  444. bool undo(Game* g) {
  445. if(g->undoMovable != MOVABLE_NOT_FOUND) {
  446. g->currentMovable = g->undoMovable;
  447. g->undoMovable = MOVABLE_NOT_FOUND;
  448. copy_level(g->board, g->boardUndo);
  449. map_movability(&g->board, &g->movables);
  450. update_board_stats(&g->board, g->stats);
  451. g->gameMoves--;
  452. g->state = SELECT_BRICK;
  453. return true;
  454. } else {
  455. g->state = SELECT_BRICK;
  456. return false;
  457. }
  458. }
  459. //-----------------------------------------------------------------------------
  460. uint8_t
  461. movable_from_solution(Game* g, const char* solutionStr, uint8_t step, PlayGround* movables) {
  462. const char solX = solutionStr[step * 2];
  463. const char solY = solutionStr[step * 2 + 1];
  464. int x, y;
  465. uint8_t dir;
  466. x = solX - 'a';
  467. if(solX <= 'Z') {
  468. dir = MOVABLE_LEFT;
  469. x = solX - 'A';
  470. }
  471. y = solY - 'a';
  472. if(solY <= 'Z') {
  473. dir = MOVABLE_RIGHT;
  474. y = solY - 'A';
  475. }
  476. if(x < 0 || x >= SIZE_X || y < 0 || y >= SIZE_Y) {
  477. end_solution(g);
  478. return 0;
  479. }
  480. clear_board(movables);
  481. (*movables)[y][x] = dir;
  482. return coord_from(x, y);
  483. }
  484. //-----------------------------------------------------------------------------
  485. void start_solution(Game* g) {
  486. copy_level(g->boardBackup, g->board);
  487. clear_board(&g->board);
  488. load_game_board(g);
  489. g->currentMovableBackup = g->currentMovable;
  490. g->solutionStep = 0;
  491. g->solutionTotal = furi_string_size(g->levelData->solution) / 2;
  492. g->solutionMode = true;
  493. if(solution_will_have_penalty(g)) {
  494. g->levelSet->scores[g->currentLevel].spoiled = true;
  495. save_set_scores(g->levelSet->id, g->levelSet->scores);
  496. recalc_score(g);
  497. }
  498. solution_select(g);
  499. }
  500. //-----------------------------------------------------------------------------
  501. void end_solution(Game* g) {
  502. g->state = SELECT_BRICK;
  503. g->currentMovable = g->currentMovableBackup;
  504. copy_level(g->board, g->boardBackup);
  505. clear_board(&g->toAnimate);
  506. map_movability(&g->board, &g->movables);
  507. update_board_stats(&g->board, g->stats);
  508. g->solutionMode = false;
  509. }
  510. //-----------------------------------------------------------------------------
  511. void solution_select(Game* g) {
  512. g->currentMovable = movable_from_solution(
  513. g, furi_string_get_cstr(g->levelData->solution), g->solutionStep, &g->movables);
  514. g->move.frameNo = 35;
  515. g->state = SOLUTION_SELECT;
  516. }
  517. //-----------------------------------------------------------------------------
  518. void solution_move(Game* g) {
  519. const uint8_t dir = movable_dir(&g->movables, g->currentMovable);
  520. start_move(g, dir);
  521. }
  522. //-----------------------------------------------------------------------------
  523. void solution_next(Game* g) {
  524. if(g->solutionStep < g->solutionTotal - 1) {
  525. g->solutionStep++;
  526. solution_select(g);
  527. } else {
  528. end_solution(g);
  529. }
  530. }
  531. //-----------------------------------------------------------------------------
  532. bool solution_will_have_penalty(Game* g) {
  533. return (g->levelSet->scores[g->currentLevel].moves == 0) &&
  534. (!g->levelSet->scores[g->currentLevel].spoiled);
  535. }