Programování pro hračičky/Andělé/Lekce 6

Z Wikiverzity
Skočit na navigaci Skočit na vyhledávání
Jak používat klasifikační nálepkuTato stránka je součástí kurzu:
středoškolská
Příslušnost: skupinová

Opakování a probrání domácího úkolu[editovat]

Ukažme si objekty, které se nám podařilo vybavit hráčskými příkazy. Vyzkoušejme si je, pokud možno též společně, abychom viděli, nakolik se nám povedlo zohlednit všechny možné situace a účastníky.

Co nám při vytváření hráčských příkazů dělalo největší problémy? Na co jsme narazili? Jaké dovednosti nám nejvíce chybějí?

A co náš hlavní objekt, tedy onen dlouhodobý úkol, který jsme si vytyčili? Doplnili jsme ho rovněž nějakým hráčským příkazem? Co všechno mu ještě chybí, aby odpovídal našim představám?

Prostorový vztah objektů[editovat]

Při výrobě objektů jsme už poznali, že k zapojení do hry nestačí objekt programátorsky vyrobit (tedy načíst do paměti ze souboru nebo naklonovat), nýbrž že je potřeba ho někam do hry umístit, aby se s ním hráči mohli setkat. V případě objektů, které nejsou místnostmi, to znamená, že musí být umístěny do jiného objektu, tedy — technicky vzato — odkaz na ně musí být předán jinému objektu a zařazen do jeho seznamu objektů, které mají být považovány za jeho inventář.

V minulé lekci jsme se již setkali s funkcí all_inventory(), která vrací tento seznam, tedy pole objektů. Zavolat ji můžeme buď bez parametru — pak vrátí pole objektů tvořících inventář objektu, v jehož programu se právě nacházíme — nebo s jedním objektovým parametrem — pak vrátí pole objektů tvořících inventář tohoto předaného objektu:[1][2]

  if (sizeof(all_inventory(mistnost))>5)
    send_message_to(this_player(),MT_LOOK,MA_LOOK,"Je tu nějak plno!\n");

Ke zjišťování, zda je určitý objekt v inventáři jiného objektu, slouží funkce present(), se kterou jsme se také již setkali. Jejím prvním parametrem je označení objektu, na jehož přítomnost v inventáři se ptáme. Druhý parametr je nepovinný a označuje objekt, v jehož inventáři dotyčný objekt hledáme — není-li druhý parametr uveden, hledá se v inventáři objektu, v jehož programu se právě nacházíme.

Objekt, který hledáme v inventáři, můžeme označit buď přímo odkazem na něj, nebo některým z jeho nastavených identifikátorů (tedy oněch řetězců, které se nastavují funkcí set_id() nebo add_id()). První způsob je samozřejmě výpočetně méně náročný,[3] proto bychom jej měli používat všude, kde je to možné — typicky tedy v případě, kdy již sledujeme osudy konkrétního objektu, na nějž máme k dispozici ukazatel:[4]

  if (present(ob))
    {
      send_message(MT_LOOK|MT_NOISE,MA_EAT,wrap("Adéla s mlasknutím polyká "
        +ten(4,ob)+"."));
      ob->remove();
    }
  else if (present(ob,this_player()))
    send_message(MT_LOOK,MA_LOOK,wrap("Adéla se hladově dívá na "+ten(4,this_player())
      +". Asi to souvisí s tím, že jí "+dan("sebral",this_player())+" "+ten(4,ob)
      +"."),wrap("Adéla se na tebe hladově dívá. Brát jí "+ten(4,ob)
      +" asi nebyl tak dobrý nápad."),this_player());

Druhý způsob volání funkce present(), tedy vyhledávání objektu v inventáři podle identifikátoru, využijeme hlavně v souvislosti s návratovou hodnotou této funkce, kterou je nalezený objekt:[5]

  if (ob=present("studna",environment(this_player())))
    {
      otrav(ob);
      send_message(MT_LOOK,MA_USE,wrap(Ten(1,this_player())+" "
        +plural("sype","sypou",this_player())+" něco do "+ten(2,ob)+"."),
        "",this_player());
      send_message_to(this_player(),MT_NOTIFY,MA_USE,wrap("Sypeš jed do "
        +ten(2,ob)+". Hm, byl to opravdu dobrý nápad?"));
    }
  else 
    send_message_to(this_player(),MT_NOTIFY,MA_USE,
      wrap("Nevidíš tu žádnou studnu, která by se dala otrávit."));

Funkce environment(), kterou vidíme v právě uvedeném příkladu, vrací okolí objektu, tedy objekt, v jehož inventáři se nachází zadaný objekt (pokud nebyl objekt explicitně zadán, bere se za něj this_object(), tedy objekt, v jehož programu se právě nacházíme). V některých případech může funkce environment() vracet i nulovou hodnotu — například když se ptáme na okolí místnosti nebo objektu, který nebyl dosud přesunut do hry. Na to je nutno pamatovat a tyto případy v případě potřeby ošetřit:

  if (kde=environment(ob)) // následující příkazy provádíme, jen existuje-li okolí
    {
      if (kde->query_room())
        ...
    }

Kromě základních a nejběžněji používaných funkcí all_inventory(), present() a environment() nabízejí programové knihovny mudu celou řadu dalších funkcí pro práci se vzájemnou polohou objektů. Přímo ve hře si pomocí encyklopedických příkazů dek inventory, dek present a dek environment můžeme nechat vypsat všechny (zdokumentované) funkce, které ve svém názvu obsahují řetězec "inventory", "present" nebo "environment", a přečíst si pak jednotlivě dokumentaci k nim. Tak například zjistíme, že při prohledávání inventáře objektu nemusíme vždy zjišťovat celý inventář pomocí all_inventory, nýbrž můžeme jej postupně procházet pomocí first_inventory() a next_inventory(), nebo že v inventáři objektu můžeme funkcí cond_present() hledat nejen objekty s určitým identifikátorem, nýbrž také splňující další podmínky, nebo že pomocí funkce absolute_environment() může zjistit svou polohu na mapě i zápalka nacházející se v krabičce nacházející se v kapse batohu nacházejícího se v truhlici nacházející se v podpalubí lodi nacházející se právě na moři na půli cesty mezi Kyprem a Levantou.

Pohyb[editovat]

Pohyb objektů po herním světě není ničím jiným, než vyřazením pohybujícího se objektu z inventárního pole jednoho objektu a jeho zařazení do inventárního pole jiného objektu. Pohybující se objekt při tom zůstává na stejném místě v paměti a až na vlastnost environment() nezměněn.

Veškerý pohyb objektů v mudu by měl být prováděn funkcí move(), definovanou ve třídě /i/move, kterou tedy musí dědit všechny objekty, které se mají pohybovat.[6][7] Funkce má čtyři parametry, z nichž pouze první je povinný:

cíl pohybu
Jako cíl pohybu může být uveden buď objekt, do jehož inventáře se má pohybovaný objekt přesunout, nebo jméno souboru tohoto objektu, nebo směr pohybu (tj. nám ze hry známé "sever", "dolů" a pod.), pokud se pohybovaný objekt nachází v místnosti.
způsob pohybu
Způsob pohybu se zadává celočíselnými konstantami definovanými v hlavičkovém souboru <move.h> (který tedy musíme před jejich použitím načíst příkazem #include): MOVE_NORMAL znamená běžný pohyb z místnosti do místnosti, MOVE_MAGIC pohyb mimořádný (typicky magický, jak napovídá název konstanty), MOVE_SECRET pohyb tajný, o kterém se nemají vydávat žádné hlášky a který se nemá projevit ani programátorsky. Není-li způsob pohybu zadán (nebo je zadána hodnota 0), jedná se o pohyb implicitní, kterým se předměty přesouvají do hry, resp. ve hře přesouvají programátorsky — ten není provázen žádnými hláškami, ale programátorsky se projevuje například voláním funkce just_moved() po dokončení pohybu.
odchodová a příchodová hláška
Hlášky, kterými se má ohlásit odchod objektu z místnosti (nebo jiného objektu), v níž se dosud nacházel, a příchod do cílové místnosti (nebo jiného objektu). Pokud tyto parametry nejsou zadány, použijí se při pohybu MOVE_NORMAL hlášky nastavené pomocí set_msg_out() a set_msg_in(),[8] při pohybu MOVE_MAGIC hlášky nastavené pomocí set_mmsg_out() a set_mmsg_in(),[9] při jiných druzích pohybu žádné hlášky.

Funkce move() vrací celočíselný kód, který vypovídá o tom, zda se pohyb zdařil, nebo k jaké chybě při pokusu o pohyb došlo. Hodnota MOVE_OK (i tyto konstanty jsou definovány v <move.h>) znamená, že pohyb správně proběhl, ostatní návratové hodnoty popisují jednotlivé možné chyby (viz dokumentaci ? move). Pokud se pohyb nezdařil, ale není možno, aby objekt, který se měl pohnout, zůstal v původní místnosti (například proto, že objekt původní místnosti zanikl), přesune se objekt do implicitní místnosti /i/void. Pokud se totéž stane v místnosti /i/void, přesune se neúspěšně pohnutý objekt do náhradní implicitní místnosti /i/hell.

V pohybovatelném objektu je možno pomocí funkce set_no_move() nastavit, že se objekt nesmí pohybovat (zavolání s parametrem 1 pohyb zakáže, zavolání s parametrem 0 jej opět povolí). Funkcí set_no_move_reason() je možno nastavit hlášku, kterou má dostat hráč, který se pokusí nepohybovatelným objektem pohnout:

  vaza->set_no_move(1);
  vaza->set_no_move_reason("Přece tu nebudeš krást vázy!");

Průchody mezi místnostmi[editovat]

Všechno, co jsme si dosud řekli o poloze objektů a jejich pohybu mezi inventáři jiných objektů, platí jen pro objekty, které nejsou místnostmi. Místnosti nemohou být v inventáři žádných jiných souborů ani se nemohou pohybovat. Mají však definovánu vzájemnou polohu, na které je založena prostorová struktura celého mudu.

Vzájemnou polohu místností definují průchody. Každá místnost má definován seznam sousedních místností (zjistitelný funkcí query_exit_list()) a seznam příslušných směrů, resp. příkazů, jimiž může hráč do těchto místností dojít (zjistitelný funkcí query_command_list()). Oba seznamy mají podobu pole řetězců, v prvním případě objektových jmen místností (resp. jejich programových souborů), ve druhém případě jednoslovných příkazů, které dobře známe ze hry z popisu místnosti. Průchody mohou mít různé zvláštní vlastnosti — například být pro hráče neviditelné, zamčené, nebo naopak nabízet otevřený pohled do vedlejší místnosti (tyto vlastnosti zjistíme funkcí query_exit_flag()).[10]

Sousední místnosti, pohybové příkazy i vlastnosti průchodů nastavíme nejlépe přímo funkcí set_exit(), add_exit(), set_exits(), add_exits() (funkce pro jednotlivé nastavování si můžeme vyhledat v dokumentaci, například pomocí encyklopedického příkazu dek exit, který vypíše všechny zdokumentované funkce, obsahující ve svém jméně "exit"). Funkcí set_exit_msg() můžeme navíc pro jeden či více průchodů nastavit zvláštní pohybové hlášky (případně funkcí set_msg() takto nastavit hlášky pro všechny průchody najednou):

  set_exit_msg("severovýchod",
     "$Ten(1,OBJ_TP) šplhá pěšinkou na severovýchod.",
     "$Ten(1,OBJ_TP) sem udýchaně dolézá po pěšince od jihozápadu.",
     "Šplháš pěšinkou na severovýchod.");

Procházení do jiných místností však můžeme umožnit i hráčským příkazem. Můžeme například definovat příkaz prolez, který bude očekávat dodatek, čím chce hráč prolézt, a v případě, že identifikuje díru v plotě (která je v místnosti popsána), přesune hráče do místnosti, která jinak není přímo přístupná. Definice takových příkazů můžeme najít například v modulech /i/room/enter, /i/room/jump nebo /i/room/breh.

Úkoly do příští lekce[editovat]

V hláškách hráčského příkazu, který jste vytvořili, se pokuste zohlednit, jaké předměty jsou v okolí, v inventáři jednajícího hráče a pod. — například nechte hráče po šňupnutí z tabatěrky nejenom kýchnout, nýbrž i praštit se o některý z předmětů v místnosti.[11]

Naučte nějaký ze svých objektů nebo místností pohybovat jinými objekty: Vytvořte hráčský příkaz, který nejen vydá nějaké hlášky, ale též pohne nějakým objektem — například kluzké mýdlo, které hráči vyskočí z ruky, magnety, které se budou vzájemně odpuzovat, kouzelný proutek, jehož mávnutím se hráč přesune do jiné místnosti či pod.

Obohaťte své místnosti pohybovými hláškami. Zkuste místo standardních hlášek (Michael odchází na sever.) používat co nejbarvitější hlášky šité na míru konkrétní místnosti (Michael se opatrně rozhlédne, a pak vklouzne do komůrky na severu.), a to nejenom pro okolí, nýbrž i pro pohybujícího se hráče (Tiše proklouzáváš severními dvířky.).

Dodejte do některé své místnosti hráčský příkaz, který hráče přesune do jiné místnosti — například průlez sklepním okénkem, přelezení zídky, skok do studny a pod.

Poznámky[editovat]

  1. Následně uvedený příklad zde nevysvětlujeme, protože všechny použité konstrukce již byly sdostatek probrány. Laskavý čtenář si předsevezmiž pochopení tohoto příkladu jakožto cvičení.
  2. Přímo ze hry můžeme zjišťovat inventář objektů pomocí andělského příkazu obsah.
  3. Výpočetní náročností míníme zatížení ovladače, potažmo procesoru vykonáním dotyčné funkce.
  4. V následujícím příkladu používáme několik nových konstrukcí, na základě dosud probraného ovšem dobře pochopitelných:
    • Funkce remove(), zavolaná v objektu, způsobí jeho zničení.
    • Klíčové slovo else uvozuje příkaz (nebo do složených závorek uzavřenou skupinu příkazů), který se má vykonat v případě, že podmínka uvedená u předchozího if nebyla splněna. Spojení else if tedy znamená, že při nesplnění první podmínky hned začneme testovat další podmínku.
    • Čtvrtý a pátý parametr funkce send_message() nepovinně uvádějí alternativní hlášku a objekt nebo skupinu objektů, kterým má tato hláška být poslána místo hlášky prvně uvedené — v našem příkladu se tedy prvně uvedená hláška pošle všem okolním objektům kromě this_player(), tedy kromě hráče, s nímž právě jednáme, a druhá hláška se pošle právě tomuto hráči.
  5. Rovněž následující příklad obsahuje několik nových konstrukcí:
    • Při volání funkce present() její návratovou hodnotu (nalezený objekt) nejprve uložíme do proměnné ob, abychom s ní mohli dále pracovat, a pak otestujeme pomocí if. Pokud byl nalezen nějaký objekt, provede se následující skupina příkazů, pokud žádný nalezen nebyl (tedy funkce present() vrátila 0), provede se příkaz následující po else.
    • Funkce otrav() je fiktivní, předpokládáme ji v tomto příkladu jako nějakou lokální funkci objektu, v němž se nacházíme (funkce by pak musela v nalezené studni najít objekt vody a nahradit jej objektem otrávené vody, ale tím se nyní podrobně zabývat nebudeme).
    • Rozdělené volání send_message() a send_message_to() (tedy nevyužití onoho čtvrtého a pátého parametru funkce send_message(), které jsme poznali v minulém příkladu) v případě zdařilého nalezení studny je nutné proto, že hláška hráči a hláška okolí má odlišný typ (hráči se jakožto MT_NOTIFY má posílat vždy, okolí jen jako MT_LOOK, tedy zrakový vjem); prázdný řetězec poslaný hráči zajistí, že se hráči nepošle hláška určená pro okolí.
  6. Objekty, které se nemají pohybovat, ale mají mít k dispozici některé vlastnosti a metody spojené s pohybem (například hmotnost nebo hlášku o tom, že a proč není možno objektem pohnout), musí dědit buď také třídu /i/move a mít zvlášť nastavenu nepohyblivost, nebo zvláštní třídu pro nepohyblivé objekty /i/install.
  7. Přímo ze hry můžeme sebe přesouvat pomocí andělského příkaz cíl, jiné objekty pomocí andělského příkazu sem.
  8. Přesněji řečeno se nejprve zavolá funkce query_msg_out() v místnosti, kterou objekt opouští, a query_msg_in() v místnosti, do níž se objekt pohybuje. Pokud některá z těchto funkcí vrátí 0, zavolá se stejnojmenná funkce v samotném pohybujícím se objektu. Pokud i ta vrátí 0, použije se implicitní hláška.
  9. Přesněji řečeno se nejprve zavolají funkce query_mmsg_out() a query_mmsg_in() v pohybujícím se objektu, a pokud některá z nich vrátí 0, použije se implicitní hláška.
  10. Úplný soupis průchodů dané místnosti můžeme ze hry zjistit andělským příkazem průchody.
  11. Předpokládáme již, že účastník kursu je schopen si též sám v encyklopedii dohledávat další potřebné funkce a podle dokumentace je využívat.

Pomocné stránky[editovat]