| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- #include "game.h"
- #include "utils.h"
- #include "move.h"
- Game* alloc_game_state(int* error) {
- *error = 0;
- Game* game = malloc(sizeof(Game));
- game->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- if(!game->mutex) {
- FURI_LOG_E(TAG, "cannot create mutex\r\n");
- free(game);
- *error = 255;
- return NULL;
- }
- game->levelData = alloc_level_data();
- game->levelSet = alloc_level_set();
- game->stats = alloc_stats();
- game->currentLevel = 0;
- game->gameMoves = 0;
- game->score = 0;
- game->currentMovableBackup = MOVABLE_NOT_FOUND;
- game->solutionMode = false;
- game->solutionStep = 0;
- game->solutionTotal = 0;
- game->undoMovable = MOVABLE_NOT_FOUND;
- game->currentMovable = MOVABLE_NOT_FOUND;
- game->nextMovable = MOVABLE_NOT_FOUND;
- game->menuPausedPos = 0;
- game->mainMenuBtn = MODE_BTN;
- game->mainMenuMode = NEW_GAME;
- game->mainMenuInfo = false;
- game->hasContinue = false;
- game->selectedSet = furi_string_alloc_set(assetLevels[0]);
- game->selectedLevel = 0;
- game->continueSet = furi_string_alloc_set(assetLevels[0]);
- game->continueLevel = 0;
- game->setPos = 0;
- game->setCount = 1;
- game->state = INTRO;
- game->gameOverReason = NOT_GAME_OVER;
- game->move.frameNo = 0;
- memset(game->parLabel, 0, PAR_LABEL_SIZE);
- game->errorMsg = furi_string_alloc();
- return game;
- }
- //-----------------------------------------------------------------------------
- void load_game_board(Game* g) {
- bool levelLoadable = false;
- // Open storage
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(load_level(storage, g->levelSet->id, g->currentLevel, g->levelData, g->errorMsg)) {
- levelLoadable = parse_level_notation(furi_string_get_cstr(g->levelData->board), &g->board);
- }
- // Close storage
- if(!levelLoadable) {
- handle_ivalid_set(g, storage, g->levelSet->id, g->errorMsg);
- }
- furi_record_close(RECORD_STORAGE);
- }
- //-----------------------------------------------------------------------------
- void free_game_state(Game* game) {
- view_port_free(game->viewPort);
- furi_mutex_free(game->mutex);
- free_level_data(game->levelData);
- free_level_set(game->levelSet);
- free_stats(game->stats);
- furi_string_free(game->selectedSet);
- furi_string_free(game->continueSet);
- furi_string_free(game->errorMsg);
- free_level_list(&game->levelList);
- free(game);
- }
- //-----------------------------------------------------------------------------
- GameOver is_game_over(PlayGround* mv, Stats* stats) {
- uint8_t sumMov = 0;
- uint8_t sum = 0;
- uint8_t x, y;
- for(uint8_t i = 0; i < WALL_TILE; i++) {
- sum += stats->ofBrick[i];
- }
- for(y = 0; y < SIZE_Y; y++) {
- for(x = 0; x < SIZE_X; x++) {
- sumMov += (*mv)[y][x];
- }
- }
- if((sum > 0) && (sumMov == 0)) {
- return CANNOT_MOVE;
- }
- for(uint8_t i = 0; i < WALL_TILE; i++) {
- if(stats->ofBrick[i] == 1) return BRICKS_LEFT;
- }
- return NOT_GAME_OVER;
- }
- //-----------------------------------------------------------------------------
- bool is_level_finished(Stats* stats) {
- uint8_t sum = 0;
- for(uint8_t i = 0; i < WALL_TILE; i++) {
- sum += stats->ofBrick[i];
- }
- return (sum == 0);
- }
- //-----------------------------------------------------------------------------
- Neighbors find_neighbors(PlayGround* pg, uint8_t x, uint8_t y) {
- Neighbors ne;
- ne.u = (y > 0) ? (*pg)[y - 1][x] : EMPTY_TILE;
- ne.l = (x > 0) ? (*pg)[y][x - 1] : EMPTY_TILE;
- ne.d = (y < SIZE_Y - 1) ? (*pg)[y + 1][x] : EMPTY_TILE;
- ne.r = (x < SIZE_X - 1) ? (*pg)[y][x + 1] : EMPTY_TILE;
- ne.dl = ((y < SIZE_Y - 1) && (x > 0)) ? (*pg)[y + 1][x - 1] : EMPTY_TILE;
- ne.ur = ((y > 0) && (x < SIZE_X - 1)) ? (*pg)[y - 1][x + 1] : EMPTY_TILE;
- ne.ul = ((y > 0) && (x > 0)) ? (*pg)[y - 1][x - 1] : EMPTY_TILE;
- ne.dr = ((x < SIZE_X - 1) && (y < SIZE_Y - 1)) ? (*pg)[y + 1][x + 1] : EMPTY_TILE;
- return ne;
- }
- //-----------------------------------------------------------------------------
- void index_set(Game* game) {
- const char* findSetId = furi_string_get_cstr(game->levelSet->id);
- game->setCount = level_count(game);
- game->setPos = 0;
- for(uint8_t i = 0; i < ASSETS_LEVELS_COUNT; i++) {
- if(strcmp(findSetId, assetLevels[i]) == 0) {
- game->setPos = i;
- return;
- }
- }
- if(game->levelList.ids != NULL) {
- for(uint8_t j = 0; j < game->levelList.count; j++) {
- if(strcmp(findSetId, furi_string_get_cstr(game->levelList.ids[j])) == 0) {
- game->setPos = ASSETS_LEVELS_COUNT + j;
- return;
- }
- }
- }
- }
- //-----------------------------------------------------------------------------
- void recalc_score(Game* g) {
- g->score = 0;
- for(uint8_t i = 0; i < g->levelSet->maxLevel; i++) {
- if(g->levelSet->scores[i].moves > 0) {
- g->score += g->levelSet->scores[i].moves - g->levelSet->pars[i];
- }
- if(g->levelSet->scores[i].spoiled) {
- g->score += 5;
- }
- }
- }
- //-----------------------------------------------------------------------------
- void handle_ivalid_set(Game* game, Storage* storage, FuriString* setId, FuriString* errorMsg) {
- mark_set_invalid(storage, setId, errorMsg);
- list_extra_levels(storage, &game->levelList);
- furi_string_set(game->errorMsg, "Invalid level: ");
- furi_string_cat(game->errorMsg, setId);
- furi_string_set(game->selectedSet, assetLevels[0]);
- game->mainMenuMode = CUSTOM;
- game->selectedLevel = 0;
- game->mainMenuBtn = LEVELSET_BTN;
- load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg);
- game->state = INVALID_PROMPT;
- }
- //-----------------------------------------------------------------------------
- void initial_load_game(Game* game) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- list_extra_levels(storage, &game->levelList);
- game->hasContinue = load_last_level(game->continueSet, &game->continueLevel);
- if(game->hasContinue) {
- furi_string_set(game->selectedSet, game->continueSet);
- game->selectedLevel = game->continueLevel + 1;
- game->mainMenuMode = CONTINUE;
- } else {
- furi_string_set(game->selectedSet, assetLevels[0]);
- game->selectedLevel = 0;
- game->mainMenuMode = NEW_GAME;
- }
- if(!load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg)) {
- handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
- }
- furi_record_close(RECORD_STORAGE);
- index_set(game);
- recalc_score(game);
- if(game->selectedLevel > game->levelSet->maxLevel - 1) {
- game->selectedLevel = game->levelSet->maxLevel - 1;
- }
- randomize_bg(&game->bg);
- }
- //-----------------------------------------------------------------------------
- void new_game(Game* game) {
- forget_continue(game);
- FuriString* setName = furi_string_alloc_set(assetLevels[0]);
- load_gameset_if_needed(game, setName);
- furi_string_free(setName);
- start_game_at_level(game, 0);
- }
- //-----------------------------------------------------------------------------
- void load_gameset_if_needed(Game* game, FuriString* expectedSet) {
- if(furi_string_cmp(expectedSet, game->levelSet->id) != 0) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!load_level_set(storage, expectedSet, game->levelSet, game->errorMsg)) {
- handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
- }
- furi_record_close(RECORD_STORAGE);
- }
- index_set(game);
- recalc_score(game);
- }
- //-----------------------------------------------------------------------------
- const char* level_on_pos(Game* game, int pos) {
- if(pos < ASSETS_LEVELS_COUNT) {
- return assetLevels[pos];
- } else {
- int adjPos = pos - ASSETS_LEVELS_COUNT;
- FURI_LOG_D(TAG, "Level for exra %d, %d", pos, adjPos);
- if((game->levelList.ids != NULL) && (adjPos < game->levelList.count)) {
- if(game->levelList.ids[adjPos] != NULL) {
- return furi_string_get_cstr(game->levelList.ids[adjPos]);
- } else {
- return assetLevels[ASSETS_LEVELS_COUNT - 1];
- }
- } else {
- return assetLevels[ASSETS_LEVELS_COUNT - 1];
- }
- }
- return assetLevels[0];
- }
- //-----------------------------------------------------------------------------
- int level_count(Game* game) {
- return ASSETS_LEVELS_COUNT + ((game->levelList.ids != NULL) ? game->levelList.count : 0);
- }
- //-----------------------------------------------------------------------------
- void start_game_at_level(Game* game, uint8_t levelNo) {
- if(levelNo < game->levelSet->maxLevel) {
- game->currentLevel = levelNo;
- refresh_level(game);
- } else {
- game->mainMenuBtn = LEVELSET_BTN;
- game->mainMenuMode = CUSTOM;
- game->setPos = (game->setPos < game->setCount - 1) ? game->setPos + 1 : 0;
- furi_string_set(game->selectedSet, level_on_pos(game, game->setPos));
- load_gameset_if_needed(game, game->selectedSet);
- game->selectedLevel = 0;
- game->state = MAIN_MENU;
- }
- }
- //-----------------------------------------------------------------------------
- void score_for_level(Game* g, uint8_t levelNo, char* buf, size_t max) {
- if(g->levelSet->scores[levelNo].moves == 0) {
- snprintf(buf, max, "???");
- } else {
- if(g->levelSet->scores[levelNo].moves == g->levelSet->pars[levelNo]) {
- snprintf(buf, max, "par");
- } else {
- snprintf(
- buf, max, "%+d", g->levelSet->scores[levelNo].moves - g->levelSet->pars[levelNo]);
- }
- }
- }
- //-----------------------------------------------------------------------------
- void refresh_level(Game* g) {
- clear_board(&g->board);
- clear_board(&g->boardUndo);
- clear_board(&g->toAnimate);
- furi_string_set(g->selectedSet, g->levelSet->id);
- furi_string_set(g->continueSet, g->levelSet->id);
- g->selectedLevel = g->currentLevel;
- load_game_board(g);
- map_movability(&g->board, &g->movables);
- update_board_stats(&g->board, g->stats);
- g->currentMovable = find_movable(&g->movables);
- g->undoMovable = MOVABLE_NOT_FOUND;
- g->gameMoves = 0;
- g->state = SELECT_BRICK;
- memset(g->parLabel, 0, PAR_LABEL_SIZE);
- score_for_level(g, g->selectedLevel, g->parLabel, PAR_LABEL_SIZE);
- }
- //-----------------------------------------------------------------------------
- void level_finished(Game* g) {
- g->hasContinue = true;
- furi_string_set(g->selectedSet, g->levelSet->id);
- furi_string_set(g->continueSet, g->levelSet->id);
- g->continueLevel = g->currentLevel;
- uint16_t moves = (uint16_t)g->gameMoves;
- if((moves < g->levelSet->scores[g->currentLevel].moves) ||
- (g->levelSet->scores[g->currentLevel].moves == 0)) {
- g->levelSet->scores[g->currentLevel].moves = moves;
- }
- save_last_level(g->levelSet->id, g->currentLevel);
- save_set_scores(g->levelSet->id, g->levelSet->scores);
- recalc_score(g);
- }
- //-----------------------------------------------------------------------------
- void forget_continue(Game* game) {
- game->hasContinue = false;
- furi_string_set(game->selectedSet, assetLevels[0]);
- furi_string_set(game->continueSet, assetLevels[0]);
- game->selectedLevel = 0;
- game->continueLevel = 0;
- delete_progress(game->levelSet->scores);
- recalc_score(game);
- }
- //-----------------------------------------------------------------------------
- void click_selected(Game* game) {
- const uint8_t dir = movable_dir(&game->movables, game->currentMovable);
- switch(dir) {
- case MOVABLE_LEFT:
- case MOVABLE_RIGHT:
- start_move(game, dir);
- break;
- case MOVABLE_BOTH:
- game->state = SELECT_DIRECTION;
- break;
- default:
- break;
- }
- }
- //-----------------------------------------------------------------------------
- void start_gravity(Game* g) {
- uint8_t x, y;
- bool change = false;
- clear_board(&g->toAnimate);
- // go through it bottom to top so as all the blocks tumble down on top of each other
- for(y = (SIZE_Y - 2); y > 0; y--) {
- for(x = (SIZE_X - 1); x > 0; x--) {
- if((is_block(g->board[y][x])) && (g->board[y + 1][x] == EMPTY_TILE)) {
- change = true;
- g->toAnimate[y][x] = 1;
- }
- }
- }
- if(change) {
- g->move.frameNo = 0;
- g->move.delay = 5;
- g->state = MOVE_GRAVITY;
- } else {
- g->state = SELECT_BRICK;
- start_explosion(g);
- }
- }
- //-----------------------------------------------------------------------------
- void stop_gravity(Game* g) {
- uint8_t x, y;
- for(y = 0; y < SIZE_Y - 1; y++) {
- for(x = 0; x < SIZE_X; x++) {
- if(g->toAnimate[y][x] == 1) {
- g->board[y + 1][x] = g->board[y][x];
- g->board[y][x] = EMPTY_TILE;
- }
- }
- }
- start_gravity(g);
- }
- //-----------------------------------------------------------------------------
- void start_explosion(Game* g) {
- uint8_t x, y;
- bool change = false;
- clear_board(&g->toAnimate);
- // go through it bottom to top so as all the blocks tumble down on top of each other
- for(y = 0; y < SIZE_Y; y++) {
- for(x = 0; x < SIZE_X; x++) {
- if(is_block(g->board[y][x])) {
- if(((y > 0) && (g->board[y][x] == g->board[y - 1][x])) ||
- ((x > 0) && (g->board[y][x] == g->board[y][x - 1])) ||
- ((y < SIZE_Y - 1) && (g->board[y][x] == g->board[y + 1][x])) ||
- ((x < SIZE_X - 1) && (g->board[y][x] == g->board[y][x + 1]))) {
- change = true;
- g->toAnimate[y][x] = 1;
- }
- }
- }
- }
- if(change) {
- g->move.frameNo = 0;
- g->move.delay = 12;
- g->state = EXPLODE;
- } else {
- g->state = SELECT_BRICK;
- movement_stoped(g);
- }
- }
- //-----------------------------------------------------------------------------
- void stop_explosion(Game* g) {
- uint8_t x, y;
- for(y = 0; y < SIZE_Y - 1; y++) {
- for(x = 0; x < SIZE_X; x++) {
- if(g->toAnimate[y][x] == 1) {
- g->board[y][x] = EMPTY_TILE;
- }
- }
- }
- start_gravity(g);
- }
- //-----------------------------------------------------------------------------
- void start_move(Game* g, uint8_t direction) {
- if(!g->solutionMode) {
- g->undoMovable = g->currentMovable;
- copy_level(g->boardUndo, g->board);
- g->gameMoves++;
- }
- g->move.dir = direction;
- g->move.x = coord_x(g->currentMovable);
- g->move.y = coord_y(g->currentMovable);
- g->move.frameNo = 0;
- if(!g->solutionMode) {
- g->nextMovable =
- coord_from((g->move.x + ((direction == MOVABLE_LEFT) ? -1 : 1)), g->move.y);
- }
- g->state = MOVE_SIDES;
- }
- //-----------------------------------------------------------------------------
- void stop_move(Game* g) {
- uint8_t deltaX = ((g->move.dir & MOVABLE_LEFT) != 0) ? -1 : 1;
- uint8_t tile = g->board[g->move.y][g->move.x];
- g->board[g->move.y][g->move.x] = EMPTY_TILE;
- g->board[g->move.y][cap_x(g->move.x + deltaX)] = tile;
- start_gravity(g);
- }
- //-----------------------------------------------------------------------------
- void movement_stoped(Game* g) {
- if(g->solutionMode) {
- solution_next(g);
- } else {
- map_movability(&g->board, &g->movables);
- update_board_stats(&g->board, g->stats);
- g->currentMovable = g->nextMovable;
- g->nextMovable = MOVABLE_NOT_FOUND;
- if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
- find_movable_down(&g->movables, &g->currentMovable);
- }
- if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
- find_movable_right(&g->movables, &g->currentMovable);
- }
- if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
- g->currentMovable = MOVABLE_NOT_FOUND;
- }
- g->gameOverReason = is_game_over(&g->movables, g->stats);
- if(g->gameOverReason > NOT_GAME_OVER) {
- g->state = GAME_OVER;
- } else if(is_level_finished(g->stats)) {
- g->state = LEVEL_FINISHED;
- level_finished(g);
- } else {
- g->state = SELECT_BRICK;
- }
- }
- }
- //-----------------------------------------------------------------------------
- bool undo(Game* g) {
- if(g->undoMovable != MOVABLE_NOT_FOUND) {
- g->currentMovable = g->undoMovable;
- g->undoMovable = MOVABLE_NOT_FOUND;
- copy_level(g->board, g->boardUndo);
- map_movability(&g->board, &g->movables);
- update_board_stats(&g->board, g->stats);
- g->gameMoves--;
- g->state = SELECT_BRICK;
- return true;
- } else {
- g->state = SELECT_BRICK;
- return false;
- }
- }
- //-----------------------------------------------------------------------------
- uint8_t
- movable_from_solution(Game* g, const char* solutionStr, uint8_t step, PlayGround* movables) {
- const char solX = solutionStr[step * 2];
- const char solY = solutionStr[step * 2 + 1];
- int x, y;
- uint8_t dir;
- x = solX - 'a';
- if(solX <= 'Z') {
- dir = MOVABLE_LEFT;
- x = solX - 'A';
- }
- y = solY - 'a';
- if(solY <= 'Z') {
- dir = MOVABLE_RIGHT;
- y = solY - 'A';
- }
- if(x < 0 || x >= SIZE_X || y < 0 || y >= SIZE_Y) {
- end_solution(g);
- return 0;
- }
- clear_board(movables);
- (*movables)[y][x] = dir;
- return coord_from(x, y);
- }
- //-----------------------------------------------------------------------------
- void start_solution(Game* g) {
- copy_level(g->boardBackup, g->board);
- clear_board(&g->board);
- load_game_board(g);
- g->currentMovableBackup = g->currentMovable;
- g->solutionStep = 0;
- g->solutionTotal = furi_string_size(g->levelData->solution) / 2;
- g->solutionMode = true;
- if(solution_will_have_penalty(g)) {
- g->levelSet->scores[g->currentLevel].spoiled = true;
- save_set_scores(g->levelSet->id, g->levelSet->scores);
- recalc_score(g);
- }
- solution_select(g);
- }
- //-----------------------------------------------------------------------------
- void end_solution(Game* g) {
- g->state = SELECT_BRICK;
- g->currentMovable = g->currentMovableBackup;
- copy_level(g->board, g->boardBackup);
- clear_board(&g->toAnimate);
- map_movability(&g->board, &g->movables);
- update_board_stats(&g->board, g->stats);
- g->solutionMode = false;
- }
- //-----------------------------------------------------------------------------
- void solution_select(Game* g) {
- g->currentMovable = movable_from_solution(
- g, furi_string_get_cstr(g->levelData->solution), g->solutionStep, &g->movables);
- g->move.frameNo = 35;
- g->state = SOLUTION_SELECT;
- }
- //-----------------------------------------------------------------------------
- void solution_move(Game* g) {
- const uint8_t dir = movable_dir(&g->movables, g->currentMovable);
- start_move(g, dir);
- }
- //-----------------------------------------------------------------------------
- void solution_next(Game* g) {
- if(g->solutionStep < g->solutionTotal - 1) {
- g->solutionStep++;
- solution_select(g);
- } else {
- end_solution(g);
- }
- }
- //-----------------------------------------------------------------------------
- bool solution_will_have_penalty(Game* g) {
- return (g->levelSet->scores[g->currentLevel].moves == 0) &&
- (!g->levelSet->scores[g->currentLevel].spoiled);
- }
|