Zasady gry programistycznej Skarb Labiryntu znajdującej się w systemie Meridius [1].
Podstawowe informacje [2]
Protokół komunikacji [3]
Przykłady rozgrywki [4]
Wskazówki strategiczne [5]
Przykładowe boty [6]
Gra Skarb Labiryntu rozgrywa się na kwadratowej planszy pomiędzy dwoma graczami: poszukiwaczem przygód, który ma zadanie zdobyć znajdujący się na planszy skarb oraz architektem labiryntu, który stawia przeszkody tak, aby jak najbardziej opóźnić poszukiwaczowi osiągnięcie celu.
Na początku każdej rozgrywki labirynt składa się z trawy otoczonej żywopłotem, poszukiwacz znajduje się w prawym dolnym rogu, natomiast skarb w lewym górnym. Poszukiwacz przygód w trakcie swojej tury może się przemieścić o jedno pole w poziomie lub w pionie pod warunkiem, że nie rośnie tam żywopłot. Architekt w każdej turze może posadzić żywopłot na dowolnym z pól planszy pod warunkiem, że pole to:
W trakcie wizualizacji rozgrywki pola, podczas oglądania ruchu architekta, pola nie spełniające powyższych warunków są podświetlone na pomarańczowo.
Podczas wyboru zawodników do rozgrywki architekt musi się znajdować na pierwszej pozycji, poszukiwacz na drugiej. Rozmiar labiryntu (długość boku) jest ustalana parametrem, który przyjmuje wartości całkowite z zakresu 5 - 20. Istnieją dwa tryby rozgrywki - warianty zasad. W pierwszym z nich poszukiwacz posiada magiczną mapę, dzięki której wie co się dzieje na całej planszy i zna położenie wszystkich stawianych przez architekta przeszkód. W drugim wariancie poszukiwacz nie ma mapy, przez co widzi jedynie obszar w promieniu dwóch pól wokół siebie. W trakcie wizualizacji rozgrywki, pola których poszukiwacz nie widzi są w trakcie jego tury przyciemnione.
Gracze wykonują swoje ruchy na przemian, zaczynając od architekta. Gra kończy się w momencie gdy poszukiwacz wejdzie na pole ze skarbem lub skończy się limit tur. Limit jest ustalony w zależności od trybu gry i rozmiaru labiryntu. Jeśli rozmiar labiryntu jest równy n
to w trybie z mapą limit wynosi n*n
natomiast w trybie bez mapy 3*n*n
. Na zakończenie rozgrywki pomiędzy obu graczy jest rozdzielane 100 punktów. Poszukiwacz dostaje maksymalną notę jeśli zdąży dojść do skarbu w ciągu 2*n-2
tur, im bardziej zwleka tym więcej punktów przydzielonych zostaje architektowi, 100 jeśli poszukiwacz przegra przez przekroczenie limitu tur.
Każde pole labiryntu jest oznaczone dwiema liczbami, pierwsza oznacza numer kolumny, druga numer wiersza. Numeracja rozpoczyna się od zera, przy czym labrynt jest rozszerzany o pas z żywopłotu otaczający go dookoła. Poniższy rysunek przedstawia układ pól w labiryncie o rozmiarze 5. Pasek na dole pokazuje aktualny rozkład punktów pomiędzy architektem (po lewej stronie) oraz poszukiwaczem skarbu (po prawej).
Tryby gry: Z mapą, Bez mapy
Typy zawodników: Poszukiwacz przygód, Architekt labiryntu
Liczba zawodników: 2
Parametry: Rozmiar labiryntu
Punktacja: 0-100
Czas tury (bot): 10 sekund
Czas tury (gracz): 100 sekund
W grze zawsze bierze udział dwóch uczestników, pierwszy z graczy musi mieć typ Architekt labiryntu, drugi Poszukiwacz przygód. Tryb bez mapy jest dużo trudniejszy dla poszukiwacza, ponieważ nie ma on bieżącej informacji o stanie labiryntu, a jedynie o swojej najbliższej okolicy. Parametr rozmiar labiryntu przyjmuje wartości od 5 do 25.
Po uruchomieniu gry w której uczestniczy profil użytkownika (w polu wyboru botów zostało wybrane Ja lub Gracz), użytkownik ten otrzyma stosowną informację na panelu Moje zaproszenia. Kliknięcie zaproszenia spowoduje przeniesienie do okna z rozgrywką gdzie można to zaproszenie potwierdzić lub odrzucić.
Jeśli gracz jest architektem, pole na którym stawiana jest przeszkoda wybiera sklikając na nie kursorem (pola na których nie można postawić przeszkody są oznaczone kolorem pomarańczowym). Pole aktualnie wskazywane, na którym można postawić przeszkodę jest podświetlone. Aby zrezygnować z możliwości wykonania ruchu należy kliknąć znajdującą się w lewym górnym rogu ikonę klepsydry.
Podobnie ma się rzecz w przypadku poszukiwacza. Pola na które można wykonać ruch (bezpośrednio sąsiadujące z poszukiwaczem i niezablokowane przez żywopłot) zostają podświetlone po najechaniu na nie kursorem. Klepsydra pozawalająca na opuszczenie tury znajduje się w prawym dolnym rogu mapy. Alternatywnie, każdy z uczestników może przekazywać polecenia wysyłając je za pomocą konsoli gracza, zgodnie z protokołem opisanym w następnej sekcji.
W trakcie trwania tury komunikacja z grą odbywa się za pomocą zapytań i komend, czyli komunikatów wypisanych na standardowe wyjście i zakończonych znakiem nowej linii. Aby zakończyć turę bot musi wysłać specjalny komunikat #END
. Wszystkie instrukcje po wysłaniu tego komunikatu będą miały miejsce w kolejnej turze gry.
Zapytania są to polecenia na które system natychmiast wysyła odpowiedź (zakończoną znakiem nowej linii) na standardowe wejście programu. W grze Skarb labiryntu dostępne są następujące zapytania:
Clock
Zwraca pojedynczą liczbę oznaczająca numer aktualnej tury (liczone od 1).
WithMap
Zwraca napis Yes
jeśli gra jest rozgrywana w trybie z mapą, lub No
w przeciwnym wypadku.
MazeSize
Zwraca pojedynczą liczbę będąca rozmiarem mapy wraz z obrzeżami. Jeśli rozmiar labiryntu podany podczas tworzenia rozgrywki wynosi n
, to zapytanie zwraca wartość n+2
.
AdventurerPosition
Zwraca dwie liczby oddzielone spacją, kolejno współrzędne x
i y
pola na którym znajduje się poszukiwacz przygód.
MazeMap
Dopuszczalność polecenia: poszukiwacz - tryb z mapą, architekt - dowolny tryb.
Zwraca widok całego labiryntu. Dla mapy której rozmiar (odpowiedź na zapytanie MazeSize
) wynosi n
, polecenie zwraca n
linii w których znajduje się po n
symboli oddzielonych pojedynczymi spacjami. Symbole te oznaczają zawartość danego pola na planszy, zgodnie z legendą:
A
- poszukiwacz przygódT
- skarbO
- przeskoda-
- puste poleFieldOfView
Przedstawia widok labiryntu w promieniu wzroku poszukiwacza przygód (2 pola). Bez względu na jego położenie, polecenie zwraca pięć linii, po pięć, oddzielonych pojedynczymi spacjami, symboli w każdej. Oznaczenia symboli są takie same jak przy poleceniu MazeMap
, przy czym pole znajdujące się poza labiryntem jest traktowane jak ściana. Środkowym symbolem jest zawsze symbol poszukiwacza przygód.
W grze występują dwie komendy - jedna przeznaczona dla poszukiwacza a druga dla architekta.
Komendy można wywołać dowolnie wiele razy, za wiążące posunięcie przyjmuje się ostatnie wywołanie. Wykonanie komendy następuje dopiero po zakończeniu tury (odpowiedzi na zapytania nie ulegają więc zmianie). Zakończenie tury bez wysłania jakiejkolwiek komendy jest zgodne z zasadami (w takim przypadku gracz po prostu nie modyfikuje sytuacji na planszy, w statystykach jest to traktowane jako zbędny ruch).
MakeStep ?d
Dopuszczalność polecenia: tylko poszukiwacz.
Deklaruje przemieszczenie poszukiwacza na sąsiednie pole zgodnie z podanym w parametrze kierunkiem. Kierunki zakodowane są jako liczby w następujący sposób:
0
- północ1
- wschód2
- południe3
- zachódPodanie wartości innej niż wymienione powyżej nie powoduje błędu, a jedynie jest traktowane w statystykach jako zbędny ruch.
PlaceObstacle ?x ?y
Dopuszczalność polecenia: tylko architekt.
Deklaruje postawienie przeszkody na polu zadanym współrzędnymi (?x, ?y)
. Podanie nieprawidłowych współrzędnych mapy oraz próba postawienia przeszkody na polu blokującym drogę lub zbyt blisko poszukiwacza/skarbu, skutkuje nie postawieniem żadnej przeszkody i jest traktowane w statystykach jako zbędny ruch.
Przykładowe zapisy komunikacji pomiędzy graczami a sędzią gry. Komunikaty oznaczone są kolorami: niebieski - zapytania, czerwony - komendy, zielony - komunikaty systemowe. Otrzymane odpowiedzi oznaczone są kolorem czarnym.
Tryb z mapą, tura architektaWithMap
Yes
MazeSize
12
Clock
26
AdventurerPosition
7 4
MazeMap
O O O O O O O O O O O O
O T - - - - - O - - - O
O - - - - - - - - O - O
O - - - - O O O - O - O
O - - - O - - A O O - O
O - - O - - O - - - - O
O - - O - O - O O - - O
O - O - - O - - - O - O
O O - - O - - - - - O O
O O - O - - - - O - - O
O O - - - - - - - - - O
O O O O O O O O O O O O
PlaceObstacle 3 3
PlaceObstacle 6 1
#END
WithMap
No
MazeSize
10
Clock
10
AdventurerPosition
7 4
FieldOfView
- O - - -
- O - - -
- O A - -
- O - - -
- O - - -
MakeStep 3
MakeStep 0
#END
Poniżej, w czterech językach programowania, zamieszczone zostały kody źródłowe dwóch przykładowych botów: architekta i poszukiwacza. Oba boty potrafią grać we wszystkich dostępnych trybach. Implementują one pewne proste strategie oraz pokazują zasady działania systemu i możliwe sposoby obsługi protokołu komunikacji. Boty te są dostępne w systemie pod następującymi nazwami: architekt labiryntu - SL_DocumentationArchitect.py, poszukiwacz skarbu - SL_DocumentationAdventurer.py.
Strategia działania przedstawionego bota, zobrazowana poniższym rysunkiem, jest następująca:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | size = int(raw_input("MazeSize\n")) x, y = size-5, size-1 go = "UP" while True: print "#PRINT go", go if y == 2 and go == "UP": x, y = x - 2, 1 go = "DOWN" elif y == size - 3 and go == "DOWN": x, y = x - 2, size - 2 go = "UP" elif go == "DOWN": y += 1 elif go == "UP": y -= 1 if x + y > 3 and (x, y) != (2, 2): print "PlaceObstacle", x, y else: go = "NOWHERE" print "#END" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include <iostream> using namespace std; int main () { int size; cout << "MazeSize" << endl; cin >> size; int x=size-5, y=size-1; string go = "UP"; while (true) { cout << "#PRINT go " << go << endl; if (y==2 && go=="UP") { x -= 2; y = 1; go = "DOWN"; } else if (y==size-3 && go=="DOWN") { x -= 2; y = size-2; go = "UP"; } else if (go=="DOWN") { y++; } else if (go=="UP") { y--; } if (x+y>3 && (x != 2 || y != 2)) cout << "PlaceObstacle " << x << " " << y << endl; else go = "NOWHERE"; cout << "#END" << endl; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | using System; namespace TOMDocumentationBot { class Program { static void Main(string[] args) { Console.WriteLine("MazeSize"); int size = int.Parse(Console.ReadLine()); int x = size - 5, y = size - 1; var go = "UP"; while (true) { Console.WriteLine("#PRINT go " + go); if (y == 2 && go == "UP") { x -= 2; y = 1; go = "DOWN"; } else if (y == size - 3 && go == "DOWN") { x -= 2; y = size - 2; go = "UP"; } else if (go == "DOWN") { y++; } else if (go == "UP") { y--; } if (x + y > 3 && (x != 2 || y != 2)) Console.WriteLine("PlaceObstacle {0} {1}", x, y); else go = "NOWHERE"; Console.WriteLine("#END"); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import java.util.Scanner; public class program { public static void main(String args[]) { Scanner input = new Scanner(System.in); System.out.println("MazeSize"); int size = input.nextInt(); int x=size-5, y=size-1; String go = "UP"; while (true) { System.out.println("#PRINT go " + go); if (y==2 && "UP".equals(go)) { x -= 2; y = 1; go = "DOWN"; } else if (y==size-3 && "DOWN".equals(go)) { x -= 2; y = size-2; go = "UP"; } else if ("DOWN".equals(go)) { y++; } else if ("UP".equals(go)) { y--; } if (x+y>3 && (x != 2 || y != 2)) System.out.println("PlaceObstacle " + x + " " + y); else go = "NOWHERE"; System.out.println("#END"); } } } |
Strategia działania przedstawionego bota jest następująca:
FieldOfView
jest dostępne w obu trybach rozgrywki).1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import random backward = -1 continuation = -1 moves = [False, False, False, False] while True: view = [] print "FieldOfView" for y in range(5): view.append(raw_input().split()) print "#PRINT Pole widzenia to", view moves[0] = view[1][2] != 'O' moves[1] = view[2][3] != 'O' moves[2] = view[3][2] != 'O' moves[3] = view[2][1] != 'O' print "#PRINT Lista ruchow:", moves print "#PRINT Kierunek do kontynuacji:", continuation if continuation >= 0 and moves[continuation]: print "MakeStep", continuation print "#END" continue possibleMoves = [i for i in range(4) if moves[i]] if len(possibleMoves) > 1 and backward in possibleMoves: possibleMoves.remove(backward) print "#PRINT Losowy wybor z kierunkow", possibleMoves continuation = random.choice(possibleMoves) backward = (continuation + 2) % 4 print "MakeStep", continuation print "#END" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #include <iostream> #include <vector> #include <algorithm> #include <stdlib.h> #include <time.h> using namespace std; int main () { srand ( time(NULL) ); int backward = -1; int continuation = -1; string row; int moves[4] = {false}; while (true) { vector<vector<char> > view(5, vector<char>(5)); cout << "FieldOfView" << endl; for (int y = 0; y < 5; y++) { string row; getline(cin, row); for (int x = 0; x < 5; x++) view[y][x] = row[2*x]; } cout << "#PRINT Pole widzenia to"; for (int y = 0; y < 5; y++) { cout << " ["; for (int x = 0; x < 5; x++) cout << " " << view[y][x]; cout << " ]"; } cout << endl; moves[0] = view[1][2] != 'O'; moves[1] = view[2][3] != 'O'; moves[2] = view[3][2] != 'O'; moves[3] = view[2][1] != 'O'; vector<int> possibleMoves; cout << "#PRINT Lista ruchow: ["; for (int i = 0; i < 4; i++) { cout << " " << moves[i]; if (moves[i]) possibleMoves.push_back(i); } cout << " ]" << endl; cout << "#PRINT Kierunek do kontynuacji: " << continuation << endl; if (possibleMoves.size() > 1) possibleMoves.erase(remove(possibleMoves.begin(), possibleMoves.end(), backward), possibleMoves.end()); cout << "#PRINT Losowy wybor z kierunkow: ["; for (int i = 0; i < possibleMoves.size(); i++) cout << " " << possibleMoves[i]; cout << " ]" << endl; continuation = possibleMoves[rand()%possibleMoves.size()]; backward = (continuation + 2) % 4; cout << "MakeStep " << continuation << endl << "#END" << endl; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | using System; using System.Collections.Generic; namespace TOMDocumentationBot { class Program { static void Main(string[] args) { var rand = new Random(); var backward = -1; var continuation = -1; var moves = new[] {false, false, false, false}; while (true) { var view = new List<string[]>(); Console.WriteLine("FieldOfView"); for (var y = 0; y < 5; y++) view.Add(Console.ReadLine().Split()); Console.Write("#PRINT Pole widzenia to"); foreach (var row in view) { Console.Write(" ["); foreach (var s in row) Console.Write(" " + s); Console.Write(" ]"); } Console.WriteLine(); moves[0] = view[1][2] != "O"; moves[1] = view[2][3] != "O"; moves[2] = view[3][2] != "O"; moves[3] = view[2][1] != "O"; Console.Write("#PRINT Lista ruchow: ["); foreach (var b in moves) Console.Write(" " + b); Console.WriteLine(" ]"); Console.WriteLine("#PRINT Kierunek do kontynuacji: " + continuation); if (continuation >= 0 && moves[continuation]) { Console.WriteLine("MakeStep " + continuation); Console.WriteLine("#END"); continue; } var possibleMoves = new List<int>(); for (int i = 0; i < 4; i++) if (moves[i]) possibleMoves.Add(i); if (possibleMoves.Count > 0 && possibleMoves.Contains(backward)) possibleMoves.Remove(backward); Console.Write("#PRINT Losowy wybor z kierunkow ["); foreach (var m in possibleMoves) Console.Write(" " + m); Console.WriteLine(" ]"); continuation = possibleMoves[rand.Next(possibleMoves.Count)]; backward = (continuation + 2) % 4; Console.WriteLine("MakeStep " + continuation); Console.WriteLine("#END"); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | import java.util.ArrayList; import java.util.Arrays; import java.util.Random; import java.util.Scanner; public class program { public static void main(String args[]) { Scanner input = new Scanner(System.in); Random rand = new Random(); int backward = -1; int continuation = -1; Boolean[] moves = {false, false, false, false}; while (true) { ArrayList<ArrayList<String>> view = new ArrayList<ArrayList<String>>(); System.out.println("FieldOfView"); for (int y = 0; y < 5; y++) view.add(new ArrayList<String>(Arrays.asList(input.nextLine().split(" ")))); System.out.println("#PRINT Pole widzenia to " + view); moves[0] = ! view.get(1).get(2).equals("O"); moves[1] = ! view.get(2).get(3).equals("O"); moves[2] = ! view.get(3).get(2).equals("O"); moves[3] = ! view.get(2).get(1).equals("O"); System.out.println("#PRINT Lista ruchow: " + Arrays.asList(moves)); System.out.println("#PRINT Kierunek do kontynuacji: " + continuation); if (continuation >= 0 && moves[continuation]) { System.out.println("MakeStep " + continuation); System.out.println("#END"); continue; } ArrayList<Integer> possibleMoves = new ArrayList<Integer>(); for (int i = 0; i <4; i++) if (moves[i]) possibleMoves.add(i); if (possibleMoves.size() > 0 && possibleMoves.contains(backward)) possibleMoves.remove(new Integer(backward)); System.out.println("#PRINT Losowy wybor z kierunkow " + possibleMoves); continuation = possibleMoves.get(rand.nextInt(possibleMoves.size())); backward = (continuation + 2) % 4; System.out.println("MakeStep " + continuation); System.out.println("#END"); } } } |
Odnośniki:
[1] http://www.gryprogramistyczne.pl
[2] http://informatyka.wroc.pl/?page=0,0
[3] http://informatyka.wroc.pl/?page=0,1
[4] http://informatyka.wroc.pl/?page=0,2
[5] http://informatyka.wroc.pl/?page=0,3
[6] http://informatyka.wroc.pl/?page=0,4