L8: Szachy Magów#
Korespondencyjna drużyna zdalnych magów nudziła się podczas telepat-konferencji. Archibald i Eleonora wymyślili własny wariant szachów — z pomocnikami i ognistymi kulami! Do tej pory grali przez Sieć[1] za pomocą jasnowidztwa. Działało to nieźle, ale istniało ryzyko zobaczenia przyszłych ruchów przeciwnika — dając nieuczciwa przewagę. Zamiast rzucać zaklęcia w ciemno, chcą wysyłać datagramy.
[1] — Tkanina magii przenikająca całą materialną i duchową rzeczywistość.
Przygotuj dla nich serwer UDP, który reaguje na wiadomości o stałym rozmiarze 16 bajtów w następującym formacie:
- 1 bajt — znak (
char) definiujący typ wiadomości - 1 bajt — bez znaczenia, tzw. padding
- 14 bajtów — treść wiadomości uzupełniona znakami null (
'\0'), zależnie od jej typu:l— wiadomośćlogin— ciąg znaków zawierający nazwę gracza o maksymalnej długości 14c— wiadomośćcast— 3 dwubajtowe liczby całkowite (uint16_t) reprezentujące kolejno: wybór zaklęcia, współrzędną X celu oraz współrzędną Y celu.q— wiadomośćquit— puste ciało
Program przyjmuje jeden argument — port, na którym nasłuchuje serwer:
./sop-mages <port>Etapy:#
Etap 1 (6 pkt)#
Program oczekuje na datagramy na podanym porcie. Obsługuje 3 typy wiadomości; po otrzymaniu odpowiedniej wypisuje na standardowe wyjście:
[Login] Welcome, <name>[Cast] Someone casts <spell_name> onto <X>,<Y>[Quit] Someone quit. Goodbye!
Ponadto program kończy działanie po odebraniu i obsłużeniu 4 wiadomości.
Po otrzymaniu datagramu o nieprawidłowym rozmiarze, nieprawidłowym pierwszym bajcie (innym niż 'l', 'c' lub 'q'), nieprawidłowym wyborze zaklęcia (poza tablicą spell_names) lub nieprawidłowych współrzędnych (mniejszych niż 0, większych lub równych BOARD_SIZE), należy wypisać odpowiedni komunikat o błędzie, ale nie przerywać działania serwera.
Wskazówka: Przy użyciu kompilatora z rodziny gcc, aby zapewnić przewidywalny układ pamięci struktury, można użyć atrybutu
__attribute__((__packed__))— przykład użycia znajduje się wsop-mag.c.
Etap 2 (7 pkt)#
Po otrzymaniu wiadomości cast, zapisz komendę z trzema otrzymanymi liczbami w kolejce FIFO. Równolegle z odbieraniem datagramów uruchom THREAD_COUNT wątków-pomocników, które pobierają najstarszą komendę i rzucają zaklęcie — czyli czekają FAMILIAR_DELAY ms i wypisują odpowiedni komunikat na standardowe wyjście jak w etapie 1. Kolejka ma pojemność MAX_QUEUE komend. Gdy nie ma miejsca na kolejną komendę, odrzuć ją, wypisz odpowiedni komunikat o błędzie i kontynuuj działanie. Do synchronizacji użyj semafora lub zmiennej warunkowej.
Przypomnienie: Aktywne oczekiwanie (tzw. busywaiting) jest zabronione.
Etap 3 (6 pkt)#
Program poprawnie obsługuje logowanie. Odrzucaj wszystkie wiadomości inne niż login przed rozpoczęciem gry — odebraniem wiadomości login od dwóch graczy. Gracze muszą mieć różny port i/lub adres IP. Po rozpoczęciu gry odrzucaj wszystkie wiadomości niepochodzące od graczy. Zalogowani gracze zaczynają z 10 kamykami w sakiewkach — pewna miara punktów w grze.
Zaklęcia rzucane przez pomocników będą kosztować graczy kamyki. Zależnie od rodzaju zaklęcia, będzie to kolejno: 1 kamyk za „Wróżenie" (wybór zaklęcia 0), 3 za „Przyzwanie Żywiołaka" (wybór 1) i 4 za „Kulę Ognia" (wybór 2). Jeśli gracz nie ma wystarczającej liczby kamyków, pomocnik wypisuje na standardowe wyjście [tee hee] Not enough pebbles, <name>!, czeka FAMILIAR_DELAY ms i czeka na kolejną komendę.
Komunikat wypisywany przez pomocnika po pomyślnym rzuceniu zaklęcia od tego etapu uwzględnia również imię rzucającego: [Cast] <name> casts <spell_name> onto <X>,<Y>.
Wiadomość quit od zalogowanego gracza po rozpoczęciu gry oznacza poddanie się. Jego przeciwnik wygrywa. Wypisz komunikat o poddaniu się [Quit] <name> quit. Goodbye!, a następnie ogłoszenie zwycięzcy -= Congratulations, <name>, you win! =-. Następnie program kończy działanie.
Etap 4 (5 pkt)#
Gra rozgrywa się na planszy o wymiarach BOARD_SIZE x BOARD_SIZE. Każde pole może być puste lub zajęte przez żywiołaka należącego do jednego z graczy.
Oprócz dotychczasowych wątków, pojawia się wątek sędziego. Od momentu rozpoczęcia gry (tzn. gdy obaj gracze się zalogują), co sekundę wypisuje on na standardowe wyjście stan planszy i sakiewek graczy wraz z legendą wyjaśniającą, który znak reprezentuje żywiołaka którego gracza, używając ich imion.
Poza tym, co sekundę sędzia zmienia stan sakiewek obu graczy o \(\Delta_K = \lfloor \frac{N}{2} \rfloor - 1\) kamyków, gdzie \(\text{N}\) to liczba żywiołaków danego gracza na planszy. Po takiej zmianie sędzia wysyła do każdego gracza 2-bajtowy datagram z aktualną liczbą kamyków jako liczbą całkowitą bez znaku. Jeśli gracz nie ma już kamyków i \(\Delta_K < 0\), jego przeciwnik wygrywa. Jeśli miałoby to nastąpić jednocześnie dla obu graczy, wygrywa ten, który zalogował się pierwszy. Sędzia ogłasza zwycięzcę na standardowym wyjściu -= Congratulations, <name>, you win! =- i wysyła 1-bajtowy datagram ze znakiem 'w' do zwycięzcy i 'l' do przegranego. Następnie program kończy działanie.
Zaklęcie „Wróżenie" (wybór zaklęcia 0) pozwala graczowi poznać zawartość pól w kwadracie 5x5 wokół wskazanej lokalizacji. Serwer wysyła graczowi datagram o długości 50 bajtów zawierający 25 2-bajtowych liczb całkowitych reprezentujących zawartość 25 pól otaczających cel zaklęcia:
| zawartość pola | puste | żywiołak wróżącego gracza | żywiołak przeciwnika | poza planszą |
|---|---|---|---|---|
| wysłana liczba | 0 | 1 | 2 | 3 |
Zaklęcie „Przyzwanie Żywiołaka" (wybór zaklęcia: 1) przywołuje żywiołaka należącego do rzucającego gracza na wskazane pole, jeśli jest ono puste. W przeciwnym razie nic się nie dzieje, a kamyki są tracone.
Zaklęcie „Kula Ognia" (wybór zaklęcia: 2) niszczy wszystkie (!) żywiołaki w kwadracie 3x3 wokół wskazanej lokalizacji.
Po pomyślnym rzuceniu zaklęcia, pomocnik wysyła graczowi 2-bajtowy datagram zawierający liczbę kamyków w sakiewce tego gracza. Oznacza to, że po zaklęciu wróżenia wysyłane są 2 datagramy.