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

Relevanta dokument
Institutionen för datavetenskap, DAT060, Laboration 2 2 För denna enkla simulerings skull kommer handen att representeras som ett par tal μ värdet på

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 20

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

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 DD1310. Programmering. Programspråk

Tentamen i Introduktion till programmering

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

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

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

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

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

Laboration: Whitebox- och blackboxtesting

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Nu lär vi oss tre i rad

Kortspel. Ett spel - tusen upplevelser

Agenda. Objektorienterad programmering Föreläsning 13

Datalogi, grundkurs 1

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

Introduktion till programmering SMD180. Föreläsning 9: Tupler

Grundläggande datalogi - Övning 1

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

HI1024 Programmering, grundkurs TEN

TENTAMEN PROGRAMMERING I JAVA, 5P SOMMARUNIVERSITETET

Abstrakta datatyper Laboration 2 GruDat, DD1344

Uppgifter teknik HT17 (uppdaterad v. 40)

Repetition i Python 3. Exemplen fac. Exemplen fac motivering. Exemplen fac i Python

Språket Python - Del 1 Grundkurs i programmering med Python

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

Föreläsning 2 Programmeringsteknik och C DD1316

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

Sätt att skriva ut binärträd

Pythons standardbibliotek

Erfarenheter från labben

Chapter 4: Writing Classes/ Att skriva egna klasser.

PROJEKTRAPPORT EDA095 NÄTVERKSPROGRAMMERI

Att skriva till och läsa från terminalfönstret

Använda Python Laboration 1 GruDat, DD1344

Instruktion för laboration 1

C++ Slumptalsfunktioner + switch-satsen

Övning 1 - Abstrakta datatyper

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

Instruktion för laboration 1

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

Ordlistor, filhantering och ut på webben. Linda Mannila

Planering av ett större program, del 2 - for och listor. Linda Mannila

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

Hur man programmerar. TDDC66 Datorsystem och programmering Föreläsning 3. Peter Dalenius Institutionen för datavetenskap

Introduktion till programmering SMD180. Föreläsning 2: Variabler, uttryck och satser

729G04 Programmering och diskret matematik. Python 3: Loopar

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

Det finns en referensbok (Java) hos tentavakten som du får gå fram och läsa men inte ta tillbaka till bänken.

MATLAB. Python. Det finns flera andra program som liknar MATLAB. Sage, Octave, Maple och...

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

JAVAUTVECKLING LEKTION 4

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

Genetisk programmering i Othello

Grundläggande datalogi - Övning 4

Objektorienterad Programkonstruktion. Föreläsning jan 2016

Föreläsning 6: Introduktion av listor

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

Grundläggande Datalogi

//Använd main som ett "handtag" för att hålla ihop programmet. //Själva programmet finns i övriga klasser.

Tenta i Grundläggande programmering DD klockan

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Lektion Kapitel Uppgift Lösning med programmering

Objektorientering i liten skala

Spelregler för restaurangkasinospel

Introduktion till programmering SMD180. Föreläsning 8: Listor

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy. Funktioner / metoder. Punktnotation. Evalueringsordning

Det finns en referensbok (Java) hos vakten som du får gå fram och läsa men inte ta tillbaka till bänken.

Arrayer (vektorer) Murach s: kap Elektronikcentrum i Svängsta AB

Datalogi för E Övning 3

729G04 Programmering och diskret matematik

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

Lite om reella tal. Programmering. I java. Om operatorers associativitet och prioritet

TDDI16 Datastrukturer och algoritmer. Algoritmanalys

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)

Recap Mera om nya typer Kort Fält. Programmering. Sommarkurs Verónica Gaspes. IDE-sektionen.

Relationer mellan objekt

Instruktioner - Datortentamen TDDD73 Funktionell och imperativ programmering i Python TDDE24 Funktionell och imperativ programmering del 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.

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 12

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

Visual Basic, en snabbgenomgång

DIAGNOSTISKT PROV. Tid. Hjälpmedel. Antaganden. Rättning. Övrigt. Diagnostiskt Prov. Klockan Inga

samma sätt. Spara varje uppgift som separat Excelfil. För att starta Excel med Resampling-pluginet, välj Resampling Stats for Excel i Start-menyn.

Digitalitet. Kontinuerlig. Direkt proportionerlig mot källan. Ex. sprittermometer. Elektrisk signal som representerar ljud.

Sammanfattning. Listor. List-manipulering. Matris. /home/lindahlm/activity-phd/teaching/11dd1310/exercise3/exercise3.py September 13, 20111

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å

Uppgift: Algoritm för att beräkna kontrollsiffran i ett personnummer givet de 9 första siffrorna. Torrsimning av algoritm för personnummer

Tentamen i Grundläggande Programvaruutveckling, TDA548

Självlärande Othello-spelare

Exempel på ett litet Ada-program

Problemlösning och funktioner Grundkurs i programmering med Python

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

Funktionell programmering DD1361

Lösningsförslag till tentamen i EDA011, lördagen den 16 december 2006

DD1320 Tillämpad datalogi. Lösning (skiss) till tenta 20 okt 2011

En kort text om programmering i C.

Fly me to the moon. Laboration om relationer, TDDC75 Diskreta strukturer. Mikael Asplund. 5 september 2017

Transkript:

1 Artificiell intelligens En agent som spelar Black Jack Andreas Perjons [andpe813] Linköpings Universitet 2019

2 Innehåll Introduktion...3 Metod..4 Programmets komponenter.4 Resultat...5 Diskussion...7 Referenser...8 Appendix.9

Introduktion 3 Black Jack är ett kortspel som grundar sig på probabilitet. Det spelas mot en dealer (casinot) och går ut på att dra kort för att försöka komma så nära (men inte överskrida) 21 som möjligt. I normala fall börjar spelet med en insats från spelaren. Skulle spelare vinna får den tillbaka dubbla insatsen, men förlorar spelare behåller dealern insatsen. Dealern ger spelaren två kort, vars summa motsvarar spelarens poäng. Dealern ger även sig själv ett kort. Klädda kort är dock värda 10 och ess 11, eller 1 om poängen annars skulle överskrida 21. Spelaren får sedan välja om den vill dra ett till kort eller om den vill stanna på de kort som den redan har. Att dra kort kan upprepas så länge spelaren vill, eller tills att dess poäng överskridit 21. Spelaren kan även välja att dubbla, vilket betyder att den bara drar ett kort, men vinner dubbla insatsen om så skulle vara fallet. Om en spelare får två kort av samma valör (ej ess), kan spelaren även välja att splitta, vilket betyder att handen istället spelas som två separata händer. Om detta sker ger dealern de nya händerna varsitt kort till och spelaren får därefter välja att fortsätta dra eller stanna. När spelaren har valt att stanna så drar dealern kort tills att dess poäng blivit, eller överskridit 17. Beroende på lokala regler så kan dealern behöva fortsätta dra kort om dess hand innehåller ett ess (så kallad mjuk 17), att dealern behöver dra på en mjuk 17 minskar husets övertag med ungefär 0,2%. ( Blackjack, 2019, 12 april) Då Black Jack är ett relativt simpelt spel i termerna av vilka drag som kan göras betyder att det är mycket lätt att göra om dem till states som sedan kan användas för att automatisera spelandet. Black Jack spelas i en delvis observerbar miljö, där den information som finns tillgänglig är de kort spelaren har dragit, det kort dealern har dragit och i teorin de kort som finns kvar i leken. I detta Black Jack-spel så används åtta standard-kortlekar och det tordes orimligt att en människa hela tiden skulle kunna hålla koll på vilka kort som finns kvar att dra. En dator kan dock hålla reda på detta, vilket gör att datorn kan använda sig av något som kallas för en Monte Carlo-metod (Towards Data Science, 2018) för att bestämma hur många kort den ska dra och därmed möjliggöra ett automatiserat spelande. Grundtanken i en Monte Carlo-metod är att använda slumpad sampling ett stort antal gånger för att lösa ett i grunden deterministiskt problem ( Deterministic algorithm, 2019, 14 januari). Detta gör att istället för att få en exakt probabilitet så fås en approximering, där ju fler iterationer som körs gör att den approximerade probabiliteten närmar sig den faktiska probabiliteten. Monte Carlo-metoder hanterar slump väldigt bra, vilket gör att de lämpar sig väl att använda i denna situation, då den avgörande faktorn för vinst eller förlust är vilket kort som slumpmässigt dras.

Metod 4 För att genomföra detta projekt var det första som gjordes att programmera ett Black Jack spel från grunden, vilket gjordes i Python 3. Detta gjordes för att underlätta överblicken över variabler samt vetskapen om hur programmet fungerar. Värt att notera här är att det finns hundratals olika variationer av Black Jack ( Blackjack, 2019, 12 april) och de regler och bestämmelser som använts här följer den europeiska standarden i mesta möjliga mån. En kompromiss som dock fick göras var att ingen split-funktion implementerades. Detta berodde på att mängden arbete som skulle behöva läggas ner på att implementera detta inte är proportionerlig mot den inverkan som det skulle ha på programmet. Det intressanta är istället den beslutsprocess som genomförs då agenten ska välja hur många kort den vill dra, vilket kan genomföras utan en split-funktion. En annan förenkling som gjorts är att inget ekonomisystem är implementerat, vilket eliminerar val av insats. Istället så avgör bara programmet om spelaren eller dealern vann. Tillvägagångssättet för beslutsprocessen i detta program är som följande: när agenten fått sina två kort och dealern sitt är det upp till agenten att bestämma om den vill dubbla, fortsätta att dra kort eller stanna. För att avgöra detta kopieras spelets nuvarande state (spelarens kort, dealerns kort, de kort som finns kvar att dra och det val som agenten gjort) och spelas i en simulering 100 gånger, med leken blandad för varje ny gång, där agenten provar att dra olika mängder kort. Detta ger då en approximering av hur många kort som ska dras för att ge den största vinstchansen i det faktiska spelet. Är antalet 0 så stannar agenten, 1 dubblar agenten och 2+ fortsätter agenten att dra kort. Detta kan sen köras i en for-loop för att bestämma hur många faktiska omgångar som ska spelas. I detta fall spelas 100 faktiska omgångar, där antalet vinster blir lika med den procentuella vinstchansen för algoritmen. Programmets komponenter Makedeck Det första som görs i programmet är att en kortlek (bestående av 6 standardkortlekar) genereras. Kortleken är en lista där varje element representerar ett kort och elementen i listan är en tupel som till exempel ser ut som följande: ('Club', 'A'). Kortleken blandas sedan med hjälp av numpy-funktionen np.random.shuffle (The SciPy community, 2019) för att slumpa ordningen. Drawcard

5 Detta är funktionen för att dra ett kort från kortleken och lägga till det till spelarens eller dealerns hand. Detta görs med den inbyggda pop-funktionen där det första elementet i kortleken tas bort och sedan läggs till i endera spelarens eller dealerns hand (som också är listor). Getscore Detta är funktionen för att beräkna en hands poäng. Funktionen tar in en hand (lista) som parameter och går igenom den elementvis med hjälp av en for-loop som summerar och slutligen returnerar poängen. Choicesequence I denna funktion så utförs handlingar beroende på om kort ska dras eller ej. Detta har blivit bestämt i funktionen gameinstance. Gameinstance Denna funktion tar en kopia (deepcopy) (Python Software Foundation, 2019) av spelets nuvarande state, testar att dra olika många kort och utvärderar resultatet för vilken den approximerade optimala mängden kort är att dra. Actionsdone När agenten är klar med sina val körs denna funktion, den lägger automatiskt till kort till dealerns hand via Drawcard tills att summan av dealerns hand blivit, eller överstiger 17. När detta är klart jämförs sedan spelarens hand med dealerns för att avgöra vem som vann. Resultat Programmets resultat värderas genom hur stor procent av spelen som vanns. Nedan följer några exempelkörningar av programmet. Värt att notera är att resultaten varierar rejält mellan körningarna (vinstprocent varierar mellan ungefär 35 59%), men att resultatet vid de flesta körningarna är större än om slumpmässiga val görs (vinstprocent varierar då mellan ungefär 20 35%).

6 Figur 1: Sammansättning av 5 separata körningar med slumpmässiga val. Figur 2: Sammansättning av 5 separata körningar med Monte Carlo-metod

Diskussion 7 Användning av Monte Carlo-metod för att automatisera Black Jack-spelande har visat sig vara ett effektivt tillvägagångssätt. Det var inte från början tänkt att agenten skulle ha en genomsnittligt positiv vinstprocent, utan istället hur nära en genomsnittlig positiv vinstprocent den kan komma med tanke på de förutsättningar som hafts. Även om agenten för det mesta har en vinstchans som ligger under 50% så påvisar den i alla fall att beslutsprocessen oftast är bättre än att göra slumpmässiga val. En bidragande faktor till den vinstchans som fåtts fram är att Black Jack i grunden är designat så att dealern (casinot) alltid har ett övertag. Detta gör att ju närmare antalet rundor som spelas är till oändligheten, ju närmare blir den approximerade vinstchansen den faktiska vinstchansen som då är under 50%. En ytterligare bidragande faktor till vinstchansen som fås är saknaden av split-funktionen i programmet, då möjligheten att splitta endast är till spelarens fördel. Nämnvärt är att agenten inte heller använder sig av någon allmän strategi när den fattar beslut (lookup-table för alla möjliga kombinationer av kort), vilket om använt som heuristik skulle kunna tänkas förbättra agentens vinstchans. Vad som skulle vara intressant att vidareutveckla i detta projekt är en split-funktion, vilket dock kräver att den grundläggande kodningen för hur spelarens hand (händer) hanteras skrivs om. Ett ekonomisystem skulle även kunna implementeras där agenten själv får bestämma insats, vilket då integrerar en ytterligare form av beslutsfattande. Något som skulle vara intressant att undersöka är hur väl agenten presterar i jämförelse med en människa som spelar. I detta fall så behöver inte agenten spela perfekt, utan bara så länge agenten presterar bättre än människan gör att det finns belägg för det som vi skulle kalla för artificiell intelligens.

Referenser 8 Blackjack (2019, 12 april) I Wikipedia. Hämtad 2019-04-15 från https://en.wikipedia.org/wiki/blackjack Determinsitic algorithm (2019, 14 januari) I Wikipedia. Hämtad 2019-04-15 från https://en.wikipedia.org/wiki/deterministic_algorithm Python Software Foundation (2019). Shallow and deep copy operations. Hämtad 2019-04-15 från https://docs.python.org/2/library/copy.html The SciPy community (2019). numpy.random.shuffle. Hämtad 2019-04-15 från https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.shuffle.html Towards Data Science (2018). An Overview of Monte Carlo Methods. Hämtad 2019-04-15 från https://towardsdatascience.com/an-overview-of-monte-carlo-methods- 675384eb1694?fbclid=IwAR0SQxI9AvmXaTjL6bnKr1v06E_xAHZWmYpqNdFEsrKcYWj HmnuHw65gWsc

Appendix 9 Kod import numpy as np import os import copy from collections import Counter def choicesequence(deck, hand, dealerhand, choice): """Make an action. Parameters: deck (list) - List with the current cards in the deck. hand (list) - List of tuples representing the player's cards. dealerhand (list) - List of tuples representing the dealer's cards. choice (string) - Which choice of action that has been made. Returns: A list containing the player's hand and which choice of action that has been made. """ statedeck = copy.deepcopy(deck) statehand = copy.deepcopy(hand)

10 statedealerhand = copy.deepcopy(dealerhand) statechoice = copy.deepcopy(choice) state = [statedeck, statehand, statedealerhand, statechoice] action = gameinstance(state) if action == 'DD': drawcard(deck, hand, 1) choice = 'DD' return [hand, choice] elif action == 'DC': drawcard(deck, hand, 1) choice = 'DC' return [hand, choice] elif action == 'NA': choice = 'NA' return [hand, choice] def gameinstance(state): """Play 50 simulations of the current actual hand. Determine which action is most likely to result in a win. Parameters: state (list) - A copy of the current actual game (hands, deck, choice).

11 Returns: bestaction (string) - The best action to take in the current state. """ idrawcounter = Counter() for cards in range(0, 21 - getscore(copy.deepcopy(state[1]))): for games in range(0, 50): ideck = copy.deepcopy(state[0]) np.random.shuffle(ideck) iplayerhand = copy.deepcopy(state[1]) idealerhand = copy.deepcopy(state[2]) ichoice = copy.deepcopy(state[3]) if cards > 0: drawcard(ideck, iplayerhand, cards) ichoice) iresult = actionsdone(ideck, iplayerhand, idealerhand, if iresult == 'W': idrawcounter[cards] += 1 if not idrawcounter: bestaction = 'NA' elif int(idrawcounter.most_common(1)[0][0]) == 0: bestaction = 'NA' elif int(idrawcounter.most_common(1)[0][0]) == 1: bestaction = 'DD' else:

12 bestaction = 'DC' return bestaction def drawcard(deck, recipient, howmany): """Pop a card from deck to hand. Parameters: deck (list) - List with the current cards in the deck. recipient (list) - The hand that recieves the card. Returns: recipient (list) - The hand that recieved the card. """ for card in range(0, howmany): if len(deck) == 0: deck = makedeck() np.random.shuffle(deck) drawncard = deck.pop(0) recipient.append(drawncard) return recipient def getscore(hand): """Determine the current score of a hand. Parameters: hand (list) - The hand of which to determine the score.

13 Returns: score (integer) - The score of the hand. """ score = 0 aces = 0 for card in hand: if card[1] == 'J' or card[1] == 'Q' or card[1] == 'K': score += 10 elif card[1] == 'A': score += 11 aces += 1 else: score += card[1] if aces > 0 and score > 21: score -= 10 aces -= 1 return score def makedeck(): """Return a list representing six decks""" deck = [] for deckcounter in range(0, 6): for counter1to53 in range(1, 53):

14 if counter1to53 < 14: if counter1to53 == 1: deck.append(('spade', 'A')) elif counter1to53 == 11: deck.append(('spade', 'J')) elif counter1to53 == 12: deck.append(('spade', 'Q')) elif counter1to53 == 13: deck.append(('spade', 'K')) else: deck.append(('spade ', counter1to53)) elif counter1to53 > 13 and counter1to53 < 27: if counter1to53-13 == 1: deck.append(('heart', 'A')) elif counter1to53-13 == 11: deck.append(('heart', 'J')) elif counter1to53-13 == 12: deck.append(('heart', 'Q')) elif counter1to53-13 == 13: deck.append(('heart', 'K')) else: deck.append(('heart ', counter1to53-13)) elif counter1to53 > 26 and counter1to53 < 40: if counter1to53-26 == 1:

15 deck.append(('club', 'A')) elif counter1to53-26 == 11: deck.append(('club', 'J')) elif counter1to53-26 == 12: deck.append(('club', 'Q')) elif counter1to53-26 == 13: deck.append(('club', 'K')) else: deck.append(('club ', counter1to53-26)) else: if counter1to53-39 == 1: deck.append(('diamond', 'A')) elif counter1to53-39 == 11: deck.append(('diamond', 'J')) elif counter1to53-39 == 12: deck.append(('diamond', 'Q')) elif counter1to53-39 == 13: deck.append(('diamond', 'K')) else: deck.append(('diamond ', counter1to53-39)) return deck def actionsdone(deck, playerhand, dealerhand, choice):

16 """Dealer draws and the game ends. Parameters: deck (list) - List with the current cards in the deck. hand (list) - List of tuples representing the player's cards. dealerhand (list) - List of tuples representing the dealer's cards. choice (string) - Which choice of action that has been made. Returns - A character (W - win, L - loss, D - draw) to indicate who won. """ gameloss = False if getscore(playerhand) > 21: gameloss = True else: while getscore(dealerhand) < 17: drawcard(deck, dealerhand, 1) os.system('clear') print('your hand: ', playerhand, getscore(playerhand)) print('dealers hand: ', dealerhand, getscore(dealerhand)) if getscore(playerhand) > getscore(dealerhand) and getscore(playerhand) < 22 and not gameloss: return 'W' elif getscore(playerhand) == getscore(dealerhand) and getscore(playerhand) < 22 and not gameloss:

17 return 'D' elif getscore(dealerhand) > 21 and getscore(playerhand) < 22: return 'W' else: return 'L' def main(): deck = [] deck = makedeck() np.random.shuffle(deck) wins = 0 losses = 0 draws = 0 for games in range(0, 100): playerhand = [] dealerhand = [] cardsdrawn = 0 gameloss = False acesinhand = 0 choice = 'ST' os.system('clear') while choice!= 'NA': if len(playerhand) < 2:

18 drawcard(deck, playerhand, 2) if len(dealerhand) < 1: drawcard(deck, dealerhand, 1) choicetuple = choicesequence(deck, playerhand, dealerhand, choice) playerhand = choicetuple[0] choice = choicetuple[1] if getscore(playerhand) > 22 or choice == 'DD': break result = actionsdone(deck, playerhand, dealerhand, choice) if result == 'W': wins += 1 elif result == 'L': losses += 1 elif result == 'D': draws += 1 print('wins: ', wins, 'Losses: ', losses, 'Draws: ', draws) print('win percentage: ', (wins/(wins + losses + draws)) * 100, '%') if name == " main ": main()