Inhoudsopgave Inleiding 2 1 Spelregels Pünct 3 2 Werkwijze programma 6 2.1 Representatie bord 2.2 Representatie speelstukken 2.3 De stand van stukken op het bord 2.4 Controle of een (ver)plaatsing is toegestaan 2.5 Controle of winnende eindstand is bereikt 2.6 Stellingen 2.7 Exit-codes 3 Strategie 9 4 Discussie 12 5 Bronvermelding 13 1
Inleiding In 1995 deed ik de uitspraak dat er in de tien jaar die daarop zouden volgen geen computerprogramma zou komen dat mij zou verslaan in go. Gelukkig heb ik die uitspraak niet enige jaren later gedaan, want in 2006 is een nieuw algoritme bedacht waarmee computergo grote sprongen vooruit heeft gemaakt. De tien jaar niet verslagen worden heb ik nog net gehaald, inmiddels is het sterkste computerprogramma iets sterker dan ik nu ben. Ik vind het intrigerend om te zien hoe een spel waarbij ik fervent voorstander ben van het spelen op intuïtie inmiddels zo goed door een computer gespeeld kan worden. Het is duidelijk dat vele andere factoren ook een rol spelen. In 2010 maakte ik kennis met het spel Pünct. Het geheel ontbreken van toevalsfactoren in combinatie met de onverwachte mogelijkheden zorgden ervoor dat dit spel mij meteen greep. Omdat een onderdeel van de kunstmatige intelligentie zich bezighoudt met methoden vinden om computers dit soort spellen te laten spelen vond ik dit een mooi onderwerp voor het eindwerkstuk waarmee ik mijn bachelor CKI afsluit. 2
1 Spelregels Pünct Het spel wordt gespeeld door twee spelers op een hexagonaal bord waarbij de ene speler met de witte stukken speelt en de andere speler met de zwarte stukken. Alle velden op het speelbord die niet aan de zijkant liggen zijn verbonden met zes andere punten. In het midden van het speelbord is een gekleurd hexagon te zien. De spelers beginnen met een leeg bord en hebben per persoon achttien speelstukken. De speler met de witte stukken is als eerste aan de beurt. Het doel van het spel is om als eerste twee tegenoverliggende zijkanten met elkaar te verbinden. Dit is het geval als van bovenaf gezien een keten van stukken van dezelfde kleur op aaneensluitende velden is gelegd, welke van de ene kant naar de andere kant loopt. Beide spelers beschikken over de volgende typen stukken: Type Afbeelding Omschrijving Aantal stukken per speler I Een driehoek. De witte of bij zwarte stukken de zwarte stip noemen we de Pünctstip of Pünct. 6 II Een recht stuk met de Pünctstip in het midden. 2 III Een recht stuk met de Pünctstip aan een uiteinde. 4 3
IV Een gebogen stuk met de Pünctstip linksboven. 2 V Een gebogen stuk met de Pünctstip in het midden. 2 VI Een gebogen stuk met de Pünctstip aan de onderkant. 2 Tabel 1.1 De stukken In een speelbeurt kan men twee acties uitvoeren, namelijk het plaatsen van een nieuw stuk op het bord of het verplaatsen van een reeds geplaatst stuk. Het plaatsen van een nieuw stuk: Dit mag overal op het bord gebeuren zolang alle punten van het stuk binnen de grenzen van het bord vallen en buiten het gekleurde middenhexagon worden geplaatst. Een stuk mag in iedere willekeurige richting liggen. Een nieuw geplaatst stuk moet op de begane grond geplaatst worden, het mag dus niet meteen bovenop andere stukken gelegd worden. Het verplaatsen van een reeds geplaatst stuk: Een stuk van de eigen kleur dat al op het bord ligt mag verplaatst worden. Dit doet men door de Pünctstip in een rechte lijn te verplaatsen. Na het verplaatsen mag men het stuk roteren om de Pünctstip heen. Een stuk dat verplaatst wordt mag geheel of gedeeltelijk in het gekleurde middenhexagon komen te liggen. Ook mag men stukken bovenop andere stukken plaatsen. Een stuk mag over een willekeurig aantal velden verplaatst worden, bovendien mag men over andere stukken springen. Na verplaatsen en roteren moet het gehele stuk op de toegestande velden van het bord liggen. Een stuk roteren zonder de Pünctstip te verplaatsen is ook toegestaan. De regels voor het plaatsen van stukken bovenop andere stukken: De Pünctstip moet altijd boven een stuk van de eigen kleur liggen. Verder moet het stuk geheel en al op 4
dezelfde laag gelegd worden. De uiteinden van het stuk moeten op een ander stuk liggen, het middelste gedeelte mag los zweven, dit noemt men een brug. De stukken waarboven een ander stuk geplaatst is zijn geblokkeerd en mogen niet meer bewegen. Het is niet toegestaan een stuk onder een brug te schuiven. NB: Een driehoek heeft alleen uiteinden, hiermee kan men dus geen brug vormen. Tijdens het spelen kunnen spelers op ieder moment van elkaar zien welke stukken de ander nog tot zijn/haar beschikking heeft. Als een speler zijn of haar laatste stuk plaatst en geen van de twee spelers heeft een verbinding van tegenoverliggende zijden wint de speler die van bovenaf gezien de meeste punten van het gekleurde middenhexagon bezet. Een winnende eindstand voor zwart ziet er bijvoorbeeld zo uit: Figuur 1.2 Winnende eindstand 5
2 Werkwijze programma Om een programma te maken dat Pünct kan spelen zijn verschillende ontwerpbeslissingen genomen. Allereerst zal ik de representatie bespreken, daarna een aantal procedures. 2.1 Representatie bord Het bord bestaat vanaf de bovenkant gezien uit maximaal 17 bij 17 velden, waarbij de velden die buiten het hexagon van het speelbord vallen niet geldig zijn. De hoogte van een stapel speelstukken kan maximaal 18 zijn, omdat er altijd een eigen stuk voor nodig is om een verdieping hoger te kunnen spelen en er per persoon 18 speelstukken zijn. Er is voor gekozen om het bord op te nemen in een array van 17x17x18 waarbij een aantal velden uitgesloten zijn. In het programma bestaat een aparte klasse Bord, waarin geldige velden worden bijgehouden en de velden van het gekleurde middenhexagon. 2.1 Representatie speelstukken Een stuk wordt gerepresenteerd door de richtingen die opgegaan worden vanaf het eerste punt van het stuk. De richtingen zijn als volgt genummerd: Figuur 2.1.1 Richtingen Als we als voorbeeld een gebogen stuk zoals in figuur 2.1.2 pakken nemen we het onderste punt als stip 1. Van daaruit gaan we met richting 0 omhoog naar stip 2. In stip 2 nemen we richting 5 om in de laatste stip te komen. We zien dat de Pünctstip de tweede stip is. In de representatie van het Pünctstuk zien we nu de waarden: draaihoektweedepunt = 0 draaihoekderdepunt = 5 punctpos = 2 Daarnaast worden nog de kleur, positie, draaiïng en het type bijgehouden. Bij een driehoek beginnen we in het punt aan de linkerkant, nemen daarna het punt rechtsonder en daarna het bovenste punt. De waarden zijn als volgt: Figuur 2..1.2 Gebogen pünctstuk met Pünct op stip 2 6
draaihoektweedepunt = 2 draaihoekderdepunt = 0 punctpos = 1 In principe is het ook mogelijk om de volgorde van de laatste twee punten om te draaien. Omdat we niet met een grafische userinterface werken hebben we een standaardpositie voor de stukken nodig om mee te werken. Als standaard zijn gekozen de posities zoals weergegeven in Tabel 1.1. Op deze manier hebben de stukken een draaiïng van 0. Als een stuk gedraaid wordt draait het altijd om de Pünctstip, deze laatste blijft op zijn plaats liggen. De types zijn genoemd naar de vorm (driehoek, recht of gebogen) waarbij aan recht of gebogen nog toevoegingen als boven, midden of onder gedaan om aan te geven waar de Pünct op het stuk zit. 2.3 De stand van stukken op het bord In een stelling wordt per geldig veld van het bord bijgehouden wat de veldinhoud is. Dit wordt gedaan door een link naar een stuk en een boolean die vertelt of er wel of geen Pünctstip op het veld ligt. Als er geen stuk op het veld ligt bevat de verwijzing naar een stuk de waarde null. Omdat stukken ook verschoven moeten worden is het handig als ook per stuk de positie wordt bijgehouden. Op zich is deze positie af te leiden uit de inhoud per veld, maar het is erg inefficiënt om dat iedere keer op te moeten zoeken. Daarom wordt per stelling een map bijgehouden van de stukken en hun posities, en de stukken en hun draaiïng ten opzichte van de standaardpositie. 2.4 Controle of een (ver)plaatsing is toegestaan Iedere keer als een stuk ge- of verplaatst wordt controleert het programma of deze actie is toegestaan. Deze controle wordt aan de hand van de spelregels uitgevoerd. 2.5 Controle of winnende eindstand is bereikt Aan het einde van iedere beurt controleert het programma of een winnende eindstand is bereikt. Hiervoor wordt eerst gekeken of een van beide spelers een verbinding heeft gemaakt van twee tegenoverliggende zijden. Als dit niet het geval is kijkt het programma of de speler die net aan de beurt was zijn/haar laatste stuk heeft geplaatst. Als het laatste stuk geplaatst is kijkt het programma wie de meeste punten van bovenaf gezien heeft bezit in het gekleurde middenhexagon. De controle voor een winnende verbinding is op twee manieren geïmplementeerd. De methode CheckVerbondenheidRanden werkt volgens de volgende stappen: Allereerst worden, voor de kleur waarop gezocht wordt, groepen gemaakt van aaneengesloten stukken. 7
Per groep wordt bepaald of er stukken zijn die aan tegenoverliggende zijden liggen. Zo ja, dan is er een winnende verbinding. De methode CheckVerbondenheidRandenmethode2 werkt volgens het volgende algoritme: Het programma begint bij een zijde (de beginrand) en kijkt voor een kleur of daar velden bezet zijn. Als er velden bezet zijn categoriseert hij deze als zijnde verbonden met de beginrand. Vanuit deze velden worden de buren bekeken, als deze bezet zijn door de desbetreffende kleur worden deze toegevoegd aan de categorie verbonden met beginrand. Zolang er buren worden toegevoegd wordt er gezocht naar nieuwe buren. Als er geen nieuwe buren meer zijn loopt het programma deze stappen af voor de andere twee randen als beginrand. Zodra de tegenoverliggende rand bereikt is merkt het programma op dat er een winnende eindstand is bereikt, meldt dit en stopt dan. In de procedure EindeBeurt(Stelling stelling, boolean printmelding) is een keuze uit deze twee procedures te maken. 2.6 Stellingen Omdat er ten behoeve van het strategische gedeelte met meerdere stellingen gewerkt moet kunnen worden, worden per stelling de volgende eigenschappen bijgehouden: - De inhoud van de velden op het bord (als het ware de stand) - Wiens beurt het is - Of er al een winnaar is en zo ja, wie - De speelstukken, hun posities en draaistanden 2.7 Exit-codes Voor de duidelijkheid geeft het programma als het stopt in verschillende situaties een eigen exit-code. Exitcode Situatie 0 Standaardcode als een programma uit zichzelf is afgelopen, dit komt hier niet voor 1 Standaardcode als de gebruiker het programma handmatig stopt vanuit Java 2 De gebruiker heeft ervoor gekozen het programma te stoppen (via q ) 3 Eén van beide gebruikers heeft gewonnen door een verbinding te creëren 4 De speler die het laatst aan de beurt was heeft zijn/haar laatste stuk neergelegd, de winnaar is bepaald aan de hand van overmacht in het gekleurde middenhexagon Tabel 2.6.1 Exit-codes 8
3 Strategie Over het onderwerp hoe leer je een computer strategisch spelen is al veel nagedacht, en op dat gebied valt nog veel te ontdekken. In het verleden heeft men langzaam maar zeker een zeer sterke schaakcomputer ontwikkeld, terwijl een go-computer op professioneel niveau nog op zich laat wachten. Wat is het verschil tussen deze twee, eveneens two player zero sum spellen, als een computer intelligente zetten moet bedenken? En wat heb ik daar aan bij Pünct? Allereerst schaken. In 1996 is voor het eerst een regerend wereldkampioen, Garry Kasparov, verslagen door een schaakcomputer (Deep Blue) in een officiële partij. Sindsdien zijn er nog een aantal goede schaakcomputers bijgekomen. Opvallend is dat de computers vaker winnen van topschakers in snelle partijen dan in partijen met een reguliere speeltijd van grofweg gezegd drie uur. Blijkbaar weegt de extra rekenkracht die een computer door de extra tijd krijgt niet op tegen de vaardigheden van een sterke menselijke speler, die juist naar boven komen als hij/zij meer tijd krijgt. Bij het bouwen van een schaakprogramma komen een aantal belangrijke beslissingen naar voren, namelijk: De bordrepresentatie Zoektechnieken Evaluatiefunctie ten behoeve van het waarderen van posities Eventueel: een database met gespeelde partijen Een veelgebruikte techniek om een sterk schaakprogramma te maken is Alpha-Beta search (vanaf nu αβ genoemd). Bij αβ wordt een zoekboom gemaakt waarbij zo snel mogelijk takken gestopt worden als ze niets opleveren. Op deze manier hoeven veel minder takken doorzocht te worden en wordt de zoekboom zo klein mogelijk. Noodzaak voor αβ is een goede evaluatiefunctie. Een goede evaluatiefunctie vinden is een van de problemen bij go en eveneens een van de verschillen tussen computergo en computerschaken. Allereerst zijn er bij go meer mogelijkheden dan bij schaken. Voor de eerste zet zijn er 361 mogelijkheden, voor de tweede zet 360, en in het gunstigste geval wat aantal mogelijkheden betreft blijft dat aantal iedere beurt met één afnemen. Een gemiddelde go-partij duurt ongeveer 200 zetten, waardoor in totaal bij benadering 1,9 10 481 verschillende partijen gespeeld kunnen worden. Een goede methode om in de takken van de zoekboom te snoeien is dus noodzakelijk. Een ander probleem is het feit dat na meer zetten een go-partij er niet eenvoudiger op wordt. Bij schaken verdwijnen in de loop van het spel stukken van het bord en wordt de situatie eenvoudiger. Bij go komen er steeds stenen bij en wordt de situatie met bijna iedere zet die gedaan is ingewikkelder. Een derde probleem is de complexiteit van het eindspel. Deze blijkt PSPACE-hard te zijn [5], onder andere omdat eindspelzetten niet alleen lokaal invloed hebben maar over het hele bord, 9
omdat er meer aspecten bij komen kijken zoals leven en dood problemen, en omdat de eindspelgebieden elkaar weer beïnvloeden. Bij go heeft men het volgende antwoord bedacht op deze problemen: Monte Carlo Tree Search (MCTS) [6]. Hiermee zijn op dit moment go-programma s te maken die net zo sterk zijn als een sterke amateur. Dat is meer dan men tien jaar geleden verwachtte. MCTS genereert een lijst mogelijke zetten. Uit al die zetten wordt een aantal random games uitgespeeld. De zet die leidt tot de beste verzameling random games is gekozen als de beste zet. Evenals bij αβ-search worden zo snel en zoveel mogelijk takken van de zoekboom verwijderd. In 2006 werd hier nog de zoektechniek Upper Confidence Bounds Applied to Trees (UCT) [5] aan toegevoegd. UCT gebruikt de resultaten van de tot dan toe random uitgespeelde spellen en vervolgt de zoektocht naar een goede zet langs de paden van de meer succesvolle uitkomsten, terwijl alternatieven ook nog kunnen worden onderzocht. De combinatie van MCTS en UCT leidt tot de sterkte van computergo zoals we dat nu kennen. Uit bovenstaande kunnen we afleiden dat spellen waarbij er een goede evaluatiefunctie is goed kunnen worden berekend door αβ, voor spellen waarbij dit niet zo is werkt MCTS beter. Dus moet de vraag beantwoord worden of er een goede evaluatiefunctie voor Pünct mogelijk is. Het zou mogelijk kunnen zijn. Er zijn verschillende stukken, waarbij het ene stuk beter inzetbaar blijkt te zijn dan het andere. Zo is een driehoek niet altijd handig te gebruiken. De afstand die ermee wordt overbrugd is kleiner dan bij de andere stukken, hij kan niet overal tussen gelegd worden en ook bij het naar een hogere verdieping springen heeft de driehoek minder mogelijkheden. Het is dus niet handig om veel driehoeken over te hebben als laatste stukken. Ook is het goed voor een speler om lokaal gezien dusdanig bovenop te liggen met zijn/haar stukken dat de andere speler daar niets meer aan kan doen. Hier moet echter evenwicht in worden gezocht, want als een speler zich alleen richt op het bouwen van verdiepingen kan de andere speler er gewoon stukken omheen leggen en heeft hij/zij alsnog eerder een winnende verbinding. Waar bij een evaluatiefunctie ook rekening mee moet worden gehouden is het aantal zetten dat nog nodig is om te winnen. Zodra een speler stukken uit zijn eigen potentiële verbinding moet gaan gebruiken om de tegenstander te blokkeren is dat een teken dat er iets niet goed gaat voor deze speler. Dit moet ook worden meegenomen. Een evaluatiefunctie is dus wellicht mogelijk, maar wordt redelijk ingewikkeld. Het is interessant voor verder onderzoek om beide methoden (αβ en MCTS) eens uit te proberen en te zien met welke methode men de beste resultaten bereikt. Mijn stelling is de volgende: Aangezien er al MCTS ontwikkeld is waarbij een goede evaluatiefunctie van minder belang is geworden lijkt het mij het eenvoudigst MCTS toe te passen voor een goede spelende computer. 10
In het programma heb ik al het een en ander aan strategie ingebouwd. In eerste instantie was het idee om niet met brute force te gaan werken, vanwege het grote aantal mogelijkheden. Het bord bestaat uit 211 velden. Op de velden van het middenhexagon mag niet onmiddellijk een stuk geplaatst worden. Als er eenmaal stukken liggen kunnen deze wel weer verplaatst worden, wat extra mogelijkheden oplevert. Per beurt is een voorzichtige benadering dat er 200 6 (type stukken) 6 (aantal draaiïngen) = 7200 mogelijke zetten zijn. Als een partij ongeveer 45 zetten duurt zijn er dus bij benadering 7200 45 3,8 10 173 mogelijke partijen. Met de huidige snelheid van computers is dat in een mensenleven niet uit te rekenen. Een optie was het gebruik gaan maken van de groepen zoals die bij het bepalen van een winnende eindstand gebruikt worden (zie paragraaf 2.5). Hier zouden bepaalde berekeningen aan gedaan kunnen worden, zoals de afstand onderling zodat het aantal benodigde stukken voor een winnende eindstand bepaald zou kunnen worden. Ook kan met deze groepen bekeken worden of een verbinding van de tegenstander af te snijden is. Een andere optie was om een zettengenerator te bouwen en daarmee wat voorwerk te doen voor αβ of MCTS. Gezien de beschikbare tijd moest er een keuze gemaakt worden. Bij het werken met groepen is wellicht al iets van een evaluatiefunctie in te bouwen, maar dat kan ook op een andere, minder complexe manier. Daarom heb ik gekozen voor het schrijven van een zettengenerator omdat daarmee makkelijker een uitbreiding te maken is naar MCTS. Ook bij αβ komt deze van pas. In de zettengenerator zit al een stap ingebouwd om het aantal takken van de zoekboom te beperken aan het einde van een spel. Op het moment dat bij de diepste stap met een bepaald type stuk een winnend resultaat wordt behaald, gaat het systeem desgewenst terug (door middel van de boolean laatalleszien op false te zetten) naar een stap hoger, om aan te geven dat met de voorafgaande zetten een winnende stelling wordt bereikt. Dit scheelt aanzienlijk in tijd en geeft nog steeds winnende zetten als ze aanwezig zijn. Deze keuze is gemaakt omdat de lijst met winnende zetten vaak van aanzienlijke lengte is als er een winnende zet is. Op het moment dat er nog geen winnende zetten zijn met diepte 2 worden er geen takken gesnoeid dus duurt de optie om zetten te geven met deze diepte nog wel lang. 11
4 Discussie Er is nog veel interessant onderzoek te verrichten naar mogelijke strategieën om een sterk Pünct-programma te maken. Op de MCTS-methode blijkt nog een variant te zijn, genaamd Monte-Carlo Tree Search Solver [7]. Het verschil tussen deze twee zit in de backpropagatie en in de selectie strategie. Het lijkt een goede methode voor Pünct te zijn. Helaas ben ik hier door de beperkte tijd niet aan toegekomen. Ook heb ik een keuze moeten maken tussen een begin van de strategie en een grafische userinterface. Omdat strategie veel interessanter is voor het vakgebied van kunstmatige intelligentie heb ik voor de eerste gekozen. Ook interessant is wellicht om te zien hoe het programma vorderde. Tijdens het programmeren heb ik veel ervaring opgedaan met Java en aan het eind van dit bachelorproject kwam ik af en toe dingen tegen die ik in het begin geprogrammeerd had die ik nu zou overwegen om anders aan te pakken. Een aantal dingen heb ik ook daadwerkelijk uitgevoerd, zoals bijvoorbeeld een aparte klasse maken voor Stelling, en de posities en draaiïngen van de stukken bijhouden in een map in plaats van in een array. 12
Bronvermelding [ 1 ] De spelregels behorende bij het spel Pünct, Kris Burm, GIPF-project, versie 2005-2006 [ 2 ] www.gipf.com [ 3 ] http://en.wikipedia.org/wiki/computer_chess [ 4 ] http://en.wikipedia.org/wiki/alpha-beta_search [ 5 ] http://en.wikipedia.org/wiki/computer_go [ 6 ] Chaslot, Bakkes, Szita en Spronck. Monte-Carlo Tree Search: A New Framework for Game AI. http://sander.landofsand.com/publications/monte-carlo_tree_search_- _A_New_Framework_for_Game_AI.pdf [ 7 ] Winands, Björnsson and Saito. Monte-Carlo Tree Search Solver. http://www.ru.is/faculty/yngvi/pdf/winandsbs08.pdf 13