ma kau se jinvi mi la lojban.…czyli co sądzę o lojbanie.
Powracam do tematu z notki sprzed kilku tygodni, mimo swoich przeczuć, że nie jest to wcale coś, na co wszyscy z napięciem oczekiwali ;-) Spróbuję jednak przekazać porcję swoich nieco głębszych przemyśleń na temat lojbanu po tym, jak poświęciłem trochę więcej czasu na przyjrzenie się temu językowi. Witam więc tych nielicznych, którzy chcą się z nimi zapoznać :]
Dla osoby zupełnie niewtajemniczonej najbardziej rzucającą się w oczy cechą tego języka jest to, że zdecydowana większość słów jest zupełnie niepodobna do swoich odpowiedników w innych językach – mimo że zostały one skonstruowane właśnie z nich. Chociaż na początku może to się wydawać minusem, w ostatecznym rozrachunku ma chyba więcej zalet niż wad. To dlatego, że – jak pisałem już wcześniej – w lojbanie nie ma tradycyjnych części mowy ani konstrukcji gramatycznych. Nie ma więc uzasadnienia dla tego, żeby słownictwo było w nim podobne do innych języków; to mogłoby wręcz być mylące. Zgadza się to też z założeniem kulturowej bezstronności („znajome” słowa byłyby faworyzowaniem języków europejskich, jak to jest chociażby w esperanto).
Drugim widocznym atrybutem jest wszechobecność krótkich, zwykle dwu- lub trzyliterowych słów, często z apostrofem w środku (czytanym jako ‘h’). To cmavo (wym. szmawo), słowa strukturalne. W nich zawarta jest zdecydowana większość gramatyki lojbanu. Pełnią one mnóstwo różnorodnych funkcji, będąc odpowiednikami zarówno przyimków, spójników czy zaimków, jak i rodzajników, znaków przestankowych, a nawet podkreśleń czy cudzysłowów (w lojbanie wszystko to ma postać słów). Istnieje więc całe multum różnych cmavo, z których wiele jest logicznie zorganizowanych w grupy mające części wspólne, jak np. ze’i/ze’a/ze’u – krótki/średni/długi odcinek czasu. Nie dotyczy to jednak wszystkich, zatem nierzadko pomyłka o jedną literę może skutkować zupełnie innym znaczeniem wypowiedzi lub jej niepoprawnością.
Jednak cmavo są na szczęście w dużej mierze nieobowiązkowe, bo w wielu przypadkach służą uszczegółowieniu tego, co wynika z kontekstu. Jedną z takich kontekstowych informacji jest często czas (teraźniejszy, przeszły, itp.), który w lojbanie – w przeciwieństwie do wielu innych języków – nieczęsto trzeba podawać wprost, bo reguły gramatyki tego nie wymuszają. Jeśli jednak ktoś sobie życzy sobie, aby go doprecyzować, to ma do dyspozycji bardzo ciekawe rozwiązanie, rozdzielające problem na dwie części. Pierwsza odpowiada relacji czasowej między aktualnym punktem a zdarzeniem – czyli temu, czy mówimy o przeszłości (pu), teraźniejszości (ca) czy przyszłości (ba). Druga (zwana niekiedy aspektem) określa, o którym punkcie osi czasu wokół zdarzenia mówimy, np.: przed jego początkiem (pu’o), dokładnie na początku (co’a), w środku jego trwania (ca’o), dokładnie na końcu (mo’u) lub po jego zakończeniu (ba’o). W sumie daje to kilkanaście kombinacji, z których większość da się odnieść na przykład do czasów w języku angielskim (tenses), ale które jednocześnie są o wiele łatwiejsze do zrozumienia.
Trzecia istotną właściwością lojbanu jest fakt, że znacznie częściej niż w innych językach mówimy w nim o „nie-rzeczach”, takich jak zdarzenia, informacje, wypowiedzi, właściwości, ilości, koncepcje, procesy, stany, itp. Nazywa się je tu ogólnie abstrakcjami i regularnie występują one jako argumenty do predykatów (czyli selbri). Istnieją oczywiście ich odpowiedniki w innych językach, lecz zwykle przyjmują one postać zdań podrzędnych (‘to, że…’), jako że rzeczownikami nazywa się w większości rzeczy lub osoby. W lojbanie tak nie jest (no bo przecież rzeczowników nie ma :)) i w sumie wychodzi mu to na dobre. Można rzecz jasna wskazać dziwne „skutki uboczne” – nie można na przykład chcieć (djica) rzeczy jako takich, a jedynie zdarzeń z nimi związanych – ale ogólnie obecność abstrakcji korzystnie wpływa na specyfikowanie tego, o czym tak naprawdę mówimy.
C++ i tablice asocjacyjneZwykłe tablice (jednowymiarowe) w "normalnych" językach programowania to po prostu ciągłe obszary pamięci, do których pierwszego elementu posiadamy odwołanie. Dostęp do kolejnych polega na korzystaniu z arytmetyki wskaźników, więc niedziwne jest, że takie tablice indeksuje się wyłącznie liczbami - i to z określonego zakresu.
Ci, którzy programowali w na przykład w Pythonie lub PHP znają jednak koncepcję tablic asocjacyjnych, dla których to ograniczenie nie obowiązuje. W C++ do ich symulowania używa się często map z STL-a:
Jest to oczywiście odpowiednie przeciążenie operatora [], a map jest rodzajem pojemnika. Skoro jednak ma to udawać tablicę, to dobrze by było, żeby narzut na obsługę dostępu do elementów nie różnił się zbytnio od tego z prawdziwych tablic. A ten, jak wiemy, jest stały.
Jak to osiągnąć?... Przede wszystkim powinniśmy - jeśli tylko możemy - nie korzystać z kontenera map. Problem z nim polega na tym, że poświęca on dużo uwagi sortowaniu elementów według ich kluczy ("indeksów"). Gdy klucze te są złożone - bo są nimi np. łańcuchy znaków - może to zająć trochę czasu i jednocześnie nie dawać nam żadnej korzyści. Dla tablicy asocjacyjnej najważniejsze są bowiem operacje wyszukiwania wartości ("indeksowania") i dodawania nowych elementów, względnie ich usuwania. To one muszą działać szybko; wewnętrzny porządek elementów nie jest dla nas istotny.
Dlatego też lepsza jest mapa korzystająca z dobrodziejstw mechanizmu haszowania. W Visual C++ (podobnie zresztą jak w GCC) takie cudo dostępne jest od dawna jako klasa hash_map. Długo było ono niestandardowym rozszerzeniem biblioteki STL, ale wraz z nową wersją standardu staje się ono jego częścią. Poza tym "od zawsze" istnieje też rozwiązanie z Boosta w postaci klasy unordered_map. Przyjemną cechą wszystkich tych implementacji jest niemal jednolity interfejs, tożsamy ze standardową klasą map.
Oprócz używania właściwego pojemnika powinniśmy też wiedzieć, jak go z niego korzystać - a dokładniej mówiąc, czego unikać. Felerną operacją jest próba dodania elementu poprzez zwykłe indeksowanie z przypisaniem:
Skutkuje to stworzeniem na chwilę obiektu domyślnego dla danego typu wartości, a potem jego zastąpieniem (przypisaniem) tym właściwym, który ma się w 'tablicy' znaleźć. W przypadkach, gdy konstruktor i operator przypisania nie są trywialne, będzie to strata na wydajności. Powinniśmy więc używać raczej metody insert do wstawiania. Może jest to niezupełnie "tablicowe", ale cóż - albo rybka, albo akwarium ;P
Jak się robi screenyW robieniu zrzutów ekranowych (screenshotów) nie ma, wydawałoby się, nic nadzwyczajnego. Wciskamy po prostu klawisz Print Screen i obraz ekranu ląduje nam w systemowym Schowku, po czym możemy go użyć w dowolny sposób. Przynajmniej tego właśnie się spodziewamy zazwyczaj.
W rzeczywistości robienie screenów to dość śliski temat, jeśli mamy do czynienia z czymś więcej niż zwykłymi aplikacjami okienkowymi. Dotyczy to chociażby pełnoekranowych gier, korzystających z bibliotek graficznych typu DirectX i OpenGL. Wykorzystują one mechanizm znany jako hardware overlay, pozwalający - w skrócie - na ominięcie narzutu systemu operacyjnego związanego z zarządzeniem oknami wielu aplikacji na współdzielonym ekranie monitora. Skutkiem ubocznym jest między innymi to, że wbudowany w system mechanizm tworzenia zrzutów ekranu staje się w tej sytuacji bezużyteczny, jeśli chcemy wykonać screenshota tego rodzaju aplikacji.
Skąd w takim razie biorą się screeny, a nawet całe filmy ze współczesnych gier?... Cóż, istnieją oczywiście metody pozwalające na przechwytywanie obrazów generowanych przez aplikację - można bowiem skłonić ją do wywoływania naszej własnej wersji metody IDirect3DDeviceN::Present zamiast tej wbudowanej :) Brzmi to pewnie tajemniczo i nieco hakersko, ale tak mniej więcej działają programy do przechwytywania wideo typu Fraps.
Do zwykłych screenów zazwyczaj jednak przewiduje się odpowiednie rozwiązania w samych grach. Jednym ze sposobów jest, w przypadku wykrycia wciśnięcia klawisza odpowiadającego za screenshoty (czyli zwykle Print Screen), wyrenderowanie sceny najpierw do tekstury, a ją potem na ekran. Rzeczoną teksturę można później zapisać do pliku.

Czy to dobre rozwiązanie? Otóż nie, i to aż z trzech powodów. Stosunkowo najmniej ważnym jest to, iż implementacja może być kłopotliwa, bo wymaga dobrania się do początku i do końca potoku renderingu w aplikacji z kolejnym render targetem. Bardziej istotny jest brak multisamplingu (a więc wygładzania krawędzi wielokątów znanego jako anti-aliasing) w powstałym obrazku. Nie będzie on więc wyglądać zbyt ładnie, już nie wspominając o tym, że nie będzie on odpowiadał temu, co widzimy na ekranie.
Jak więc należy robić screeny? Dokładnie tak, jak nam podpowiada intuicja: należy wziąć to, co widać na ekranie - czyli zawartość bufora przedniego (front buffer) i zapisać to do pliku. W DirectX mamy do tego metodę urządzenia o nazwie GetFrontBufferData, która pobiera nam to jako powierzchnię:
Ważne jest, by pamiętać o właściwych rozmiarach powierzchni - takich, jakie ma bufor przedni. W trybie pełnoekranowym odpowiada to buforowi tylnemu, ale dla aplikacji okienkowej wcale nie musi, jeśli rozmiar jej okna możemy zmieniać.
W OpenGL podobną rolę spełnia do GetFrontBufferData spełnia funkcja glReadPixels. Z zapisaniem pobranego obrazu może być tylko "nieco" trudniej ;-)
Cokolwiek, czyli Boost.AnyZasadniczo w C++ zmienne mają jednoznacznie określone typy, znane w czasie kompilacji. Istnieją oczywiście pewne mechanizmy, które zdają się tę zasadę lekko naciągać (dziedziczenie i polimorfizm, szablony), ale bez znaczącej nieścisłości można przyjąć, że jest ona prawdziwa. W języku kompilowanym nie jest to zresztą nic nadzwyczajnego.
Z kolei w językach skryptowych i interpretowanych dość powszechne jest używanie zmiennych bez określonego (tj. jawnie podanego w deklaracji) typu. To, co obecnie przechowuje liczbę, za chwilę może zawierać ciąg znaków czy odwołanie do obiektu i nie ma z tym specjalnego problemu. Typy w takich językach oczywiście istnieją, ale mają je wartości, nie zaś zmienne.
Czasami coś podobnego - czyli zmienna mogąca przechowywać wartości różnego rodzaju - przydaje się i w C++. Wtedy niektórzy przypominają sobie o void*, ale "ogólny wskaźnik na coś" rzadko jest w tym przypadku szczytem marzeń. Jego podstawową wadą jest to, że wymaga on przechowania gdzieś poza nim informacji o docelowym typie wartości, na którą pokazuje - jeśli chcemy ją później wykorzystać, rzecz jasna. Równie poważną jest fakt, że mamy tu do czynienia z wskaźnikiem; pamięć, na którą on pokazuje, musi być kiedyś zwolniona.
Dlatego też lepszym rozwiązaniem jest biblioteka Any z Boosta, umożliwiająca korzystanie ze zmiennych "typu dowolnego". Reprezentuje go klasa boost::any, mogąca przechowywać wartości prawie dowolnego rodzaju (wymaganiem jest głównie to, by ich typy posiadały zwykły konstruktor kopiujący):
Ponieważ jednak jest to wciąż C++, a nie język skryptowy, do wartości zapisanych w obiekcie any bezpośrednio dostać się nie da. W szczególności nie można liczyć na niejawne konwersje powyższych zmiennych do typów int, string czy Foo. Zamiast tego korzysta się ze specjalnego operatora rzutowania any_cast, który pozwala na potraktowanie wartości zapisanej w any zgodnie z jej właściwym typem:
W przeciwieństwie do void*, próba jej reinterpretacji na inny typ danych skończy się wyjątkiem. Nie trzeba jednak polegać na jego łapaniu, jeśli docelowego typu nie jesteśmy pewni: boost::any pozwala też pobrać jego type_info (tj. to, co zwraca operator typeid) bez dokonywania rzutowania.
I to w sumie wszystko jeśli chodzi o tę klasę, a jednocześnie i całą bibliotekę. Mimo swej niewątpliwej prostoty jest ona bardzo przydatna tam, gdzie potrzebujemy zmiennych przechowujących różne rodzaje wartości. Warto więc się z nią zapoznać :)
Tower OffenceZamieszczam grę stworzoną przez moją drużynę (Rzeźnicy Inc. :]) w ramach konkursu Compo, który odbył się w zeszłą niedzielę. Jak co roku trwa on około 7 godzin, podczas których należy stworzyć w zasadzie kompletną grę (z dokładnością do ewentualnych, wcześniej przygotowanych frameworków) na zadany temat. Nasza praca zajęła drugie miejsce, przegrywając o włos - w dodatkowym głosowaniu publiczności - ze zwycięską.
Gra jest wariantem 'Tower Defence', czyli obrony przed nadciągającymi hordami wrogów za pomocą strategicznie poustawianych wież. Najważniejszą różnicą jest to, że mamy tutaj gracza, który sam może się po planszy poruszać i strzelać do stworków (i jednocześnie musi ich unikać); stąd zresztą nazwa Tower Offence :) Ponadto budowanie wieżyczek jest możliwe tylko w polach sąsiadujących z aktualną pozycją gracza. Ogólnie chcieliśmy, żeby rozgrywka była bardziej dynamiczna niż w zwykłym TD, a jednocześnie eliminowała jeden bardzo frustrujący element tego rodzaju gier: sytuację, gdy jeden stworek przebija się przez naszą linię obrony, a my nie możemy nic zrobić, jak tylko patrzeć jak powoli idzie w kierunku naszej bazy... Tutaj możemy wtedy wziąć sprawy we własne ręce i po prostu go ustrzelić :)
[2010-04-11] Tower Offence (5,7 MiB, 196 ściągnięć)
Zapraszam więc do rzucenia okiem na tę produkcję. Niech dodatkową rekomendacją będzie to, że mi osobiście gra się w nią bardzo przyjemnie - a z gatunkiem TD bynajmniej nie przepadam ;P
Do działania gra wymaga aktualnych runtime'ów DirectX-a.
Na Lodowym Tronie
Nieczęsto pozwalam sobie na aż taki off-topic, ale w tym przypadku uważam, że spokojnie mogę być usprawiedliwiony. Jeśli bowiem ktoś grał swego czasu w Warcrafta 3, to pewnie pamięta, że kampania w tej grze (a właściwie dodatku do niej) kończyła się powstaniem jednego z najpotężniejszych antagonistów, jakich świat gier tej doskonałej serii kiedykolwiek widział - w polskiej wersji znanego jako Król Lisz, a w oryginale jako Lich King. Jego długi cień rzucał się potem na pierwszą edycję kolejnej gry z serii, czyli World of Warcraft. Aż w końcu teraz możliwe jest stanięcie mu naprzeciw i rozprawienie się z nim... miejmy nadzieję, że raz na zawsze.
I to właśnie udało mi się zrobić dzisiaj, co widać na obrazku powyżej. Ci, którzy nie mają specjalnego pojęcia o serii Warcraft mogą zareagować co najwyżej wzruszeniem ramion. Co bardziej hardcore'owi gracze WoW-a mogą z kolei wskazywać, że to przecież tylko wersja 10-osobowa, na normalnym poziomie trudności i w dodatku o 10% łatwiejsza niż oryginalnie.
Ale ja się tym zupełnie nie przejmuję :) Jak na moje ograniczone możliwości czasowe oraz niewielką zdolność do wciskania klawiszy WSAD oraz przycisków myszki naraz, uważam to za satysfakcjonujące osiągnięcie. A że ma też ono dla mnie pewne znaczenie symboliczne, pozwalam sobie podzielić się z nim ze światem.
IGK nr 7
W tym nieprzewidywalnym, szybko zmieniającym się i zaskakującym niespodziewanymi wydarzeniami świecie są jednak pewne rzeczy, które się nie zmieniają - i mam nadzieję, że zmieniać się nie będą. Jedną z nich jest to, że co roku w okolicach początku wiosennego kwietnia koderzy z Warsztatu i okolic zjeżdżają do Siedlec, by wziąć udział w konferencji poświęconej tworzeniu gier komputerowych - czyli IGK. To już sześć lat minęło od pierwszej takiej imprezy i z początku chyba tylko nieliczni wyrażali nieśmiałe nadzieje na to, że stanie się ona stałym punktem w ich kalendarzu...
A jednak to się kręci. Niniejszą notkę piszę właściwie na gorąco, jako że tegoroczna edycja nr 7 zakończyła się zaledwie przed kilkoma godzinami. Oficjalnie. Bo tak naprawdę IGK trwa tak długo, jak długo jej uczestnicy - pasjonaci kodowania gier - chcą się spotykać i spędzać ze sobą czas, niekoniecznie tylko na sali wykładowej, słuchając kolejnych referatów z interesujących ich dziedziny. Zdecydowanie nie tylko tam :)
W tym roku referaty dopisały ilościowo i jakościowo. Dwie odbywające się równolegle sesje plenarne oferowały ciekawe wykłady z różnych obszarów szeroko pojętej 'inżynierii gier komputerowych', z których zapewne każdy mógł wybrać dla siebie coś pasującego - i to w kilku sztukach. Częściowo pewnie przyczynił się do tego zapał organizatorów (koła naukowego studentów informatyki Akademii Podlaskiej), którzy sami przygotowali kilka prezentacji. Nie obyło się też bez przedstawicieli firm informatycznych; aczkolwiek obiecane wystąpienie Techlandu ostatecznie się nie odbyło ;(
Ale referaty to dla wielu tylko poboczny fragment części oficjalnej IGK. Nie może tu zabraknąć Compo, czyli konkursu czteroosobowych grup piszących gry na wybrany temat w ciągu kilku (ok. 7-8) godzin trzeciego dnia konferencji. Udział w nim jest niezwykle pouczającym, a jednocześnie dość nietypowym doświadczeniem (o ile ktoś nie jeździ na imprezy demoscenowe, rzecz jasna :]). Jest też bardzo satysfakcjonujący wtedy, gdy w wyniku włożonych podczas niego wysiłków powstaje gotowa, grywalna gra. Coś takiego właśnie stało się moim udziałem w tym roku. A że przy okazji udało się nią zgarnąć nagrody za drugie miejsce - to niewątpliwie miły, ale tylko dodatek :)
Nieodzowną częścią IGK jest natomiast, khm... "wieczorek integracyjny" (czytaj: LAN-party) po zakończeniu programu oficjalnego. To też świetna okazja, by niemal w czasie rzeczywistym podzielić się wrażeniami - co też właśnie czynię. Ale ponieważ czas tu jest cenny, a naprędce sklecona topologia sieci nie do końca stabilna (co oczywiście tylko dodaje jej klimatu), to na tej krótkiej impresji pozwolę sobie zakończyć. W końcu Quake 3 i Starcraft same się w siebie nie zagrają :>
(Zdjęcia dzięki uprzejmości Rega)