System GUI #5 – Okno

2007-08-28 17:43

Dawno temu w firmie Xerox wymyślono, że interfejs użytkownika można zapakować w zestaw prostokątnych, nakładających się na siebie okien. Pomysł okazał się niezwykle trafiony i zaowocował nawet wielce kreatywną nazwą pewnego systemu operacyjnego ;) Od tamtej pory trudno sobie wyobrazić zaawansowane UI posługujące się czymś innym niż właśnie zestawem okien.

Nie jest trudno otrzymać na ekranie puste okno. W środowiskach RAD – w rodzaju Visual C# czy Delphi – mamy je najczęściej dane automatycznie, gdy tworzymy nowy projekt. W przypadku programowania niewizualnego (jak np. z użyciem czystego Windows API) sprawa jest nieco bardziej skomplikowana, ale i tak zamyka się w nie więcej niż kilkudziesięciu linijkach.
Takie puste okno wydawać się może mało interesujące czy wręcz zbyt oczywiste, aby zwracać na nie uwagę. Rzadko zwracamy uwagę na to, że ten pozornie trywialny prostokąt sam w sobie potrafi bardzo wiele. Wśród typowych możliwości mamy chociażby:

  • przesuwanie za pomocą przeciągania za pasek tytułu
  • zmianę jednego z wymiarów poprzez przeciąganie brzegów
  • zmianę obu wymiarów poprzez przeciąganie rogów okna
  • Przeciąganie za pasek tytułu okna Zmiana jednego z wymiarów okna Zmiana obu wymiarów okna Przyciski sterujące oknem

  • minimalizacja, maksymalizacja i zamykanie okna przyciskami na pasku tytułu

Uzyskanie podobnej funkcjonalności, zaczynając od zera, jest bardziej pracochłonne niż może się wydawać. W moim przypadku otrzymanie czegoś, co przypomina w pełni funkcjonalne i interaktywne okno, zamknęło się w ok. dwóch tysiącach linijek kodu – nie licząc oczywiście modułu grafiki 2D, potrzebnego do rysowania okien.
To całkiem sporo. Skutkiem ubocznym tej pisaniny jest też to, że obecnie patrzę na stare poczciwe okna systemu Windows z nieco większym respektem :)

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks

Autopowitanie

2007-08-27 23:01

W programie mIRC - kliencie IRC dla Windows - możemy definiować sobie przeróżne skrypty wywoływane przez komendy rozpoczynające od znaku backslash. Zintegrowany z programem język jest wprawdzie wybitnie toporny składniowo, ale oferuje całkiem spore możliwości.

Pozwala on na przykład definiować sposób, w jaki program ma reagować na pewne zdarzenia. Tego typu odpowiedzi programujemy za pomocą wbudowanego w program edytora skryptów. Wystarczy wybrać z menu Tools | Script Editor i przejść na zakładkę Remote, by wyświetlić i edytować plik remote.ini.
Możemy tam wpisać np. taką linijkę:

on 1:TEXT:hi*:#:/msg $chan hi $nick!

W ten sposób stworzymy prosty mechanizm automatycznego odpowiadania na powitania wypowiadane przez nowo przybyłych na kanał IRC. Bardzo podobnie można też - będąc opem lub halfopem - stworzyć prosty filtr wykopujący userów, którzy w wypowiedziach użyją przekleństw czy innych niedozwolonych słów.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi:
Autor: Xion w Aplikacje, Internet » Dodaj komentarz

Losowanie tabelkowe

2007-08-26 14:35

W niemal każdym programie chociaż raz zdarza się potrzeba, by "rzucić kośćmi" i pozwolić, by wydarzyło się coś losowego. Oznacza to skorzystanie z generatora liczb pseudolosowych, aby uzyskać 'przypadkową' wartość. Nie będzie ona faktycznie losowa, lecz dzięki zastosowaniu matematycznych formuł o dużej nieregularności rezultat może być bardzo zbliżony do ideału. Istnieje oczywiście wiele algorytmów wyznaczania liczb pseudolosowych, różniących się faktyczną przypadkowością uzyskiwanego wyniku.
W różnych językach programowania mamy natomiast odmienne sposoby na uzyskanie liczby 'losowej'. Zwykle najwygodniejszym jest wartość z zakresu [0..1], bo odpowiada to matematycznemu pojęciu prawdopodobieństwa. Aby w C++ uzyskać taki rezultat, wystarczy napisać proste opakowanie na biblioteczną funkcję rand:

float Random() { return rand() / (float)RAND_MAX; }

Mając taki generator, możemy już łatwo sprawdzić, czy "zdarzyło się" coś, czego prawdopodobieństwo znamy:

// doświadczenie losowe z określonym prawdopodobieństwem
bool RandomOccured(float fProbability) { return Random() <= fProbability; }

W ten sposób możemy na przykład rzucać wirtualną monetą.

Przykładowa tabelka ataku w World of WarcraftSprawa się jednak komplikuje, jeżeli możliwych wyników doświadczenia jest więcej, a przy okazji mają one różne prawdopodobieństwa zajścia. Tak się dzieje na przykład w grach RPG, w których konieczne jest obliczanie rezultatów zadanych ciosów (trafienie, pudło, unik, blok, itp.). Skuteczność postaci w walce zależy zwykle od jej statystyk, więc szanse poszczególnych wyników nie są stałe i zmieniają się w trakcie gry.
Dobre generatory liczb pseudolosowych są zaś nierzadko względnie kosztowne obliczeniowo. Dlatego zamiast wykonywać po jednym losowaniu dla każdego możliwego rezultatu (zaszedł - nie zaszedł), znacznie lepiej jest załatwić wszystko jednym losowaniem. Nie jest to trudne:

// doświadczenie losowe z prawdopodobieństwem określonym tabelką
int RandomResult(const float* aProbs, int n)
{
   float fRand = Random();
   
   float fAccum = 0.0f;
   for (int = 0; i <n; ++i)
   {
      // sprawdzamy, czy wylosowana liczba mieści się w zakresie p-stwa
      if (fAccum <= fRand && fRand <fAccum + aProbs[i])
         return i;
      fAccum += aProbs[i];
   }

   // błąd
   return -1;
}

Tak naprawdę liczymy tutaj dla każdego możliwego rezultatu wartość tzw. dystrybuanty. Ale chyba nie warto wnikać w takie teoretyczne szczegóły - grunt, że powyższa metoda działa w praktyce :) Trzeba tutaj jednak pamiętać, aby prawdopodobieństwa sumowały się do 1. Jeśli tak nie jest, można przeskalować wylosowaną liczbę.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks

Bolączki C++ #1 – Pimp(l) my code

2007-08-25 12:13

Kiedy programuje się w języku, który jest - jak to ładnie mówią Amerykanie - 'standardem przemysłowym' w danej dziedzinie, chcąc nie chcąc trzeba się do niego przyzwyczaić. A to oznacza, że musimy nauczyć się żyć z jego wadami, które niekiedy mogą być tylko irytujące, a niekiedy bardzo nieprzyjemne. Ważne, by mieć świadomość ich istnienia i w miarę możliwości sobie z nimi radzić.
C++ jako ustandaryzowany język ma już prawie dziesięć lat, więc lista jego niedoskonałości w porównaniu z nowszymi językami siłą rzeczy staje się coraz dłuższa. Takie listy są jednak w dużym stopniu subiektywne, zatem i ten, który rozpoczynam poniżej, absolutnie nie pretenduje do miana ostatecznej wyroczni :) Na pewno inni usunęliby z niego część pozycji, niektóre uznali za mniej lub bardziej dotkliwe, a także dodali własne propozycje.

Dla mnie sprawą, która w C++ jest powodem największego bólu zębów, jest sposób organizacji kodu zaproponowany w tym języku. Jest on bodaj jedynym mi znanym (poza swoim poprzednikiem C), w którym występuje podział plików z kodem na dwa rodzaje: pliki nagłówkowe (*.h, *.hpp) i moduły kodu (*.cpp). W tych pierwszych teoretycznie umieszczany jest interfejs klas i funkcji, czyli informacje potrzebne do ich użycia w innym miejscu programu. W tych drugich jest zaś zawarta implementacja rzeczonych klas oraz funkcji.
Tyle teorii. W praktyce osiągnięcie idealnej separacji obu tych elementów wymaga sztuczki nazywanej szumnie wzorcem projektowym, o nazwie Pimpl (skrót od private implementation). Wygląda on na przykład następująco:

// -- foo.hpp --
class CFoo_Impl;   // deklaracja zapowiadająca

// właściwa klasa
class CFoo
{
    private:
        CFoo_Impl*  m_pImpl;

    public:
        CFoo();
        ~CFoo();
        void SomeOperation1();
};

// -- foo.cpp --
#include "foo.hpp"

class CFoo_Impl
{
     // implementacja
};

CFoo::CFoo() : m_pImpl(new CFoo_Impl) { /* ... */}
CFoo::~CFoo() { delete m_pImpl; }
void CFoo::SomeOperation1() { m_pImpl->SomeOperation1(); }
// itd.

Wtedy faktycznie nie widać, co siedzi w środku naszej klasy, ale cena takiej hermetyzacji to konieczność stworzenia dodatkowej klasy i przekierowania do niej metod. Może nie jest to dwa razy więcej roboty, ale przynajmniej 10-20%.
Z drugiej strony większość języków nowszych niż C++, jak Java, C# czy Python, w ogóle zna takich pojęć jak 'prototyp funkcji' czy 'deklaracja zapowiadająca'. Tam kod piszemy od początku do końca: funkcję zawsze z jej treścią, a klasę zawsze w całości łącznie ze wszystkimi polami i kodem wszystkich metod. Nie ma podziału na pliki z 'interfejsem' i z implementacją.
A w C++ ten podział istnieje i tak naprawdę służy on tylko i wyłącznie... wygodzie kompilatora. Dzięki temu, że treść plików nagłówkowych jest taka, a nie inna (i np. zawierają one deklaracje prywatnych pól klas, które są przecież częścią implementacji), mogą być one zwyczajnie dołączane do modułów przy pomocy arcyprymitywnej dyrektywy #include. Dla kompilatora jest to najprostsze rozwiązanie, bo może on pracować nad każdym modułem osobno i nie musi się martwić żadnymi niejawnymi zależnościami między plikami. A dla programisty oznacza to wybór między wygodą kodowania, a jakością i "odpornością" stworzonego kodu.

C++ stoi więc w pewnym sensie pośrodku i wcale nie jest to złoty środek. Z jednej strony mógłby pójść w kierunku wyznaczonym przez wspomniane nowsze języki, czyli wprowadzenia jednego typu plików źródłowych, zawierających kod bez podziału na deklaracje i implementacje. To by było jednak mało oryginalne :) Bardziej interesujące byłoby, jak sądzę, polepszenie istniejącego rozwiązania w odwrotnym kierunku; być może automatyczne stosowanie wzorca Pimpl dla każdej klasy jest jednym ze sposobów.
Niestety, patrząc na obecny roboczy szkic standardu C++0x, w którym nic na ten temat nie znajdziemy, nie możemy raczej oczekiwać, że coś tu się zmieni w przewidywalnej przyszłości.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks

I ty zostaniesz transportowcem

2007-08-24 20:55

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.

Screen z gry OpenTTDSamo 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...
Screen z gry OpenTTDŚ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 ;]

Screen z gry OpenTTDW 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 640x480. 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
Screen z gry OpenTTDJeż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:

  1. Zaopatrzyć się w pierwotną wersję gry, czyli Transport Tycoon Deluxe. Możesz ją ściągnać np. z serwisu StareGry.pl, jako że posiada ona status abandonware.
  2. Ściągnąć i zainstalować OpenTTD, podając w trakcie instalacji katalog z TTD.
  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi: ,
Autor: Xion w Gry » Komentarze (3)

Piaskownica dla programistów

2007-08-23 14:08

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.

Screen z przykładu Processing: falujący tekstW 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.
Screen z przykładu Processing: obracający się sześcianOstatnio 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.

Screen z przykładu Processing: kule z oświetleniem kierunkowymOd 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.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi:
Autor: Xion w Programowanie » Dodaj komentarz

Cztery metody debugowania

2007-08-22 11:55

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:

  1. Po owocach ich poznacie. Przyczynę błędu można często znaleźć, patrząc na to, co tak naprawdę się psuje. Najłatwiej jest rzecz jasna wtedy, gdy program kończy się krzykiem komunikatu w rodzaju wyjątku ochrony pamięci czy przekroczenia zakresu tablicy. Trudniej jest, jeśli aplikacja działa stabilnie, lecz produkuje błędne rezultaty. Wówczas wymagana jest dogłębna znajomość własnego kodu oraz pewna doza intuicji, a czasami zdolności profetycznych ;]
  2. Śledzenie kodu - czyli praca krokowa, oferowana przez każde sensowne środowisko programistyczne - to dość skuteczna metoda. Dokładne przyjrzenie się przebiegowi programu oraz rezultatom pośrednim często pomaga zidentyfikować problem. Przy okazji pracy krokowej mamy też większe szanse na znalezienie wrednych literówek, jak np. napisanie x zamiast y.
  3. Pluszowy miś, niezrównany w znajdowaniu błędówPrzegląd kodu bez asysty debuggera wydaje się krokiem wstecz, lecz tak naprawdę jest on bardziej skuteczny (i jednocześnie bardziej czasochłonny). To sposób znany też jako 'metoda pluszowego misia'. Polega na on wytłumaczeniu wspomnianemu misiowi działania naszego kodu - linijka po linijce - a miś znajdzie błąd. Oczywiście prawdziwą przyczyną rozwiązania problemu będzie fakt, że zastanawiając się ponownie nad działaniem napisanego kodu mamy znacznie większe szanse na dostrzeżenie popełnionym w nim gaf. Podobno mistrzowie tej techniki debugowania potrafią nawet obywać się bez misia :)
  4. Komentowanie fragmentów kodu to najcięższe działo, jakie można wytoczyć. Wyłączając wpierw sporą partię wychodzimy od czegoś, co wprawdzie działa, ale nie robi wszystkiego, co było przewidziane. Następnie zmniejszamy ilość wykomentowanego kodu i patrzymy, kiedy wszystko się zepsuje. Gdy tak się stanie, obszar potrzebujący intensywnej inspekcji będzie już znacznie zawężony. Niestety, czasem może się okazać, że w końcu ponownie włączymy do kompilacji cały kod, a wtedy błąd po prostu... zniknie. Wówczas wbrew pozorom nie ma się z czego cieszyć, gdyż najpewniej oznacza to, że natrafiliśmy na wyjątkowo wredny rodzaj błędów znanych jako heisenbugs.

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 :)

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi:
Autor: Xion w Programowanie » Komentarze (6)
 



© 2010 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.