Metody programowania -- laboratorium

Zadanie semestralne -- gra Halma

Wyniki

Są już dostępne wyniki turnieju wraz z informacjami o nagrodach. Gratulujemy zwycięzcom!

Zadanie

Zadanie polega na napisaniu programu grającego w grę Halma. Program zaliczeniowy ma umożliwiać grę, w której każdy z graczy może być człowiekiem lub komputerem. Dodatkowo zostanie zorganizowany turniej, w którym programy uczestników będą pojedynkowały się ze sobą. Udział w turnieju nie jest obowiązkowy, jednak dla autorów najlepszych programów przewidziane zostały cenne nagrody w postaci zwolnienia z egzaminu z Metod programowania z oceną bardzo dobrą oraz dodatkowe punkty na tym egzaminie.

Zasady gry Halma

Gra toczy się na kwadratowej planszy n × n. Graczy jest od 1 do 4. Każdy z graczy ma pewną ustaloną liczbę pionków. Pionki każdego z gracza są ustawione w rogu szachownicy w zadanym z góry układzie startowym. Pola na których stoją te pionki nazywamy polami domowymi gracza. Różni gracze mają przypisane różne rogi. Gracze poruszają się cyklicznie (gdy jest ich dwóch, to na przemian). Ruch polega na wybraniu jednego ze swoich pionków, jednego z czterech kierunków i przeniesieniu tego pionka na pierwsze wolne pole jakie znajduje się w tym kierunku. Błędem jest wybranie takiego pionka i kierunku, w którym nie ma wolnych pól. Celem każdego z graczy jest jak najszybsze przeprowadzenie swoich pionków do pól domowych w przeciwległym rogu. Wygrywa ten z graczy, który jako pierwszy przeniesie swoje pionki do przeciwległego rogu.

Poniższy rysunek przedstawia przykładową sytuację w grze z zaznaczonymi wszystkimi dozwolonymi ruchami białych pionków:

Ruchy

Zasady turnieju

Pojedynki turniejowe zostaną rozegrane na następujących zasadach:

Pola są oznaczane za pomocą współrzędnych (x, y). Współrzędna x oznacza pozycję poziomą, a y pionową. Lewy dolny róg planszy ma współrzędne (1, 1), a prawy górny róg (n, n).

Poniższy rysunek przedstawia sytuację początkową dla n=8 i k=3.

Plansza

Interfejs wejścia/wyjścia

Programy grające będą komunikowały się z programem sędziego przez standardowe wejście i wyjście.

Kierunki są oznaczane liczbami od 1 do 4 w sposób pokazany na rysunku:

Kierunki

Na początku program powinien wczytać parametry gry. Będzie to jeden wiersz zawierający pięć liczb całkowitych oddzielonych pojedynczymi odstępami. Liczby te to kolejno parametry n, k, N1, N2, nr. Pierwsze cztery zostały opisane powyżej, nr zaś jest numerem gracza (1 dla gracza białego i 2 dla gracza czarnego).

Gracz biały zaczyna grę od wykonania ruchu, a gracz czarny od wczytania ruchu przeciwnika. Wykonanie ruchu polega na wczytaniu ze standardowego wejścia jednego wiersza zawierającego jedną liczbę całkowitą będącą liczbą milisekund, jakie zostały programowi do końca partii, oraz wypisaniu na standardowe wyjście jednego wiersza zawierającego trzy liczby całkowite oddzielone pojednyczymi odstępami oznaczające kolejno współrzędną x wybranego pionka, jego współrzędną y oraz kierunek ruchu. Wczytanie ruchu przeciwnika polega na wczytaniu jednego wiersza w formacie takim, jak przy wypisywaniu własnego ruchu.

Program nie musi kończyć swojego działania po wczytaniu lub wypisaniu ruchu kończącego rozgrywkę. Sędzia sam wtedy przerwie działanie programów.

Można założyć, że ruchy przeciwnika oraz inne informacje podawane programowi na standardowe wejście będą poprawne i zgodne z opisaną składnią.

Sposób przeprowadzenia turnieju

Turniej będzie rozgrywany na zasadach każdy z każdym przy różnych limitach czasowych na różnych planszach. Na całą partię każdy z programów będzie mieć określane ograniczenie na czas działania i będzie to liczba pomiędzy 1-15min. Każdych dwóch zawodników będzie grało ze sobą jedną lub więcej partii na różnych planszach. Limity czasowe i liczba rozgrywanych partii między dwoma zawodnikami jest odwrotnie proporcjonalna do kwadratu liczby uczestników. W przypadku bardzo dużej liczby uczestników może być przyjęty inny system rozgrywek niż na zasadach każdy z każdym.

Konkurs najprawdopodobniej zostanie przeprowadzony w dniach 28-29 maja.

Środowisko

Konkurs będzie przeprowadzony w systemie Linux na kumputerach znajdujących się u nas w laboratorium. Program grający będzie miał do własnej dyspozycji 96 MB RAM w tym 8 MB na stos. Program grający stanowi plik lub pliki z kodem źródłowym oraz pliki z danymi. Źródła programów grających będą kompilowane na komputerach u nas w labach.

Program będzie uruchamiany w katalogu, gdzie znajdują się pliki z danymi. Jedyne dozwolone operacje wejścia/wyjścia to: czytanie ze standardowego wejścia, czytanie z plików z danymi i pisanie na standardowe wyjście.

Dozwolone są dwa języki programowania:

Należy używać języka wykładanego na Metodach Programowania.

Pascal

Źródła programu w Pascalu mogą się składać z jednego lub więcej plików o rozszerzeniu .pas. Dokładnie jeden z tych plików musi być kodem programu głównego, a pozostałe muszą być kodami modułów. Ponadto program będzie kompilowany za pomocą polecenia:

	ppc386 -O2 program.pas
gdzie program.pas jest nazwą kodu programu głównego.

Ograniczenia dla programów w Pascalu:

Ocaml

Źródła powinny składać się z jednego lub więcej plików z modułem grającym. Każdy plik powinien mieć rozszerzenie .ml lub .mli. Jeśli program składa się z więcej niż jednego pliku należy też dołączyć plik Makefile kompilujący program.

W przypadku braku pliku Makefile program powinien być zapisany w pliku program.ml i będzie kompilowany poleceniem:

	ocamlopt program.ml -o program

Jeśli plik Makefile zostanie dostarczony, to program będzie kompilowany poleceniem:

	make
Powinno ono stworzyć w bieżącym katalogu wykonywalny plik o nazwie program.

Ograniczenia dla programów w Ocamlu:

Dodatkowe ograniczenia

W następujących dodatkowych przypadkach działanie programu będzie uznawane za niewłaściwe, a co za tym idzie będzie uznawane za porażkę:

Ponadto zabronione jest też wykorzystywanie jakichkolwiek dodatkowych modułów; w szczególności używanie mechanizmów wątków, dodatkowych procesów, dodatkowych zasobów systemowych, niedozwolonych operacji wejścia/wyjścia i wszelkiego innego rodzaju niesportowego wykorzystywania systemu operacyjnego i dostępnego środowiska.

Zgłoszenie programu do konkursu

Program można wysyłać w nieprzekraczalnym terminie 27.05.2005 do godziny 10:00 na adres pan@mimuw (adresat zażyczył sobie, by jego adres był skrócony w celu utrudnienia spamerom wykorzystania go; trzeba uzupełnić podany adres o to, co zazwyczaj stoi w adresach po słowie mimuw).

Należy przysyłać źródła (nie binarki). Źródła muszą się kompilować u nas w labach tak jak jest to omówione w sekcji Środowisko. Ponadto można wysyłać pliki z danymi (o ile są). Proszę nie wysyłać programów generujących te pliki.

Jedna osoba może wysłać co najwyżej jeden program grający.

Wszelkie błędy, które pojawią się w waszych programach nie będą usuwane. Nawet jeśli są to proste błędy komunikacyjne na przykład jeśli zapomnicie wykomentować kod debugujący. Najlepiej przetestować swój program przed wysłaniem przy użyciu sędziego.

Sędzia

Sędzia do rozgrywania jednej partii dla dwóch programów judge.
Sposób użycia:

judge	[ -s, --silent ] [ -v, --verbose ] [ -h, --help ]
        [ -i, --init initial_scheme_file ] [ -T, --time game_msec ]
	[ -S, --stack stack_size ] [ -D, --data data_size ]
	player_one player_two

Znaczenie poszczególnych argumentów:
-s, --silent nie wypisywanie niczego
-v, --verbose wypisywanie stanu planszy oprócz standardowych komunikatów
-h, --help wyświetlenie wszystkich opcji
-i, --init initial_scheme_file   wczytanie parametrów gry z pliku initial_scheme_file, przy czym plik pownien składać się z czterech liczb n, k, N1, N2 poodzielanych pojedynczymi odstępami; w przypadku podania nazwy pliku `-' (minus) opis parametrów gry będzie czytany ze standardowego wejścia
-T, --time game_msec ustawienie limitu czasowego na mecz w milisekundach; możliwe są przyrostki h m s oznaczające odpowiednio liczbę godzin, minut lub sekund; na przykład 5m oznacza 5 minut na mecz, czyli 300000 milisekund
-S, --stack stack_size
-D, --data data_size
ustawienie limitów pamięciowych na rozmiar stosu oraz na rozmiar pamięci wirtualnej przeznaczonej dla programu w bajtach; możliwe są przyrostki m k b oznaczające odpowiednio liczbę megabajtów, kilobajtów lub bajtów
player_one
player_two
ścieżki do programów wykonywalnych

Przykładowe wywołanie:

judge -v -i gra.in -T 5m -S 8m -D 48m halma1 halma2

Przykładowa zawartość pliku gra.in z n=8, k=3, N1=10, N2=100 wygląda tak:

8 3 10 100

Wszelkie uwagi dotyczące sędziego proszę słać na pan@mimuw (adresat zażyczył sobie, by jego adres był skrócony w celu utrudnienia spamerom wykorzystania go; trzeba uzupełnić podany adres o to, co zazwyczaj stoi w adresach po słowie mimuw).

Szkielet programu grającego w Pascalu

procedure MojRuch;
begin
  ReadLn(cz); { pozostały czas w milisekundach do końca partii }
  WriteLn(x, ' ', y, ' ', d)
  { współrzędne pionka i kierunek wykonywanego ruchu }
end;

procedure JegoRuch;
begin
  ReadLn(x, y, d)
  { współrzędne pionka i kierunek ruchu przeciwnika }
end;

begin
  ReadLn(n, k, N1, N2, nr);
  { n, k, N1, N2, jak w opisie, nr -- numer gracza }
  if nr=1 then
    MojRuch;
  while true do begin
    JegoRuch;
    MojRuch
  end
end.

Szkielet programu grającego w Ocamlu

let split_string s =
  let rec split s i a =
    try
      let j = String.index_from s i ' ' in
      split s (j + 1) ((String.sub s i (j - i))::a)
    with Not_found ->
      List.rev ((String.sub s i ((String.length s) - i))::a)
  in split s 0 []

let read_integers () =
  List.map int_of_string (split_string (read_line ()))

let moj_ruch () =
  match read_integers () with
    [t] -> (* pozostały czas w milisekundach do końca partii *)
      (* współrzędne pionka i kierunek wykonywanego ruchu *)
      Printf.printf "%d %d %d\n" x y d
  | _ -> failwith "błąd"

let jego_ruch () =
  match read_integers () with
    [x; y; d] -> (* współrzędne pionka i ruchu przeciwnika *)
  | _ -> failwith "błąd"

let graj n k n1 n2 nr =
  if nr = 1 then moj_ruch ();
  while true do
    jego_ruch ();
    moj_ruch ()
  done

let main () =
  match read_integers () with
    [n; k; n1; n2; nr] ->
      (* n, k, n1, n2 jak w opisie, nr -- numer gracza *)
      graj n k n1 n2 nr
  | _ -> failwith "błąd"

main ()

Wymagania dla programów zaliczeniowych

Historia zmian

14.02.2005 - pierwsza wersja

21.03.2005 - dodanie ograniczeń na n, k, N1 i N2

25.03.2005 - dodanie nazwy

05.05.2005 - dodanie szczegółów turnieju

30.05.2005 - wyniki