Programowanie Funkcyjne Prezentacja projektu, 7 stycznia 2008, Juliusz Sompolski
Projekt Uniwersalny szablon pozwalający w łatwy sposób podłączać do siebie różne gry (lub ogólniejszą zawartość). Dla gier dwóch graczy uniwersalny moduł generujący sztuczną inteligencję gracza komputerowego z wykorzystaniem algorytmu MiniMax z α-β obcięciem.
Moduł Main Służy podpinaniu pod główne okno różnych pasków menu, aby móc kompilować różne cele program z tylko jedną grą, program z wszystkimi grami dostępnymi z jednego menu etc. Odpalany raz w trakcie wykonania programu, modyfikuje globalne okno.
Moduł Main module Main (Menu : MENU) = sig (** Uruchomienie programu *) val start : unit -> unit end (** Typ gornego menu *) module type MENU = sig (** Tworzy menu *) val menu: unit -> GMenu.menu_shell (** Tekst pomocy *) val help: string (** Tytul okna *) val title: string end
Moduł Window Globalne okno gry. Zapewnia dostęp do pewnych elementów okna które muszą być dostępne globalnie. Pozwala odpalać w oknie nową zawartość na tym poziomie zawartością tą nie musi być jeszcze gra.
Moduł Window (** Modul glownego okna programu *) module Window : sig (** Glowne okno programu *) val window: GWindow.window (** Glowny vbox okna *) val vbox: GPack.box (** Menu pomocy *) val help: unit -> unit (** Uruchomienie podanej zawartosci, niszczac dotychczasowa *) val run: GBin.frame * string * string -> unit end
Moduł Window Główna funkcja: run. Pobiera nową zawartość okna (w postaci dowolnego obiektu Gbin.frame GTK, niekoniecznie gry), tytuł okna i tekst pomocy i wyświetla je w oknie. help dostępny na zewnątrz ponieważ daje dla menu (tworzonego raz) aktualne okienko pomocy vbox dostępny na zewnątrz ponieważ Main wrzuca do niego odpowiednie menu window dostępny na zewnątrz ponieważ jest odpalany przy startowaniu Main
Moduł GameController Tworzy właściwą zawartość okna będącą grą. Jedynym ograniczeniem jest to, że gra musi polegać na klikaniu myszką w plansze. GameController automatycznie generuje historie gry, możliwość cofania i ponawiania ruchów oraz ogólny wygląd interfejsu.
Moduł GameController module type CONTROLLER = sig (** Reprezentacja rozgrywki *) type board (** Zwroc nazwe gry na tytul ramki *) val name : string (** Zwroc tekst pomocy *) val help : string (** Szerokosc graficznej reprezentacji planszy *) val graph_width : int (** Wysokosc graficznej reprezentacji planszy *) val graph_height : int (** Zwroc graficzna reprezentacje planszy *) val draw : board -> GDraw.pixmap (** Zwroc tytul dla ramki *) val title : board -> string option (** Zwroc punkty do panelu bocznego *) val score : board -> string option (** Zwroc historie gry do panelu bocznego *) val history : board -> string option (** Zareaguj na klikniecie w plansze. * Zwroc czy jest to stan przejsciowy, czy do zapisania w historii *) val click : board -> GdkEvent.Button.t -> board * bool (** Zwraca nowa gre *) val init : unit -> board end
Moduł GameController (** Tworzy gre z danego kontrolera *) module Main (Game : CONTROLLER) : sig (** Tworzy ramke z gra, zwraca tekst pomocy i tytul *) val new_game: unit -> GBin.frame * string * string end Na podstawie CONTROLLERa tworzy zawartość okna, którą można odpalić poprzez moduł Window.
Zaimplementowane gry Tic-Tac-Toe pierwsza prosta gra zaimplementowana w czasie testowania czy wyższe poziomy abstrakcji działają jak należy Nim druga gra, zaimplementowana żeby pokazać, że można obsługiwać gry wymagające wielokrotnego klikania w plansze dla wykonania ruchu Gomoku właściwy cel projektu.
Zaimplementowane gry Implementowanie gry jest mechaniczną czynnością polegającą na spełnieniu wymogów funktora CONTROLLER Obsługiwanie różnych graczy odbywa się tak, że gra jest jeszcze funktorem od dwóch graczy, do których delegowane jest obsłużenie kliknięcia w plansze. Gracze podłączani są pod grę wyżej, na poziomie menu wybierania nowej gry.
Zaimplementowane gry Przykładowy typ gracza (to jaki jest typ gracza leży w gestii samej gry, bo to ona jest funktorem od graczy. W przypadku zaimplementowanych przeze mnie gier jednak różni się on tylko typem planszy) module type PLAYER = sig val click : Base.board -> GdkEvent.Button.t -> Base.move option end
Zaimplementowane gry Gracz ludzki pobiera kliknięcie w plansze i zwraca ruch na podstawie tego kliknięcia. Gracz komputerowy ignoruje kliknięcie i zwraca ruch na podstawie AI. Trzeba jednak kliknięcia w plansze aby komputer się ruszył wynika to z założenia, że gry polegają na klikaniu w plansze.
Moduł MinMax Uniwersalny generator sztucznych inteligencji dla gier z wykorzystaniem algorytmu MiniMax z α-β obcięciem. Korzysta z pomocniczych podmodułów Stream oraz Tree do obsługi strumieni i leniwych drzew. Drzewo gry jest zdefiniowane w sposób leniwy. Algorytm chodząc po drzewie generuje je więc na bieżąco przez co fragmenty drzewa które zostały obcięte nie zostaną wygenerowane. Oddzielony w ten sposób jest sam algorytm obcinania od generowania drzewa.
Moduł MinMax (** Parametry drzewa MinMaxa *) module type PARAM = sig (** Glebokosc przeszukiwanego drzewa *) val depth : int (** Rozgalezienie przeszukiwanego drzewa *) val degree : int end (** Typ modułu planszy rozgrywki dla AI *) module type AI_BOARD = sig (** Typ przechowujacy informacje o grze *) type state (** Typ przechowujacy ruch w grze *) type move (** Ruch pusty - symbolizujacy blad *) val nullmove : move (** Lista ruchow dostepnych z danej sytuacji, wraz z heurystycznymi ocenami *) val next : state -> (move * int) list (** Wykonanie ruchu *) val make_move : state -> move -> state (** Ocena stanu gry *) val eval : state -> int end
Moduł MinMax (** Algorytm Min-Max z Alfa-Beta obcieciem *) module Main (Q : AI_BOARD) (P : PARAM) : sig (** AI - podaj jaki ruch nalezy wykonac *) val next : Q.state -> Q.move end Algorytm bierze ruchy od razu wraz z heurystycznymi ocenami ze względów wydajnościowych gra często może szybko ocenić jakość danych ruchów. Gdyby to MinMax miał oceniać same stany, to żeby obciąć rozgałęzienie drzewa do najlepszych ruchów musiałby wykonać wszystkie ruchy, ocenić stany po ruchach i dopiero wybrać z nich najlepsze. Mając wstępne oceny jakości ruchów algorytm może od razu obciąć rozgałęzienie drzewa.
Gomoku Oceny ruchów w Gomoku dokonywane są na podstawie przeszukiwania planszy i zaznaczania na niej różnych wzorców. Wykorzystywany jest do tego dodatkowy pomocniczy moduł służący do ogólnego wyszukiwania wzorców (liniowych) na dwuwymiarowych prostokątnych planszach. MinMax gracza komputerowego ustawiony jest na głębokość 6ciu ruchów i rozgałęzienie 4rech najlepszych ruchów.
Podsumowanie jullass@hermes:~/proj/pf$ wc -l *.ml 30 CommonMenu.ml 260 GameController.ml 499 Gomoku.ml 83 GomokuMenu.ml 53 MainAll.ml 51 MainGomoku.ml 50 MainNim.ml 50 MainTicTacToe.ml 203 MinMax.ml 252 Nim.ml 95 NimMenu.ml 253 TicTacToe.ml 52 TicTacToeMenu.ml 67 Window.ml 1998 total
Podsumowanie John Hughes, Why Functional Programming Matters http://www.math.chalmers.se/~rjmh/papers/whyfp.html Projekt w sieci http://students.mimuw.edu.pl/~js248396/home/works/pf.tgz