Back

edit.c coverage

overall coverage: 0.0%

line# code(run#)
1 #include "edit.h"
2 #include "file.h"
3 #include "input.h"
4 #include "coordinates.h"
5 #include "tiles.h"
6
7 #include <SDL.h>
8 #include <SDL_image.h>
9 #include <SDL_ttf.h>
10 #include <stdbool.h>
11
12 #include "debugmalloc.h"
13
14 typedef struct EditState
15 {
16 Level *firstLevel;
17 Level *level;
18 Coordinates edit;
19 int selection;
20 int result;
21 bool ctrl;
22 bool unsaved;
23 char *filename;
24 SDL_Renderer *renderer;
25 SDL_Texture *tiles;
26 TTF_Font *font;
27 } EditState;
28
29 // Convert ot tileState to tile
30 // Returns brown floor on error
31 static Tile tileStateToTile(TileState state)(0)
32 {
33 switch (state)(0)
34 {
35 case wallS:(0)
36 return wall;(0)
37 case playerS:(0)
38 return player;(0)
39 case playerOnTargetS:(0)
40 return player;(0)
41 case crateS:(0)
42 return crate;(0)
43 case crateOnTargetS:(0)
44 return crateOnTarget;(0)
45 case targetS:(0)
46 return target;(0)
47 case floorTileS:(0)
48 return brownFloor;(0)
49 default:(0)
50 return brownFloor;(0)
51 }
52 }
53
54 // Get tile at coordinates
55 // Returns invalidS for tiles outside of play area
56 static TileState getTileState(Level *level, int x, int y)(0)
57 {
58 if (x < 0 || y < 0 || x >= level->size.x || y >= level->size.y)(0)
59 {
60 return invalidS;(0)
61 }
62 return level->tiles[x + y * level->size.x];(0)
63 }
64
65 // Set state of tile at coordinates
66 // Does nothing for tiles outside of play area
67 static void setTileState(Level *level, int x, int y, TileState state)(0)
68 {
69 if (x < 0 || y < 0 || x >= level->size.x || y >= level->size.y)(0)
70 {
71 return;(0)
72 }
73 level->tiles[x + y * level->size.x] = state;(0)
74 }
75
76 // Render current state of play to renderer
77 // State must include proper renderer, tiles, and font
78 static void render(EditState *state)(0)
79 {
80 // easier to work with variables this way
81 SDL_Renderer *renderer = state->renderer;(0)
82 SDL_Texture *tiles = state->tiles;(0)
83 TTF_Font *font = state->font;(0)
84 Level *level = state->level;(0)
85
86 SDL_RenderClear(renderer);(0)
87
88 SDL_Color white = {255, 255, 255};(0)
89 renderTiles(renderer, tiles, greyFloor, 0, 0, 19, 11); // background for entire window, based on level finishedness(0)
90 renderTile(renderer, tiles, home, 0, 0); // control buttons(0)
91 renderTile(renderer, tiles, save, 0, 1);(0)
92 renderTile(renderer, tiles, add, 1, 11);(0)
93 renderTile(renderer, tiles, add, 18, 11);(0)
94
95 if (state->level != NULL)(0)
96 {
97 for (int i = 0; i < 7; i++)(0)
98 {
99 renderTile(renderer, tiles, tileStateToTile(i + 1), 0, i + 2);(0)
100 if (i + 1 == playerOnTargetS)(0)
101 renderTile(renderer, tiles, target, 0, i + 2);(0)
102 if (i == state->selection)(0)
103 renderTile(renderer, tiles, selection, 0, i + 2);(0)
104 }
105 // start positions for level to center it on screen
106 int startX = 1 + (19 - level->size.x) / 2;(0)
107 int startY = 0 + (11 - level->size.y) / 2;(0)
108
109 renderTiles(renderer, tiles, brownFloor, startX, startY, startX + level->size.x - 1, startY + level->size.y - 1); // background for level area(0)
110
111 for (int i = 0; i < level->size.x; i++) // render tiles except floors(0)
112 {
113 for (int j = 0; j < level->size.y; j++)(0)
114 {
115 TileState tileState = level->tiles[i + j * level->size.x];(0)
116 Tile tile = tileStateToTile(tileState);(0)
117 if (tile != brownFloor)(0)
118 renderTile(renderer, tiles, tile, startX + i, startY + j);(0)
119 if (tileState == playerOnTargetS)(0)
120 renderTile(renderer, tiles, target, startX + i, startY + j);(0)
121 }
122 }
123
124 // render selection
125 renderTile(renderer, tiles, selection, state->edit.x + startX, state->edit.y + startY);(0)
126
127 if (state->level->prev != NULL) // prevoius button is previous level exists(0)
128 renderTile(renderer, tiles, left, 0, 11);(0)
129 renderFont(renderer, font, white, level->name, 10, 11, true, true); // level name(0)
130 if (state->level->next != NULL) // next button in next level exists(0)
131 renderTile(renderer, tiles, right, 19, 11);(0)
132 renderTile(renderer, tiles, delete, 0, 10);(0)
133 }
134
135 SDL_RenderPresent(renderer); // render creation(0)
136 }(0)
137
138 // Save current levels to file
139 // Warns user in case of saving error
140 // Sets sate->result according to user popup state
141 static void saveState(EditState *state)(0)
142 {
143 int result = textInput(state->renderer, state->tiles, state->font, "File neve", state->filename, 63);(0)
144 if (result == 0)(0)
145 {
146 state->result = 0;(0)
147 return;(0)
148 }
149 if (result == 2)(0)
150 return;(0)
151
152 bool saveSuccess = saveLevel(state->firstLevel, state->filename);(0)
153 if (saveSuccess)(0)
154 {
155 state->unsaved = false;(0)
156 if (alertBox(state->renderer, state->tiles, state->font, "Sikeres mentés!") == 0)(0)
157 {
158 state->result = 0;(0)
159 }
160 }
161 else
162 {
163 if (alertBox(state->renderer, state->tiles, state->font, "Sikertelen mentés") == 0)(0)
164 {
165 state->result = 0;(0)
166 }
167 }
168 }
169
170 // Create level object based on user input
171 // Return values: 0 - SDL_Quit, 1 - success/malloc error (based on *level), 2 - user canceled
172 static int createLevel(EditState *state, Level **level)(0)
173 {
174
175 Coordinates size = {0, 0};(0)
176 do
177 {
178 char sizeString[64]; // create empty string
179 sizeString[0] = '\0';(0)
180 int result = textInput(state->renderer, state->tiles, state->font, "Méret (szélesség x magasság)", sizeString, 63); // prompt user size(0)
181 if (result == 0)(0)
182 return 0;(0)
183 if (result == 2)(0)
184 return 2;(0)
185 int count = sscanf(sizeString, "%dx%d", &size.x, &size.y); // read size from string(0)
186 if (count != 2 || size.x < 1 || size.y < 1 || size.x > 19 || size.y > 11) // if size is invalid / sscanf failed(0)
187 {
188 if (alertBox(state->renderer, state->tiles, state->font, "Hibás méret!") == 0)(0)
189 {
190 return 0;(0)
191 }
192 }
193 } while (size.x < 1 || size.y < 1 || size.x > 19 || size.y > 11); // while size not correct(0)
194 *level = (Level *)malloc(sizeof(Level)); // allocate memory for new level(0)
195 if (*level == NULL) // return if failed(0)
196 return 1;(0)
197 (*level)->name = (char *)malloc(sizeof(char) * 17); // allocate memory for name(0)
198 if ((*level)->name == NULL) // return if failed(0)
199 {
200 free(*level); // free allocated memory before return(0)
201 return 1;(0)
202 }
203 strcpy((*level)->name, "Névtelen szint"); // fill name(0)
204 (*level)->size = size;(0)
205 (*level)->tiles = (TileState *)malloc(size.x * size.y * sizeof(TileState)); // allocate memory for tiles(0)
206 if ((*level)->tiles == NULL) // return if failed(0)
207 {
208 free((*level)->name); // free allocated memory before return(0)
209 free(*level);(0)
210 return 1;(0)
211 }
212 (*level)->prev = NULL; // set linked list pointers(0)
213 (*level)->next = NULL;(0)
214 return 1;(0)
215 }
216
217 // Add new level to current list of levels
218 // Dir: true - before current level, false - after current level
219 static void addLevel(EditState *state, bool dir)(0)
220 {
221 Level *newLevel = NULL;(0)
222 int result = createLevel(state, &newLevel); // create new level based on user input(0)
223 if (result != 1) // return if not success/malloc error(0)
224 {
225 if (result == 0) // set state if needed(0)
226 state->result = 0;(0)
227 return;(0)
228 }
229 if (newLevel == NULL) // if memory allocation error(0)
230 {
231 if (alertBox(state->renderer, state->tiles, state->font, "Memóriafoglalási hiba") == 0)(0)
232 {
233 state->result = 0;(0)
234 }
235 return;(0)
236 }
237 if (dir)(0)
238 { // after current level
239 if (state->level == NULL) // linked list is empty(0)
240 {
241 state->firstLevel = newLevel; // first level is this level(0)
242 newLevel->prev = NULL; // no other levels(0)
243 newLevel->next = NULL;(0)
244 }
245 else
246 { // linked list is not empty
247 newLevel->prev = state->level; // set prev and next levels(0)
248 newLevel->next = state->level->next;(0)
249 if (state->level->next != NULL) // if not adding after last element(0)
250 state->level->next->prev = newLevel; // set previous pointer of next element to this one(0)
251 state->level->next = newLevel; // set next pointer of current element to this one(0)
252 }
253 }
254 else
255 { // before current level
256 if (state->level == NULL) // if linked list is empty(0)
257 {
258 state->firstLevel = newLevel; // first level is this level(0)
259 newLevel->prev = NULL; // no other levels(0)
260 newLevel->next = NULL;(0)
261 }
262 else
263 {
264 newLevel->next = state->level; // set prev and next levels(0)
265 newLevel->prev = state->level->prev;(0)
266 if (state->level->prev != NULL) // if not inserting before first element(0)
267 state->level->prev->next = newLevel; // set previous pointer of next element to this one(0)
268 else
269 state->firstLevel = newLevel; // else set first element to this one (as this is the new first one)(0)
270 state->level->prev = newLevel; // set prevoius pointer of current element to this one(0)
271 }
272 }
273 state->level = newLevel; // set current level to new one(0)
274 state->unsaved = true; // modified file(0)
275 state->edit.x = 0; // set selection(0)
276 state->edit.y = 0;(0)
277 }
278
279 // Paprikás krumplit főz
280 // A state-ben benne kell lennie minden alapanyagnak
281 // Sikertelen főzés esetén a state->result -2 lesz
282 // Mosatlan edények száma mindig egyel nő
283 static void deleteCurrent(EditState *state)(0)
284 {
285 if (state->level == NULL)(0)
286 return;(0)
287 int result = dialogBox(state->renderer, state->tiles, state->font, "Biztosan törlöd a szintet?");(0)
288 if (result != 1)(0)
289 {
290 if (result == 0)(0)
291 state->result = 0;(0)
292 return;(0)
293 }
294 state->unsaved = true;(0)
295 state->edit.x = 0;(0)
296 state->edit.y = 0;(0)
297 if (state->level->prev == NULL && state->level->next == NULL)(0)
298 {
299 free(state->level->tiles);(0)
300 free(state->level->name);(0)
301 free(state->level);(0)
302 state->level = NULL;(0)
303 state->firstLevel = NULL;(0)
304 return;(0)
305 }
306 if (state->level->prev == NULL)(0)
307 {
308 state->firstLevel = state->level->next;(0)
309 state->firstLevel->prev = NULL;(0)
310 free(state->level->tiles);(0)
311 free(state->level->name);(0)
312 free(state->level);(0)
313 state->level = state->firstLevel;(0)
314 return;(0)
315 }
316 if (state->level->next == NULL)(0)
317 {
318 state->level->prev->next = NULL;(0)
319 Level *prev = state->level->prev;(0)
320 free(state->level->tiles);(0)
321 free(state->level->name);(0)
322 free(state->level);(0)
323 state->level = prev;(0)
324 return;(0)
325 }
326 state->level->prev->next = state->level->next;(0)
327 state->level->next->prev = state->level->prev;(0)
328 Level *next = state->level->next;(0)
329 free(state->level->tiles);(0)
330 free(state->level->name);(0)
331 free(state->level);(0)
332 state->level = next;(0)
333 }
334
335 // Swtiches to next level if possible
336 static void nextLevel(EditState *state)(0)
337 {
338 if (state->level->next == NULL)(0)
339 return;(0)
340 state->level = state->level->next;(0)
341 state->edit.x = 0;(0)
342 state->edit.y = 0;(0)
343 }
344
345 // Swtiches to prevoius level if possible
346 static void prevLevel(EditState *state)(0)
347 {
348 if (state->level->prev == NULL)(0)
349 return;(0)
350 state->level = state->level->prev;(0)
351 state->edit.x = 0;(0)
352 state->edit.y = 0;(0)
353 }
354
355 // Exit to main menu
356 // Prompts user if work is unsaved
357 static void handleExitToMenu(EditState *state)(0)
358 {
359 if (state->unsaved)(0)
360 {
361 int dialogResult = dialogBox(state->renderer, state->tiles, state->font, "Biztosan szeretnél mentés nélkül kilépni?");(0)
362 if (dialogResult == 0)(0)
363 {
364 state->result = 0;(0)
365 }
366 if (dialogResult == 1)(0)
367 {
368 state->result = 1;(0)
369 }
370 }
371 else
372 {
373 state->result = 1;(0)
374 }
375 }(0)
376
377 // Process selection movement
378 // Direction: 0 - left, 1 - up, 2 - right, 3 - down
379 // Returns true if rerender is needed
380 static bool moveSelection(int dir, EditState *state)(0)
381 {
382 switch (dir)(0)
383 {
384 case 0: // left(0)
385 if (state->edit.x > 0)(0)
386 {
387 state->edit.x--;(0)
388 return true;(0)
389 }
390 return false;(0)
391 case 1: // up(0)
392 if (state->edit.y > 0)(0)
393 {
394 state->edit.y--;(0)
395 return true;(0)
396 }
397 return false;(0)
398 case 2: // right(0)
399 if (state->edit.x < state->level->size.x - 1)(0)
400 {
401 state->edit.x++;(0)
402 return true;(0)
403 }
404 return false;(0)
405 case 3: // down(0)
406 if (state->edit.y < state->level->size.y - 1)(0)
407 {
408 state->edit.y++;(0)
409 return true;(0)
410 }
411 return false;(0)
412 default:(0)
413 return false;(0)
414 }
415 }
416
417 // Set tile on edit coordinates to selected tile
418 static void modifyTile(EditState *state)(0)
419 {
420 if (state->edit.x >= 0 && state->edit.x < state->level->size.x && state->edit.y >= 0 && state->edit.y < state->level->size.y)(0)
421 {
422 state->unsaved = true;(0)
423 setTileState(state->level, state->edit.x, state->edit.y, state->selection + 1);(0)
424 }
425 }(0)
426
427 // Rename current level
428 static void renameLevel(EditState *state)(0)
429 {
430 if (state->level == NULL)(0)
431 return;(0)
432 char name[64];
433 name[0] = 0;(0)
434 if (state->level->name != NULL)(0)
435 strcpy(name, state->level->name);(0)
436 int result = textInput(state->renderer, state->tiles, state->font, "Szint neve", name, 63);(0)
437 if (result != 1)(0)
438 {
439 if (result == 0)(0)
440 state->result = 0;(0)
441 return;(0)
442 }
443 free(state->level->name);(0)
444 int len = strlen(name);(0)
445 state->level->name = (char *)malloc((len + 1) * sizeof(char));(0)
446 strcpy(state->level->name, name);(0)
447 state->unsaved = true;(0)
448 }
449
450 // Handle SDL key down event
451 // Returns true if erernder is needed
452 static bool handleKeydown(EditState *state, SDL_Scancode key)(0)
453 {
454 switch (key)(0)
455 {
456 case 0x50: // left arrow(0)
457 case 0x04: // letter A
458 if (state->ctrl)(0)
459 return false;(0)
460 return moveSelection(0, state);(0)
461 case 0x52: // up arrow(0)
462 case 0x1A: // letter W
463 if (state->ctrl)(0)
464 return false;(0)
465 return moveSelection(1, state);(0)
466 case 0x4F: // right arrow(0)
467 case 0x07: // letter D
468 if (state->ctrl)(0)
469 return false;(0)
470 return moveSelection(2, state);(0)
471 case 0x51: // down arrow(0)
472 case 0x16: // letter S
473 if (state->ctrl)(0)
474 {
475 saveState(state);(0)
476 state->ctrl = false;(0)
477 return true;(0)
478 }
479 return moveSelection(3, state);(0)
480 case 0x29: // esc(0)
481 if (state->ctrl)(0)
482 return false;(0)
483 handleExitToMenu(state);(0)
484 return true;(0)
485 case 0x4b: // page up(0)
486 if (state->ctrl)(0)
487 return false;(0)
488 nextLevel(state);(0)
489 return true;(0)
490 case 0x4e: // page down(0)
491 if (state->ctrl)(0)
492 return false;(0)
493 prevLevel(state);(0)
494 return true;(0)
495 case 0xe0: // left ctrl(0)
496 case 0xe4: // right ctrl
497 state->ctrl = true;(0)
498 return false;(0)
499 case 0x4c: // delete(0)
500 if (state->ctrl)(0)
501 return false;(0)
502 deleteCurrent(state);(0)
503 return true;(0)
504 case 0x4a: // home(0)
505 if (state->ctrl)(0)
506 return false;(0)
507 addLevel(state, false);(0)
508 return true;(0)
509 case 0x4d: // end(0)
510 if (state->ctrl)(0)
511 return false;(0)
512 addLevel(state, true);(0)
513 return true;(0)
514 case 0x15: // letter r(0)
515 if (state->ctrl)(0)
516 return false;(0)
517 renameLevel(state);(0)
518 return true;(0)
519 case 0x28: // enter(0)
520 case 0x2c: // spacebar
521 if (state->ctrl)(0)
522 return false;(0)
523 modifyTile(state);(0)
524 return true;(0)
525 case 0x1e: // 1 key(0)
526 if (state->ctrl)(0)
527 return false;(0)
528 state->selection = 0;(0)
529 return true;(0)
530 case 0x1f: // 2 key(0)
531 if (state->ctrl)(0)
532 return false;(0)
533 state->selection = 1;(0)
534 return true;(0)
535 case 0x20: // 3 key(0)
536 if (state->ctrl)(0)
537 return false;(0)
538 state->selection = 2;(0)
539 return true;(0)
540 case 0x21: // 4 key(0)
541 if (state->ctrl)(0)
542 return false;(0)
543 state->selection = 3;(0)
544 return true;(0)
545 case 0x22: // 5 key(0)
546 if (state->ctrl)(0)
547 return false;(0)
548 state->selection = 4;(0)
549 return true;(0)
550 case 0x23: // 6 key(0)
551 if (state->ctrl)(0)
552 return false;(0)
553 state->selection = 5;(0)
554 return true;(0)
555 case 0x24: // 7 key(0)
556 if (state->ctrl)(0)
557 return false;(0)
558 state->selection = 6;(0)
559 return true;(0)
560 default:(0)
561 //printf("0x%02x\n", event.key.keysym.scancode);
562 return false;(0)
563 }
564 }
565
566 // Handle SDL key up event
567 // Returns true if erernder is needed
568 static bool handleKeyup(EditState *state, SDL_Scancode key)(0)
569 {
570 switch (key)(0)
571 {
572 case 0xe0: // left ctrl(0)
573 case 0xe4: // right ctrl
574 state->ctrl = false;(0)
575 return false;(0)
576 default:(0)
577 return false;(0)
578 }
579 }
580
581 // Handle SDL mouse click
582 // Returns true if rerender is needed
583 static bool handleClick(EditState *state, int x, int y)(0)
584 {
585 if (clickTile(0, 0, x, y)) // back to main menu(0)
586 {
587 handleExitToMenu(state);(0)
588 return true;(0)
589 }
590 if (clickTile(0, 1, x, y)) // save level(0)
591 {
592 saveState(state);(0)
593 return true;(0)
594 }
595 if (clickTile(0, 11, x, y)) // previouse level(0)
596 {
597 prevLevel(state);(0)
598 return true;(0)
599 }
600 if (clickTile(19, 11, x, y)) // next level(0)
601 {
602 nextLevel(state);(0)
603 return true;(0)
604 }
605 if (clickTile(1, 11, x, y)) // add level left(0)
606 {
607 addLevel(state, false);(0)
608 return true;(0)
609 }
610 if (clickTile(18, 11, x, y)) // add level right(0)
611 {
612 addLevel(state, true);(0)
613 return true;(0)
614 }
615 if (clickTile(0, 10, x, y)) // delete level(0)
616 {
617 deleteCurrent(state);(0)
618 return true;(0)
619 }
620 if (state->level != NULL)(0)
621 {
622 for (int i = 0; i < 7; i++)(0)
623 {
624 if (clickTile(0, i + 2, x, y))(0)
625 {
626 state->selection = i;(0)
627 return true;(0)
628 }
629 }
630 int startX = 1 + (19 - state->level->size.x) / 2;(0)
631 int startY = 0 + (11 - state->level->size.y) / 2;(0)
632
633 for (int i = 0; i < state->level->size.x; i++)(0)
634 {
635 for (int j = 0; j < state->level->size.y; j++)(0)
636 {
637 if (clickTile(startX + i, startY + j, x, y))(0)
638 {
639 state->edit.x = i;(0)
640 state->edit.y = j;(0)
641 modifyTile(state);(0)
642 return true;(0)
643 }
644 }
645 }
646
647 if (clickTiles(2, 11, 17, 11, x, y))(0)
648 {
649 renameLevel(state);(0)
650 return true;(0)
651 }
652 }
653 return false;(0)
654 }
655
656 // Handles SDL event
657 // Returns true if rerender is needed
658 static bool handleEvent(SDL_Event event, EditState *state)(0)
659 {
660 switch (event.type)(0)
661 {
662 case SDL_KEYDOWN:(0)
663 return handleKeydown(state, event.key.keysym.scancode);(0)
664 case SDL_KEYUP:(0)
665 return handleKeyup(state, event.key.keysym.scancode);(0)
666 case SDL_MOUSEBUTTONDOWN:(0)
667 return handleClick(state, event.button.x, event.button.y);(0)
668 case SDL_QUIT: // exit program(0)
669 state->result = 0;(0)
670 return false;(0)
671 default:(0)
672 return false;(0)
673 break;
674 }
675 }
676
677 // Load and edit levels given in file
678 // Returns 0 on SDL_Quit, 1 on exit to menu
679 int editLevel(SDL_Renderer *renderer, SDL_Texture *tiles, TTF_Font *font, char *filename)(0)
680 {
681 LoadLevelResult result = loadLevel(filename);(0)
682 switch (result.result)(0)
683 {
684 case 2:(0)
685 unloadLevel(result.level);(0)
686 return alertBox(renderer, tiles, font, "Memóriafoglalási hiba");(0)
687 break;
688 case 3:(0)
689 unloadLevel(result.level);(0)
690 return alertBox(renderer, tiles, font, "A fájl hibás karaktereket tartalmaz");(0)
691 break;
692 case 4:(0)
693 switch (dialogBox(renderer, tiles, font, "Néhány szint túl nagy. Biztosan megnyitod?"))(0)
694 {
695 case 0:(0)
696 unloadLevel(result.level);(0)
697 return 0;(0)
698 break;
699 case 1:(0)
700 break;(0)
701 case 2:(0)
702 unloadLevel(result.level);(0)
703 return 1;(0)
704 break;
705 default:(0)
706 break;(0)
707 }
708 default:(0)
709 break;(0)
710 }
711
712 EditState state;
713 state.level = NULL;(0)
714 state.firstLevel = result.level;(0)
715 state.level = result.level;(0)
716 state.result = -1;(0)
717 state.renderer = renderer;(0)
718 state.tiles = tiles;(0)
719 state.font = font;(0)
720 state.ctrl = false;(0)
721 state.unsaved = false;(0)
722 state.filename = filename;(0)
723 state.edit.x = 0;(0)
724 state.edit.y = 0;(0)
725 state.selection = 0;(0)
726
727 render(&state);(0)
728
729 SDL_Event ev;
730 while (SDL_WaitEvent(&ev))(0)
731 {
732 bool rerender = handleEvent(ev, &state);(0)
733 if (state.result != -1) // if result was set(0)
734 {
735 unloadLevel(state.firstLevel);(0)
736 return state.result; // return to main(0)
737 }
738 if (rerender) // if rerender is needed(0)
739 render(&state);(0)
740 }
741 }(0)