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

Z Wikiverzity
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]

Podařilo se Vám vyzkoušet programátorské možnosti probrané v minulé lekci (práce s inventářem místností a dalších objektů, pohybování objekty i hráči, diferencované pohybové hlášky)?

Projděte si své výtvory a vzájemně si je ozkoušejte. Snažte se odhalit nedostatky a chyby, a odhalené se pak pokuste buď odstranit, nebo aspoň rozpoznat, jakým směrem se k jejich odstranění vydat.

Řídicí funkce[editovat]

Víme už, že objekty se vzájemně ovlivňují tím, že si posílají zprávy. I zavolání funkce v jiném objektu je vlastně jen posláním zprávy s žádostí, a záleží na osloveném objektu, nakolik a jak žádost splní (tedy jak je v něm splnění dotyčné žádosti naprogramováno). Veškeré interakce objektů ve hře jsou založeny na takovémto vzájemném posílání zpráv, tedy v objektu se spustí nějaký proces vždy jen v reakci na takto posílanou zprávu.

Ponejprv jsme se naučili pro běh hry takto využívat zprávy posílané objektu LPC ovladačem bezprostředně po vytvoření objektu (tedy výzvy ke spuštění funkce create()). Poté jsme se seznámili s funkcí reset(), která nám umožnila reagovat na výzvy k obnově objektu v pravidelných odstupech rozesílaných herním ovladačem. Následně nám funkce init() umožnila reagovat na setkání objektu s živou bytostí, a znalost definice hráčských příkazů nám dala možnost reakce na vstup ze strany hráče.

Pro dobrou režii průběhu hry nám však tyto možnosti stále nestačí. Potřebovali bychom reagovat nejen na výslovně zadané příkazy hráče, nýbrž i na jeho pohyb, na jeho zacházení s předměty, na různé stavy a vztahy, do kterých se objekty ve hře vzájemně dostávají.

Tuto možnost nám dávají řídicí funkce. Jsou to funkce, které jsou volány objekty účastnými akcí (například objektem, který se právě pohnul, bytostí, která začíná něco jíst a pod.) a které mohou spustit reakce v těchto i dalších objektech.

Implicitní řídicí funkce[editovat]

Některé řídicí funkce se volají vždy, když se objekt ocitne v určitém stavu. Pokud se například objekt ob pohne z objektu odkud do objektu kam, pak se v objektu odkud zavolá funkce moved_out(), v objektu kam funkce moved_in() a v objektu ob funkce just_moved(). Tak mohou všechny tři objekty reagovat na provedený pohyb.

Příkladem použití těchto funkcí nám poslouží místnosti hlediště cesarejského hipodromu, v nichž se každý odložený předmět zprvu ztratí mezi řadami kamenných lavic:

varargs void moved_in(object co, object odkud, string smer, int jak)
{
  ::moved_in(co,odkud,smer,jak);
  if (!living(co) && !co->query_invis())
    co->set_hidden_until_next_move();
}

Pokud se do takovéto místnosti přesune nějaký objekt, pak se v místnosti zavolá tato funkce moved_in() a jako první parametr se jí předá přesunutý objekt, jako druhý parametr objekt, odkud se onen objekt přesunul, jako třetí směr pohybu, pokud se jednalo o normální pohyb průchodem mezi místnostmi (tedy třeba "sever"), a jako čtvrtý způsob pohybu (tedy třeba MOVE_NORMAL). V našem konkrétním příkladu tato funkce moved_in() nejprve zavolá zděděnou funkci moved_in() (na to nesmíme zapomenout, jinak by některé věci při pohybu přestaly fungovat), a pak v případě, že přesunutý objekt není živá bytost a není neviditelný, nastaví tento přesunutý objekt jako skrytý (tedy nevypisující se v popisu místnosti a v hláškách zmiňovaný jen jako „něco“) až do jeho příštího pohybu.

Za další příklad (který již nebudeme rozebírat v důvěře v důvtip čtenářův) si můžeme vzít použití funkce just_moved() v objektu prodavače vody v Raffě, který si při pohybu rozsvěcí a zhasíná pochodeň podle toho, zda je v místnosti, do které přišel, světlo:[1]

void just_moved()
{
  object poch;
  
  if (!can_see(environment()))
    {
      if (!(poch=present("pochodeň")))
        (poch=clone_object("/obj/pochoden"))->move(this_object());
      if (!poch->query_is_lighted())
        poch->light_on();
    }
  else
    {
      if ((poch=present("pochodeň")) && poch->query_is_lighted()
          && environment()->query_light()>1)
        poch->light_off();
    }
}

Zatímco funkce moved_in() a moved_out() se volají ve zdrojovém a cílovém objektu po úspěšném provedení pohybu, takže objekty mohou na pohyb už jen dodatečně zareagovat, volají se implicitní řídicí funkce let_not_in() a let_not_out() v těchto objektech před započetím pohybu. Obě funkce mají celočíselnou návratovou hodnotu, a vrátí-li některá z nich nenulové číslo, pak se pohyb neprovede. Objekty tedy pomocí těchto funkcí mohou nejenom zareagovat na pohyb, který se teprve má odehrát, ale také tomuto pohybu zabránit.

Příkladem použití těchto funkcí může být třeba kousek kódu, který s drobnými obměnami najdeme v mnoha řemeslných dílnách nebo obchodech, v nichž má být stále přítomen řemeslník nebo prodavač (konkrétní příklad je z jedné kovárny):

int let_not_out(object who, object kam, string dir)
{
  if (kovar && (who == kovar)) 
    {
      kovar->set_not_moved_reason(Ten(1,kovar)
               +" se odsud nechce hnout.");
      return 1;
    }
  return ::let_not_out(who, kam, dir);
}

Funkce let_not_out() obdrží jako parametry objekt, který se chce pohnout, objekt, do kterého se má onen objekt pohnout, a při normálním pohybu mezi místnostmi ještě směrový řetězec. V našem příkladu se jedná o kovárnu, která má v proměnné kovar uložen objekt kováře v této kovárně pobývajícího. Pokud objekt kováře existuje (tedy kovář například nebyl právě někým zabit) a zároveň je objektem, který se chce pohnout, pak se do objektu kováře nastaví hláška zdůvodňující, proč se pohyb nemůže provést, a pohyb se vrácením hodnoty 1 zakáže. V jiných případech se prostě zavolá zděděná funkce let_not_out() s týmiž parametry, a vrátí se hodnota touto vrácená.

Podobně jako let_not_in() a let_not_out() pracují funkce no_take(), no_put(), no_push(), no_wield() a no_attack(), které se volají v objektu, který má být odněkud vzat, někam položen, postrčen, tasen nebo napaden, a které příslušné akci zabrání, pokud vrátí nenulovou hodnotu.[2]

Svou implicitní řídicí funkci má přiřazen rovněž každý průchod, který vede z místnosti. Funkce je pojmenována složením filter_ a jména průchodu zbaveného háčků a čárek (tedy třeba filter_sever(), filter_jihovychod(), filter_dolu() atd.)[3] a volá se v okamžiku, kdy má nějaká bytost nebo předmět tímto průchodem opustit místnost. Tím může místnost jednak zareagovat na pohyb daným průchodem, jednak pohybu zabránit, pokud příslušná funkce vrátí nenulovou hodnotu.

Příklad použití takovéto funkce najdeme třeba u vchodu do starých skladů v Morii, do kterých se nesmí dostat žádný skřet:

int filter_jih(object kdo)
{
  if (kdo->query_race()=="skřet")
    {
      send_message_to(kdo,MT_NOTIFY,MA_MOVE,wrap("Jako skřet tudy "
        "neprojdeš. Elfské kouzlo dosud chrání tuto bránu."));
      kdo->send_message(MT_LOOK,MA_MOVE,wrap(Ten(1,kdo)+" se "
        +dan("pokusil",kdo)+" projít na jih, ale zastavilo "+ho(4,kdo)
        +" elfské kouzlo, které dosud chrání bránu morijských skladů "
        "přede všemi skřety."));
      return 1;
    }
}

Explicitní řídicí funkce[editovat]

Většina řídicích funkcí, s nimiž se můžeme v programech mudu setkat, se nevolá implicitně, ale pouze na základě explicitní deklarace. Chceme-li například, aby místnost dostala zprávu, že v ní byl pokácen strom, nebo aby nějaká bytost nesměla sníst nějaký objekt, najdeme sice připravené řídicí funkce, které mohou takovou zprávu nebo takový zákaz zprostředkovat, ale tyto funkce se nezavolají, pokud si to v programu výslovně nevyžádáme.

Volání těchto řídicích funkcí zajišťují čtyři implicitní řídicí funkce notify(), forbidden(), allowed() a concerned(), které však na rozdíl od výše popsaných nereagují na konkrétní situace, nýbrž na celou abstraktní kategorii situací, a to tím, že zavolají postupně všechny řídicí funkce dané kategorie.

Funkce notify(), forbidden(), allowed() a concerned() jsou definovány ve třídě /i/item/control, kterou dědí všechny místnosti, bytosti a věci, s nimiž se můžeme setkat ve hře. Tato třída zároveň deklaruje proměnnou, která obsahuje seznam objektů, v nichž mají být volány konkrétní řídicí funkce, a ke správě tohoto seznamu sloužící funkce add_controller(), delete_controller(), is_controller() a query_controller().

Řídicí funkce typu notify[editovat]

Pro první z uvedených příkladů — místnost má zareagovat na to, že v ní byl pokácen strom — bychom nejprve do programu místnosti zařadili funkci notify_strom_pada(), která by prováděla kýženou reakci na pád stromu:

void notify_strom_pada(object strom, object kdo, object cim)
{
  // následuje všechno, co má místnost provést, když byl strom pokácen
  ...
}

Následně bychom pak s uvedením této funkce objekt místnosti přihlásili mezi řídicí objekty stromu. Pokud by to měl udělat objekt stromu, bylo by na vhodné místo v jeho programu (například do just_moved(), které se zavolá poté, co byl strom přesunut do místnosti) zařazeno volání funkce add_controller():

void just_moved()
{
  ...
  add_controller("notify_strom_pada",environment());
  ...
}

Jiná možnost by byla zařadit přihlášení místnosti jako řídicího objektu stromu do programu místnosti (například do reset(), ve kterém by se nejspíše objekt stromu vytvářel):

void reset()
{
  object strom;

  ...
  strom->add_controller("notify_strom_pada",this_object());
  ...
}

Přihlásit místnost za řídicí objekt stromu by však bylo možno právě tak i z jakéhokoliv třetího objektu, pokud by k tomu byl programátorský důvod (například z objektu, který by celkově spravoval růst stromů v určité oblasti hry).

V okamžiku, kdy někdo podťal strom natolik, že by měl spadnout, se v objektu stromu zavolá funkce notify(). Pokud předpokládáme, že strom byl podťat hráčem hrac sekerou sekera, volá se:

  notify("strom_pada",this_object(),hrac,sekera);

Funkce notify() projde seznam všech přihlášených řídicích objektů, a ve všech, které jsou přihlášeny pro řídicí funkci notify_strom_pada(), tuto řídicí funkci zavolá. V našem případě to znamená:

  mistnost->notify_strom_pada(strom,hrac,sekera);

Řídicí funkce typu forbidden[editovat]

Pro druhý z příkladů uvedených v úvodu — zabránit tomu, aby nějaká bytost snědla nějaký objekt — bychom mohli použít buď funkci forbidden_eat(), která se přihlašuje v objektu bytosti, nebo funkci forbidden_eat_self(), která se přihlašuje v objektu potravy. Kdybychom zvolili první z nich, bylo by ve většině případů logické definovat ji v objektu bytosti a rovněž do objektu bytosti zařadit její přihlášení:

void create()
{
  set_name("labužník");
  ...
  add_controller("forbidden_eat",this_object());
  ...
}

...

int forbidden_eat(object potrava, object kdo)
{
  if (kdo==this_object() && potrava->id("# billa #"))
    {
      send_message(MT_LOOK,MA_EAT,wrap(Ten(1,this_object())
        +" si znechuceně prohlíží "+ten(4,potrava)
        +". Ne, tuhle levnou náhražku jíst nebude."));
      return 1;
    }
  return 0;
}

Když nastane situace, že by dotyčná bytost měla jíst objekt potrava, pak objekt potrava (v němž je definován příkaz jedení) zavolá před začátkem jedení v objektu bytosti funkci forbidden():

  bytost->forbidden("eat",potrava,bytost);

Funkce forbidden() potom projde seznam přihlášených řídicích objektů, a ve všech, které jsou přihlášeny s funkcí forbidden_eat(), tuto funkci zavolá. Tak se zavolá tato funkce i v objektu bytosti, která se sama sobě přihlásila jako řídicí objekt:

  bytost->forbidden_eat(potrava,bytost);

Pokud libovolná z takto zavolaných funkcí forbidden_eat() vrátí nenulovou hodnotu, vrátí i funkce forbidden() nenulovou hodnotu (konkrétně 1) a akce jedení se nespustí.[4]

Řídicí funkce typu allowed a concerned[editovat]

Vedle řídicích funkcí typu notify a forbidden, jejichž užití jsme si ukázali ve výše uvedených příkladech, existují méně užívané, ale v některých případech užitečné typy allowed a concerned:

  • Funkce typu notify se při určité události prostě jen zavolají v přihlášených objektech, takže přihlášené objekty mohou zareagovat na určitou událost.
  • Funkce typu forbidden se před určitou událostí zavolají ve všech přihlášených objektech, a pokud některá z nich vrátí nenulovou hodnotu, událost se neprovede. Přihlášené objekty mohou tedy jednak reagovat na to, že se nějaká událost chystá, jednak této události zabránit.
  • Funkce typu allowed se před určitou událostí zavolají ve všech přihlášených objektech, ovšem událost se provede jen tehdy, pokud všechny vrátí nenulovou hodnotu. Přihlášené objekty tedy chystanou událost implicitně zakazují, ale mohou ji za některých okolností povolit.
  • Funkce typu concerned se před určitou událostí zavolají ve všech přihlášených objektech; každé toto volání vrátí nezápornou celočíselnou hodnotu, a souhrnná funkce concerned() pak vrátí buď 0 (pokud všechna volání vrátila 0), nebo objekt, z něhož volání dotyčné funkce vrátilo největší hodnotu; tomuto objektu se pak svěří provedení události. Přihlášené objekty tedy mohou zareagovat na chystanou událost, nebo dokonce přímo převzít její provedení.

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

  • Použijte někde ve svých místnostech funkci moved_in() nebo moved_out().
  • Použijte v některém objektu just_moved().
  • Použijte někde ve svých místnostech či objektech let_not_in() nebo let_not_out().
  • Použijte v některé ze svých místností průchodový filtr.
  • Pomocí encyklopedického příkazu deklarace (či zkráceně dek) si dejte vypsat všechny řídicí funkce typu notify, forbidden, allowed a concerned:
> dek notify_
...
> dek forbidden_
...
> dek allowed_
...
> dek concerned_
...
  • Přečtěte si dokumentaci k řídicím funkcím, které vás něčím zaujaly nebo u kterých máte nápad, jak by se daly využít (dokumentace též obsahuje celou řadu příkladů možného využití).
  • Použijte někde funkci typu notify.
  • Použijte někde funkci typu forbidden.

Poznámky[editovat]

  1. Všimněme si, že funkci just_moved() se nepředávají žádné parametry. Na samotný přesunutý objekt (v jehož programu se právě nacházíme) pak můžeme odkazovat funkcí this_object(), a na objekt, do něhož se onen objekt přesunul, funkcí environment(). K objektu, z něhož se onen objekt přesunul, a ke směru a způsobu pohybu však nemáme (přinejmenším beze složitých programátorských fíglů) přístup.
  2. Jako u všech takto letmo zmiňovaných funkcí, i zde předpokládáme, že čtenář si může podle zájmu a potřeby dohledat jejich dokumentaci v Podprahové encyklopedii.
  3. Společnou encyklopedickou dokumentaci pro všechny tyto funkce najdeme v Podprahové encyklopedii pod názvem filter_xxx.
  4. Můžeme si všimnout, že hlášku, která má případně oznámit, že k nějaké akci nedošlo, musíme sami naprogramovat do příslušné řídicí funkce. Automaticky se žádné hlášky nevydávají.

Pomocné stránky[editovat]