Gry, które są dobre, potrafią przykuć uwagę przeciętnego gracza na kilka czy kilkanaście miesięcy. Gry wyjątkowe są pamiętane nawet po kilku latach i ciągle mają zagorzałych zwolenników. Ale tylko nieliczne produkcje obrastają taką legendą, że po jakimś pojawiają się serwisy podobne do Diablo Evolution. Można tam obejrzeć w szczegółach, jak wyglądały kolejne stadia (alfa, beta, itd.) powstawania kultowego cRPG Diablo. Jeśli ktoś pamięta, jaka nowatorska była w swoim czasie ta gra, nie powinien być zdziwiony, że jej maniacy tworzą podobne strony.
Samo wspominanie przeszłości mało jednak wnosi i dlatego o wiele bardziej odpowiadają mi inicjatywy reanimacji starych gier. Taka operacja może być tylko dostosowaniem jej do uruchamiania na aktualnym sprzęcie i we współczesnych systemach operacyjnych – czego przykładem jest choćby emulator DOSa DOSBox. Niekiedy jednak fani mogą pokusić się o coś więcej…
Świetnym przykładem takiej udanej reanimacji jest OpenTTD – remake doskonałej gry ekonomicznej Transport Tycoon Deluxe sprzed, bagatelka, 12 lat. Muszę przyznać, że mam do niej ogromną słabość, gdyż TT było jedną z pierwszych ‘prawdziwych’ gier (czyli żadnych tam platformówek), w którą miałem okazję grać. Ahh, stare dobre czasy ;]
W obecnych czasach oryginalna wersja Transport Tycoon Deluxe zdecydowanie trąciłaby już myszką (bynajmniej nie tą z rolką i przyciskami), lecz jej open source’owa wersja – OpenTTD jest jak najbardziej do przyjęcia. Zmian w stosunku do oryginału jest sporo i obejmują też te techniczne, jak choćby możliwość gry w znacznie wyższych rozdzielczościach niż oryginalne 640×480. Pewnym modyfikacjom uległ też gameplay, ale są one na tyle przemyślane, że absolutnie nie zabijają pierwotnego ducha gry. Aż dziw, że taka subtelność uchowała się w programie rozwijanym jako open source ;P
Jeżeli więc też pamiętasz oryginalny Transport Tycoon, albo po prostu lubisz tego rodzaju starsze produkcje, OpenTTD powinien przypaść ci do gustu. Aby się o tym przekonać, musisz zrobić dwie rzeczy:
Zdarza się, że chcemy napisać coś “na szybko”. Możliwe na przykład, że znaleźliśmy przepis na ciekawy efekt graficzny i chcemy go od razu wypróbować. Albo natrafiliśmy na nietypowy algorytm rozwiązujący jakiś problem i mamy ochotę go wypróbować w akcji. Zależnie od tego, jak bardzo lubimy eksperymentować, takie sytuacje mogą się zdarzać się z różną częstotliwością.
Tradycyjne środowiska programistyczne nie są w tym celu zbyt pomocne, gdyż stworzenie w nich nawet prostego programu wymaga ustawienia przynajmniej kilku opcji, stworzenia katalogu na kilkanaście wygenerowanych przy okazji plików – i tak dalej. Zamiast sedna musimy więc skupić się na nieistotnych szczegółach.
Kiedyś pomyślałem sobie, że dobrze byłoby mieć takie proste środowisko, w którym można by było oszczędzić sobie całego tego zachodu. Gdzie nie trzeba przejmować się takimi sprawami, jak kompilacja, wejście od użytkownika, ustawienie trybu graficznego , właściwa konstrukcja głównej pętli aplikacji, itp.
W zamierzchłych i na szczęście dawno minionych czasach namiastką czegoś takiego było środowisko dla “języka” LOGO, którym to niekiedy zamęczano biedne dzieci w gimnazjach :) W czasach nieco bliższych powstały interpretowane języki programowania z linią poleceń – jak na przykład Python – mogące służyć do wspomnianej ‘niezobowiązującej zabawy’. Fachowo nazywa się ją prototypowaniem.
Ostatnio zaś znalazłem (za sprawą noogi z kanału #warsztat) coś, co wydaje się o wiele bliższe ideałowi. To open source’owe środowisko o ambitnej nazwie Processing, mające nieporównywalnie większe możliwości niż te wspomniane wyżej. Język programowania w nim zawarty jest bardzo przyjemny i ma “normalną” składnię z wyodrębnionymi blokami kodu – jak C(++/#), PHP czy Java – i możliwości zbliżone do ww. języków. Programy napisane w oparciu o Processing są krótkie i zwięzłe, bowiem większość potrzebnych narzędzi, takich jak inicjalizacja grafiki (opartej o OpenGL) czy podpięcie wejścia klawiatury i myszy, zostało już zintegrowanych. Naszym zadaniem jest tylko wypełnienie treścią dość oczywistych funkcji jak
draw
, setup
czy mousepressed
; niekoniecznie zresztą wszystkich.
Od strony technicznej Processing też prezentuje się całkiem znośnie. Jego IDE ma aczkolwiek jeden bolesny mankament: jest napisane w Javie i wszyscy wiemy, co to oznacza dla szybkości jego uruchamiania :) Cały system jest zresztą oparty właśnie o Javę, ale to staje się akurat bardziej zaletą niż wadą. Jedną z nich jest ładny język programowania, a inną to choćby możliwość bezbolesnego eksportu napisanych programów do postaci zwykłych plików wykonywalnych dla różnych systemów operacyjnych. Można też tworzyć z nich aplety do umieszczenia na stronach WWW, co pokazuje choćby ten atraktor Lorenza.
Aplikacje wygenerowane w ten sposób na pewno nie zastąpią tych pisanych z użyciem prawdziwych kompilatorów, ale podejrzewam, że nie taki jest cel. Chodzi przede wszystkim o tę ‘piaskownicę’, w której można bezboleśnie wypróbowywać nowe idee.
Nikt nie jest nieomylny – zwłaszcza jeżeli chodzi o programistów. Błędy w kodzie zawsze były, są i będą się pojawiały z mniejszą lub większą regularnością. Najważniejsze więc to umieć sobie z nimi radzić. Według mnie jest to tak samo ważna umiejętność jak posługiwanie się jakimś językiem programowania czy biblioteką graficzną. Tyle że jest ona o wiele przydatniejsza, bo znacznie bardziej uniwersalna.
Często – przede wszystkim w firmach tworzących oprogramowanie – usuwaniem błędów zajmują się wyspecjalizowane osoby, czyli testerzy. Jak wiadomo w każdym programie jest przynajmniej jeszcze jeden błąd, a przy użyciu odpowiednich technik można wyłapać przynajmniej te, z którymi miałaby szansę zetknąć się przynajmniej pewna część końcowych użytkowników.
Tego rodzaju błędy muszą być zwykle specjalnie wyszukiwane, jednak na co dzień koderzy spotykają się z takimi, które same ‘znajdują’ programistów. Mówiać wprost, często (o wiele za często) zdarza się, że po prostu coś nie działa – albo nie działa tak, jak powinno.
Co wtedy zrobić? Osobiście stosuję poniższe metody. Uszeregowałem je w kolejności od najmniej do najbardziej drastycznej, a jednocześnie od najmniej do najbardziej skutecznej. Kryteriami rosnącymi w dół listy jest także czasochłonność oraz stopień desperacji programisty :) A rzeczone sposoby są następujące:
x
zamiast y
.Jeżeli wszystko zawodzi, pozostaje jeszcze ostatnia deska ratunku, czyli szukanie pomocy z innych źródeł, np. na forach. Z praktyki widać jednak, że często preferowana kolejność postępowania jest dokładnie odwrotna. A to na dłuższą metę nie jest to rozsądne, ponieważ osobą najlepiej przygotowaną do znalezienia błędu w kodzie jest sam jego autor.
Świat były aczkolwiek o wiele piękniejszy, gdyby konieczność takich poszukiwań nie zdarzała się zbyt często :)
Co można powiedzieć o czymś tak prozaicznym, jak zwykły przycisk? Ano to, że przycisk jest po to, aby go wciskać :) Od strony użytkownika wygląda to więc bardzo prosto – może nawet zbyt prosto, co czasami kończy się źle, jeśli nie czytamy komunikatów przed pochopnym wciśnięciem OK.
Kwestia oprogramowania takiego tworu jak przycisk, aby zachowywał się zgodnie z oczekiwaniami, jest już jednak trochę trudniejsza.
Przy okazji przycisku wychodzi bowiem kwestia tak zwanego mouse capture, które to pojęcie nie ma dobrego tłumaczenia na język polski. Pozwala ono użytkownikowi rozmyślić się i nie dokonać kliknięcia nawet wtedy, gdy zdążył już wcisnąć przycisk myszki. Wystarczy, że – nie puszczając go – odjedzie kursorem poza obszar kontrolki i tam puści przycisk myszy. Wówczas zarówno zdarzenie wciśnięcia, jak i puszczenia przycisku myszy będzie zarejestrowane, lecz to najważniejsze – kliknięcia – już nie.
Ten dość abstrakcyjny byt mouse capture może być przez kontrolkę posiadany lub nie. Jeżeli kontrolka go posiada, wtedy otrzyma ona informacje o zdarzeniach myszy nawet gdy kursor znajduje się poza jej obszarem. Dzięki temu kontrolka przycisku może być poinformowana o tym, że użytkownik zrezygnował z jej wciśnięcia.
Pozostaje jeszcze drobna kwestia graficzna. Otóż w Windows kontrolka przycisku “wyciska się”, jeżeli odjedziemy kursorem poza jej obszar (oczywiście cały czas trzymając wciśnięty przycisk myszki). Kiedy znów wrócimy, wciśnie się ponownie, i tak dalej (ciekawym zalecam własnoręczne eksperymenty ;]). Osobiście nie sądzę, żeby takie zachowanie było bardzo intuicyjne, bo chyba bardziej by mi odpowiadało, gdyby kontrolka pozostała cały czas wciśnięta.
Niestety, nie ja ustanawiam standardy interfejsu, więc postanowiłem się dostosować i zaimplementować windowsowe rozwiązanie :)
W ramach kontynuacji przeglądu nietypowych konstrukcji językowych – który to nieopatrznie rozpocząłem, zajmując się pętlami w Pythonie – obejrzymy sobie jeden z elementów języka Object Pascal. Są to referencje do klas, zwane też czasem metaklasami.
Ten dziwny twór działa jak odwołanie wskazujące na klasę jako typ, a nie na jej konkretny obiekt. Deklaruje się go mniej więcej w taki sposób:
[delphi]type TClass = class of TObject;[/delphi]
Zmienne należące do tak zdefiniowanego typu TClass
mogą pokazywać na wszystkie klasy dziedziczące po TObject
. Innymi słowy, takie zmienne są swego rodzaju dynamicznymi aliasami na nazwy klas; używając ich, nie musimy nawet wiedzieć, z jakiego typu klasą konkretną mamy do czynienia. Przypomina to oczywiście normalny dynamiczny polimorfizm obiektów, osiągany przy pomocy funkcji wirtualnych. Tutaj jest to niejako dynamiczny polimorfizm samych klas.
Użycie takiego typu referencyjnego może wyglądać choćby tak:
[delphi]type TFoo = class(TObject) // klasa dziedzicząca po TObject
// …
end;
var
AnyClass : TClass; // zmienna będąca referencją do klasy
AnyObject : TObject; // zwykłe odwołanie do obiektu
begin
AnyClass := TFoo; // referencja pokazuje na klasę TFoo
AnyObject := AnyClass.Create; // tworzy obiekt klas TFoo przy pomocy referencji
end;[/delphi]
Ten przykład pokazuje, że w Delphi przy pomocy referencji do klas możliwe jest łatwe zrealizowanie wzorca wirtualnego konstruktora. Nie musimy bowiem wiedzieć, na jaką klasę wskazuje referencja, a utworzony obiekt możemy “odebrać” posługując się zmienną typu bazowego (tutaj TObject
).
Co na to C++? Nie ma tam naturalnie podobnej konstrukcji. Zbliżone do niej – w sensie możliwości korzystania z jakiegoś typu bez wiedzy, czym on naprawdę jest – są parametry szablonów. Podstawowa różnica polega jednak na tym, że szablony są rozwijane w trakcie kompilacji i “wartości” tych parametrów są niezmienne.
Nie wiem, czy można w jakiś sposób zaimplementować w C++ metaklasy o funkcjonalności zbliżonej do powyższej. Znając możliwości C++, to całkiem prawdopodobne :) Ich ewentualny brak nie były jednak jakoś szczególnie dotkliwy, gdyż większość ich zastosowań z powodzeniem daje się zastąpić szablonami lub zwykłymi funkcjami wirtualnymi.
Dla odmiany zająłem się ostatnio czymś nieco prostszym niż GUI, a mianowicie systemem logującym. To zdecydowanie niezbędne narzędzie, które docenia się zwłaszcza wtedy, kiedy coś innego psuje. Jak dotąd aczkolwiek nic poważnego nie zdążyło się jeszcze, ale w programowaniu jest to, jak wiadomo, tylko kwestią czasu ;)
Do logowania istnieje mnóstwo podejść, które różnią się głównie tym, gdzie i jak zapisujemy nasze informacje. Bo jeśli o to, co mamy logować, to odpowiedź jest prosta: wszystko albo jak najwięcej.
Dość obszerny przegląd możliwych wyjść loggera przedstawił TeMPOraL w jednej swoich notek. Jakkolwiek długa ta lista by nie była, pewne jest jedno: na pewno nie może być ona zapisana “na sztywno” w kodzie głównej klasy systemu logującego. Same ‘wyjścia’ należy bowiem opakować w osobne klasy, wywodzące się ze wspólnej bazy i wykorzystać zalety polimorfizmu. W ten sposób można kierować komunikaty zarówno na konsolę, do zwykłego pliku tekstowego, jak i do funkcji OutputDebugString
.
Pomyślałem jednak nad rozszerzeniem tego pomysłu, który wyraża się w tytułowym równaniu: wyjście = format + ujście. Postanowiłem oto podzielić wyjście logowania na dwie części:
Nie jest to oczywiście żadna rewolucja. Czasami też obie części muszą być do siebie ściśle dopasowane, aby osiągnąć pożądany efekt (np. wypisywanie kolorowych komunikatów w asynchronicznej konsoli Windows). Zwykle jednak można je zestawiać dowolnie i otrzymywać ciekawe rozwiązania. Najważniejsze, że tą dodatkową elastyczność da się osiągnąć małym kosztem.
Programy rezydujące w zasobniku systemowym (system tray) są zawsze wyposażone w menu kontekstowe. Zwykle pojawia się ono po kliknięciu prawym przyciskiem myszy na ikonkę aplikacji i zawiera najczęściej używane opcje programu – co czasem oznacza też “wszystkie opcje programu” :)
Czasami jednak to menu jest uparte i jeśli nie zdecydujemy się na wybranie żadnej pozycji, ono pozostaje otwarte nawet mimo tego, że już dawno zajęliśmy się czymś innym. Zupełnie jakby Windows “zagapił się” i przeoczył moment, gdy menu utraciło fokus (wtedy właśnie powinno było zniknąć). Bywa to o tyle denerwujące, że takie osierocone menu przykrywa wszystkie inne okna w systemie.
Niekiedy pomaga wtedy ponowne kliknięcie w ikonkę programu. Jeżeli jednak to nie działa, trzeba jakoś przywrócić feralnemu menu utracony fokus, aby mogło go ponownie stracić – i, miejmy nadzieję, zniknąć na dobre. Jak to zrobić bez uaktywniania żadnej pozycji menu? Są co najmniej dwa sposoby:
Kiedy następnie klikniemy w jakiekolwiek miejsce poza menu, oporna lista powinna się w końcu schować.