Back

file.c coverage

overall coverage: 61.3%

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 }