• No results found

Automatisk draggenerator för spel (Automatic move generator for games)

N/A
N/A
Protected

Academic year: 2021

Share "Automatisk draggenerator för spel (Automatic move generator for games)"

Copied!
85
0
0

Loading.... (view fulltext now)

Full text

(1)

 

Examensarbete inom Datalogi

Avancerad nivå, 30 hp Stockholm, Sverige 2013

Automatisk draggenerator för spel

ULF RUSTAS

(2)

Automatisk draggenerator för spel (Automatic move generator for games)

ULF RUSTAS (URUSTAS@KTH.SE)

Maj 2013

Examensrapport i Datalogi vid KTH-CSC

Handledare/Uppdragsgivare: Fredrik Niemelä, KTH-CSC Examinator: Stefan Arnborg, KTH-CSC

TRITA xxx yyyy-nn

(3)
(4)

Referat

Målet med detta exjobb var att undersöka hur man kan skapa en draggenerator för spel definierade i ett predikatlogiskt språk, GDL.

Draggeneratorn är tänkt att fungera som ett verktyg för att förvand- la spelregler till spelträd som i sin tur kan automatiskt avsökas och analyseras av program för automatisk klassificering eller spelande.

GDL är snarlikt Prolog och draggeneratorn konstruerades på ett liknande sätt som man skulle ha gjort för en Prolog-interpretator. Det gjordes dock en mängd anpassningar, tillägg och optimeringar specifika för GDL.

Draggenerering är i mycket en imperativ process och att använda ett deklarativt, predikatlogiskt språk som GDL visar sig medföra pre- standaproblem trots optimeringar och anpassningar.

Abstract

Automatic move generator for games

The purpose of this project is to investigate how to create a move generator for games defined in the predicate logic language GDL. The move generator is supposed to be used as a tool for converting game rules to playtrees that can be automatically traversed and analysed by programs for classification and playing.

GDL is similar to Prolog so the move generator was implemented by modification of a method to make a Prolog interpreter, though a number of adaptations, additions and optimizations specific to GDL were made.

Move generation is a very iterative process, so using a declarative, predicate logic language like GDL will be shown to have problems with regards to performance, despite adaptations and optimizations.

(5)

Innehåll

1 Introduktion 1

1.1 General Game Playing . . . 1

1.2 Språket GDL . . . 1

1.3 Draggeneratorns roll . . . 2

1.4 Projekt- och rapportöversikt . . . 2

2 GDL 4 2.1 Grammatik . . . 5

2.1.1 Kommentarer . . . 5

2.1.2 termer . . . 5

2.1.3 literaler . . . 6

2.1.4 Satser . . . 7

2.1.5 Förfrågningar (queries) mot fakta . . . 7

2.1.6 Relationer . . . 9

2.1.7 Regler . . . 9

2.1.8 Särskilda literaler . . . 11

2.2 Deklarativ och procedurell innebörd . . . 12

2.3 Semantiska begränsningar . . . 13

2.3.1 Stratification . . . 13

2.3.2 Recursion restriction . . . 13

2.3.3 Safety . . . 13

2.4 Uppdaterade versioner av GDL . . . 14

2.4.1 Prefix / Infix GDL . . . 14

2.4.2 GDL-II . . . 14

2.5 Egna tillägg . . . 15

2.5.1 Fristående förfrågningar . . . 15

2.5.2 Negationer . . . 15

2.5.3 Positionsrelationer . . . 16

3 Spelgång 17 3.1 Fördefinierade relationer . . . 17

3.1.1 role . . . 17

3.1.2 true . . . 18

3.1.3 init . . . 18

3.1.4 legal . . . 18

3.1.5 does . . . 19

3.1.6 next . . . 19

3.1.7 terminal . . . 20

3.1.8 goal . . . 20

3.2 Korrekthet . . . 20

(6)

4 Exempel på Spel 21

5 Kompilatorn 24

5.1 Grammatiken . . . 26

5.2 Tillvägagångssätt för implementationen . . . 27

5.3 L0 - Matchning . . . 28

5.3.1 L0 - Grammatik . . . 28

5.3.2 L0 - Kompilering . . . 29

5.3.3 L0 - Exekvering . . . 30

5.3.4 Unifiering . . . 33

5.3.5 L0 - Hämta resultat . . . 34

5.4 L1 - Länkning . . . 34

5.4.1 L1 - Grammatik . . . 35

5.4.2 L1 - Kompilering . . . 36

5.4.3 L1 - Exekvering . . . 37

5.4.4 L1 - Hämta resultat . . . 37

5.5 L2 - Regler . . . 37

5.5.1 L2 - Grammatik . . . 37

5.5.2 L2 - Kompilering och länkning . . . 39

5.5.3 L2 - Exekvering . . . 40

5.5.4 Optimeringar - Chain rule och LCO . . . 41

5.5.5 L2 - Hämta Resultat . . . 42

5.6 distinct . . . 42

5.7 L3 - Relationer . . . 43

5.7.1 try-instruktioner . . . 43

5.7.2 backtracking . . . 45

5.8 Disjunktioner och konjunktioner . . . 46

5.8.1 OR . . . 46

5.8.2 AND . . . 47

5.9 Negationer . . . 48

5.10 Indexering . . . 51

6 Draggeneratorns uppbyggnad 53 6.1 GDL_Player . . . 53

6.2 GameManager . . . 53

6.2.1 Ladda program . . . 53

6.2.2 Ladda/Hämta position . . . 53

6.2.3 Läsa drag . . . 54

6.2.4 Utföra drag . . . 54

6.2.5 Förfrågningar . . . 54

6.3 GDL_Compiler . . . 54

6.3.1 Kompilera program . . . 55

6.3.2 Kompilera förfrågan . . . 55

6.3.3 Hämta kompilerad kod . . . 55

6.3.4 Tolka resultat . . . 55

6.4 VM . . . 56

6.4.1 Metoder i VM . . . 57

7 Optimeringar 58 7.1 Optimeringar i GameManager . . . 58

7.1.1 Återanvändning av program och queries . . . 58

7.1.2 Up och down . . . 59

(7)

7.2 Optimeringar i kompilatorn . . . 59

7.2.1 CONSTANTS . . . 59

7.2.2 Chain rule och LCO . . . 59

7.2.3 Index . . . 59

7.2.4 Linecachen . . . 60

7.3 Optimeringar i VM . . . 61

7.3.1 Heapcell -> int . . . 61

7.4 Framtida optimeringar . . . 61

7.4.1 Statisk Memoization . . . 61

7.4.2 Memoization . . . 62

7.4.3 Kompilera till JVM kod / C kod. . . 62

8 Arbetsverktyg 63 8.1 Java . . . 63

8.2 Eclipse . . . 63

8.3 ANTLR . . . 64

8.4 ANT . . . 64

8.5 Enhetstester - JUnit . . . 64

8.6 Subversion . . . 65

9 Analys 66 9.1 Begränsningar hos kompilatorn . . . 66

9.2 Andra klienter för GDL . . . 66

9.3 Alternativa tillvägagångssätt . . . 67

9.4 Alternativt spelspråk . . . 67

9.4.1 Multigame . . . 67

10 Slutsats 69

Litteraturförteckning 70

A Grammatik för GDL 72

B GDL_Player README.txt 75

(8)

Kapitel 1

Introduktion

Exjobbet gick ut på att utveckla en kompilator som, givet en beskrivning av ett spel, kan agera som en draggenerator för detta spel. En draggenerator för ett spel kan generera alla tillåtna drag från en given position. Genom att anropa draggeneratorn rekursivt på de olika positionerna som uppkommer kan man bygga ett träd över alla positioner som är nåbara från en given position inom ett givet antal drag. Det ingår inte i draggeneratorns uppgift att värdera eller prioritera positioner eller drag.

Uppdragsgivare för exjobbet var KTH-CSC och det var tänkt som en delkom- ponent i en automatisk spelarklient för General Game Playing.

1.1 General Game Playing

Med General Game Playing (GGP)[6] menas spel mellan datorsystem där spelreg- lerna inte är bestämda på förhand, utan bestäms inför varje spelparti. Begreppet presenterades i [17] som en reaktion på att AI för spel var allt för skräddarsydda för just de spel som de skulle spela, så att all analys av spelet skedde av människor innan matchens början.

GGP är intressant för att det ställer stora krav på systemens anpassningsgrad, genom att tvinga dem lösa många olika sorters problem. Det i sin tur leder för- hoppningsvis till system som skulle kunna användas i komplicerade och varierande situationer i det verkliga livet, såsom processhantering, E-handel eller militära ope- rationer.

För att utvärdera olika system anordnas turneringar där de får spela mot varand- ra. I [17] föreslås det att reglerna för spelen på turneringen skulle genereras slump- vis. Ett exempel på en sådan turnering är den som hålls i samband med den årli- ga AAAI-konferensen (http://www.aaai.org/Conferences), International Gene- ral Game Playing Competition. (http://games.stanford.edu/)

1.2 Språket GDL

För att reglerna ska kunna förstås av systemen måste de vara skrivna i ett på förhand känt språk. Game Description Language (GDL, [7]) är ett språk som utvecklats av Stanford Logic Group just för att användas till General Game Playing-matcher.

För att spela matcherna använder man sig av internet och varje spelare (klient) kommunicerar med en server, Game Manager (GM). GM delar ut den uppsättning spelregler som kommer att användas i matchen till klienterna. Reglerna kan både beskriva kända spel, som Schack eller Othello, och helt nya spel, framtagna just för detta tillfälle. GM delar även ut rollerna till de olika spelarna.

(9)

KAPITEL 1. INTRODUKTION

Spelarna gör sedan sina drag och skickar in dem till GM. GM kollar att dragen är korrekta, ser om spelet har avslutats och skickar sedan ut den informationen till samtliga spelare. Det finns även begränsningar på hur lång tid man får ta på sig för att räkna ut ett drag.

1.3 Draggeneratorns roll

Draggeneratorn fungerar som en hjälpreda till en spelare, mänsklig eller maski- nell, på så sätt att den kan abstrahera bort kompilerings-momentet och förvandla spelreglerna till ett spelträd (med positioner som noder) som man kan använda algoritmer för trädutforskning på.

Draggeneratorn är inte en komplett spelklient för GDL och är inte heller tänkt att vara det. Det saknas helt funktioner för att kommunicera med en GM. Om man vill användas sig av draggeneratorn i tävlingssammanhang, så får man sköta det manuellt eller troligare skriva ett klientprogram som gör det.

GameManager (Server)

Draggenerator Spelare/AI

(Klient) 1

2 3

- Spelregler

4

- Startposition - Andra spelares drag

- Regler - Positioner - Drag - Poäng?

- Slutposition?

- Övr. förfrågningar

- Möjliga drag

- Resulterande positioner - Svar på förfrågningar

Valt drag

Figur 1.1. Kommunikationsflöden i en match

I figur 1.1 ges en bild av hur server, klient och draggenerator kommunicerar med varandra. Eftersom klienten har tillgång till alla regler så kan den simulera effekterna av andra spelares tänkta drag och själv spela valfritt antal drag framåt innan den väljer ett tänkt optimalt drag för nuvarande position.

God prestanda är väldigt viktig för draggeneratorn. I tävlingssammanhang har klienter begränsat med tid på sig att utvärdera positioner och göra drag och en AI har stor hjälp av hinna få fram så stora / djupa spelträd som möjligt. Mycket av arbetet i projektet handlar om att optimera prestanda.

1.4 Projekt- och rapportöversikt

Projektet genomfördes fram till en fungerande draggenerator under 2006. Då fram- ställdes även det första utkastet till rapporten, som sedan har komplementerats och

(10)

1.4. PROJEKT- OCH RAPPORTÖVERSIKT

färdigställts under 2012 / 2013. Draggeneratorn är en del i ett större projekt där ytterligare två examensarbeten genomförts [19] [18].

Rapporten består i huvudsak av dessa delar:

Beskrivning av GDL Först presenteras lite bakgrundsinformation om hur GDL är uppbyggt och används. Kapitel 2 beskriver språket GDL och kapitel 3 spelgången. Till sist ges i kapitel 4 ett komplett exempel på ett spel.

Implementation av draggeneratorn Sedan beskrivs hur draggeneratorn imple- menterades. Kapitel 5 beskriver hur en befintlig arkitektur för en Prolog- kompilator implementeras och anpassas för att klara GDL. Detta är ett gans- ka tungt och tekniskt kapitel, men en implementation av en kompilator är ett tungt, detaljerat och tekniskt arbete. Det var arbetet här som tog mest tid i anspråk, både under själva projektet och rapportskrivandet. Kapitel 6 går ige- nom övriga delar av draggeneratorn och hur de samverkar med kompilatorn.

Eftersom prestanda är väldigt viktigt för draggeneratorn så sammanfattas arbetet med optimeringar för sig, i kapitel 7. Dessutom diskuteras vilka ytter- ligare optimeringar som skulle kunna prövas. I kapitel 8 räknas de viktigaste verktygen som användes för projektet upp.

Analys Till sist kommer kapitel för analys (Kapitel 9) och slutsats för projektet (Kapitel 10).

(11)

Kapitel 2

GDL

Detta projekt använder den version av GDL som fanns tillgängligt i början av 2006.

Det har senare gjorts uppdateringar av språket som tar bort vissa begränsningar, se 2.4.2. Det finns numera även två varianter på syntax för GDL, Infix och Prefix GDL (se 2.4.2). Detta projekt stöder bara Prefix GDL, som var den enda varianten som fanns vid projektets början.

För att ordentligt testa de datorsystem som ingår i GGP-matcherna måste GDL vara så flexibelt att det kan användas till att beskriva många olika, vitt skilda sorters spel. Några egenskaper hos spelen är:

• Valfritt antal spelare, men spelare får inte tillkomma eller försvinna under spelets gång.1

• Det finns ingen inbyggd turordning för spelarna, utan den kan variera under spelets gång.

• Det är tillåtet för flera spelare att utföra drag samtidigt.

Språket har dock en mängd begränsningar som gör att vissa typer av spel inte kan beskrivas:

• Det får inte finnas gömd information. Alla spelare har tillgång till samma data. Poker, och andra typiska kortspel där varje spelare bara ser sin egen hand, går alltså inte att beskriva i GDL.

• Det får inte finnas slump. Givet en position och en uppsättning drag av spe- larna så kommer man alltid att få samma resultat. Backgammon går därmed inte att beskriva i GDL.

Dessa begränsningar är borttagna i en uppdaterad version av språket (GDL-II), se 2.4.2.

Det sätt som GDL använder för att lagra positioner i spelet påverkar också vilken sorts spel som är rimliga att beskriva.

• Det finns inga aritmetiska eller strängmanipulerande funktioner. GDL förstår sig inte på siffror, utan ser dem bara som abstrakta symboler. Alla möjli- ga tillstånd måste kunna byggas upp från distinkta “byggstenar” som ges i regelbeskrivningarna. Sättet på vilket man bygger strukturer och framförallt komplexiteten hos dessa är också begränsad i grammatiken. Man kan inte beskriva luffarschack eftersom brädet kan bli hur stort som helst.

1Man kan simulera att en spelare lämnar spelet genom att se till att den från och med nu inte kan utföra någon handling som påverkar någon framtida position

(12)

2.1. GRAMMATIK

• Spelet fortgår i distinkta omgångar. Alla spelare ser den nuvarande positionen och spelet fortlöper inte förrän alla spelare har valt en handling. Man kan inte på något naturligt och enkelt sätt beskriva spel som pågår i realtid. Det innebär att det flesta vanliga dataspel inte går att beskriva, framförallt inte spel där reaktionstid ingår som ett skicklighetsmoment.

På sätt och vis arbetar alla datorspel med distinkta tillstånd, eftersom allt till slut måste lagras som ettor och nollor. Dessutom har även rena actionspel någon sorts tidskvanta som skulle kunna ses som spelomgångar. I värsta fall skulle man alltid kunna simulera spelet på en virtuell CPU, och använda dess instruktions- exekverande som spelomgångar. Men även om det skulle vara teoretiskt möjligt att implementera denna typ av spel, så skulle det inte låta sig göras praktiskt.

Att en så grundläggande sak som aritmetik saknas kan först tyckas lite märkligt, men det har sin grund i semantiska begränsningar man gjort på språket för att kunna bevisa att varje speldefinition alltid har en lösning, se sektion 2.3.2.

Språket har inte något som helst stöd för I/O, såsom filer, nätverkskopplingar, textinmatning eller utskrift. Det är helt och håller utanför GDL:s definition hur man läser in program och positioner och hur man får ut resultat, gör drag eller kommunicerar med andra spelare.

Trots alla dessa begränsningar kan man beskriva en stor mängd spel som kan ha stor (eller liten) komplexitet. Om man tittar på de klassiska sällskapsspelen som uppfunnits under mänsklighetens historia, så är den enda större bristen avsaknaden av gömd information (och därmed även slump).

2.1 Grammatik

GDL är en delmängd av predikatlogik och är baserat på språket Datalog [13]. Data- log används framförallt till att beskriva kunskapsdatabaser på ett kompakt vis som man sedan kan ställa förfrågningar mot.

Datalog liknar Prolog men har vissa begränsningar som ärvs av GDL, se 2.3.

Man kan säga att ett predikatlogiskt språk skiljer sig från ett imperativt språk genom att man talar om hur en lösning på ett problem ska se ut och låter kompila- torn räkna ut hur man ska hitta lösningarna, medan man i imperativa språk såsom C eller Java i detalj måste specificera exakt hur man går till väga för att hitta en lösning.

Prolog är ett predikatlogiskt språk som är bra på symbolisk, icke-numerisk be- arbetning, där fokus ligger på objekt och deras relationer [12].

2.1.1 Kommentarer

Kommentarer börjar med “;” och går till radslut.

; These are the two players in the game.

(role white) (role player) 2.1.2 termer

GDL består på lägsta nivå av följande tre komponenter, vars samlingsnamn är termer :

objektkonstanter (object constants) a, black, c, no-op, 12. Helt enkelt ett namn på ett objekt.

(13)

KAPITEL 2. GDL

funktionskonstanter (function constants) (p a b), (mark (cell 2 1)). En funktionskonstant är en objektkonstant som tar ett antal argument. I uttrycket (p a b) är p funktionskonstanten och a och b argumenten. Antalet argument i en funktionskonstant kallas även för funktionskonstantens ställighet. Argu- menten består av andra termer, som är subtermer till funktionskonstanten.

Notera att detta medger att man kan nästla funktionskonstanter i varandra, som i exemplet (mark (cell 2 1)), där både mark och cell är funktionskon- stanter.

variabler (variables) ?a, ?player. Ett frågetecken följt av ett namn. Variab- ler används, som kommer att visas senare, för att matcha både objekt- och funktionskonstanter.

Funktionskonstanter skiljer GDL från Datalog som inte tillåter dessa.

I den GDL-specifikation som fanns i början på 2006 anges inte explicit vilka tecken som är tillåtna i namnen på de olika konstanterna och variablerna, inte heller om de är skiftlägeskänsliga. I praktiken definieras detta av de spel som används i tävlingen. I draggeneratorn är de skiftlägeskänsliga och de tecken som tillåts är : a-z A-Z 0-9 _ -

Senare specifikationer, som kommit efter detta projekts påbörjats, har dock angett att den variant av GDL som används här (Prefix GDL, se 2.4.1) ska vara icke-skiftlägeskänslig. Detta verkar inte ha utnyttjas av några av de program som fanns utdelade som exempel på GDL-siten.

Notera att heltal räknas som konstanter. Det finns inget stöd för aritmetik, så ‘3’

är bara ett namn, utan någon djupare betydelse än ordet ‘tre’ eller bokstavsföljden

‘qwertyuiop’.

Ställigheten för en funktionskonstant är signifikant. (p a) och (p a a) är två helt skilda funktionskonstanter. För att skilja på funktionskonstanter med sam- ma namn men med olika ställighet lagras de internt i kompilatorn på formen namn”/”ställighet. Objektkonstanter ses som funktionskonstanter med ställighet 0. Med denna namngivning skulle alltså (p a b) vara (p/2 a/0 b/0).

Sitt namn till trots liknar funktionskonstanter bara delvis funktioner i imperati- va språk. De är lika på så sätt att de “ger” objekt och därmed kan används på alla ställen där man använder objekt. Det är därför som det går alldeles utmärkt att nästla funktionskonstanter : (f (f a) (b a(c d))). De skiljer sig dock i att det objekt de ger aldrig kan vara lika med något annat objekt än sig själva, det objekt (square-root 4) står för är inte, och kan aldrig göras till, samma som (add 1 1).

Det enda (f a) är samma sak som är (f a). Funktionskonstanterna motsvarar det som kallas totala funktioner på så sätt att de alltid ger ett unikt objekt och att de alltid är definierade, oavsett vilka termer man stoppar in. Man kan inte på något vis underkänna (sqrt -1) eller ens (sqrt blue) som “syntaktisk felaktiga”.

2.1.3 literaler

Nästa abstraktionsnivå ovanför termer är literaler.

predikatsymbol (relation constants) (true a), (init c d), terminal. Här är true, init och terminal predikatsymboler. Ställigheten och ordningen på termerna är även här signifikant.

atomär formel (atomic sentence) En predikatsymbol tillsammans med sina ar- gument. I fallet (init c d e) är alltså den atomära formeln hela uttrycket, inklusive parenteserna.

(14)

2.1. GRAMMATIK

literal En literal är antingen en atomär formel eller negationen av denna. Negatio- ner uttrycks med hjälp av det reserverade ordet “not” och är på formen (not atomär sentens). Så (not a) och (not (p ?a (b a))) är literaler, men även (p a) och terminal.

sentens (Ground expression) En literal där det inte förekommer några variabler någonstans, exempelvis terminal, (p a) och (not (p c (d e))). Däremot är ?a, (p a ?b) och (p c (d ?e)) inte sentenser.

Atomära formler är alltså syntaktiskt identiska med funktionskonstanter. Skill- naden mellan den atomära formeln (f a) och funktionskonstanten (f a) är att formeln är ett påstående om predikatsymbolen f som kan vara sant eller falskt medan funktionskonstanten f är ett objekt.

2.1.4 Satser

Ett program i GDL består av en mängd satser. Satser uttrycker vad som gäller i ett program, vad som är sant. I ett GDL-program uttrycker satserna både spelreglerna och den nuvarande positionen.

Den enklaste sortens satser består bara av en atomär formel och kallas fakta.

Ett faktum är ett påstående som är ovillkorligt sant i det givna programmet (father homer bart)

(true (cell 1 1 b)) (father abe homer)

Fakta kan även innehålla variabler:

(father santa-claus ?x)

Detta säger att för alla termer som går att konstruera i ett givet program så är satsen (father santa-claus term) sann.

Alla variabler i satser i program är implicit bundna med en allkvantifikator vilket också innebär att alla variabler med samma namn inom en sats alltid måste vara samma variabel. Variabler med samma namn, fast i olika satser, påverkar däremot inte varandra.

2.1.5 Förfrågningar (queries) mot fakta

När man har ett program kan man ställa förfrågningar mot det. Förfrågningar består av en eller flera literaler. Varje literal kan vara sann eller falsk i det givna programmet. Om alla literaler i förfrågan är sanna är även förfrågan som helhet sann.

Unifiering och matchning Olika böcker ger handlingen att undersöka om en förfrågan är sann olika namn. Inom logiken brukar man använda en algoritm som kallas för unifiering, så där ser man om man kan unifiera förfrågningen med något faktum. I [12, s. 58] använder man sig av en snarlik algoritm med vissa begränsningar av prestandaskäl (se sektion 2.3.2), och för att särskilja den från logiken kallar man den för matchning. I [4] använder man båda begreppen, utan att definiera dem närmare, men generellt sett verkar man använda unifiering när man är på predikatnivå och matchning när man pratar om komponenter i predikat. Så en förfrågan och ett faktum kan unifiera, medan två termer matchar eller ej. Det är så dessa definitioner används i denna rapport.

(15)

KAPITEL 2. GDL

Den enklaste sortens förfrågning man kan ha består bara av en atomär formel och är alltså syntaktiskt identisk med ett faktum. Det är kontexten som avgör om en atomär formel är ett faktum eller en förfrågning.

(father homer bart) (true (cell 2 2 b))

Det som avgör om en literal i en förfrågan är sann är om det finns någon sats i programmet som unifierar med den. För att en literal ska unifiera med en sats måste två krav uppfyllas. För det första måste de ha samma predikatsymbol och ställighet. Literalerna (father homer) och (sant (cell 2 2 b)) kan inte unifiera med de satser som står ovan och är i den kontexten falska. För det andra så måste deras termer matcha varandra. Termer matchar varandra om och endast om de är identiska objekt. För funktionsobjekt innebär detta alltså de måste ha samma namn och ställighet och att alla deras subtermer måste matcha varandra i tur och ordning. Med programmet givet som ovan skulle alltså (father homer bart) vara sann, men (true (cell 2 1 b)) vara falsk.

Variabler kan matcha vilka objekt som helst, inklusive andra variabler. Både literalerna (father ?x bart) och (father homer ?y) är sanna. När man matchar en variabel med ett objekt binder man det objektet till variabeln. Om variabeln förekommer på fler ställen i satsen kan man bara matcha den mot det redan bundna objektet. I fallet ovan skulle alltså ?x vara bunden till homer och ?y till bart. Det innebär också att (father ?x ?x) inte är sant, eftersom det inte finns något sätt att binda ?x till ett objekt så att hela uttrycket unifierar med något faktum i programmet. Däremot är (true (cell ?x ?x b)) sann, med ?x bunden till 2.

När man matchar två obundna variabler med varandra så binder man den ena variabeln till den andra. Man “hittar inte på” ett funktionsobjekt och binder båda variablerna till detta, även om detta också skulle göra matchningen sann. Anled- ningen till detta är att någon av variablerna kan finnas på flera ställen i uttrycket, och att man på detta vis inte i onödan begränsar möjligheterna att matcha även där genom att binda alltför snävt.

Denna princip för matchning, där man begränsar sig så lite som möjligt, kallas

“Most general unifier”. “Most general unifier” är alltid unik, sånär som på namn- givning [14, s. 277].

När man ska matcha en variabel som är bunden till något så matchar man mot det den är bunden till. Om detta också är en variabel så fortsätter man med att följa bindningarna tills man når något som inte är en bunden variabel, d.v.s. antingen en obunden variabel eller ett objekt, sedan används någon av det ovanstående reglerna för matchningen.

Genom att systematiskt tillämpa de regler som ges ovan och komma ihåg vilka variabler som bundits till vad så kan man matcha ganska komplicerade uttryck.

(p (f ?X) (h ?Y (f a)) ?Y) (p ?Z (h ?Z ?W) (f ?W)))

1. Testa att predikatsymbolerna inklusive ställighet matchar. (p/2 matchar p/2).

Gå sedan igenom subtermerna en efter en.

2. (f ?X) och ?Z matchar om man binder ?Z till (f ?X) 3. Matchar (h ?Y (f a)) och (h ?Z ?W)?

a) h/2 matchar h/2. Gå sedan igenom alla deras subtermer.

(16)

2.1. GRAMMATIK

b) ?Y matchar ?Z, (d.v.s (f ?X)) om man binder ?Y till ?Z.

c) ?W matchar (f a) om man binder ?W till (f a).

4. Matchar ?Y och (f ?W)?, d.v.s. matchar (f ?X) och (f (f a))?

a) f/2 matchar f/2.

b) ?X matchar (f a) om man binder ?X till (f a).

Sedan finns det inget mer att matcha, och hela uttrycket matchade med följande variabelbindningar : ?X = (f A), ?W = (f a), ?Z = (f (f a)), ?Y = (f (f a)).

Variabler i förfrågningar kan liknas vid predikatlogikens existenskvantifikatorer i det att förfrågningen är sann om det existerar objekt som man kan binda till varje variabel i förfrågningen och få en sats som matchar en sats i programmet.

2.1.6 Relationer (father homer bart) (father abe homer)

Man kan ha flera satser med samma predikatsymbol. Alla satser med samma predikatsymbol definierar tillsammans en relation med samma namn som predikat- symbolen.

När man ställer en förfrågan mot ett program så testar man förfrågningen mot alla satserna i tur och ordning. Om det finns flera satser med samma predikatsymbol måste man alltså vara beredd på att man kan få flera svar. I programmet ovan skulle alltså (father abe ?x) misslyckas att matcha den första satsen, men lyckas med den andra (med bindningen ?x = homer). (father ?x ?y) skulle få två svar, ett med bindningarna ?x = homer och ?y = bart och ett med ?x = abe och ?y = homer.

Satserna i en relation behöver inte grupperas ihop i ett GDL-program, man kan blanda satser från flera relationer med varandra, även om detta inte är att rekommendera för läsbarhetens skull.

2.1.7 Regler

Den andra sortens satser man kan ha i ett program är regler. Regler skiljs syntaktiskt åt från fakta genom att de startar med en implikationsoperator (<=) och att de sedan består av en lista av literaler eller disjunktioner. Disjunktioner kommer att gås igenom senare, så till att börja med består listan enbart av literaler. Regler skiljer sig även från fakta i det att de inte automatiskt är sanna.

(<= (grandfather ?x ?y) (father ?x ?z) (father ?z ?y))

Listan av literaler startas alltid av en atomär sentens, som kallas regelns huvud.

Resten av regeln kallas dess kropp.

En regel fungerar på så vis att den atomära sentensen i dess huvud är sann om alla literaler och disjunktioner i kroppen kan fås att bli sanna. Huvudet uppför sig alltså som ett faktum, medan kroppen är som en förfrågan.

(father homer bart) (father abe homer)

(17)

KAPITEL 2. GDL

Så med ovan givna fakta skulle man kunna få grandfather att bli sann om man band ?x till abe, ?y till bart och ?z till homer, och man skulle då få att (grandfather abe bart) är sann. Eftersom variablers bindningar gäller i hela sat- sen så måste man matcha en variabel med en konstant på samma sätt i alla relationer i hela satsen.

Namnet implikationsoperator är ingen slump. Regeln (<= D A B C) kan ju ut- läsas “Om A OCH B OCH C så D”, vilket är samma sak som (A & B & C) -> D.

Fakta skulle kunna ses som regler utan kropp, alltså regler som gäller förbehålls- löst, true -> D. En förfrågan kan ses som en regel utan huvud, de ger alltså ingen ny kunskap, utan “testar” bara den befintliga kunskapen.

Satser på formen (P1 & P2 & ... & Pn) -> G kallas för en“Horn-clause”, när- mare bestämt en “definite clause”. Poängen med att ha sådana är att system av

“definite clauses” går att lösa algoritmisk för att se om ett påstående är sant. Inom logiken kan man använda sig av en algoritm som kallas SLD-resolution [2], men inom Prolog brukar man använda en snarlik algoritm som i [5, s.39] kallas för left-most resolution och i [12, s.42] för scanning.

För att matcha listor av förfrågningar Q1, Q2, ..., Qn : 1. Om listan är tom så returnera att matchningen lyckades.

2. Matcha den första förfrågningen i listan (Q1) mot det givna programmet. Håll reda på den första satsen i programmet som matchar, och kalla den för C.

Finns det ingen sats som matchar, returnera att matchningen misslyckades.

3. Det som matchas är på formen (<= H B1 B2 ... Bm) eller bara H för fakta.

Byt ut variablerna i de matchade uttrycket så att ingen av dem finns i Q1, ..., Qn. Matcha Q1 mot H (vilket vi ju vet går) och kom ihåg variabelbind- ningarna som S.

4. Byt ut Q1 mot B1 ... Bm. Förfrågningslistan blir då alltså B1, ..., Bm, Q2, ... Qn. Om H var ett faktum blir listan alltså bara Q2, ...,Qn. Använd variabelbindningarna i S på den nya förfrågningslistan.

5. Anropa algoritmen rekursivt på den lista man nu har. Om det lyckas, returne- ra att matchningen lyckades. Om det misslyckades, släng bort variabelbind- ningarna och återställ listan till Q1, ..., Qn. Kör om från steg 2 men starta sökningen efter satser som matchar på nästa sats efter C.

Givet följande program ...

(<= (p ?a ?c) (q ?a) (q ?c)) (q a)

(q b) (r b)

... så skulle följande lista av förfrågningar ...

(q b) (p ?a ?b) (r ?b)

... leda till följande exekvering

(18)

2.1. GRAMMATIK

Q1 : (q b) matchar (q b).

Q2 : (p ?a ?b) Q3 : (r ?b)

Rent faktum och inga variabelbindingar, ta bara bort Q1 och kör om algoritmen på Q2 till Q3

Q1 : (p ?a ?b) matchar (p ?a1 ?c) med {?a1 = ?a, ?c = ?b}

Q2 : (r ?b)

Notera variabelnamnbytet av ?a till ?a1 för att de inte ska krocka. Byt ut Q1 mot kroppen på p.

Q1 : (q ?a) matchar (q a) med {?a = a.}

Q2 : (q ?b) Q3 : (r ?b)

Q1 : (q ?b) matchar (q a) med {?b = a.}

Q2 : (r ?b)

Q1 : (r b) matchar ingenting!

Hoppa tillbaka en nivå och lös upp variabelbindningen och leta efter fler match- ningar

Q1 : (q ?b) matchar (q b) med {?b = b.}

Q2 : (r ?b)

Q1 : (r b) matchar (r b)

Listan är nu tom, och därför lyckades matchningen!

De viktigaste punkterna i algoritmen är dels rekursionen och dels omstarten av sökningen på nästa rad, backtrackingen. De två tillsammans innebär att man gör en djupet först-sökning av trädet av giltiga variabelbindningar.

Man kan även använda sig av backtracking för att få fram samtliga svar till en förfrågan, inte bara det första bästa som är rätt. Det är bara att spara undan det svar man fått och starta algoritmen igen genom en backtrack.

2.1.8 Särskilda literaler (or L1 L2 ... Ln)

(and L1 L2 ... Ln)

or är sann om någon av L1, ..., Ln är det, och and är sann om ALLA L1, ..., Ln är det. and är bara meningsfull som subterm till en or. Båda des- sa är redundanta i den mening att GDL redan har motsvarande funktionalitet via andra konstruktioner, and i kroppen på förfrågningar och or via backtracking inom en relations satser.

(distinct term1 term2)

(distinct term1 term2) är sann om term1 INTE matchar term2. Om term1 och term2 båda är konstanter (objekt- eller funktion-) så kollar den helt enkelt om det är samma konstant och i så fall blir (distinct term1 term2) falsk, i annat fall

(19)

KAPITEL 2. GDL

så är den sann. Om någon eller båda termerna är variabler jämför den de konstanter de är bundna till. För att distinct ska fungera ordentligt så måste alla variabler vara bundna redan innan man testar distinct.

(not A) är sann om predikatet A inte kan fås att bli sann. Återigen är detta oproblematiskt om A är en sentens, d.v.s. inte innehåller variabler, men om man har variabler så måste dessa vara bundna redan innan man testar (not A). För att undvika problem som detta har man en mängd semantiska begränsningar i GDL, se sektion 2.3.

2.2 Deklarativ och procedurell innebörd

Ordningen på satserna i en relation ska teoretiskt inte spela någon roll. I praktiken kommer dock de metoder som använts i detta exjobb att göra att ordningen spelar roll både för prestanda och lösbarhet. Detta innebär också att man kan behöva ha ett viss imperativt tänkande, man kan behöva stuva om i programmet så att sökningen av lösningar påverkas. I Prolog-sammanhang brukar man skilja på den deklarativa och den procedurella betydelsen av ett program (“Declarative vs. Pro- cedural meaning”). Skillnaden mellan dem är att man i den deklarativa meningen bara bryr sig om vilka relationer som existerar för vilka objekt, medan man i den procedurella även tar hänsyn till ordningen på satserna.

; Förfrågan (p ?a a)

; Program 1 (p a a)

(<= (p ?c ?d) (p ?c ?c) (p ?d ?d))

; Program 2 (<= (p ?c ?d)

(p ?c ?c) (p ?d ?d)) (p a a)

Med Program 1 kan man snabbt få svaret (p a a). För program 2 kommer man aldrig får ett svar eftersom (p ?c ?d) kommer att leda till att förfrågningslistan växer till (p a a) och (p a a), som i sin tur kommer att öka förfrågningslistan ytterligare, o.s.v. I värsta fall kan alltså ordningen på satser avgöra om man får något svar alls. Mindre allvarligt så kan även prestandan påverkas av vilken ordning satserna matchas mot.

För att åtminstone göra beteendet förutsägbart så kommer satserna matchas i den ordning de ges i programmet.

Detta påverkar kompilatorns lämplighet i tävlingssammanhang. Inget av de pro- gram som delats ut på GDL:s hemsida har dock ställt till några problem.

(20)

2.3. SEMANTISKA BEGRÄNSNINGAR

2.3 Semantiska begränsningar

Följande begränsningar i GDL har man tagit från Datalog. De är till för att undvika oklarheter och problem vid exekveringen av program.

Dessa begränsningar gör att Datalog inte är ett Turing-komplett språk och för- frågningar är åtminstone teoretiskt garanterade att alltid avsluta med ett svar.

Kompilatorn kontrollerar inte att programmen uppfyller dessa villkor, utan utgår från att de gör det. Beteendet av att köra sådana felaktiga program är odefinierat, man kan t.ex. fastna i oändliga rekursioner.

2.3.1 Stratification

Om man i en kedja av anrop till predikat startar med (p x) i ett huvud så får inte (not (p x)) finnas med i kroppen på något av predikaten.

Stratification finns mest till för att säkerställa att det finns en unik minimal mo- dell (“tolkning” eller tilldelning av värden) som hör till varje program. Den hindrar att man säger “(p x) är sann då inte (p x)) är sant”, antingen direkt eller indirekt.

2.3.2 Recursion restriction

Om ett predikat p i sin kropp har ett predikat q som i sin tur beror på predikat p igen, antingen direkt eller indirekt, så får huvudet i p och anropet till q bara innehålla samma variabel om antingen de termer som befinner sig i både p och q är identiska, eller om termen i anropet i q finns i något annat relationsanrop i p som inte beror cykliskt på p.

Recursion restriction finns framförallt för att förbjuda konstruktioner av typen:

(<= (n ?x) (n (s ?x)))

Detta blir en oändlig loop, där man gång på gång anropar n med en större och större funktions-konstant på formen (s(s(s(s(...))))).

Denna begränsning gör att matchningen i GDL inte är Turing-komplett och i teorin alltid ska avslutas med ett svar.

Occurs check Ett sätt som matchningen i Prolog skiljer sig mot Unification inom logiken är att Prolog har problem med rekursionen i satsen ovan. I Unification har man ett särskilt steg, Occurs-Check, där man vid varje matchning av en variabel mot en funktionskonstant kollar att variabeln inte finns någonstans inuti denna. Av prestandaskäl så struntar man i det i Prolog. Eftersom den typen av konstruktion är förbjuden i GDL så struntar även denna draggenerator i att köra en Occurs-Check.

Den hindrar dock inte detta, som är ett riktigt GDL-program, men kommer att ge oändlig rekursion:

(<= (p ?x) (p ?x)) 2.3.3 Safety

Om en variabel finns i huvudet eller en negation i ett predikat så måste den också finnas i en positiv literal i kroppen på samma predikat. (I [7] nämns inget om hur man ska hantera variabler i termerna i distinct, men denna kompilator ställer samma krav även på dem).

Safety är till för att man ska vara säker på att variablerna är bundna, om det är möjligt, innan man anropar distinct eller not. Det ställs dock inga övriga krav

(21)

KAPITEL 2. GDL

på ordningen på literalerna i en sats, man måste inte själv stoppa in dessa literaler sist i kroppen.

Det som fortfarande kan ställa till problem är om man har predikat som ser ut som equals:

(equals ?x ?x) (<= (p ?x ?y)

(equals ?x ?y) (not (q ?x))

Om man sedan ställer förfrågan (p ?a ?b) så kommer ?x att vara obunden vid anropet till (not (q ?x)).

I många fall kan man lätt omvandla felaktig användning av not. Om man t.ex.

har denna sats (vilken kommer ifrån ett exempel-spel):

(<= terminal

(not (canmove ?p ?fx ?fy ?tx ?ty))) ... så kan man göra om den till detta:

(<= terminal

(not (canmove))) (<= canmove

(canmove ?p ?fx ?fy ?tx ?ty))

2.4 Uppdaterade versioner av GDL

Sedan detta projekt påbörjades har språket GDL fått några tillägg. Inget av dessa stöds av draggeneratorn.

2.4.1 Prefix / Infix GDL

I den version av specifikationen för GDL som fanns i början på 2006 så används bara prefix-varianten av GDL och den är den enda som draggeneratorn stöder. Infix GDL ser mer ut som Prolog och skiljer sig bl.a. i predikatsymbolers placering, och hur man anger satser och negationer.

;Prefix GDL

(<= (q ?y) (and (p a ?y) (p ?y c)))

; Infix GDL för samma sats q(Y) :- p(a,Y) & p(Y,c) 2.4.2 GDL-II

GDL-II (GDL with Incomplete/Imperfect Information) [8] är ett tillägg till GDL som möjliggör spel med slump och gömd information.

För att få slump i ett spel inkluderar man en särskild spelare, random, som automatiskt kommer att välja ett av sina tillåtna drag på slump.

(role random)

(22)

2.5. EGNA TILLÄGG

Gömd information hanteras genom att man lägger till en särskild relation, sees, som anger att en viss spelare ser något, t.ex. en motståndares drag.

(sees ?role ?move)

För att simulera ett tärningskast (som är synligt för spelarna) skulle man ex- empelvis kunna skriva på följande vis:

(role random)

(<= (legal random (roll 1)) (true (control random)))

; Samma för 2,3,4,5

(<= (legal random (roll 6)) (true (control random))) (<= (next (die ?n))

(does random (roll ?n))) (<= (sees ?p ?m)

(role ?p)

(distinct ?p random) (does random ?m))

Ovanstående kommer från [9] där det även finns fler exempel för kortspel, etc.

2.5 Egna tillägg

Det finns i detta projekts implementation av draggeneratorn vissa egna tillägg till språket som man bör känna till för att undvika överraskningar.

2.5.1 Fristående förfrågningar

GDL definierar inte något sätt att skapa fristående förfrågningar, men eftersom det är ett användbart sätt att undersöka eller felsöka ett program så finns det inlagt i den interaktiva delen av draggeneratorn. Rent syntaktiskt ser en förfrågan ut som en lista av literaler.

@query/0 är det reserverade, interna predikatnamnet för en förfrågan som ställs mot ett program. Man kan inte matcha mot detta predikat, så om man har en relation med detta namn i sitt program kommer det inte att fungera som förväntat.

2.5.2 Negationer

För att handskas med negationer så finns det också !, @true, @false. Ingen av dem är egentligen tänkt att användas direkt av program, men finns ändå definierade för att kunna testas.

@true är inte strikt nödvändig som särskild symbol. Den skulle kunna varit en fördefinierad konstant som alltid finns (och därmed alltid är sann). I praktiken innebär det att man skulle ha klistrat in “@true” som första rad i varje program.

@false är ett särskild predikat som inte matchar någonting, så om man kommer till det predikatet i en sats så misslyckas unifieringen.

! kallas cut-operatorn och påverkar backtrackingen vid unifiering. Mer om det i kompilatorkapitlet, se 5.9

(23)

KAPITEL 2. GDL

2.5.3 Positionsrelationer

true/1 och does/2 har särskild betydelse i GDL, och vissa optimeringar kan göras om man förutsätter att dessa relationer bara består av fakta.

(24)

Kapitel 3

Spelgång

GDL är ett enkelt uppbyggt språk med få nyckelord och andra syntaktiska konstruk- tioner. För att entydigt kunna beskriva ett spels tillstånd och dess regler krävs dock ett minimalt antal fördefinierade relationer. Dessa relationer är kända av Game- Manager och kompilatorn, och särbehandlas.

3.1 Fördefinierade relationer

Relationer som beskriver spelets regler. Dessa är konstanta under spelets gång.

role, init Starttillstånd.

legal Tillåtna drag givet en position.

next Effekter av ett givet drag.

terminal, goal Regler för att avgöra om en position är ett mål och/eller slutposi- tion.

Relationer som beskriver spelets nuvarande tillstånd. Byts ut under spelets gång.

true Nuvarande tillstånd.

does Spelarnas nästa handling.

Till de olika definitionerna finns det semantiska begränsningar som säger hur de får användas och definieras. För närvarande finns det ingen kontroll i kompilatorn som ser om dessa begränsningar efterlevs.

3.1.1 role

Relationen role anger vad spelarna heter. Antalet role-relationer ger indirekt an- talet spelare.

(role white) (role black)

Semantisk begränsning : role-relationen får bara förekomma som atomär formel eftersom uppsättningen spelare inte ska kunna ändras under spelets gång.

(25)

KAPITEL 3. SPELGÅNG

3.1.2 true

Tillståndet vid en position lagras i en vektor som består av en mängd fakta och görs tillgänglig för spelreglerna genom predikatet true. Om det är svart spelares tur att göra ett drag skulle man kunna ange detta genom att ha följande funktionskonstant i tillståndet:

(control black)

Detta innebär att följande är sant, och därmed inlagt som ett faktum:

(true (control black))

Det finns inga förutbestämda funktionskonstanter för en position i GDL, förutom true-konstruktionen själv. Det är inte säkert att uppsättningen av relationer eller ens deras antal är detsamma i olika positioner under en och samma match. Att lagra den eller de spelare vars tur det är att göra draget i relationen control är bara en konvention, inte ett krav.

Relationen true anger fullständigt tillståndet för en position. Det finns inga övriga mekanismer såsom räknare eller slump som kan påverka spelets gång. Två tillstånd som innehåller samma relationer anger exakt samma position, och uppsätt- ningen positioner som kan nås från dessa är densamma.

Detta innebär att man inte får använda information om vad de andra spelarna gjorde för drag (via does) för att räkna fram nästa drag.

Semantisk begränsning : true får bara förekomma i kroppar på regler, inte hu- vudet. Detta innebär att det enda sättet att ändra true är via next-relationen.

3.1.3 init

För att ange vad som gäller vid spelets början använder man init-predikatet. I den första omgången av spelet är helt enkelt true satt till det som fanns i init i programmet.

(init (control white))

Med hjälp av true kan man sedan räkna ut vilka drag som är tillåtna.

Semantisk begränsning : Tvärt emot true får init bara förekomma i huvudet på regler (eller som atomära formler). Man får inte använda sig av init i kroppen på någon relation, varken egen eller fördefinierad. Man kan alltså inte basera vilka drag som är lagliga på hur init såg ut. Om man använder sig av regler i init-relationen får dessa i sin tur inte innehålla någon av de andra fördefinierade relationerna.

3.1.4 legal

För att ange vilka handlingar som är möjliga för spelarna i de olika spelomgångarna används predikatet (legal role move). role anger en spelare och move det drag som denna kan göra. Ett exempel från Tic-Tac-Toe’:

(<= (legal ?w (mark ?x ?y))

(true (cell ?x ?y b)) ; b == denna ruta är tom.

(true (control ?w))

Denna regel säger att den spelare vars tur det är får markera en tom ruta.

(26)

3.1. FÖRDEFINIERADE RELATIONER

3.1.5 does

Relationen legal anger vilka drag som är möjliga för spelarna givet en position.

Sen är det upp till spelaren, mänsklig eller programmerade med AI-teknik, att välja något av dessa enligt något kriterium. Efter att alla spelarna valt sina drag läggs dessa in i relationen does. Formatet på does är:

(does player move)

GDL har ingen inbyggd turordning för spelarna. Alla spelare måste göra ett drag efter varje position. I de spel där det egentligen inte är tillåtet för varje spe- lare att alltid göra ett drag kan man lägga in ett särskilt drag som inte påverkar nästa positions tillstånd. Man brukar kalla detta drag för noop, men det är bara en konvention utan djupare innebörd vad GDL anbelangar.

Ett möjligt drag för vit i början av Tic-Tac-Toe skulle att vara att markera mitt-rutan, samtidigt som svart inte får göra något drag alls. Det innebär att does skulle kunna ha följande innehåll:

(does white (mark 2 2)) (does black noop)

Semantisk begränsning : does får bara förekomma i kroppen på regler, och man får inte använda sig av does i någon av relationerna legal, goal eller terminal, varken direkt eller indirekt. Detta innebär att spelarnas drag inte är en del av till- ståndet i spelet. Hur man kommer till en position spelar ingen roll, bara positionen i sig.

3.1.6 next

För att tala om vad som ska finnas i true till nästa drag använder man predikatet next. För att växla mellan svart och vit spelare kan man alltså använda sig av följande regler:

(<= (next (control white)) (true (control black)) (<= (next (control black)) (true (control white))

Troligtvis kommer också även de drag som spelarna gjorde och som las in i does påverka hur true ändras. För att ange att den ruta som vit markerade blir fylld av ett kryss kan man skriva:

(<= (next (cell ?m ?n x)) (does white (mark ?m ?n)) (true (cell ?m ?n b)))

Efter att man gått igenom alla next-predikat tar man sedan bort den gamla true-relationen och ersätter den med det som finns i next.

(<= (true ?t) (next ?n))

Sedan plockas alla next-relationer bort.

Semantisk begränsning : next får bara förekomma i huvudet på regler. Man kan alltså inte direkt använda sig av next för att räkna ut tillåtna drag.

(27)

KAPITEL 3. SPELGÅNG

3.1.7 terminal

Predikatet terminal används för att skapa regler som anger när spelet anses avslu- tat. Om en spelare i Tic-Tac-Toe lyckas fylla i en linje med sitt tecken har denne vunnit och spelet är över. Förutsatt att man redan har en relation för att kolla efter fyllda rader skulle man kunna uttrycka detta på följande vis:

(<= terminal (line x)) (<= terminal

(line o)) 3.1.8 goal

När ett spel har avslutats, antingen genom att det inte går att göra något drag eller för att terminal är sant, kan man använda sig av relationen goal för att tilldela positionen en poäng för de olika spelarna.

Denna relation är själva målet med spelandet, att slutligen nå en position där goal-relationen ger så hög poäng som möjligt för spelaren. Att använda sig av denna information ligger dock utanför för detta exjobbs omfattning. Det enda som görs här är att rapportera poängen.

3.2 Korrekthet

GDL ställer också ett par ytterligare krav på spelreglerna för att de ska räknas som korrekta, till exempel att varje spelare alltid kan göra något drag i varje position som kan nås med korrekta drag från startpositionen. I “drag” inkluderas markörer för inaktivitet som noop, det enda man kräver att man för varje spelare ska ha något lagligt drag i does-relationen. Positioner som matchar terminal, och därmed markerar avslut på spelet, behöver inte ha några möjliga drag.

Dessa påverkar inte draggeneratorn, dels då den inte bryr sig om slutpositioner och poäng mer än att den rapporterar dem när den stöter på dem, dels därför att det inte garanterat skulle gå att kontrollera (inom rimlig tid). Möjligtvis skulle man kunna rapportera brott mot reglerna när man väl stöter på dem, men detta görs inte i dagsläget.

Se 6.11 i [7] för en komplett beskrivning av alla krav för korrekthet.

(28)

Kapitel 4

Exempel på Spel

Här visar jag hur ett faktiskt spel, TicTacToe, definieras i GDL. Reglerna kommer från filen TicTacToe.gdl från [6]. Jag har lagt till egna kommentarer.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; role - Spelarna

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(role white) (role black)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; init - Startpostion

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Brädet. 3x3 rutor som startar tomma, (’b’ som i blank).

(init (cell 1 1 b)) (init (cell 1 2 b)) (init (cell 1 3 b)) (init (cell 2 1 b)) (init (cell 2 2 b)) (init (cell 2 3 b)) (init (cell 3 1 b)) (init (cell 3 2 b)) (init (cell 3 3 b))

; Den som ska göra nästa drag.

(init (control white))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; next - Regler för nästa position givet nuvarande position och

; drag.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Om vit markerar en cell så fylls den med ’x’

(<= (next (cell ?m ?n x)) (does white (mark ?m ?n)) (true (cell ?m ?n b)))

; Om svart markerar en cell så fylls den med ’o’

(<= (next (cell ?m ?n o)) (does black (mark ?m ?n)) (true (cell ?m ?n b)))

; Redan ifyllda celler har kvar sina värden.

(<= (next (cell ?m ?n ?w))

(29)

KAPITEL 4. EXEMPEL PÅ SPEL

(true (cell ?m ?n ?w)) (distinct ?w b))

; Tomma celler är fortfarande tomma så länge ingen gjorde ett drag

; i dem.

(<= (next (cell ?m ?n b)) (does ?w (mark ?j ?k)) (true (cell ?m ?n b))

(or (distinct ?m ?j) (distinct ?n ?k)))

; Alternerande turordning (<= (next (control white))

(true (control black))) (<= (next (control black))

(true (control white)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; legal - Regler för vilka drag som är tillåtna för en given

; position

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Den spelare vars tur det är kan markera en tom cell.

(<= (legal ?w (mark ?x ?y)) (true (cell ?x ?y b)) (true (control ?w)))

; Om det är svarts tur, måste vit vänta genom att göra ett

; meningslöst noop-drag. De testar också efter att det finns

; tomma celler kvar, vilket verkar redundant med tanke på att

; en position som inte har det matchar terminal.

(<= (legal white noop) (true (cell ?x ?y b)) (true (control black)))

; ... och vice versa.

(<= (legal black noop) (true (cell ?x ?y b)) (true (control white)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; goal - Poäng för spelarna för vissa positioner.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; 100 poäng för den som har en linje (3 egna symboler i rad),

; 50 poäng var om brädet är fyllt utan att någon har en linje,

; 0 poäng om den andra spelaren har en linje.

(<= (goal white 100) (line x))

(<= (goal white 50) (not (line x)) (not (line o)) (not open)) (<= (goal white 0)

(line o))

(<= (goal black 100) (line o))

(<= (goal black 50) (not (line x))

(30)

(not (line o)) (not open)) (<= (goal black 0)

(line x))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; terminal - Positioner som markerar spelavslut

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Om någon får en linje eller om brädet är fyllt.

(<= terminal (line x)) (<= terminal

(line o)) (<= terminal

(not open))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Hjälppredikat - Definierar vad som menas med line, row, column,

; open, etc.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(<= (row ?m ?x)

(true (cell ?m 1 ?x)) (true (cell ?m 2 ?x)) (true (cell ?m 3 ?x))) (<= (column ?n ?x)

(true (cell 1 ?n ?x)) (true (cell 2 ?n ?x)) (true (cell 3 ?n ?x))) (<= (diagonal ?x)

(true (cell 1 1 ?x)) (true (cell 2 2 ?x)) (true (cell 3 3 ?x))) (<= (diagonal ?x)

(true (cell 1 3 ?x)) (true (cell 2 2 ?x)) (true (cell 3 1 ?x))) (<= (line ?x)

(row ?m ?x)) (<= (line ?x)

(column ?m ?x)) (<= (line ?x)

(diagonal ?x)) (<= open

(true (cell ?m ?n b)))

(31)

Kapitel 5

Kompilatorn

Kompilatorn är i grund och botten en modifierad Prolog-kompilator. Det viktigaste skälet till att basera den på Prolog är att det är ett redan etablerat predikatlogiskt språk med god tillgång till dokumentation.

En nackdel med en Prolog-liknande lösning är som tidigare visats att prestandan, och till och med lösbarheten, kan bero på ordningen av predikaten inom en relation.

Man har valt att lägga upp handledningen i [4] på så vis att man startar med en delmängd av Prolog och stegvis bygger ut det med mer och mer funktionalitet.

Man får på så vis en uppsättning allt mer avancerade språk som kallas L0, L1, L2 och L3.

L0 - Matchning Programmet består bara av ett enda faktum.

L1 - Länkning Här kan man ha flera fakta, men endast en för varje predikat- symbol.

L2 - Regler Nu tillåts även regler i programmet.

L3 - Relationer Hantera att det finns flera satser för samma predikatsymbol.

I detta projekt lägges det sen till ytterligare funktionalitet som finns i GDL men inte i Prolog.

WAM - VM I [4] ges en handledning hur man kan konstruera en kompilator som översätter Prologkällkod till maskinkod för en “WAM”[3] (Warren Abstract Machine). En “Abstract Machine” är en modell av ett datorsystem konstruerat så att man kan göra en detaljerad och noggrann analys av hur systemet fungerar [1].

WAM är en abstract machine med en minnesarkitektur och instruktionsuppsättning speciellt anpassad för Prolog. En implementation av en abstract machine i mjukvara kallas för en virtuell maskin (VM)[1]. För att kunna exekvera koden som skapas av kompilatorn så inkluderas en sådan VM i projektet.

Man kan tycka ett det är en omväg att kompilera till program som exekverar på en särskild VM istället för att bara manipulera datastrukturer direkt. I slutändan skulle det dock inte bli så mycket skillnad. Instruktionerna är skräddarsydda för sin uppgift och svarar mot de handlingar man annars skulle ha gjort på datastruktu- rerna.

Skillnaderna mot hur WAM i detta exjobb ser ut jämfört med hur den ser ut i[4] är inte så stora, men det har tillkommit ett par strukturer för att handskas med CODE-arean, och X och A-register ligger på samma plats. I figur 5.1 är de egna tilläggen markerade med ‘*’.

(32)

...

A1 / X1 A2 / X2 An / Xn

CP

S HB

H

B B0

P

TR does*

true*

jumpTables*

jumpAddresses*

CODE Instruktioner

Data

HEAP

STACK

TRAIL

PDL

...

Y1 Y2 Yn Register

Figur 5.1. Komponenter i den WAM som används i draggeneratorn

(33)

KAPITEL 5. KOMPILATORN

Minnesceller Minnet i en WAM består av olika områden med specialiserade funktioner. De delar dock adressrymd och detta utnyttjas för vissa lättare optime- ringar i [4].

På varje adress ligger en minnescell. Storleken på en minnescell är inte specifi- cerad i [4], men i detta exjobb så är de lagrade som Java-int:ar, dvs. 32-bit stora.

Minnescellerna har i sin tur en intern struktur som kommer att gås igenom senare.

5.1 Grammatiken

GDL och Prolog liknar varandra, men det finns skillnader. Prolog använder postfix- notation, där namnet på predikatet eller funktionskonstanten ges innan parentes- paret som innehåller termerna, och termerna separeras med komma. Variabler i Prolog börjar med versal.

Ett predikat som i GDL ser ut så här:

(p a (b ?c (d ?ex)) f)

... skulle i Prolog se ut så här:

p(a, b(C, d(Ex)), f)

I Prolog kallas functional constants för structures, men i övrigt har GDL och Prolog samma terminologi.

Vissa begrepp som gås igenom i [4] saknar motsvarighet i GDL såsom listor och anonyma variabler. Det finns även begrepp i GDL som saknar motsvarighet i den bantade version av Prolog man tar upp där, såsom distinct och konjunktioner.

I kompilatorn har namnet på instruktionerna bibehållits från WAM, däremot kommer grammatiken från GDL.

ANTLR Grammatiken formaliseras som en ANTLR 2-fil (Se bilaga A). Lite kort- fattat kan ANTLR [11] beskrivas som ett verktyg som bl.a. kan skapa lexers och parsers från grammatiker. Se 8.3 för en diskussion om vad ANTLR är och varför den valdes.

Från ANTLR-grammatiken genereras en lexer och en parser. En lexer tar en ström av tecken och grupperar ihop till en ström av tokens. En parser tar en ström av tokens och bygger språkkonstruktioner i form av träd. Man kan se det som att lexern bygger ord av tecken och parsern bygger meningar av ord.

Strikt taget skulle man i ANTLR inte behöva två steg. ANTLR använder samma sätt att uttrycka grammatiska regler för både lexern och parsern. Det är dock ett ganska naturligt sätt att arbeta, och lexern kommer att vara samma för alla ovan nämnda språk, det är bara parsern som byts ut.

En närmare genomgång av hur grammatiken är definierad finns på [11].

// Identify and ignore whitespace (but increase linenumber // if newline). The linenumber is used in error-messages.

WS : ( ’ ’

| ’\t’

| ’\r’ ’\n’ { newline(); } // DOS

| ’\r’ { newline(); } // MAC

| ’\n’ { newline(); } // Unix )

{$setType(Token.SKIP);} //ignore this token

(34)

5.2. TILLVÄGAGÅNGSSÄTT FÖR IMPLEMENTATIONEN

;

// Comments are ignored COMMENT

: ";" (~(’\n’|’\r’))*

{ $setType(Token.SKIP); }

;

// These tokens are kept for later use in the parser, // and given names to use when constructing rules.

LPAREN : ’(’;

RPAREN : ’)’;

CONSTANT

: (’a’..’z’|’A’..’Z’|’0’..’9’|’_’|’-’)+

;

// Syntactically, variables are just constants with a // questionmark in front of them.

VARIABLE

: ’?’ (CONSTANT)

;

Grammatiken ovan identifierar konstanter, variabler och parenteser innan de skickas till parsern. Blanktecken och kommentarer identifieras också, men de har

$setType(Token.SKIP) som action, vilket innebär att de ignoreras och alltså inte skickas vidare till parsern.

Från grammatiken genereras Java-kod som kan parsa GDL-program. Det är vanliga Java-klasser som sedan kompileras tillsammans med resten av projektet, så klassgenerering behöver bara göras om man gjort om grammatiken.

När man kör de genererade java-klasserna mot GDL-källkod får man ut AST:s (Abstrakta Syntaxträd). Ett syntaxträd är en trädstruktur med tokens i sina noder.

AST:s är abstrakta i betydelsen att de inte innehåller varenda detalj från orginal- strömmen av tecken, utan koncentrerar sig på den syntaktiska meningen av koden.

Blanktecken och kommentarer är borttagna, och parenteser finns inte kvar som egna noder, de har bara hjälpt till att bygga upp strukturen i trädet. Se figur 5.2 för ett exempel på hur källkod förvandlas till en AST.

Felhantering Klasserna som ANTLR genererar kommer att kasta undantag när de stöter på grammatiska fel. Det första felet som stöts på rapporteras med rad- nummer och sedan avbryts inläsningen.

5.2 Tillvägagångssätt för implementationen

I det följande avsnitten beskrivs hur kompilatorn och VM som användes i detta projekt för att tolka GDL-program implementerades. Det skedde i flera steg, där man efter varje steg har möjlighet att köra allt mer avancerade och kompletta GDL- program. Program från tidigare steg är fortfarande giltiga i senare steg, så under arbetets gång byggdes det upp en bank av test-program i form av Unit-tester. Steg motsvarar i stort de steg som finns i [4], men med tillägg för att hantera GDL.

(35)

KAPITEL 5. KOMPILATORN

18 tecken (pred (b cal ?a))

... blir 8 tokens ...

LPAREN

CONSTANT(type:STRUCTURE, id:pred) LPAREN

CONSTANT(type:STRUCTURE, id:b) CONSTANT(id:cal)

VARIABLE(id:?a) RPAREN

RPAREN

...som i sin tur blir 4 AST:noder

VARIABLE : ?a CONSTANT : cal

STRUCTURE : b STRUCTURE : pred

child

child

sibling child

Figur 5.2. De omvandlingar som ANTLR gör av GDL-källkod

5.3 L0 - Matchning

I det första språket (L0) består “programmen” av ett enda faktum. Man kan sedan matcha en förfrågan (begränsad till en literal) mot faktumet och se om man kan få dem att unifiera. Om det går kommer man att presenteras med de variabelbind- ningar som gör förfrågningen sann.

Trots att språket minst sagt är begränsat introducerar det en mängd av de strukturer och begrepp som används även i mera avancerade program.

5.3.1 L0 - Grammatik

Grammatiken för L0 i ANTLR-notation är denna:

query : term;

program : term;

term :

(CONSTANT^ | VARIABLE^ | structure);

structure:

LPAREN! c:CONSTANT^ (term)* RPAREN!

{#c.setType(STRUCTURE);};

Grammatiken kan inte själv skilja på förfrågningar och program. Den kontexten får man själv ange genom att välja att tolka koden som antingen en förfrågan eller ett program.

(36)

5.3. L0 - MATCHNING

I grammatiken används begreppet term istället för faktum (clause). I L0 är dessa identiska, men senare kommer man att tillåta att faktum innehåller annat. Termer kommer däremot att se likadana ut i samtliga grammatiker.

; Exempel på program.

(p a b (c d))

; Exempel på förfrågning (p ?a b ?c)

5.3.2 L0 - Kompilering

Varje nod i de AST:er man får ut från parsningen motsvarar en term, antingen en variabel, en objektkonstant eller en funktionskonstant. Att omvandla dessa noder till instruktioner är en process i flera steg, som börjar med att tilldela alla variabler och funktionskonstanter i en förfrågan eller program till var sitt register.

X register Man börjar vid roten av AST-trädet och tilldelar den termen registret X1 och arbetar sig sedan ner igenom trädet och ger variabler och funktionskonstan- ter register löpande på formen Xn. Ordningen, förutom vilken nod som är X1, är inte viktig. Den enda ytterligare saken man ska hålla redan på är att alla variabler med samma namn ska ha samma register, så har man stött på en variabel tidigare får denna inget nytt register.

Poängen med att tilldela register till variabler och funktionskonstanter är att reservera minnesytor som de instruktioner som ska genereras kan arbeta mot. Det är därför som det är viktigt att en variabel som förekommer flera gånger i en sats alltid har samma minnesområde:

(f ?x (g ?x a b (c d)) a) X1 : f/3

X2 : ?x X3 : g/4 X4 : c/1

Efter detta plattas trädet ut till en ström av registertokens, d.v.s man besöker trädets noder en gång var i en specifik ordning och lägger en kombination av trädets nod tillsammans med dess register i strömmen. Funktionskonstanter ger upphov till en serie med registertokens, en för själva funktionskonstanten och en registerreferens för var och en av dess termer. Man ignorerar variablers och konstanters noder, förutom referensen som ligger i den funktionskonstant som de hör till. Se tabell 5.1 för de olika registertokens som finns.

Nodtyp Data

CONSTANT id : a/0 VARIABLE registret : X2

STRUCTURE register=id följt av subtermer : X3=g/3, (X2, a/0, b/0)

Tabell 5.1. Registertokens för olika nodtyper

Besöksordningen skiljer sig mellan förfrågningar och program. För förfrågningar går man igenom trädet så att ingen nod som innehåller en funktionskonstant besöks

(37)

KAPITEL 5. KOMPILATORN

innan alla dess subtermer har besökts, d.v.s. botten-upp. För program går man top- down. Anledningen till detta kommer att återkommas till senare.

Ovanstående sats skulle plattas ut till följande strömmar:

Förfrågan:X4=c/1, d/0, X3=g/3,X2,a/0, b/0, X4, X1=f/3, X2, X3, a/0 Program:X1=f/3, X2, X3, a/0, X3=g/3, X2, a/0, b/0, X4, X4=c/1, d/0

Strömmen av noder görs sedan om till instruktioner. Varje nod blir en instruk- tion, och de läggs i samma ordning som noderna. Program och förfrågningar ger upphov till olika instruktioner. Se tabell 5.2 för vilka instruktioner som genereras.

Typ av sats Nodtyp Instruktion

Förfrågan STRUCTURE (X7=c/1) put_structure c/1, X1

CONSTANT (a/0) set_constant a/0

VARIABLE (X2), första gången set_variable X2 VARIABLE (X2), övriga gånger set_value X2

Program STRUCTURE (X7=c/1) get_structure c/1, X1

CONSTANT (a/0) unify_constant a/0

VARIABLE (X2), första gången unify_variable X2 VARIABLE (X2), övriga gånger unify_value X2

Tabell 5.2. Genererade instruktioner för olika registertokens

Notera att det genereras olika instruktioner första gången (inom en sats) ett register förekommer som variabel och de andra gångerna. Så exemplet ovan skulle generera följande instruktioner för ett program:

; (f X (g X a b (c d)) a) put_structure c/1, X4 set_constant d/0 put_structure g/3, X3 set_variable X2

set_constant a/0 set_constant b/0 set_value X4

put_structure f/3, X1 set_value X2

set_value X3 set_constant a/0

5.3.3 L0 - Exekvering

När man har genererat alla instruktioner för både förfrågningen och programmet så lägger man in dem i ett program-objekt. I programmet läggs också in de identifierar- ids man stött på under kompileringen tillsammans med deras text-strängar. Dessa används sedan för resultatredovisning och felsökning.

CODE, P Programmet skickas sedan till VM. Instruktionerna läggs in i ett sär- skilt minnesområde i VM, CODE. Först kommer alla förfrågningens instruktioner, sedan programmets.

Nästa instruktion som ska exekveras pekas ut av ett särskilt register, P, som innehåller en adress till en position i CODE. P initialiseras till den första instruk- tionen i förfrågningen och ökas på med 1 efter varje instruktion som körs. Så att

References

Related documents

Subject D, for example, spends most of the time (54%) reading with both index fingers in parallel, 24% reading with the left index finger only, and 11% with the right

De beskrivna gudasalarna är alltså hus m e d tak eller takdetaljer av guld, där finns också det evigt gröna, vida trädet (vars art ingen känner, som i fallet m e d Mimameid),

2. Ingen mötesordförande valdes. Thomas Gilljam valdes som mötessekreterare och Cecilia Gunnarsson och Anneli Svensson till justerare. Stellan Mörner rapporterade

I betänkandet hänvisar utredningen bland annat till de bestämmelser som gäller för hälsodataregister och argumenterar för att det inte finns någon anledning att inte tillåta

Att individualiserad musik eller sång påverkar kommunikationen under omvårdnadsarbetet mellan vårdare och personer med demens redogörs i flera studier (Götell m fl 2002; Götell m

Eftersom myndighetens registerförfattning endast medger elektroniska utlämnanden i särskilt angivna situationer kan det medföra att en person som exempelvis förekommer som part i

När en myndighet inte tillför underlaget till det enskilda målet eller ärendet ska myndigheten se till att information kan lämnas om vilken eller vilka databaser eller andra

I det första systemet så är hela VGA- lösningen gjord i VHDL från grunden, där kod för att sköta alla synkning och pixelräkning har skrivits själv samt en lösning för