| line# | code(run#) |
|---|---|
| 1 | #include "file.h" |
| 2 | |
| 3 | #include <stdio.h> |
| 4 | #include <string.h> |
| 5 | #include <stdbool.h> |
| 6 | #include <stdlib.h> |
| 7 | |
| 8 | #include "debugmalloc.h" |
| 9 | |
| 10 | typedef struct LoadState |
| 11 | { |
| 12 | char line[64]; |
| 13 | int lineLength; |
| 14 | char name[64]; |
| 15 | TileState level[209]; // 11 x 19 squares |
| 16 | int rowIndex; |
| 17 | int maxLength; |
| 18 | } LoadState; |
| 19 | |
| 20 | // Check if given character is a valig sokoban tile |
| 21 | // Returns true if valid |
| 22 | static bool checkTile(char tile)(171) |
| 23 | { |
| 24 | if (tile == '#' ||(171) |
| 25 | tile == '@' ||(55) |
| 26 | tile == '+' ||(55) |
| 27 | tile == '$' ||(46) |
| 28 | tile == '*' ||(46) |
| 29 | tile == '.' ||(37) |
| 30 | tile == ' ') |
| 31 | return true;(161) |
| 32 | return false;(10) |
| 33 | } |
| 34 | |
| 35 | // Convert character to tilestate |
| 36 | // Returns proper tile on success, invalidS on faliure |
| 37 | static TileState charToTile(char tile)(139) |
| 38 | { |
| 39 | switch (tile)(139) |
| 40 | { |
| 41 | case '#':(89) |
| 42 | return wallS;(89) |
| 43 | case '@':(5) |
| 44 | return playerS;(5) |
| 45 | case '+':(0) |
| 46 | return playerOnTargetS;(0) |
| 47 | case '$':(9) |
| 48 | return crateS;(9) |
| 49 | case '*':(0) |
| 50 | return crateOnTargetS;(0) |
| 51 | case '.':(9) |
| 52 | return targetS;(9) |
| 53 | case ' ':(27) |
| 54 | return floorTileS;(27) |
| 55 | default:(0) |
| 56 | return invalidS;(0) |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | // Convert tile to character |
| 61 | // Returns sokoban char on success, space on faliure |
| 62 | static char tileToChar(TileState tile)(0) |
| 63 | { |
| 64 | switch (tile)(0) |
| 65 | { |
| 66 | case wallS:(0) |
| 67 | return '#';(0) |
| 68 | case playerS:(0) |
| 69 | return '@';(0) |
| 70 | case playerOnTargetS:(0) |
| 71 | return '+';(0) |
| 72 | case crateS:(0) |
| 73 | return '$';(0) |
| 74 | case crateOnTargetS:(0) |
| 75 | return '*';(0) |
| 76 | case targetS:(0) |
| 77 | return '.';(0) |
| 78 | case floorTileS:(0) |
| 79 | return ' ';(0) |
| 80 | default:(0) |
| 81 | return ' ';(0) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Fill entire level with floor tiles, except first one |
| 86 | // First tile is invalidS |
| 87 | static void emptyLevel(TileState *level)(6) |
| 88 | { |
| 89 | level[0] = '\0';(6) |
| 90 | for (int i = 1; i < 209; i++)(1254) |
| 91 | { |
| 92 | level[i] = floorTileS;(1248) |
| 93 | } |
| 94 | }(6) |
| 95 | |
| 96 | // Add level to current linked list |
| 97 | // If level is too large, nothing will be saved and will result in success |
| 98 | // Returns 2 on faliure, 0 on success |
| 99 | static int addLevel(LoadState *state, Level **first, Level **current)(5) |
| 100 | { |
| 101 | if (state->maxLength <= 19 && state->rowIndex <= 11) // if level is not too large(5) |
| 102 | { |
| 103 | if (*first == NULL) // if linked list is empty(5) |
| 104 | { |
| 105 | *first = *current = (Level *)malloc(sizeof(Level)); // allocate memory for first element(1) |
| 106 | if (current == NULL) // if failed, return and free memory(1) |
| 107 | { |
| 108 | unloadLevel(*first);(0) |
| 109 | return 2;(0) |
| 110 | } |
| 111 | (*current)->prev = NULL; // set next and prev to null(1) |
| 112 | (*current)->next = NULL;(1) |
| 113 | } |
| 114 | else |
| 115 | { |
| 116 | Level *new = (Level *)malloc(sizeof(Level)); // allocate memory for new element(4) |
| 117 | if (new == NULL) // if failed, return and free memory(4) |
| 118 | { |
| 119 | unloadLevel(*first);(0) |
| 120 | return 2;(0) |
| 121 | } |
| 122 | (*current)->next = new; // appent to current list(4) |
| 123 | new->prev = *current; // also do it backwards(4) |
| 124 | new->next = NULL; // this is the last element(4) |
| 125 | *current = new; // make the new element the current element(4) |
| 126 | } |
| 127 | |
| 128 | (*current)->size.x = state->maxLength; // set sizes(5) |
| 129 | (*current)->size.y = state->rowIndex;(5) |
| 130 | (*current)->tiles = (TileState *)malloc(sizeof(TileState) * (state->maxLength * state->rowIndex)); // allocate memory for tiles(5) |
| 131 | if ((*current)->tiles == NULL) // if failed, return and free memory(5) |
| 132 | { |
| 133 | unloadLevel(*first);(0) |
| 134 | return 2;(0) |
| 135 | } |
| 136 | (*current)->name = (char *)malloc(sizeof(char) * (strlen(state->name) + 1)); // allocate memory for name(5) |
| 137 | if ((*current)->name == NULL) // if failed, return and free memory(5) |
| 138 | { |
| 139 | unloadLevel(*first);(0) |
| 140 | return 2;(0) |
| 141 | } |
| 142 | |
| 143 | strcpy((*current)->name, state->name); // copy name(5) |
| 144 | |
| 145 | for (int i = 0; i < state->rowIndex; i++) // copy tiles to new place(27) |
| 146 | { |
| 147 | for (int j = 0; j < state->maxLength; j++)(161) |
| 148 | { |
| 149 | // state level storage is 19 x 11, but the new level storage is only as big as big as it needs to be |
| 150 | // so cannot use memcpy |
| 151 | (*current)->tiles[j + state->maxLength * i] = state->level[j + 19 * i];(139) |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | emptyLevel(state->level); // empty current level(5) |
| 157 | state->name[0] = 0; // set variables to inital values(5) |
| 158 | state->rowIndex = 0;(5) |
| 159 | state->maxLength = 0;(5) |
| 160 | return 0; // success(5) |
| 161 | } |
| 162 | |
| 163 | // Load level based on filename |
| 164 | // Returns result containing linked list of levels and statuc code |
| 165 | // status code: 0 - success, 1 - failed to open file, 2 - failed to allocate memory |
| 166 | // 3 - invalid file format, 4 - valid, but large levels were removed |
| 167 | // linked list contains dinamically allocated memory, so must be freed after use |
| 168 | LoadLevelResult loadLevel(char *filename)(1) |
| 169 | { |
| 170 | LoadLevelResult result; // initialize variables |
| 171 | result.result = 0;(1) |
| 172 | FILE *file; |
| 173 | Level *first = NULL;(1) |
| 174 | Level *current; |
| 175 | LoadState state; |
| 176 | |
| 177 | state.rowIndex = 0;(1) |
| 178 | state.maxLength = 0;(1) |
| 179 | emptyLevel(state.level); // clear level(1) |
| 180 | |
| 181 | file = fopen(filename, "r");(1) |
| 182 | if (file == NULL) // if failed, return(1) |
| 183 | { |
| 184 | result.result = 1;(0) |
| 185 | result.level = NULL;(0) |
| 186 | return result;(0) |
| 187 | } |
| 188 | |
| 189 | while (fgets(state.line, 64, file) != NULL) // while lines can be read from file(33) |
| 190 | { |
| 191 | state.lineLength = strlen(state.line);(32) |
| 192 | while (state.line[state.lineLength - 1] == '\n' || state.line[state.lineLength - 1] == '\r') // remove trailing newline and carriage returns(64) |
| 193 | { |
| 194 | state.line[state.lineLength - 1] = '\0';(32) |
| 195 | state.lineLength--;(32) |
| 196 | } |
| 197 | |
| 198 | if (state.line[0] == ';' && state.lineLength >= 3) // if comment <==> level name(32) |
| 199 | { |
| 200 | if (state.line[1] == ' ') // if name contains leading space(5) |
| 201 | strcpy(state.name, state.line + 2);(5) |
| 202 | else |
| 203 | strcpy(state.name, state.line + 1);(0) |
| 204 | } |
| 205 | |
| 206 | if (checkTile(state.line[0])) // if line starts with valid character(32) |
| 207 | { |
| 208 | if (state.lineLength > state.maxLength) // track maximum line length(22) |
| 209 | state.maxLength = state.lineLength;(5) |
| 210 | |
| 211 | if (state.lineLength <= 19 && state.rowIndex <= 10) // if size is still within limits(22) |
| 212 | { |
| 213 | for (int i = 0; i < state.lineLength; i++) // copy each tile while checking if valid(161) |
| 214 | { |
| 215 | char c = state.line[i];(139) |
| 216 | if (checkTile(c))(139) |
| 217 | state.level[i + 19 * state.rowIndex] = charToTile(c);(139) |
| 218 | else |
| 219 | { |
| 220 | unloadLevel(first); // free memory and return on faliure(0) |
| 221 | result.result = 3;(0) |
| 222 | result.level = NULL;(0) |
| 223 | return result;(0) |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | state.rowIndex++; // move to next row(22) |
| 228 | } |
| 229 | else |
| 230 | result.result = 4; // large levels were removed(0) |
| 231 | } |
| 232 | |
| 233 | if (state.lineLength <= 2 && state.level[0] != 0) // end of current level(32) |
| 234 | { |
| 235 | if (addLevel(&state, &first, ¤t) == 2) // add level to linked list(5) |
| 236 | { |
| 237 | result.result = 2; // return on memory allocation failure(0) |
| 238 | result.level = NULL;(0) |
| 239 | return result;(0) |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | if (state.level[0] != 0) // if while loop finished and has not added last level, when file ends without empty line at the end(1) |
| 245 | { |
| 246 | if (addLevel(&state, &first, ¤t) == 2)(0) |
| 247 | { |
| 248 | result.result = 2;(0) |
| 249 | result.level = NULL;(0) |
| 250 | return result;(0) |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | fclose(file); // close file(1) |
| 255 | |
| 256 | result.level = first; // set resulting linked list(1) |
| 257 | |
| 258 | return result;(1) |
| 259 | } |
| 260 | |
| 261 | // Frees specified level, does not free entire linked list! |
| 262 | static void freeLevel(Level *level)(6) |
| 263 | { |
| 264 | if (level != NULL)(6) |
| 265 | { |
| 266 | free(level->tiles);(5) |
| 267 | free(level->name);(5) |
| 268 | free(level);(5) |
| 269 | } |
| 270 | }(6) |
| 271 | |
| 272 | // Free loaded linked list of levels |
| 273 | void unloadLevel(Level *level)(1) |
| 274 | { |
| 275 | if (level == NULL) // nothing to free(1) |
| 276 | return;(0) |
| 277 | while (level->next != NULL) // while there are levels to free(5) |
| 278 | { |
| 279 | freeLevel(level->prev); // free said level(4) |
| 280 | level = level->next; // go to next one(4) |
| 281 | } |
| 282 | freeLevel(level->prev); // free last levels(1) |
| 283 | freeLevel(level);(1) |
| 284 | } |
| 285 | |
| 286 | // Save linked list of levels to specified file |
| 287 | // Returns true on success, false on failure |
| 288 | bool saveLevel(Level *level, char *filename)(0) |
| 289 | { |
| 290 | FILE *file; |
| 291 | |
| 292 | file = fopen(filename, "w");(0) |
| 293 | if (file == NULL) // return if file was not opened successfully(0) |
| 294 | return false;(0) |
| 295 | |
| 296 | while (level != NULL) // while there are levels to save(0) |
| 297 | { |
| 298 | fprintf(file, "; %s\n", level->name); // write name of level(0) |
| 299 | for (int i = 0; i < level->size.y; i++) // write rows of level(0) |
| 300 | { |
| 301 | for (int j = 0; j < level->size.x; j++) // write columns of level(0) |
| 302 | { |
| 303 | fputc(tileToChar(level->tiles[j + i * level->size.x]), file); // convert ot character and write(0) |
| 304 | } |
| 305 | fputc('\n', file); // newline at end of every line(0) |
| 306 | } |
| 307 | fputc('\n', file); // newline to separate levels(0) |
| 308 | |
| 309 | level = level->next; // go to next level(0) |
| 310 | } |
| 311 | |
| 312 | return fclose(file) == 0; // return file close success(0) |
| 313 | } |