Nu lär vi oss tre i rad

Relevanta dokument
Kurslitteraturen. C-nivå Villkorssatser [kap 8] if & elif & else and & or Loopar [kap 9] for

Genetisk programmering i Othello

Fyra i rad Javaprojekt inom TDDC32

Artificiell intelligens En agent som spelar Black Jack Andreas Perjons [andpe813]

MinMax Algoritmen Implementation och optimering. Joakim Östlund 15 juni 2004

Kan ett datorprogram spela solitär?

Lektion 2: Sökagenter. Robin Keskisärkkä

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

Kravspecifikation. Sammanfattning. Fyra i rad Javaprojekt inom TDDC32. Version 2.0. Datum Dokumentnummer

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Q-learning för fyra i rad. OLLE HASSEL och PETTER JANSE

Uppgifter teknik HT17 (uppdaterad v. 40)

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Exempelduggan. Luffarschack. Koda spel

Föreläsning 2 Programmeringsteknik DD1310. Programmering. Programspråk

Föreläsning 10 Datalogi 1 DA2001. Utskrift på skärmen. Syntax. print( Hej ) Hur är det? Hej. print( Hej,end= ) print( Hur är det? ) HejHur är det?

Föreläsning 2 Programmeringsteknik och C DD1316. Programmering. Programspråk

Föreläsning 2 Programmeringsteknik och C DD1316

DEL 2 - Praktiska programmeringsuppgifter

Grundläggande datalogi - Övning 2

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Grundkurs i programmering, 6 hp (725G61) Dugga 2 tillfälle 2

Python. Vi har ofta behov av att behandla datastrukturer på ett enhetligt sätt så att vi kan göra samma sak i flera olika program.

Föreläsning 2 Programmeringsteknik DD1310. Programmering. Programspråk

Tentamen i Introduktion till programmering

Självlärande Hare and Hounds-spelare med Q-learning. HARALD HARTWIG och MAX WESTERMARK

allt.cl Page 1 of 17 Date: torsdag 7 december 2006

TENTAMEN PROGRAMMERING I JAVA, 5P SOMMARUNIVERSITETET

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Några inbyggda funktioner (med resultat!) Introduktion till programmering D0009E. Föreläsning 4: Villkor och rekursion. Modulus-operatorn.

729G04 Programmering och diskret matematik. Föreläsning 7

729G04 Programmering och diskret matematik. Python 2: Villkorssatser, sanningsvärden och logiska operatorer

Kodexempel från Programmering 2 Tobias Wrigstad, Ph.D.

Tentamen i Objektorienterad programmering

Sätt att skriva ut binärträd

HI1024 Programmering, grundkurs TEN

TENTAMEN I PROGRAMMERING. På tentamen ges graderade betyg:. 3:a 24 poäng, 4:a 36 poäng och 5:a 48 poäng

Nätverksprogrammering, EDA095

Selektion och iteration

Chapter 4: Writing Classes/ Att skriva egna klasser.

Föreläsning 2 Programmeringsteknik och Matlab DD1312. Programspråk. Utskrift på skärmen

Introduktion till programmering SMD180. Föreläsning 4: Villkor och rekursion

Kort Sammanfattning av Schack MAS

Inom datalogin brukar man använda träd för att beskriva vissa typer av problem. Om man begränsar sig till träd där varje nod förgrenar sig högst två

Alla datorprogram har en sak gemensam; alla processerar indata för att producera något slags resultat, utdata.

Grundläggande datalogi - Övning 3

Namn Stil Dokumentation Objekt-orienterad programmering OBS OBS OBS OBS OBS OBS OBS OBS

Övning 1 - Abstrakta datatyper

Projektdokumentation för Othello

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

MMA132: Laboration 2 Matriser i MATLAB

HexaFlip. Kravspecifikation

UPPGIFT 1 V75 FIGUR 1.

729G04 Programmering och diskret matematik. Python 3: Loopar

DD1310/DD1314/DA3009 Programmeringsteknik LÄRANDEMÅL... Vilka läser kursen? ...FLER LÄRANDEMÅL. Föreläsning 1

Lektion Kapitel Uppgift Lösning med programmering

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

1/15/2013. DD1310/DD1314/DA3009 Programmeringsteknik. Lärandemål... Vilka läser kursen? ...fler lärandemål VARFÖR? Föreläsning 1

Föreläsning 8 Innehåll

Grundläggande datalogi - Övning 1

729G74 IT och programmering, grundkurs. Tema 2, Föreläsning 2 Jody Foo,

Programmering grundkurs

Lös uppgiften med ett program, t.ex. print("jag kan ha köpt två bullar och en läsk och ska betala", 2 * , "kr.") T.ex. print(5 + 3 * 10) T.ex.

Avbildningar och hashtabeller. Koffman & Wolfgang kapitel 7, mestadels avsnitt 2 4

denna del en poäng. 1. (Dugga 1.1) och v = (a) Beräkna u (2u 2u v) om u = . (1p) och som är parallell

Föreläsning 2 Programmeringsteknik och Matlab DD1312. Programspråk. Utskrift på skärmen

Fyra-i-rad med förstärkt inlärning

AGA-regler. goforbundet.se/ Referens: AGA 1991

Tenta (TEN3) i kursen 729G04 Programmering och diskret matematik 5 feb 2016, kl 14:00-18:00

Game of 40. Regler och om sidan är in princip samma sak. Det som skiljer dem åt är att de inte har samma text.

PYTHON SNABBREPETITION MEN FÖRST LITE ALLMÄNT OM PROGRAMMERING 729G04 PYTHON 2. Dagens Python

Tentamen i Objektorienterad programmering E

DD1314 Programmeringsteknik

Låt eleverna lösa uppgifterna med huvudräkning och sedan jämföra med resultatet av ett program, t.ex. print(6 + 4 * 3)

Datalogi, grundkurs 1

Självlärande Hare and Hounds spelare med Q-learning

Föreläsning 13 och 14: Binära träd

Självlärande brädspelare. EMRE BERGE ERGENEKON och ANTON JONSSON

Laboration 2. Artificiell Intelligens, Ht Lärare: Christina Olsén Handledare: Therese Edvall Daniel Ölvebrink

PROJEKTRAPPORT EDA095 NÄTVERKSPROGRAMMERI

JavaScript del 3 If, Operatorer och Confirm

Linköpings Universitet. ProjectBot. En bot för spelet Rocket League. Jesper Pettersson

Uppgifter till tenta i 729G04 Programmering och diskret matematik. 17 december 2015, kl 14:00-18:00

Hexaflip. Analysis and Design Document. Version 2.0 α Last modified: Martin Larsson

Självlärande Othello-spelare

Exempelprov. Matematik Del A, muntlig del. 1abc

Problemlösning. TDDD73 Funktionell och imperativ programmering i Python Föreläsning 10. Peter Dalenius Institutionen för datavetenskap

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

matematik Lektion Kapitel Uppgift Lösningg T.ex. print(9-2 * 2) a) b) c) d)

Ordlistor, filhantering och ut på webben. Linda Mannila

Föreläsning 8 SLUMPTAL, SIMULERING + INTRODUKTION TILL VEKTORER

Platser för att skriva och testa kod online. Workshop om programmering i matematikkurser, version 0.7 senast sparat

Gustav Johansson

6 cm. 106 Beräkna a) 3 ( 4) b) ( 2) 5 c) 4 ( 2,5) d) ( 8) 1,5. T.ex. print(3 * -4) 13 Beräkna cirkelns a) diameter b) omkrets

Objektorienterad programmering. Telefonboken igen, en bättre version. En jämförelse. Föreläsning 4

Övning 1. Abstrakta datatyper. 1. Stacken. class Stack: """A representation of a last-in-first-out (LIFO) stack of objects."""

Instruktioner - Datortentamen TDDD73 Funktionell och imperativ programmering i Python TDDE24 Funktionell och imperativ programmering del 2

Introduktion till programmering SMD180. Föreläsning 5: Fruktbara funktioner

Beskrivning av Gesällprov. Fia Med Knuff. Mikael Rydmark.

Föreläsning 4: Kombinatorisk sökning

Handbok Othello. Clay Pradarits Utvecklare: Mario Weilguni Granskare: Lauri Watts Översättare: Stefan Asserhäll

Transkript:

Nu lär vi oss tre i rad Hanna Johansson HT 2016 729G43

Sammanfattning Tre i rad är ett enkelt spel som de flesta förstår sig på och är ett väldigt vanligt problem att försöka lösa just maskinellt. I den här rapporten beskrivs hur det går att lära en agent spela tre i rad genom förstärkt inlärning där den spelar en rad matcher mot en kopia av sig själv. I slutet kan en användare spela mot agenten och försöka slå den, vilket inte är så lätt som en kan tro. Avslutningsvis diskuteras även eventuella sätt att förbättra programmet på. 2

Innehållsförteckning 1. Inledning... 4 1.1 Bakgrund... 4 1.2 Hur går tre i rad till?... 4 1.3 Förstärkt inlärning... 5 1.4 Syfte och metod... 5 2. Implementation... 6 2.1 Översikt... 6 2.2 Klassen Board... 6 2.3 Klassen Performance... 7 2.4 Klassen Critic... 8 2.5 Körning... 9 3. Reflektioner... 10 3.1 Vad kan förbättras?... 10 Referenslista... 11 Bilaga 1... 12 3

1. Inledning 1.1 Bakgrund Tre i rad är ett spel för två spelare. Utformningen av spelplanen ser alltid likadan ut men möjliga drag påverkas av tidigare drag från både en själv och sin motspelare. En ruta på spelplanen kan vara tom eller innehålla X eller O, där X representerar en spelare och O en annan. Figur 1. En tom spelplan för tre i rad samt en där X har vunnit. 1.2 Hur går tre i rad till? Tre i rad är ett positionsbaserat spel. I ett positionsbaserat spel finns det två spelare som gör sina drag i omgångar, alltså börjar en av dessa två spelare. Antalet positioner är ändliga och störts valbarhet har den spelare som lägger första draget. Reglerna innebär att en sekvens av personliga drag alltid kommer sluta med ett ändligt antal drag och slutet kallar för terminala positioner. En komplett sekvens som slutar med terminala positioner kallas för en spelomgång. Spelet kan vinnas av både den första eller andra spelaren eller sluta oavgjort. Båda spelarna i ett positionsbaserat spel vet vad som händer, de har alltså komplett information, och det går inte att ändra eller göra om ett drag (Beck, 2008). En spelomgång är avslutad antingen när en spelare vunnit eller när det inte finns några drag kvar att göra, har ingen vunnit då är omgången oavgjord. En spelare vinner genom att ha lyckats lägga tre av sina symboler på rad, antingen horisontellt, vertikalt eller diagonalt. Varje drag som görs av båda spelarna begränsar möjligheterna för nästkommande drag, och ett beslutsträd för just tre i rad kan bli väldigt stort. 4

Figur 2. Delar av ett beslutsträd för de två första dragen i tre i rad. 1.3 Förstärkt inlärning Förstärkt inlärning innebär att lära en agent genom att belöna för vad denne gör bra och/eller bestraffa för misslyckanden. Vid övervakad inlärning behöver agenten få veta det korrekta draget för varje position den hamnar i, men det är sällan sådan återkoppling finns tillgänglig. Istället kan en agent själv lära sig en modell för sina drag och kanske till och med kan förutspå sin motståndares drag, men det måste finnas återkoppling på vad som är bra och vad som är dåligt för att agenten ska ha något att grunda sina beslut på. Det är den här återkopplingen som kallas för belöning eller förstärkning. I vissa miljöer sker återkopplingen rullande men sett till tre i rad sker återkopplingen först i slutet på varje spelomgång. Återkopplingen kan agenten sedan använda för att skapa optimala strategier som maximerar den förväntade totala belöningen för miljön som agenten agerar i (Russel & Norvig, 2010). 1.4 Syfte och metod Syftet med den här fördjupningsuppgiften är att i programspråket Python bygga en agent som genom förstärkt inlärning lär sig spelet tre i rad. Idén inspirerades av övningsuppgift 1.5 som Mitchell (1997) presenterar i sin bok: 1.5. Implement an algorithm similar to that discussed for the checkers problem, but use the simpler game of tic-tac-toe. Represent the learned function V as a linear combination of board features of your choice. To train the program, play it repeatedly against a second copy of the program that uses a fixed evaluation function you create by hand. Plot the percent of games won by your system, versus the number of training games played. 5

Koden som skrevs baserades på Christopher J MacLellans lösning på just problemet ovan och det är hans linjära uppskattningsfunktion som används. 2. Implementation 2.1 Översikt Programmet består av tre klasser; en för spelplanen, en för agenten och en för en kritiker. Instanser av spelplanen håller koll på hur spelplanen ser ut just nu samt den historik som lett till nuvarande state. Instanser av agenten har en hypotes och en uppdateringskonstant, som kontrollerar i vilken takt vikterna uppdateras, som används för att beräkna hur bra spelplanen är. Instanser av kritiker används för att uppdatera agentens hypotes baserat på återkoppling av olika matcher, alltså vem som vunnit. Det skapas två instanser av agenter, en som ska lära sig och en som endast lägger slumpmässiga drag. För att kunna lösa problemet används en funktion som utvärderar spelplanens state genom en linjär funktion. Funktionen tar en hypotes (sju vikter) och egenskaper (sex vikter) som tas från spelplanens state. Vid varje omgång får den lärande agenten alla möjliga drag och använder sig sedan av utvärderingsfunktionen för att ta reda på vilket drag som får högst värde, som den sedan väljer som sitt nästa drag (MacLellan, 2016). Nedan kommer de tre klasser som använts förklaras mer ingående, för att se den fullständiga programkoden se bilaga 1. Rutorna på spelplanen kan ha tre olika värden, 0 för tom ruta, 1 för X och 2 för O. 2.2 Klassen Board Funktionen init skapar ett bräde med tomma rutor, alltså ett där alla värden är 0, samt påbörjar en lista som ska komma att bli spelomgångens historik. Efter varje gång funktionen set_board har använts så uppdateras brädet och historiken fylls på med det senaste gjorda draget. Det finns tre funktioner som används för att ge programmet information om hur brädet ser ut just nu, dessa är get_rows, get_columns och get_diagonals som ger ut rader, kolumner och diagonaler. För att kolla om spelomgången är färdig används funktionen isdone. Först går funktionen igenom om det finns någon tom ruta, om det gör det kanske spelomgången inte är slut ännu. Härnäst kollar den igenom alla rader, kolumner och diagonaler och letar efter om det 6

finns tre X eller tre O i någon av dessa. Hittar funktionen tre X eller O betyder det att någon har vunnit och spelomgången är slut. För att hitta vinnaren används funktionen get_winner. Funktionen kollar först att spelomgången är slut genom isdone innan den kollar igenom rader, kolumner och diagonaler och letar efter tre X eller tre O i någon av dessa. Om funktionen hittar tre X returneras 1, vid tre O returneras 2 annars är matchen oavgjord och 0 returneras. En av de viktigaste funktionerna i den här klassen är get_features som ger ut egenskaperna hos ett bräde. Egenskaperna som funktionen letar efter är följande (sekvens = rad, kolumn eller diagonal): X1 X2 X3 O1 O2 O3 Antal instanser med två X i sekvens och en öppen ruta Antal instanser med ett X i en öppen sekvens Antal instanser med tre X i en sekvens Antal instanser med 2 O i en sekvens och en öppen ruta Antal instanser med ett O i en öppen sekvens Antal instanser med tre O i sekvens Dessa används sedan i klassen Performance i kombination med hypotesen för att beräkna vilket nästkommande drag som är bäst för agenten. Funktionen get_successors används för att ge ut alla möjliga nästkommande drag för agenten så att denne vet vilka möjligheter som finns. Detta görs genom att lägga in agentens värde (1 eller 2) i en tom ruta i taget och spara ner hur spelplanen skulle se ut efter det möjliga draget. I klassen finns även funktioner som set_coordinate sätter värdet på en ruta till det mode som skickas in (1 för X och 2 för O), print_board som printar ut en bild av spelplanen så att den som sedan spelar mot agenten faktiskt kan se hur spelplanen ser ut. Funktionen get_square som returnerar värdet på en specifik ruta på brädet används för att kolla att en ruta faktiskt är tom och get_history returnerar den historik som lett till spelplanens nuvarande state. 2.3 Klassen Performance Funktionen init tar in en spelplan, en hypotes och ett mode som säger om agenten är X eller O, default mode är 1 alltså X. Funktionen ger även agenten ett default uppdateringskonstantvärde som är 0.1. 7

Funktionen evaluate_board tar in en spelplan och utvärderar det genom att ta fram dess egenskaper och multiplicera dessa med hypotesen. Funktionen ser ut som så här: def evaluate_board(self, board): f1,f2,f3,f4,f5,f6 = self.board.get_features(board) h0,h1,h2,h3,h4,h5,h6 = self.hypo return h0 + h1*f1 + h2*f2 + h3*f3 + h4*f4 + h5*f5 + h6*f6 För att beräkna hur bra just det här statet av en spelplan är tar alltså funktionen värdet i en egenskap gånger motsvarande hypotes och adderar ihop dem. Dock står hypotes 0 ensam då det vinns ett värde mer i hypotesen än i egenskaperna. Funktionen choose_move väljer agentens nästa drag baserat på vilket efterföljande state (vilken successor) som fått bäst värde vid evaluate_board till skillnad mot funktionen random_successor som randomiserar fram vilket drag agenten ska göra härnäst. För att kunna uppdatera agentens hypotes, vars default är 0.5 på alla värden, används funktionen update_hypo som tar in historik och träningsexempel. Träningsexemplen är de färdigspelade matcher som finns i historiken som även fått ett värde beroende på vem som vann. Från varje träningsexempel tas dess egenskaper ut och hypotesen uppdateras genom: nytt värde = tidigare hypotesvärde + uppdateringskonstant * (värdet från träning uppskattat värde) * motsvarande värde i träningsexempel. I kod ser det ut så här: h0 = h0 + self.updateconstant*(train - estimate) h1 = h1 + self.updateconstant*(train - estimate)*t1 h2 = h2 + self.updateconstant*(train - estimate)*t2 h3 = h3 + self.updateconstant*(train - estimate)*t3... 2.4 Klassen Critic Funktionen init tar en en hypotes och ett mode (vars default är 1) samt skapar en spelplan. Även den här klassen har en funktion som heter evaluate_board och fungerar på samma sätt som den i klassen Performance. Funktionen get_training är den som används för att gå igenom agentens historik och utvärdera de olika färdigspelade matcherna. De olika spelomgångarna i historiken sparas tillsammans med 100 poäng, 0 poäng eller -100 poäng beroende på hur spelomgången gick. Vid vinst ges positiv poäng och vid förlust negativ, att spela oavgjort ger 0 poäng. Det är 8

alltså den här funktionen som har det huvudsakliga ansvaret att ge agenten återkoppling på huruvida omgången gått bra eller dåligt. Det är den här poängen som agenten sedan använder som referens för inlärningen. 2.5 Körning För att köra programmet skapas först två instanser av klassen Performance som är de två agenter som sedan ska träna mot varandra, player1 är den agenten som tränas och spelar med X, player2 är bara en motståndare som gör slumpmässiga drag och spelar med O. Även två instanser av klassen Critic skapas för att uppdatera respektive spelares hypotes. De två spelarna får sedan spela 10 000 matcher mot varandra där player1 får välja drag och player2 kör slumpmässigt. Vem som vann sparas sedan ner för att i slutet kunna visa statistik för användaren. I slutet på varje spelomgång uppdateras spelarnas hypotes genom de två instanserna av Critic. Efter 10 000 matcher är det dags för användaren att möta den player1. Programmet börjar med att skriva ut statistiken för hur det gick när player1 spelade mot den randomiserande agenten. En spelomgång går till så att användaren först får ange vilken rad och sedan vilken kolumn denne vill lägga sitt drag i. Användaren spelar som O och player1 som X. Sedan spelar agenten och användaren tre i rad i enlighet med dess regler, försöker användaren lägga sitt drag i en ruta som är upptagen meddelar programmet detta och ber användaren välja en annan ruta. Efter varje drag agenten lagt så skrivs spelplanenut så att användaren får en bild av hur spelet ser ut just nu. Agenten lär sig även av spelen mot en användare. Figur 3. De två första rundorna mellan en agent och en användare. 9

3. Reflektioner Programmet fungerar som det ska och går i bästa fall att spela oavgjort mot efter det har kört 10 000 matcher mot en randomiserande agent. Däremot tåls det att tänka på att en randomiserad agent aldrig kan motsvara en riktig användare. I ett försök att göra agenten smartare prövade jag att låta player2 också välja drag, i hopp om att statistiken mellan de två skulle jämnas ut alternativt att det skulle bli en betydligt större del oavgjorda matcher. Istället vann nu player1 överlägset och jag misstänker att det blev så då den som börjar bör ha ett övertag, speciellt om båda spelarna har samma erfarenhet. Detta då den spelare som lägger första draget har störts valmöjlighet och därmed kan styra spelet mera. 3.1 Vad kan förbättras? En sak som skulle kunna läggas in är ett enkelt sätt för användaren att avbryta spelet, just nu avbryts endast huvudloopen om en felaktig input (en input som inte kan göras om till integer) ges när användaren ska skriva in rad eller kolumn. Sättet för användaren att välja vart hen vill lägga sitt drag är lite krångligt, exempelvis skulle det nog underlätta för användaren att ha ett grafiskt gränssnitt snarare än ett textbaserat. I ett sådant gränssnitt skulle användaren kunna klicka i vart hen vill placera sitt drag snarare än att behöva skriva in. Just nu finns det ett problem med att skriver användaren in fel rad eller kolumn är det för sent och det går inte att ångra annat än om användaren försöker lägga sitt drag i en upptagen ruta. Finns ingen upptagen ruta i raden måste användaren lägga sitt drag där. Att den lärande agenten kör mot en randomiserad agent har som sagt sina nackdelar, bland annat är det inte omöjligt att slå den lärande agenten även efter 10 000 matcher. Detta är alltså något som skulle behöva ses över för att förbättra hur agenten lär sig. 10

Referenslista Beck, J. (2008). Combinatorial Games [Elektronisk resurs] : Tic-Tac-Toe Theory. Cambridge: Cambridge University Press. MacLellan, C.J. (2016) Teaching a computer to play TicTacToe. Hämtad 2016-12-30, från http://www.christopia.net/blog/teaching-a-computer-to-play-tictactoe Mitchell, T.M. (1997). Machine learning. New York: McGraw-Hill. Russell, S.J. & Norvig, P. (2010). Artificial intelligence: a modern approach. (3.,[updated] ed.) Boston: Pearson Education. 11

Bilaga 1 # 0 representerar en tom ruta # 1 representerar ett X # 2 representerar ett O import copy import random class Board: def init (self): self.board = self.createboard() self.history = [copy.deepcopy(self.board)] def createboard(self): board = [[0,0,0], [0,0,0], [0,0,0]] return board def set_board(self, board = 0): if board == 0: print("zero board") self.board = board self.history.append(copy.deepcopy(self.board)) # Ger ut rader def get_rows(self, board = 0): if board == 0: board = self.board return board 12

# Ger ut columner def get_columns(self, board = 0): if board == 0: board = self.board columns = [] for i in range(3): column = [] for row in board: column.append(row[i]) columns.append(column) return columns # Ger diagonaler def get_diagonals(self, board = 0): if board == 0: board = self.board diagonals = [] d1 = [] d1.append(board[0][0]) d1.append(board[1][1]) d1.append(board[2][2]) diagonals.append(d1) d2 = [] d2.append(board[0][2]) d2.append(board[1][1]) d2.append(board[2][0]) 13

diagonals.append(d2) return diagonals def isdone(self, board = 0): if board == 0: board = self.board done = True for row in board: for element in row: if element == 0: done = False prospects = [] for row in self.get_rows(board): prospects.append(row) for column in self.get_columns(board): prospects.append(column) for diagonal in self.get_diagonals(board): prospects.append(diagonal) for entry in prospects: Zero = 0 X = 0 O = 0 for element in entry: if element == 0: Zero += 1 elif element == 1: X += 1 elif element == 2: 14

O += 1 if X == 3 or O == 3: done = True return done def get_winner(self, board = 0): if board == 0: board = self.board if self.isdone(board): prospects = [] for row in self.get_rows(board): prospects.append(row) for column in self.get_columns(board): prospects.append(column) for diagonal in self.get_diagonals(board): prospects.append(diagonal) for entry in prospects: Zero = 0 X = 0 O = 0 for element in entry: if element == 0: Zero += 1 elif element == 1: X += 1 elif element == 2: O += 1 if X == 3: return 1 15

elif O == 3: return 0 else: return 2 print("game not done yet") def get_history(self): return self.history def set_coordinate(self, x, y, mode): self.board[x][y] = mode def print_board(self, board = 0): if board == 0: board = self.board new_board = [] for row in board: new_row = [] for element in row: if element == 0: new_row.append(' ') elif element == 1: new_row.append('x') elif element == 2: new_row.append('o') new_board.append(new_row) for row in new_board: print(row[0] + ' ' + row[1] + ' ' + row[2]) print(' ') 16

# Ger ut egenskaperna hos brädet def get_features(self, board = 0): if board == 0: board = self.board # x1 = antal instanser med 2 x i rad och en öppen ruta # x2 = antal instaner med ett x i en öppen rad eller kolumn # x3 = antal instanser med tre x i en rad # o1 = antal instanser med 2 o i rad och en öppen ruta # o2 = antal instanser med ett o i en öppen rad eller kolumn # o3 = antal instanser med tre o i en rad prospects = [] for row in self.get_rows(board): prospects.append(row) for column in self.get_columns(board): prospects.append(column) for diagonal in self.get_diagonals(board): prospects.append(diagonal) x1 = 0 x2 = 0 x3 = 0 o1 = 0 o2 = 0 o3 = 0 for entry in prospects: zero = 0 x = 0 o = 0 for element in entry: if element == 0: 17

zero += 1 elif element == 1: x += 1 elif element == 2: o += 1 if x == 2 and zero == 1: x1 += 1 elif o == 2 and zero == 1: o1 += 1 elif x == 1 and zero == 2: x2 += 1 elif o == 1 and zero == 2: o2 += 1 elif x == 3: x3 += 1 elif o == 3: o3 += 1 return x1,o1,x2,o2,x3,o3 def get_successors(self, mode): successors = [] for x in range(0,3): for y in range(0,3): if self.board[x][y] == 0: successor = copy.deepcopy(self.board) successor[x][y] = mode successors.append(successor) return successors def get_square(self, x, y): return self.board[x][y] 18

class Performance: def init (self, board, hypothesis, mode = 1): self.board = board self.hypo = hypothesis self.mode = mode self.history = [] self.updateconstant = 0.1 def set_updateconstant(self, constant): self.updateconstant = constant def evaluate_board(self, board): f1,f2,f3,f4,f5,f6 = self.board.get_features(board) h0,h1,h2,h3,h4,h5,h6 = self.hypo return h0 + h1*f1 + h2*f2 + h3*f3 + h4*f4 + h5*f5 + h6*f6 def set_board(self, board): self.board = board def get_board(self): return self.board def set_hypothesis(self, hypothesis): self.hypo = hypothesis def get_hypothesis(self): return self.hypo 19

def random_successor(self): successors = self.board.get_successors(self.mode) successor = random.randint(0, len(successors)-1) random_board = successors[successor] self.board.set_board(random_board) def choose_move(self): successors = self.board.get_successors(self.mode) bestsuc = successors[0] bestvalue = self.evaluate_board(bestsuc) for successor in successors: if self.evaluate_board(successor) > bestvalue: bestvalue = self.evaluate_board(successor) bestsuc = successor self.board.set_board(bestsuc) def update_hypo(self, history, training): for i in range(0, len(history)): h0,h1,h2,h3,h4,h5,h6 = self.hypo t1,t2,t3,t4,t5,t6 = training[i][0] estimate = self.evaluate_board(history[i]) train = training[i][1] h0 = h0 + self.updateconstant*(train - estimate) h1 = h1 + self.updateconstant*(train - estimate)*t1 h2 = h2 + self.updateconstant*(train - estimate)*t2 h3 = h3 + self.updateconstant*(train - estimate)*t3 h4 = h4 + self.updateconstant*(train - estimate)*t4 h5 = h5 + self.updateconstant*(train - estimate)*t5 h6 = h6 + self.updateconstant*(train - estimate)*t6 20

# Sätt hypotesen till de nya värdena self.hypo = h0,h1,h2,h3,h4,h5,h6 class Critic: def init (self, hypothesis, mode = 1): self.hypo = hypothesis self.mode = mode self.checker = Board() def evaluate_board(self, board): f1,f2,f3,f4,f5,f6 = self.checker.get_features(board) h0,h1,h2,h3,h4,h5,h6 = self.hypo return h0 + h1*f1 + h2*f2 + h3*f3 + h4*f4 + h5*f5 + h6*f6 def set_hypothesis(self, hypothesis): self.hypo = hypothesis def set_mode(self, mode): self.mode = mode def get_training(self, history): training = [] for i in range(0, len(history)): h = history[i] if self.checker.isdone(h): if self.checker.get_winner(h) == self.mode: training.append([self.checker.get_features(h), 100]) elif self.checker.get_winner(h) == 0: training.append([self.checker.get_features(h), 0]) else: training.append([self.checker.get_features(h), -100]) 21

else: if i+2 >= len(history): if self.checker.get_winner(history[len(history)-1]) == 0: training.append([self.checker.get_features(h), 0]) else: else: training.append([self.checker.get_features(h), -100]) training.append([self.checker.get_features(h), self.evaluate_board(history[i+2])]) return training board = Board() hypo1 = (0.5,0.5,0.5,0.5,0.5,0.5,0.5) hypo2 = (0.5,0.5,0.5,0.5,0.5,0.5,0.5) player1 = Performance(board, hypo1, 1) player2 = Performance(board, hypo2, 2) player2.set_updateconstant(0.4) critic1 = Critic(hypo1, 1) critic2 = Critic(hypo2, 2) Xwin = 0 Owin = 0 draw = 0 for i in range(0,10000): board = Board() player1.set_board(board) player2.set_board(board) 22

while not board.isdone(): player1.choose_move() if board.isdone(): break player2.random_successor() winner = board.get_winner() if winner == 1: Xwin += 1 elif winner == 2: Owin += 1 elif winner == 0: draw += 1 critic1.set_hypothesis(player1.get_hypothesis()) critic2.set_hypothesis(player2.get_hypothesis()) history = board.get_history() player1.update_hypo(history, critic1.get_training(history)) player2.update_hypo(history, critic2.get_training(history)) print("x won " + str(xwin) + " games.") print("o won " + str(owin) + " games.") print(str(draw) + " of the games were draws") while True: board = Board() player1.set_board(board) player2.set_board(board) while not board.isdone(): player1.choose_move() 23

if board.isdone(): break board.print_board() while True: x = int(input("enter row (0-2): ")) y = int(input("enter y-coordinate (0-2): ")) if board.get_square(x, y) == 0: board.set_coordinate(x, y, 2) break else: print("that square is not empty") winner = board.get_winner() if winner == 1: print("x wins") elif winner == 2: print("o wins") elif winner == 0: print("it's a draw!") critic1.set_hypothesis(player1.get_hypothesis()) critic2.set_hypothesis(player2.get_hypothesis()) history = board.get_history() player1.update_hypo(history, critic1.get_training(history)) player2.update_hypo(history, critic2.get_training(history)) 24