2007 01 Grę każdy napisać może! [Programowanie]


dla początkujących
Tworzenie gier
Grę każdy napisać
może!
Marek Sawerwain
Tworzenie gier komputerowych to wielki przemysł, ale pierwszy krok można wykonać zupełnie
samodzielnie. Należy jednak mieć świadomość, że tworzenie gry w pojedynkę to trudne zadanie
i obecnie praktycznie nie zdarza się, aby jeden programista opracował dużą i bardzo złożoną pod
względem grafiki i fabuły grę. Istnieją jednak wyjątki, np. gry typu RogueLike bogate w różnego
rodzaju pomysły, jednak najczęściej pozbawione zupełnie grafiki. Tego typu grę istotnie można
napisać samodzielnie.
tym artykule postaram się pokazać, w ja- res o tym samym tytule. Film w swoim czasie zachwy-
ki sposób można opracować grę o zasa- cał efektami specjalnymi i fabułą jak najbardziej zwią-
dach podobnych do gry Tron dla dwóch zaną z komputerami (warto go obejrzeć (oglądnąć), aby
Wosób. Tego rodzaju grę warto potrakto- przekonać się jak kiedyś powstawały efekty specjalne
wać jako swoiste zadanie domowe, a pózniej istotnie moż- bez komputerów). Warto wiedzieć, iż pierwotnie gra
na się więcej niż świetnie bawić, szczególnie jeśli będzie Tron składała się z czterech mniejszych gier o tytułach
można zagrać z drugą osobą. Grid Bugs, Light Cycles, MCP Cone oraz Tanks. Jednakże
Zasady rozgrywki dla tego typu gier są bardzo pro- dziś kiedy mówimy o grze Tron to mamy namyśli Light
ste. Na planszy porusza się dwóch graczy, którzy zo- Cycles, czyli pojedynek dwóch graczy, którzy ścigając się
stawiają ślad. Gra toczy się tak długo, dopóki gracze pozostawiają po sobie ślady. W filmie i oryginalnej grze
nie  wpadną na przeszkodę. Przeszkodą jest także były to kolory żółty i niebieski. Wygra ten gracz który
ślad pozostawiany przez graczy. Liczba modyfikacji ja- ominie przeszkody oraz tak otoczy przeciwnika, aby ten
kie można wprowadzić do takiej gry jest ogromna. My nie mógł się wydostać się z pułapki.
w swojej edycji wprowadzimy np. dodatkowe prze-
szkody, które nie kończą gry, ale dodają punkty oraz
przyspieszają poruszanie się gracza. W ten sposób przez
pewien czas gracz zyskuje przewagę nad przeciwni-
O autorze
kiem.
Autor zajmuje się tworzeniem oprogramowania dla Li-
nuksa oraz WIN32. Zainteresowania: teoria języków
Historia gry Tron
programowania oraz dobra literatura.
Gra Tron po raz pierwszy pojawiła się w 1982 roku wraz
Kontakt z autorem: msawe@go.onet.pl
z filmem wyprodukowanym przez  Walt Disney Pictu-
28 styczeń 2007
autorzy@lpmagazine.org
dla początkujących
Tworzenie gier
Założenia co do gry
Pierwszą czynnością jaką trzeba wykonać za-
START
nim przystąpimy do pisania kodu gry, to na-
turalnie przygotowanie planu przyszłej pracy.
inicjalizacja trybu
Skoncentrujemy się tylko na najważniejszych
graficznego
elementach samej gry. Implementację wielu
dodatków jak menu, czy listę najlepszych wy-
załadowanie grafiki
ników pominiemy.
Najważniejsze zdarzenia jakie pojawią się
w naszym programie przedstawia Rysunek 1.
animacja wprowadzaj ca
Istnieje kilka problemów czysto technicznych,
jak np. uruchomienie trybu graficznego oraz
zliczanie czasu
jego wyłączenie. Innym ważnym problemem
będzie wczytanie potrzebnych grafik, jak logo
sprawdzenie
stanu klawiszy
naszej gry oraz planszy na której będzie się to-
czyć rozgrywka.
poruszanie graczy
Z mechaniką gry bezpośrednio związane
NIE
jest zliczanie czasu, punktów, obsługa kla-
wiatury oraz poruszanie się graczy. Zakła- zbieranie punktów
damy, że ślady graczy będą się różnić kolo-
Koniec gry?
rem. Gracz pierwszy będzie reprezentowa-
ny przez kolor biały, gracz drugi przez ko-
TAK
lor niebieski. Na planszy umieścimy też zie-
lone przeszkody oraz żółte pola, które będą
animacja kończ ca
dodawać punkty. Kolorem jasnoniebieskim
oznaczymy pola, które przyspieszają poru-
zamknięcie trybu
szanie się graczy.
graficznego
Musimy też zaplanować, w jaki sposób
wykryć koniec gry. Ponieważ różnymi ko-
STOP
lorami oznaczyliśmy przeszkody, a plansza
po której się poruszamy domyślnie wypeł-
Rysunek 1. Schemat projektowanej gry
niona jest kolorem czarnym  czyli zerem, to
jeśli napotkamy kolor różny od zera oraz od Dodatkowo będziemy też korzystać z pakie- Instalacja niezbędnych bibliotek
kolorów żółty i jasnoniebieski - oznacza to, tów SDL_Image oraz SDL_gfx. Biblioteka SDL_ Nasza gra korzysta z grafiki dwuwymiarowej,
iż wpadliśmy na przeszkodę i w tym miej- Image oferuje kilka funkcji ułatwiających pro- więc wystarczy zastosować tylko bibliotekę SDL
scu gra się dla nas kończy, a nasz przeciwnik ces wczytywania plików z grafiką. Druga na- która obecnie jest dostępna w 99% dystrybucji.
wygrywa partię. tomiast przyda się do rysowania linii, punk- W artykule została wykorzystana wersja 1.2.x.
Na koniec musimy wybrać biblioteki tów oraz realizacji nieskomplikowanych W większości dystrybucji dostępny jest po-
które będą nam pomagać w realizacji nasze- efektów graficznych, jak np. skalowanie ry- mocniczy pakiet do łatwiejszego wczytywania
go zadania. Podstawową biblioteką do ob- sunków, czy też ich obracanie o dowolny kąt plików graficznych o nazwie SDL_Image. Nato-
sługi grafiki w przypadku Linuksa jest SDL (co wbrew pozorom nie jest łatwe do imple- miast dodatkowy pakiet SDL_gfx, z którego
i to właśnie tę bibliotekę będziemy stosować. mentacji). korzystamy, zazwyczaj nie jest dostępny, więc
należy go samodzielnie skompilować. Wszyst-
Listing 1. Generowanie dodatkowych elementów na głównej planszy gry kie wymienione pakiety dostępne są na stronie
biblioteki SDL. Przy okazji, warto sprawdzić ja-
void area_generator() {
kie inne biblioteki polecają twórcy SDL oraz li-
int i,x,y;
stę gier oraz programów korzystających z SDL.
Sam proces kompilacji ze względu na
// extra obstacles
obecność skryptu configure jak zwykle prze-
for(i=0;i<10;i++) {
biega w trzech etapach. W pierwszym doko-
x=rand() % 760;
nujemy konfiguracji pakietu wskazując opcją
y=rand() % 550;
--prefix miejsce w którym zostaną zainstalo-
wane pliki nagłówkowe oraz biblioteki, kata-
boxRGBA( screen, x, y, x+8, y+8, 0,255,0,255 );
log /usr będzie najlepszym wyborem:
}
./configure --prefix=/usr
// pominięty kod
}
Następnie przeprowadzamy kompilację po-
leceniem make oraz instalację make install.
www.lpmagazine.org 29
dla początkujących
Tworzenie gier
cji SDL_MapRGBA dokonujemy konwersji z try-
bu RGBA (kolor zielony, czerwony, niebie-
ski oraz kanał przezroczystości) do aktualnie
stosowanego modelu otrzymanej powierzch-
ni podczas wywoływania funkcji SDL_SetVi-
deoMode. Samą konwersję wykonujemy w na-
stępujący sposób np.: dla kolorów zielonego
oraz żółtego:
col_green=SDL_MapRGBA(screen->
format, 0,255, 0, 255);
col_yellow=SDL_MapRGBA(screen->
format, 255, 255, 0,255);
W ten sposób nasz program staje się bardziej
odporny na różne tryby graficzne. Po tej kon-
wersji następne zadanie to załadowanie (np.:)
grafiki tła, co czynimy następująco:
gameAREA = (SDL_Surface *)
IMG_Load ( GameArea );
if ( gameAREA == NULL ) {
Rysunek 2. Ekran gry jaką projektujemy
fprintf(stderr, "Couldn't load %s:
Wszystkie te czynności najlepiej wykonać tworzenia gry i jej testowania lepiej korzystać %s\n", GameArea, SDL_GetError ( ) );}
jako użytkownik root, jeśli chcemy udostęp- z trybu okna, ponieważ łatwiej wtedy odzy-
nić pakiet innym użytkownikom. Z pakie- skać kontrolę nad zwieszoną aplikacją (mo- Zastosowanie funkcji IMG_Load z biblioteki
tem SDL_Image postępujemy w dokładnie ten żemy po prostu zamknąć okno), niż z trybu SDL_Image sprowadza proces wczytania gra-
sam sposób. pełno-ekranowego, gdzie nie widzimy reszty fiki z pliku do jednej linii kodu oraz trywial-
biurka. nej instrukcji warunkowej, sprawdzającej czy
Podstawowa obsługa grafiki Po inicjalizacji możemy włączyć odpowie- proces wczytywania zakończył się sukcesem.
W następnych punktach zajmiemy się bezpo- dni tryb graficzny. W naszym programie ro- Kolejną czynnością jest wykonanie animacji
średnią implementacją samej gry. Jednak za bimy to w następujący sposób: początkowej, którą realizujemy za pomocą
nim nasza gra zacznie poprawnie funkcjono- dwóch linii kodu:
wać jak zawsze trzeba wykonać całą serię po- screen = SDL_SetVideoMode( ScreenX,
mocniczych kroków. W pierwszej kolejności ScreenY, 0, SDL_HWSURFACE ); ZoomPicture( screen, imageLOGO,
inicjalizujemy bibliotekę SDL za pomocą SDL_ SMOOTHING_ON );SDL_Delay( 1000 );
Init. Podczas wywołania tej funkcji za pomo- Dwa pierwsze argumenty to rozdzielczość
cą operatora binarnego  lub łączymy ciąg pionowa i pozioma, w trzecim podajemy Samą animację wykonuje funkcja ZoomPic-
flag oznaczających poszczególne podsystemy tryb koloru. Zero oznacza, że system samo- ture, natomiast SDL_Delay wprowadza se-
z jakich będziemy korzystać. Dla nas najważ- dzielnie zadecyduje o trybie kolorów. Ostat- kundowe opóznienie przed rozpoczęciem
niejsze będą naturalnie tryb video oraz tzw. ni, czwarty parametr jest bardzo ważny. War- samej gry. Z operacjami graficznymi zwią-
timer. Inicjalizacja przedstawia się następu- tość SDL_HWSURFACE oznacza, że bitmapa, po zana jest także funkcja area_generator. Krót-
jąco: której będziemy rysować zostanie utworzona ki fragment tej funkcji znajduje się na Listin-
w pamięci karty graficznej, a co za tym idzie gu 1. Jej zadaniem jest umieszczenie na plan-
if ( SDL_Init( SDL_INIT_VIDEO | SDL_ - osiągniemy wyższą wydajność. Choć nasza szy gry dodatkowych elementów, które ma-
INIT_TIMER | SDL_INIT_NOPARACHUTE ) < gra nie stanowi problemów dla żadnej kar- ją pomagać oraz przeszkadzać w trakcie gry.
0 ) { return -1;} ty graficznej, to jednak i tak warto zastoso- Wykonujemy to za pomocą trzech pętli for.
wać tę flagę. W ten sposób odciążymy pro- W każdej z nich losujemy współrzędne, pod
W przypadku jakiegokolwiek błędu, pro- cesor komputera od niektórych operacji gra- którymi za pomocą funkcji boxRGBA rysu-
gram zakończy swoje działanie ponieważ ficznych. Jako rezultat działania SDL_SetVi- jemy kwadraty o wymiarach 8x8 w odpo-
SDL_Init jest wywoływane w funkcji main. deoMode otrzymamy tzw. powierzchnię, po wiednim kolorze, o którym wspominałem
Pozostałe używane przez nas biblioteki SDL_ której możemy rysować. w pierwszej części artykułu. Funkcja ta po-
Image i SDL_gfx nie wymagają wywoływa- Nie są to jeszcze wszystkie wstępne ope- chodzi z biblioteki SDL_gfx. Warto też pod-
nia żadnych dodatkowych funkcji inicjaliza- racje, jakie musimy wykonać. Kolejna czyn- kreślić, iż jeśli kopiujemy bloki z grafiką na
cyjnych. ność jest związana z kolorami. Ponieważ główną powierzchnię (w naszym przypad-
Jeśli proces uruchomienia poszczegól- nie wiemy ostatecznie w jakim modelu kolo- ku jest to zmienna screen), to aby zobaczyć
nych podsystemów SDL powiódł się, to mo- rów będziemy pracować (obecnie stosuje się zmiany trzeba uaktualnić wybrany fragment
żemy ustawić np. tytuł okna: SDL_WM_SetCap- model 32-bitowy albo 16-bitowy, pomijamy powierzchni lub jej całość za pomocą funkcji
tion( "TRON -- The Game", 0 );. Podczas tryb z paletą kolorów), to za pomocą funk- SDL_UpdateRect.
30 styczeń 2007
dla początkujących
Tworzenie gier
Z innych istotnych elementów technicz- Na koniec tego punktu należało by jesz- Pierwsza wyłącza obiekt timer'a generujący
nych które muszą zostać dopilnowane, szcze- cze opisać w jaki sposób możemy wyłączyć bi- nasze zdarzenie, natomiast w drugiej instruk-
gólnie istotne jest utworzenie własnej obsługi bliotekę SDL: wystarczy wywołać funkcję SDL_ cji, poprzez wpisanie jedności do zmiennej
do zdarzenia timer biblioteki SDL: Quit. done spowodujemy, iż pętla while zakończy
swoje działanie.
SDL_SetTimer( TIMER_INTERVAL, Mechanika gry Obsługa klawiszy też jest łatwa w imple-
TimerCallback ); Główna pętla zdarzeń w naszej grze składa mentacji. Musimy stwierdzić, czy wciśnięto
się tylko z pętli while oraz instrukcji switch klawisz ESC za pomocą którego przerwiemy
oraz uruchomienie powtarzania klawiszy: znajdującej się wewnątrz tej pętli. Listing 2 działanie programu, oraz odczytać klawisze
prezentuje jej schemat. Większość kodu zo- które wciskają gracze. Będą to dwa zestawy
SDL_EnableKeyRepeat(1, 3); stała pominięta, bowiem omówimy go w na- klawiszy, strzałki kursora oraz klawisze [s],
stępnym akapicie. Teraz omówione zostaną [d], [q], [a]. Wykrycie, jaki klawisz został wci-
W przypadku funkcji TimerCallback jej im- pozostałe instrukcje, które wyświetlają infor- śnięty przedstawia się następująco:
plementacja sprowadza się do wygenero- macje o czasie i punktach.
wania zdarzenia o nazwie SDL_TIMER_MY_ Jak widać obsługujemy tylko trzy zdarze- if ( event.key.keysym.sym ==
EVENT. Realizujemy to za pomocą trzech li- nia: SDL_QUIT oznaczające koniec pracy apli- SDLK_ESCAPE) done = 1;
nii kodu: kacji SDL, SDL_KEYDOWN - wciśnięcie klawisza if ( event.key.keysym.sym ==
oraz SDL_TIMER_MY_EVENT, czyli zdarzenie SDLK_LEFT ) player2_dir = dir_left ;
SDL_Event event; generowane przez nas. To właśnie w tym if ( event.key.keysym.sym ==
event.type = SDL_TIMER_MY_EVENT; zdarzeniu będziemy wykonywać wszystkie SDLK_q ) player1_dir = dir_up ;
SDL_PeepEvents( &event, 1, SDL_ najważniejsze elementy związane z naszą
ADDEVENT, 0 ); grą. W przypadku klawisza ESC kończymy działa-
W pierwszej kolejności zajmiemy się nie pętli. Natomiast w obsłudze klawiszy dla
Funkcja SDL_PeepEvents dokłada do kolejki dwoma pierwszymi zdarzeniami. W obsłu- graczy do zmiennych player2_dir oraz play-
zdarzeń SDL nowe zdarzenie. Wykorzystanie dze zdarzenia SDL_QUIT znajdują się tylko er1_dir wpisujemy kierunek w którym gracz
własnego zdarzenia generowanego w rów- dwie linie kodu: ma się przemieszczać. Dopuszczamy tylko
nych odstępach czasu, da nam możliwość cztery kierunki: lewo, prawo oraz góra i dół
uzyskania płynnej animacji poruszania się SDL_SetTimer( 0 , 0 ); ale warto zastanowić się jak zrealizować prze-
graczy. done=1; mieszczanie się po przekątnych, gdyż w tym
przypadku gra stałaby się o wiele ciekawsza.
Listing 2. Główna pętla zdarzeń w grze Tron Nie jest to wbrew pozorom zbyt trudne do im-
plementacji.
done = 0; Nim przejdziemy do opisu sposobu im-
while ( done == 0 && SDL_WaitEvent( &event ) ) plementacji samej gry warto spojrzeć raz jesz-
{ cze na kod zródłowy z Listingu 2. Znajdu-
switch(event.type) je się w nim kilka linii odpowiedzialnych za
{ wyświetlanie informacji o czasie oraz ilości
case SDL_QUIT: { punktów, jakie zostały zdobyte przez graczy.
} break; Linie te zostały umieszczone w obsłudze zda-
case SDL_KEYDOWN: { rzenia SDL_TIMER_MY_EVENT. W pierwszej ko-
} break; lejności dzięki funkcji boxColor rysujemy nie-
wielki zielony prostokąt aby na nim nanieść
case SDL_TIMER_MY_EVENT: informacje o punktach i czasie. Stosujemy
{ bardzo specyficzny kolor, bowiem podczas
boxColor( screen, 49, 589, 274, 599, 0x33E825FF); projektowania wyglądu naszej areny właśnie
sprintf( &pointsStr[0], "P1: %3d P2: %3d", player1_ ten kolor został zastosowany do narysowania
game_points, player2_game_points ) ; obszaru, w którym znajdować się będą punk-
stringRGBA( screen, 60, 591, &pointsStr[0], 255, ty i czas. Jak widać zanim za pomocą funk-
255, 255, 255 ) ; cji stringRGBA wyświetlimy ciąg znaków,
sprintf( &timeStr[0], " Time: %4d", game_time ) ; wcześniej trzeba go przygotować i do tego ce-
stringRGBA( screen, 180, 591, &timeStr[0], 255, 255, lu znakomicie nadaje się funkcja sprintf, dzię-
255, 255 ) ; ki której ciąg znaków możemy w potrzebny
sposób sformatować.
SDL_UpdateRect( screen, 0, 0, 0, 0 ) ;
Cztery najważniejsze if-y
} break; W tym punkcie omówimy wszystkie zadania
} które realizuje obsługa zdefiniowanego przez
} nas zdarzenia SDL_TIMER_MY_EVENT. Pierw-
szą czynnością jest zliczanie czasu. W tym
www.lpmagazine.org 31
dla początkujących
Tworzenie gier
Druga instrukcja warunkowa posiada
Listing 3. Wykrywanie kolizji, zliczanie punktów oraz realizacja przyspieszenia
znacznie uboższą treść, bowiem jeśli warto-
ścią zmiennej color jest kolor żółty, to stawia-
color = GetPixel( screen, player2_x, player2_y);
my biały punkt (podobnie jak to było w po-
if( color == col_black) {
przednim punkcie), ale zwiększamy też ilość
PutPixel( screen, player2_x, player2_y, 0xFFFFFFFF);
punktów.
if(player2_speed >= 0) {
Równie krótka jest także trzecia instruk-
player2_speed--;
cja warunkowa, która  włącza bonus więk-
switch ( player2_dir ) {
szej prędkości poprzez zwiększenie o sto ak-
case dir_left: { player2_x--; } break;
tualnej wartości zmiennej player2_speed.
case dir_right: { player2_x++; } break;
Ostatnia, czwarta instrukcja warunkowa
case dir_up: { player2_y--; } break;
jest odpowiedzialna za sprawdzenie, czy od-
case dir_down: { player2_y++; } break;
czytany kolor jest inny od trzech wymienio-
}
nych już kolorów. Bowiem eśli tak się zda-
PutPixel( screen, player2_x, player2_y, 0xFFFFFFFF);
rzy, to gracz trafił w przeszkodę np.: we wła-
}
sny ślad. Oznacza to, iż przegrał, więc należy
if(player2_speed < 0) player2_speed=0;
zakończyć działanie pętli wpisując do done
}
wartość jeden, a do zmiennej game_over war-
if ( color == col_yellow) {
tość dwa, która oznacza przegraną gracza
PutPixel( screen, player2_x, player2_y, 0xFFFFFFFF);
numer dwa. W takim przypadku wyświe-
player2_game_points+=10;
tlimy animację informującą, iż wygrał gracz
}
numer jeden, co zrobimy w następujący spo-
sób:
if ( color == col_lightblue) {
PutPixel( screen, player2_x, player2_y, 0xFFFFFFFF);
ZoomPicture( screen, gameOVER_P1,
player2_speed+=100;
SMOOTHING_ON );
}
SDL_Delay( 1000 );
if( color != col_black && color != col_yellow && color != col_lightblue) {
Podsumowanie
done = 1;
W ten oto sposób przedstawione zostały
game_over = 2;
wszystkie najważniejsze elementy naszej
}
wersji gry Tron. Jak zawsze w podsumowa-
celu wykorzystamy funkcję SDL_GetTicks, case dir_down: { player1_y++; } niu zachęcam do wprowadzania własnych
która zlicza upływający czas z dokładnością break; zmian do kodu gry. Możliwości jest wiele,
do tysięcznej sekundy. Sprawdzając, czy od } choćby już wspomniane wcześniej porusza-
ostatniego testu upłynęło 1000 tysięcznych nie się  na ukos , które wzbogaci naszą grę.
(jedna sekunda) zwiększamy o jeden zmien- Listing 3. przedstawia fragment kodu związa- Poprawić należy również grafikę, dodać mo-
ną game_time, co wykonujemy za pomocą ny z graczem numer dwa. Ten mimo wszyst- żemy muzykę oraz efekty dzwiękowe. To
następującej instrukcji warunkowej: ko krótki fragment zajmuje się wykryciem zadanie ułatwią biblioteki SDL_sound oraz
kolizji z przeszkodą, zliczaniem punktów SDL_mixer. Inną ciekawą innowacją było
if( SDL_GetTicks() - ticks > 1000 ) { oraz realizacją przyspieszenia. Nim jednak by wprowadzenie możliwości gry sieciowej
game_time++; zaczniemy cokolwiek obliczać, w pierwszej poprzez wykorzystanie pakietu SDL_net.
ticks=SDL_GetTicks() ; kolejności odczytujemy wartość koloru, pod Jednak, co najważniejsze, nawet przedsta-
} którym znajduje się gracz za pomocą funk- wiona wersja daje sporo rozrywki, więc na
cji GetPixel. Następnie rozpatrujemy czte- zakończenie pozostaje życzyć tylko dobrej
Następnie, w zależności od kierunku, w któ- ry przypadki. zabawy.
rym ma poruszać się gracz, w odpowied- Pierwsza instrukcja warunkowa spraw-
ni sposób zmieniamy współrzędne jego ak- dza, czy odczytany kolor jest kolorem czar-
tualnej pozycji. Dla przykładu, dla gracza nym. Jeśli tak jest, to za pomocą PutPixel sta-
W Sieci
pierwszego realizujemy to w następujący wiamy punkt na ekranie o kolorze białym
sposób: (stąd w czwartym argumencie wartości 0xFF).
" Strona domowa poświęcona bibliotece
Następnie, jeśli gracz numer dwa ma aktywny
SDL:
switch ( player1_dir ) {  bonus większej prędkości, aktualizujemy raz
http://www.libsdl.org/
case dir_left: { player1_x--; } jeszcze współrzędne i stawiamy jeszcze jeden
" Nowoczesna implementacja gry Tron:
break; punkt. Zmniejszamy także wartość zmiennej
http://www.armagetronad.net/
case dir_right: { player1_x++; } player2_speed, która odpowiada za czas trwa-
" Inna interesująca implementacja gry
break; nia przyspieszenia. Sprawdzamy również,
Tron:
case dir_up: { player1_y--; } czy wartość tej zmiennej nie jest przypadkiem
http://www.gltron.org/
break; ujemna i jeśli tak jest, wpisujemy zero.
32 styczeń 2007


Wyszukiwarka

Podobne podstrony:
2007 01 Web Building the Aptana Free Developer Environment for Ajax
2007 01 Rehabilitacja
2007 01 Boom na fizjoterapię
2005 01 Pyro i OpenSSL–bezpieczny komunikator internetowy [Programowanie]
2007 01 Praca dla fizjoterapeutów
2007 01 Amerykańskie badania nad elektrostymulacją mięśni
2007 01 Novell Security Manager–powered by Astaro [Bezpieczenstwo]
2007 01 Rehabilitacja lecznicza umowa z NFZ
SIMR ALG1 EGZ 2007 01 30a rozw
2007 01 Virtual Playground 3D Worlds with Python and Panda3D
Notice to Mariners PL 2007 01 10
2007 01 To nie tylko rehabilitacja

więcej podobnych podstron