Walka z wiewiórkami

December 10th, 2007
Wiewiórka pospolita

Kiedy chcemy skorzystać z jakiejś nietrywialnej funkcjonalności w naszym programie, mamy zazwyczaj do wyboru dwie możliwości. Albo sami wymyślamy koło, albo idziemy do sklepu (względnie komisu), aby się w takowe zaopatrzyć. Ponieważ programiści to z natury stworzenia na wskroś leniwe, z reguły będą starali się zminimalizować wkład czasowo-intelektualny w tworzone oprogramowanie. Tak było w tym przypadku: do implementacji skryptów w KNAT zdecydowaliśmy się na użycie SqPlus, będącego rozszerzeniem do Squirrel.

Korzystanie z gotowych rozwiązań (zwłaszcza w przypadku FLOSS) ma jednak często sporą niedogodność. Otóż spojrzenie do kodu rzadko potrafi z miejsca ujawnić całą intelektualną głębię autora, i z tego to powodu zmuszeni jesteśmy poprzestać na lekturze materiałów pochodnych — a w szczególności dokumentacji. Jeżeli tylko autor biblioteki zdecydował się na tak szalony i ekscentryczny krok, jakim jest dokumentacja…

Nie jest jednak tak źle, jak mogłoby się wydawać. Choć dołączone do biblioteki przykłady — wbrew zapowiedziom — nie wyczerpują tematu na tyle, aby można już pełni korzystać ze wszystkich dobrodziejstw Wiewióra, a lektura wiki i forów — wbrew pokładanych w nich nadziejom — nie rozwiewa wszystkich naszych lęków, to po pewnym wysiłku ze strony własnej mózgownicy jesteśmy w stanie całość uruchomić.

Ale do rzeczy.

Założenie było takie, że na początku implementujemy zdarzenia typu MapEvent. Polegają one na tym, że gdy nasz — oby jego lufa zawsze lśniła w słońcu — czołg wjedzie na wyznaczone pole na planszy, uruchamiane jest odpowiednie zdarzenie. Całość miała wyglądać tak:

struct MapEvent
{
	MapEvent();
	virtual ~MapEvent();
	int x;
	int y;
	virtual void DoEvent();
};

W odpowiednich momentach gra pobiera potrzebne zdarzenie i uruchamia na nim DoEvent(). No dobra, ale co z implementacją tego? Założenie było takie, żeby trzymać logikę gry w skryptach. W tym celu trzeba było nieco dostosować model zdarzenia.

struct MapEvent
{
	int x;
	int y;
	CMapEvent() {x=y=0;};
	virtual ~CMapEvent() {};
	void AddEvent();
	void CallEvent();
	HSQOBJECT scriptObjHandle;

	[...]
};

W wykropkowanej części ukryte są magiczne formuły, do których wrócimy dopiero na końcu. Co się zmieniło? Zrezygnowaliśmy z wirtualnego handlera DoEvent, ponieważ nie było prostego sposobu, aby go rozszerzać w Squirrelu i używać z powrotem w C++, natomiast dodane zostały AddEvent, który po prostu dodaje zdarzenie do gry; a także CallEvent, które wykonuje zdarzenie (wywołujemy je z C++). To, jak wygląda AddEvent oraz jak zdarzenia są przechowywane można pominąć, ponieważ nie jest to dla sprawy aż tak istotne; powiedzmy sobie za to, jak zdarzenia definiować.

Skrypt przykładowej mapy może wyglądać następująco:

my_event <- class extends MapEvent
{
	event = null;
}();

my_event.event = function()
{
	KNAT.DisplayInfo("Super zdarzenie!");
}

my_event.x = 4;
my_event.y = 1;
my_event.AddEvent();
Złowieszczy punkt na mapie

Co tu się dzieje? Tworzymy obiekt my_event, będący instancją anonimowej klasy pochodnej od MyEvent. Ponieważ w Sq nie ma jakiegoś bardzo rygorystycznego wiązania typów, tworzymy po prostu pole o nazwie event, do którego następnie przypisujemy funkcję (KNAT.DisplayInfo to zaimportowana metoda wypisująca na konsoli podany tekst). Potem ustawiamy (x, y), czyli współrzędne zdarzenia na mapie (rysujemy w tym miejscu złowieszczo wyglądające skalne pole) a następnie prosimy zdarzenie, aby łaskawie dodało się do gry. Tak też się dzieje.

Jak wygląda jego wywołanie? Całkiem prosto:

void MapEvent::CallEvent()
{
	try
	{
		SquirrelObject o(scriptObjHandle);
		SquirrelObject func = o.GetValue("event");
		SquirrelVM::BeginCall(func, o);
		SquirrelVM::EndCall();
	}
	catch (SquirrelError & e)
	{
		scprintf(_T("Error: %s, %s\n"),e.desc,_T("Squirrel::error"));
	}
}

O co tym razem chodzi? Na początku pobieramy wskaźnik do obiektu. Musi być to wskaźnik wiewiórczego świata, więc potrzebujemy scriptObjHandle. Ostatnia trudność będzie polegać na uzyskaniu powyższego. Następnie pobieramy wartość event (warto zauważyć, że zdarzenie jest traktowane jak zmienna, mimo, że jest funkcją) i tworzymy wywołanie funkcji func na rzecz obiektu o. I już!

Jeszcze magiczne scriptObjHandle. Aby je uzyskać, musimy do obiektu dodać wiewiórkowy konstruktor:

static int construct(HSQUIRRELVM v)
{
	StackHandler sa(v);
	MapEvent* self = new MapEvent();
	self->scriptObjHandle = sa.GetObjectHandle(1);
	sq_addref(SquirrelVM::GetVMPtr(), &self->scriptObjHandle);
	return SqPlus::PostConstruct(v, self, release);
}

SQ_DECLARE_RELEASE(CMapEvent)

Ta magiczna funkcja sprawia, że scriptObjHandle staje się pomostem ze świata C++ do świata wiewiórek.

Mam nadzieję, że lektura rozjaśniła komuś umysł; i uświadomiła, że wiewiórki wcale nie są tak złe, na jakie wyglądają.

Tomasz Dąbrowski

Się czołg toczy

December 9th, 2007

Z wolna się czołg toczy.

Czoł(gi)em!

Pewnie brzmi to dziwnie, bo — w zasadzie — to większość czołgów się nie toczy, ale nasz czołg jest inny. O tym, co należy zrozumieć przez ,,nasz”, czyli w gruncie o tym, czym jest KNI dowiecie się Państwo w którymś z kolejnych odcinków. W tej chwili postacią numer zero jest bowiem właśnie nasz niesamowity czołg.

Widzieliście kiedyś czołg, który się toczy? Czołg, który miast oklepanych rozwiązań w rodzaju gąsienic posiada innowacje w rodzaju sferycznych operatorów ruchu poziomego? Czołg, który po wywrotce o 360 stopni jest w stanie ruszyć dalej, niestrudzony — chwilowymi tylko — niepowodzeniami? Zapewne nie. Czołg, który jest w stanie niepostrzeżenie zinfiltrować bazę wroga? Głupie pytanie.

O wspaniałym projekcie, jakim jest bez wątpienia KNAT można by mówić długo i szeroko. Jednak z pewnością zgoła bardziej interesujące może być poznanie go w praktyce. Zwłaszcza spojrzenie od kuchni — a może nawet od łazienki — na roboczy szkic projektu może być inspirujące. Zapraszam więc do wspólnego przeżywania magicznych chwil [przejdź do sznureczka].

Gdy już oczom naszym ukaże się bezmiar fizycznej wyobraźni, do działania przystąpić możemy za pomocą nieśmiertelnej kombinacji klawiszy WSAD. W boju dopomoże nas także SPACJA. Widok na pole krwawej walki zmieniamy klawiszami od F1 do F3.

Naszym zadaniem jest siec, ciąć i rąbać kto żyw i nieżyw. Przy okazji możemy sprawdzić się jako kierowca ekstremalnych pojazdów w ekstremalnych warunkach. Dobra zabawa dla całej rodziny — gwarantowana!

Tomasz Dąbrowski

Sznurki:

Przed zażyciem przeczytaj readme bądź skonsultuj się z lekarzem, psychiatrą, bądź chociaż zaufanym administratorem systemu UNIX.