Sokoban levels

Ciele

  1. Modulárne programovanie.
  2. Dynamické údajové typy.
  3. Správa pamäte.
  4. Práca so súbormi.

Úvod

Hru samotnú robí zaujímavou to, že po úspešnom vyriešení jedného problému dostane hráč na riešenie problém ďalší a hlavne ťažší. Hra je teda tvorená súborom niekoľkých levelov (alebo úrovní), ktoré musí hráč zdolať. Ak ich všetky úspešne zdolá, hru samotnú dohrá.

Na tomto cvičení vytvoríte vo svojej hre takýto mechanizmus, pomocou ktorého bude hráč môcť prechádzať z levelu do levelu. Samostatné levely budú uložené v externom súbore a pri spustení hry sa zavedú do pamäte, v ktorej budú reprezentované pomocou spojkového zoznamu.

Postup

Krok č. 1

V prvom kroku rozdelíte celý projekt na niekoľko samostatných častí - modulov, do ktorých potom popresúvate jednotlivé časti svojho kódu. Rozdelenie kódu na menšie časti (moduly) zvyšuje jeho prehľadnosť a čitateľnosť.

Úloha 1.1:

Vytvorte v projekte hlavičkový súbor library.h a k nemu prislúchajúci súbor library.c a umiestnite do nich všetky "knižničné" funkcie, ich deklarácie a definície, ako aj všetky príslušné makrá a štruktúry.

Úloha 1.2:

Aktualizujte súbor main.c tak, aby používal hlavičkový súbor library.h a volal funkcie, ktoré sa v ňom nachádzajú.

Úloha 1.3:

Overte správnosť svojej úpravy.
Správnosť overíte prekladom takto upraveného projektu, ktorý by mal byť v prípade úspechu preložený ako aj spustený bez chýb.

Úloha 1.4:

Ak ste vo svojom kóde vytvorili makrá na zadefinovanie vlastných farieb, presuňte ich do hlavičkového súboru colors.h.

Krok č. 2

Každá správna hra má vlastné menu, pomocou ktorého viete minimálne hru spustiť a ukončiť. V tomto kroku vytvoríte takéto menu aj pre svoju hru.

Úloha 2.1:

Vytvorte jednoduché používateľské menu, ktoré sa zobrazí hneď po spustení hry a bude obsahovať minimálne položky: spustiť hru a ukončiť hru.
Význam jednotlivých položiek úvodného menu je nasledovný:
  • spustiť hru - hra sa spustí od začiatku (level 0)
  • ukončiť hru - hra sa korektne ukončí (uzatvorením všetkých otvorených súborov, uvoľnením celej obsadenej pamäte a pod; nestačí len zavolať funkciu exit())

Okrem uvedených položiek môžete do menu pridať napr. aj položku o autorovi.

Úloha 2.2:

Overte správnosť svojej implementácie.

Krok č. 3

Aby sa aj v našom prípade jednalo o skutočnú hru, je potrebné vytvoriť systém, pomocou ktorého bude možné v hre po skončení jednej úrovne prejsť plynulo do ďalšej. V tomto kroku preto vytvoríte súbory levels.h a levels.c, v ktorých sa tento systém bude nachádzať. Ak nebude uvedené ináč, budeme prioritne pracovať práve s týmito dvoma súbormi.

Úloha 3.1:

Vytvorte nový hlavičkový súbor levels.h a k nemu prislúchajúci súbor so zdrojovým kódom levels.c.

Úloha 3.2:

Vytvorte štruktúrovaný typ Level, ktorý bude reprezentovať jeden level hry a umiestnite ho do hlavičkového súboru levels.h.
Štruktúrovaný typ Level bude obsahovať nasledujúce položky:
  • name - názov levelu
  • description - (veselý) opis levelu, ktorý sa zobrazí pri jeho spúšťaní
  • password - heslo pre vstup do daného levelu
  • map - mapa samotného levelu v Sokoban formáte
  • next_level - referencia na ďalší level

Úloha 3.3:

Vytvorte funkciu levels_init(), ktorá nainicializuje dostupné levely pre hru.

Funkcia nebude mať žiadny parameter a taktiež nebude vracať žiadnu hodnotu. Funkciu volajte v hre len raz a to pri jej spustení.

Pre prvé tri levely môžete použiť nasledujúce mapy:

                        char* level1 = "---####|---#--#|---#--#|---#$.#|####--#|#-----#|#@$.--#|#######";
                        char* level2 = "-#####|##---#|#--.-#|#-$.$###|##$.$--#|-#-.@--#|-#######";
                        char* level3 = "--#####|###-@-#|#--$.$##|#--$*$-#|###.*.-#|--#-.--#|--#--###|--####";
                    
Heslá, názvy a opisy jednotlivých levelov nech sú nasledovné:
  • level 1 - názov: at the beginning...; opis: ...programmer created the world and the robot...; heslo: sokoban
  • level 2 - názov: the next level; opis: you should have no expectations!; heslo: budokan
  • level 3 - názov: level 3; opis: this is level 3; heslo: saboteur
Poznámka:
Keďže sa jedná o jednosmerný zoznam, nezabudnite si v module vytvoriť aj jednu premennú, ktorá bude uchovávať referenciu na prvý level.

Úloha 3.4:

Vytvorte funkciu get_level(), ktorá vráti príslušný level na základe parametra funkcie.
Funkcia get_level() bude mať jeden parameter, ktorý bude reprezentovať heslo príslušnej úrovne. Ak však toto heslo nebude zadané, resp. parameter bude obsahovať NULL, funkcia vráti prvý level, v opačnom prípade vráti referenciu na príslušný level.

Úloha 3.5:

Vytvorte v menu hry novú položku Zadaj heslo, pomocou ktorej bude mať hráč možnosť zadať heslo pre vstup do príslušného levelu.
Ak hráč zadá správne heslo, otvorí sa mu prislúchajúci level, od ktorého môže následne hrať. Ak zadá nesprávne heslo, vráťte hráča do hlavného menu.

Úloha 3.6:

Vytvorte funkciu levels_free(), ktorá uvoľní pamäť obsadenú všetkými levelmi.
Táto funkcia sa bude v priebehu celej hry volať len raz a to pri jej ukončení.

Krok č. 4

V tomto kroku zabezpečíte, aby sa postupnosť jednotlivých levelov hry načítala zo súboru. Tým bude aj jednoduché pridávať stále nové a nové levely do hry bez nutnosti manuálnej úpravy samotného kódu.

Úloha 4.1:

Vytvorte textový súbor levels.dat a zapíšte do neho informácie potrebné pre opis svojich levelov.
Každý riadok v súbore bude predstavovať jeden samostatný level. Štruktúra riadku nech je pritom nasledovná:
názov levelu;opis levelu;heslo;mapa
Súbor s vyššie uvedenými levelmi môže teda vyzerať nasledovne:
                    at the beginning...;...programmer created the world and the robot...;sokoban;---####|---#--#|---#--#|---#$.#|####--#|#-----#|#@$.--#|#######
                    the next level;you should have no expectations!;budokan;-#####|##---#|#--.-#|#-$.$###|##$.$--#|-#-.@--#|-#######
                    level 3;this is level 3;saboteur;--#####|###-@-#|#--$.$##|#--$*$-#|###.*.-#|--#-.--#|--#--###|--####
                    

Úloha 4.2:

Aktualizujte funkciu levels_init() tak, aby v prípade, že súbor levels.dat existuje, došlo k načítaniu levelov z tohto súboru. V opačnom prípade použite existujúcu "základnú sadu" levelov.
Ak by súbor levels.dat neexistoval, hra by bola nehrateľná. Preto v tomto prípade len vypíšte na obrazovku chybovú správu a na zostavenie postupnosti levelov využite už existujúci kód.
Poznámka:

Keďže budete potrebovať načítavať jednotlivé hodnoty dynamicky a nikdy si nebudete istí dĺžkou načítavaného reťazca, môžete využiť nasledovnú vlastnosť funkcií scanf() a fscanf() (zdroj: manuálová stránja funkcie scanf()):

The GNU C library supports a nonstandard extension that causes the library to dynamically allocate a string of sufficient size for input strings for the %s and %a[range] conversion specifiers. To make use of this feature, specify a as a length modifier (thus %as or %a[range]). The caller must free(3) the returned string, as in the following example:

           char *p;
           int n;

           errno = 0;
           n = scanf("%a[a-z]", &p);
           if (n == 1) {
               printf("read: %s\n", p);
               free(p);
           } else if (errno != 0) {
               perror("scanf");
           } else {
               fprintf(stderr, "No matching characters\n");
           }
                        

As shown in the above example, it is only necessary to call free(3) if the scanf() call successfully read a string.

The a modifier is not available if the program is compiled with gcc -std=c99 or gcc -D_ISOC99_SOURCE (unless _GNU_SOURCE is also specified), in which case the a is interpreted as a specifier for floating-point numbers (see above).

Since version 2.7, glibc also provides the m modifier for the same purpose as the a modifier. The m modifier has the following advantages:

  • It may also be applied to %c conversion specifiers (e.g., %3mc).
  • It avoids ambiguity with respect to the %a floating-point conversion specifier (and is unaffected by gcc -std=c99 etc.)
  • It is specified in the upcoming revision of the POSIX.1 standard.

Úloha 4.3:

Overte správnosť svojej implementácie.

Doplňujúce úlohy

  1. Podľa zadania sú levely uložené v čistej textovej forme, čo umožňuje jednoducho sa dostať ku ľubovoľnému heslu a teda umožňuje hráčovi hrať ľubovoľný level. Vytvorte preto funkcie char* code(char*) a char* decode(char*), z ktorých jedna bude vedieť zakódovať reťazec a druhá ho dekódovať. Na kódovanie môžete použiť ľubovoľný mechanizmus, resp. šifru (napr. môžete použiť jednoduchú substitučnú šifru známu pod názvom Cézarofa šifra).
  2. Pridajte do hry kláves 'r', pomocou ktorého budete vedieť reštartovať práve rozohratý level.
  3. Pridajte do menu položku Redefine keys (Zmena kláves), pomocou ktorej si bude môcť hráč nastaviť vlastné klávesy pre ovládanie hry.
  4. Rozšírte proces zmeny kláves o možnosť zapnúť, resp. vypnúť tzv. God mode, kedy bude hráč môcť miesto hesla zadávať priamo čísla levelov, ktoré chce hrať. Samozrejme môžete do hry vložiť aj úplne iný easter egg, ktorý nemusí byť založený na zmene kláves, ale na postupnosti stlačených kláves počas hry.
  5. Rozšírte štruktúrovaný typ Level o položku solution. Položka solution je reprezentovaná ako postupnosť hráčových ťahov na vyriešenie danej úrovne (formát). Hráč sa pritom môže pohybovať len v smere hore (u), dole (d), vľavo (l) a vpravo (r). Príklad takéhoto reťazca je nasledujúci:

    DDrdrruLruLLDllU
    Smery s veľkými písmenami znamenajú, že Sokoban bude v danom smere pred sebou tlačiť bedňu. Riešenie však musí byť platné nehľadiac na veľkosť písmen.

    Túto položku môžete aktualizovať zakaždým, keď hráč level prejde a jeho riešenie je jednoduchšie (to znamená kratšie) ako to, ktoré je v aktuálnom leveli uvedené.

    Obsah tejto položky môžete využiť ako demo pri hlavnom menu (buď ako samostatnú položku alebo sa demo spustí automaticky po uplynutí časového limitu). Počas behu dema sa samozrejme nezobrazí kompletné riešenie hry, ale len istý počet krokov z riešenia (max. polovica).

  6. Vytvorte funkciu validate_level(), ktorá:
    • sa pokúsi nájsť riešenie uvedeného levelu,
    • bude mať jeden parameter, ktorý bude obsahovať referenciu na premennú typu Level,
    • vráti hodnotu True, ak je daný level naozaj vyriešiteľný, alebo False, ak vyriešiteľný nie je.
  7. Vytvorte jednoduchý editor levelov, ktorý môže byť súčasťou samotnej hry alebo môže byť implementovaný ako samostatný program.
  8. Vytvorte samostatnú (úvodnú obrazovku) pred spustením levelu. V tejto obrazovke nech sa zobrazí názov levelu, jeho opis a heslo do tohto levelu. Pre výpis týchto textov môžete vymyslieť vhodný efekt (napr. tzv. terminálový výpis, kedy sa každý znak vypíše s miernym spozdením).
$Id$