Programování pro hračičky/Měniči/Lekce 13

Tato stránka není ještě hotová.
Jak používat klasifikační nálepkuTato stránka je součástí kurzu:
středoškolská
Příslušnost: skupinová

Automaticky volané funkce a jejich využití editovat

Funkce jako query_smell() se v objektu volají tehdy, když je o to objekt jiným objektem požádán, tedy v tomto případě když nějaká bytost objekt očichá. Vedle takovýchto funkcí, spouštěných na žádost okolních objektů v různých časech a situacích, je pět příležitostí, při nichž se v objektu určité funkce spouštějí automaticky:[1] vytvoření objektu, pravidelné obnovení objektu, pohyb objektu, setkání objektu se živou bytostí a zničení objektu. Jak následně uvidíme, dobré pochopení toho, co se při těchto příležitostech děje, může výrazně rozšířit naše měničské možnosti.

Vytvoření objektu editovat

Bezprostředně po vytvoření běžného objektu se v něm zavolá funkce create(). Ta zpravidla provede základní nastavení objektu — tedy definuje jeho vzhled, povrch, zápach, zvuk, hmotnost, cenu atd. O této funkci je důležité vědět, i když měnič ji efektivně ve svém programu použít nemůže, protože objekty nevytváří, ale mění: v objektech, na kterých spouští své programy, byla funkce create() spuštěna při jejich vytvoření, a během existence zastínění měničským programem znovu spuštěna nebude.

Jediné objekty, které působením měničovým nově vznikají (a po chvíli opět zanikají), jsou samotné objekty zastínění. Ty ovšem nepatří mezi objekty „běžné“, a tak se v nich po vytvoření nespouští funkce create(), nýbrž funkce shadow_create_action(). A to je funkce pro měniče naopak použitelná velmi dobře, protože po spuštění jeho programu na nějakém objektu nemusí čekat na nahodilé zavolání funkce jiným objektem, nýbrž může si být jist, že tato funkce se určitě hned spustí, a s ní i všechno, co do ní naprogramoval.

Pokud se měnič rozhodne funkci shadow_create_action() využít, musí si uvědomit její určitou zvláštnost: protože uvnitř zastínění je jediné místo, kde se dá objekt zastínění odlišit od zastíněného objektu, musí funkce shadow_create_action() toto odlišení vždy provádět, aby se omylem nespouštěla i tehdy, když má být spuštěna v zastíněném objektu (tedy přesněji, v nějakém zastínění, které leží zastíněnému objektu blíže). Proto na samotný začátek funkce vždy vkládáme toto odlišení, třeba v takovéto podobě:

void shadow_create_action(object sh)
{
  if (sh!=this_object())
    {
      query_shadow_owner()->shadow_create_action(sh);
      return;
    }

  /* a tady následují akce, které do této funkce naprogramuje měnič */

}

Do funkce shadow_create_action() tedy programujeme to, co se má odehrát okamžitě po zastínění objektu, resp. okamžitě po úspěšném provedení příkazu spusť, hovoříme-li o měničských dovednostech. Prvním typickým použitím může být provedení jednorázové akce, kvůli které jsme daný program vůbec vytvořili. Tak by třeba jednoduchý program na okamžité vyhnání zastíněné bytosti z místnosti mohl vypadat takto:

void shadow_create_action(object sh)
{
  if (sh!=this_object())
    {
      query_shadow_owner()->shadow_create_action(sh);
      return;
    }
  query_shadow_owner()->random_move();
}

Druhým typickým použitím funkce shadow_create_action() je definice herních příkazů, které má mít k dispozici samotná zastíněná bytost. Definici herních příkazů se budeme podrobněji věnovat později, zde si uveďme jen jednoduchý příklad příkazu start, který může zastíněná bytost zadat, aby se přesunula do místnosti, v níž hra začíná:

#include <move.h>

void shadow_create_action(object sh)
{
  if (sh!=this_object())
    {
      query_shadow_owner()->shadow_create_action(sh);
      return;
    }
  add_action("presun_na_start","start");
}

int presun_na_start(string str)
{
  this_player()->move("/room/dum/jidelna",MOVE_MAGIC,
    "Poing... a "+ten(1,this_player())+" "+jest(this_player())+" pryč!",
    "Poing... a najednou "+jest(this_player())+" tu "+ten(1,this_player())+".");
  return 1;
}

Pravidelné obnovení objektu editovat

V pravidelných časových odstupech (asi 40 minut reálného času) se v každém objektu zavolá funkce reset(). Ta zpravidla obnovuje ty vlastnosti objektu, které se mohou změnit následnou činností hráčů — například rostou-li na jabloni jablka, která mohou hráči otrhávat, může tato funkce obsahovat opětné doplnění jejich počtu.

Jednou možností měničského využití funkce reset() je její překrytí v naprogramovaném zastínění, takže v čas jejího zavolání neprovede obnovu zastíněného objektu jeho původní funkce reset(), nýbrž funkce reset() naprogramovaná měničem. Při dlouhých intervalech automatické obnovy to má ponejvíce smysl tam, kde chceme zabránit uvedení objektu do původního stavu — například v místnosti, která při obnově vždy vygeneruje útočného skřeta, po jeho likvidaci spustíme program, který zabrání jeho znovuvytvoření na dobu, kdy v místnosti plníme úkol:

void reset()
{
  // nic neprovádíme ani nevoláme, protože nechceme, aby se objekt obnovil
}

Druhou možností využití funkce reset() je její přímé volání v případech, kdy nechceme čekat na pravidelnou obnovu objektu, protože bychom jeho výchozí stav potřebovali rychleji — například v místnosti, která ve výchozím stavu obsahuje v trávě pohozený meč, jehož máme právě zapotřebí, ale někdo si ho už odnesl:

void shadow_create_action(object sh)
{
  if (sh!=this_object())
    {
      query_shadow_owner()->shadow_create_action(sh);
      return;
    }
  query_shadow_owner()->reset();
}

Pohyb objektu editovat

Pohyby objektů ve hře je možno sledovat a řídit rozličnými sofistikovanými způsoby, k jejichž pochopení jsou potřeba pokročilejší znalosti. Existuje však jednoduchá funkce just_moved(), která se v pohyblivých objektech automaticky zavolá pokaždé,[2] když objekt změnil své okolí, tedy třeba když živá bytost přešla z místnosti do místnosti nebo když byl nějaký předmět někým zvednut nebo položen.

První typický způsob, jak může měnič funkci just_moved() využít, je provedení určitých akcí v místnosti, do které se přesunul sám měnič, který svůj program spustil na sobě. V následujícím příkladu se měnič po vstupu do nové místnosti vždy nejprve dozví, jaká je v ní teplota, ale samozřejmě by se mohly provést i akce úplně jiné, třeba vyjmenovat neviditelné objekty nebo pozabíjet přítomné skřety (povšimněme si, že just_moved() v závěru funkce zavoláme též v zastíněném objektu, aby nezůstaly neprovedeny jiné s pohybem spjaté akce, pro zastíněný objekt možná životně důležité):

void just_moved()
{
  tell_object(query_shadow_owner(),"Teplota v místnosti: "
      +environment(query_shadow_owner())->query_type("teplota")+" °C.\n");
  query_shadow_owner()->just_moved();
}

Obdobně je možno využít funkci just_moved() k pozorování nebo ovlivňování místností, do kterých vstupuje nějaký jiný objekt, který byl měničem zastíněn. Jednoduchým příkladem je prosté sledování pohybu zastíněného objektu (zde hráčem Michaelem, jméno si samozřejmě každý měnič upraví podle sebe):

void just_moved()
{
  object ja;

  if (ja=find_player("michael"))
    tell_object(ja,"Sledovaný objekt se přesunul: "
      +environment(query_shadow_owner())->query_short()+".\n");
  query_shadow_owner()->just_moved();
}

Setkání objektu s živou bytostí editovat

Při každém bezprostředním setkání objektu s živou bytostí se volá funkce init(). Živou bytostí v tomto smyslu je každý objekt, kterému bylo povoleno vykonávat příkazy, což jsou typicky jednak hráči, jednak nehráčské postavy jako třeba Josef v úvodní místnosti hry. Bezprostředním setkáním zase míníme přesun objektu do inventáře živé bytosti, přesun objektu do inventáře jiného objektu (typicky místnosti), v němž se nachází živá bytost, nebo přesun živé bytosti do inventáře objektu.

Živá bytost, která svým přiblížením k objektu vyvolala spuštění funkce init(), vystupuje v rámci programu této funkce jakožto this_player(). Pokud tedy v měničském zastínění překryjeme funkci init() zastíněného objektu, můžeme pomocí this_player() různě ovlivňovat živé bytosti, které se do jeho blízkosti dostanou. Tak třeba následující příklad každé k objektu se přiblíživší živé bytosti vnukne myšlenku, že je sledována:

#include <message.h>

void init()
{
  query_shadow_owner()->send_message_to(this_player(),
    MT_SENSE,MA_LOOK,wrap("Máš pocit, jako by tě odněkud někdo sledoval."));
  query_shadow_owner()->init();
}

V právě uvedeném příkladu si všimněme závěrečného volání funkce init() v zastíněném objektu. Kdybychom toto volání do svého programu nezařadili, neprovedly by se ve funkci init() zastíněného objektu jiné funkce, kterých může být pro fungování hry zapotřebí. Typickým příkladem takových funkcí jsou definice příkazů, které mají být k dispozici živým bytostem, jež se s objektem setkají (jako například příkaz zapal, definovaný v objektu pochodně, nebo příkaz jez, definovaný v objektu sušenky).

Pro definici příkazů můžeme samozřejmě využít funkci init() i ve svých měničských programech. Následující příklad ukazuje, jak se dá předefinovat již existující příkaz rozhlédni se pro každou živou bytost, která se nově dostane do okolí objektu. Všimněme si, že volání funkce init() v zastíněném objektu je zde umístěno před nastavením příkazů — kdyby se totiž prováděla inicializace zastíněného objektu až po nich, mohlo by se stát, že init() zastíněného objektu by opět předefinoval příkaz, který jsme naprogramovali v init() zastínění:

#include <message.h>

void init()
{
  query_shadow_owner()->init();
  add_action("nic","r");
  add_action("nic","rozhlédni");
  add_action("nic","rozhlédnu");
  add_action("nic","rozhledni");
  add_action("nic","rozhlednu");
  add_action("nic","rozhlížím");
  add_action("nic","rozhlížim");
  add_action("nic","rozhlizim");
}

int nic(string str)
{
  query_shadow_owner()->send_message_to(this_player(),
    MT_NOTIFY,MA_LOOK,"Tady se nedá rozhlížet!\n");
  return 1;
}

Zničení objektu editovat

Běžné objekty se odstraňují ze hry zavoláním funkce remove(), která se zpravidla stará o to, aby objekt dokončil nezbytné započaté akce, případně uložil nějaká data na disk, a poté pošle hernímu ovladači žádost o zničení objektu. Tato žádost je automatická, tedy dochází k ní po každém zavolání funkce remove(), zatímco všechnu její ostatní náplň určuje programátor objektu.

Již víme, že objekt zastínění není běžným herním objektem. Podobně jako se při jeho vzniku nevolá funkce create(), nýbrž shadow_create_action(), při jeho zániku se místo funkce remove() volá funkce shadow_remove_action(). Její zavolání na rozdíl od remove() ovšem nezpůsobí zničení objektu zastínění (to se dělá jinak a nyní nás to tolik nezajímá), nýbrž funkce nám jen nabízí prostor k naprogramování akcí, které se mají provést těsně předtím, než zastínění skončí.

Měničsky využívat můžeme jak funkci remove(), tak funkci shadow_remove_action(). První možností je prostě ze svého programu volat funkci remove() v různých objektech, které chceme zničit, a doufat, že to s sebou nepřinese přílišné negativní důsledky.[3] Druhou možností je překrýt funkci remove() zastíněného objektu:

#include <message.h>

int remove()
{
  /* nejprve ověříme, že zastínění je už umístěno
     a nejedná se jen o interní správu objektů: */
  if (!query_shadow_owner())
    return 1;
  /* nyní otestujeme, zda zastíněný objekt je potravina,
     a pokud ano, využijeme toho, že zpravidla, když se volá remove()
     v objektu potraviny a okolím potraviny je hráč,
     pak tento hráč právě potravinu dojedl: */
  if (query_shadow_owner()->query_nahrung())
    query_shadow_owner()->send_message_to(
      environment(query_shadow_owner()),MT_NOTIFY,MA_REMOVE,
      wrap("Po "+tvuj(6,query_shadow_owner())
      +" ti zůstává v ústech divná pachuť. "+Ten(1,query_shadow_owner())
      +dan(" byl",query_shadow_owner())+" bezpochyby "
      +dany("otrávený",query_shadow_owner())+"."));
  /* remove() v zastíněném objektu voláme každopádně: */
  return query_shadow_owner()->remove();
}

Konečně, třetí možností je použití funkce shadow_remove_action():

void shadow_remove_action(object sh)
{
  object ja;

  if (sh!=this_object())
    {
      query_shadow_owner()->shadow_remove_action(sh);
      return;
    }
  // aby se poznalo, co přesně ploplo:
  if (ja=find_player("michael"))
    tell_object(ja,"Končí zastínění: "
      +object_name(query_shadow_owner())->query_short()+".\n");
}

Můžeme si všimnout, že stejně jako u výše popsaných funkcí musíme v případě remove() dbát na zavolání této funkce rovněž v zastíněném objektu (a předání návratové hodnoty této funkce příkazem return) a v případě shadow_remove_action() na předání jejího volání, pokud se netýká námi naprogramovaného zastínění.

Zpožděné procesy editovat

Někdy se hodí, aby zastíněný objekt na nějaký popud zvenčí nereagoval hned, ale až se zpožděním, případně aby po sobě nějaké akce následovaly s časovým odstupem (jako to umožňuje efekt wait při použití měničského příkazu přeonač). Nejjednodušší řešení těchto situací nabízejí tzv. zpožděné procesy. Ty se vyvolávají funkcí call_out(), jíž jako parametry předáme jméno funkce, která má být spuštěna až po nějaké době, udání této doby v sekundách a případné další parametry, které mají být předány zpožděně volané funkci:

#include <message.h>

void init()
{
  // nezapomeneme zavolat původní init():
  query_shadow_owner()->init();
  // po šesti sekundách se má spustit funkce praska():
  call_out("praska",6,this_player());
  /* třetí parametr funkce call_out() se předá funkci praska()
     jako její první parametr; 'kdo' uvnitř funkce praska()
     tedy bude stejná bytost jako this_player() zde uvnitř
     funkce init()  */
}

void praska(object kdo)
{
  // zastíněný objekt pošle zprávu bytosti, která předtím přišla:
  query_shadow_owner()->send_message_to(kdo,MT_NOISE,MA_MOVE,
    wrap("Najednou pod tebou začala praskat podlaha."));
  // bytost, která předtím přišla, rozešle zprávu okolí
  kdo->send_message(MT_NOISE,MA_MOVE,
    wrap("Pod "+ten(7,kdo)+" najednou začala praskat podlaha."));
  // a za dvě sekundy se má spustit funkce prcha():
  call_out("prcha",2,kdo);
}

void prcha(object kdo)
{
  // zastíněný objekt pošle zprávu bytosti, která předtím přišla:
  query_shadow_owner()->send_message_to(kdo,MT_NOISE,MA_MOVE,
    wrap("Rychle pryč, než se to tu s tebou propadne!"));
  kdo->random_move();
}

Zda se odpočítává zpožděné spuštění dané funkce, zjistíme funkcí find_call_out(), která vrací buď počet sekund zbývajících ke spuštění dané funkce (což může být i 0, pokud už má být funkce bezprostředně spuštěna), nebo -1, pokud se zpožděné spuštění dané funkce neodpočítává. První nalezené odpočítávání můžeme zrušit funkcí remove_call_out(). Jak funkci find_call_out(), tak funkci remove_call_out() předáváme jediný parametr, a tím je jméno funkce, o jejíž zpožděné spuštění jde:

#include <message.h>

// v proměnné 'kolik' budeme počítat za sebou následující přesuny:
int kolik=0;

void just_moved()
{
  // zastíněný objekt se přesunul do nové místnosti
  if (find_call_out("oddych")>=0)
    {
      // odpočítává se zpožděný proces,
      // tedy se bytost přesunula v posledních 2 sekundách;
      // zrušíme dosavadní odpočítávání a spustíme nové:
      remove_call_out("oddych");
      call_out("oddych",2);
      // zvětšíme počet přesunů o 1
      kolik++;
    }
  else
    {
      // zpožděný proces se neodpočítává,
      // tedy v posledních 2 sekundách se bytost nepřesunula;
      // tedy spustíme první odpočítávání:
      call_out("oddych",2);
      kolik=1;
    }
  query_shadow_owner()->just_moved();
}

void oddych()
{
  /* sem se dostaneme, když se po spuštění odpočítávání bytost 
     aspoň dvě sekundy nepohnula; pokud se předtím pohnula
     vícekrát za sebou, bude oddychovat; každopádně ale nakonec
     vynulujeme čítač 'kolik' */
  if (kolik>3)
    {
      // bytost se přesunula více než 3x -> oddechuje
      query_shadow_owner()->send_message_to(query_shadow_owner(),
        MT_NOTIFY,MA_MOVE,wrap("Po tom běhu sotva lapáš po dechu!"));
      query_shadow_owner()->send_message(MT_NOISE,MA_MOVE,
        wrap(Ten(1,query_shadow_owner())+" oddechuje, jako by "
          +dan("byl",query_shadow_owner())+" hodně rychle "
          +dan("běžel",query_shadow_owner())+"."));
    }
  kolik=0;
}

...

Uživatelský vstup editovat

...

input_to()

...

Úkoly editovat

Povinné editovat

Zde by bylo vhodné ještě něco doplnit.

Dobrovolné editovat

Zde by bylo vhodné ještě něco doplnit.

Pomocné stránky editovat

Poznámky editovat

  1. Přesněji řečeno volá tyto funkce herní ovladač neboli driver, tedy onen subsistující objekt, který udržuje celou hru v chodu.
  2. Není to tedy úplně pokaždé, existuje rovněž tajný přesun objektu, při němž se funkce just_moved() nezavolá, ovšem podrobnosti těchto výjimek už patří mezi ony pokročilejší znalosti.
  3. V této souvislosti je radno připomenout zejména hněv andělů, před nímž jsme varovali již dříve.