Harjoitustyö; 1: Tuppi

Kurssin 8101905 ensimmäinen harjoitustyö on tuppea annetun rajapinnan kautta pelaavan agentin toteuttaminen. Tuppi-korttipeli on tekoälykurssin tavoitteiden kannalta ideaalinen harjoitustyön aihe: yksinkertaiset säännöt mahdollistavat agentin ensimmäisen version nopean toteuttamisen, mutta pelissä esiintyvät monimutkaiset tilanteet ja päättelyt mahdollistavat agentin loputtoman parantamisen. Optimaalisen toiminnan etsiminen epävarmuudessa vaatii erilaisten tekoälyn käytännön tekniikoiden hyväksi käyttämistä ja toteuttamista. Ongelmaa ei voi mitenkään ratkaista täydellisesti, mutta aina voi päästä lähemmäs täydellisyyttä.

Tupen säännöt

Tuppi on neljän hengen korttipeli, jossa pelaajat pelaavat toisiaan vastaan kahden hengen joukkueissa. Jokaisen kierroksen aluksi koko pakka jaetaan pelaajien kesken, 13 korttia kullekin. Tämän jälkeen kukin pelaaja valitsee, haluaako pelata ramia vai noloa sen mukaan, uskooko hän voivansa partnerinsa kanssa kerätä ainakin seitsemän tikkiä. Jos pelaaja uskoo kykenevänsä saamaan seitsemän tikkiä, hän valitsee ramipelin ja laittaa tämän merkiksi pöydälle punaisen kortin kuvapuoli alaspäin. Nolopelin valinta tapahtuu laittamalla pöydälle musta kortti, samoin kuvapuoli alaspäin. Kun kaikki pelaajat ovat laittaneet korttinsa pöytään, kortit katsotaan yksi kerrallaan alkaen pelaajasta, joka istuu jakajasta seuraavana. Jos yksikin pelaaja laittoi pöytään punaisen kortin, niin sillä kierroksella pelataan ramia, muuten pelataan noloa. (Tässä harjoitustyössä pelaaja-agentti tosin tekee valinnan ramin ja nolon välillä pelkästään ilmoittamalla valintansa simulaattorille.)

Itse peli on yksinkertainen tikkipeli, jossa tikin kerää lähtömaan suurin kortti (ässän arvo on 14). Valtteja ei ole, ja lähdettyä maata on aina pelattava, jos kädessä sellaista on. Ylimenopakkoa ei kuitenkaan ole. Jos kädessä ei ole yhtään korttia siitä maasta, josta lähdettiin, voi pelaaja pelata minkä tahansa kortin (sakkaaminen). Tikkipelin säännöt ovat tarkalleen samat riippumatta siitä, pelataanko ramia vai noloa. Ramipelissä ensimmäisenä lähtee pelaaja, joka istuu punaisen kortin näyttäneen pelaajan oikealla puolella pelivuorojen kiertäessä myötäpäivään. Nolopelissä ensimmäisenä lähtee jakajasta vasemmalla istuva pelaaja. Jokaisen tikin jälkeen seuraavan tikin aloittaa pelaaja, joka keräsi edellisen tikin.

Kun kaikki tikit on pelattu, lasketaan pisteet:

Peli alkaa tilanteesta 0--0 (pöydästä), ja pistelaskun erikoisuus on, että vain yhdellä joukkueella voi kerrallaan olla pisteitä (olla nousussa). Toisen joukkueen sanotaan tällöin olevan alhaalla. Jos alhaalla oleva joukkue saa yhdenkin pisteen, niin peli palautuu pöytään tilanteeseen 0--0. Kierros voidaan tällaisessa tilanteessa lopettaa kesken, kun alhaalla ollut joukkue saa seitsemännen tikin, sillä ei ole merkitystä sillä, montako tikkiä yli seitsemän joukkue kierroksella lopuksi saa. Pelin tavoitteena on nousta 52 pisteeseen (tuppeen). Tällöin sen joukkue, jolla on pisteitä pääsee tuppeen eli voittaa, ja nollilla oleva joukkue joutuu tuppeen eli häviää. Peli voisi periaatteessa kestää miten kauan tahansa. Turnauspeleissä pelin pituus rajoitetaan 15 tai 24 kierrokseen, kuitenkin siten, että viimeinen nousu pelataan loppuun asti. Jos tuppea ei synny tässä ajassa, lasketaan kummankin joukkueen nousupisteet (ts. kerätyt pisteet niiltä kierroksilta, joilla pelitilanne ei palannut pöytään), ja eniten nousupisteitä kerännyt joukkue on voittaja. Koska ohjelmallisten agenttien kärsivällisyys kuitenkin riittää vaikka loputtomiin ja kukin kierros on yleensä ohi silmänräpäyksessä, tässä harjoitustyössä simulaattori peluuttaa agentit aina tuppeen asti.

Tupessa on myös kolmas mahdollinen pelimuoto sooli. Tämä tarkoittaa sitä, että vastustajan valittua ramipelin joukkueen jompikumpi pelaaja pelaa yksin molempia vastustajia vastaan nolopeliä niin, ettei hän ota yhtään tikkiä. Soolaajaa auttaa tässä se, että hän laittaa aina korttinsa tikkiin viimeiseksi, ja ässän arvo on yksi. Lisäksi ennen peliä soolannut pelaaja antaa partnerilleen kädestään pois yhden kortin, ja saa tilalle yhden kortin partnerin kädestä (partneri antaa kortin ennen kuin näkee, minkä kortin soolannut pelaaja antoi pois). Jos sooli onnistuu niin ettei soolaaja ota yhtään tikkiä, hänen joukkueensa saa 24 pistettä, mutta jos soolaaja ottaa yhdenkin tikin, vastustajan joukkue saa 24 pistettä. Yksinkertaisuuden vuoksi tästä harjoitustyöstä jätetään kuitenkin soolausmahdollisuus pois.

Tupen strategiaa

Valinta sen välillä, haluaako pelata ramia vai noloa, voi olla joskus vaikea. Eräs yksinkertainen nyrkkisääntö on laskea yhteen kädessä olevat kuvakortit pisteyttäen ne ässä=4, kuningas=3, kuningatar=2, jätkä=1, ja jos pisteiden summa on 14 tai enemmän, harkita ramin pelaamista. Kyseessä on kuitenkin vain nyrkkisääntö, josta on poikkeuksia molempiin suuntiin.

Ramipelin keskeinen perusstrategia on aloittaa pisimmän maansa (sen maan, josta kädessä on eniten kortteja) pienimmällä kortilla. Tämä kertoo partnerille, mitä maata kannattaa jatkossa pelata, ja partneri vastaa kyselyyn pelatun maan parhaalla kortillaan. Jos partneri saa tikin, hän vastaa pääsääntöisesti äsken pelatun maan parhaalla jäljelle jääneellä kortillaan. Koko homman tarkoitus on, että aloittava pelaaja saa selville, missä hänen parhaan maansa isot kortit sijaitsevat, jolloin hän voi suunnitella pelinsä tilanteen mukaiseksi. Jos pelaajalla on esimerkiksi kädessään jonkin maan kuningas, kuningatar ja jätkä, näiden korttien hyötyarvo riippuu suuresti siitä, kenellä on kyseisen maan ässä kädessään. (Jos pelaajalla on kuitenkin kädessään kuvakorttien lisäksi myös ässä, nämä kortit kannattaa pelata heti pelaajan päästessä lähtemään, sillä kukaan muu pelaaja ei koskaan tule lähtemään kyseisellä maalla.)

Erityisesti on vältettävä kiusausta lähteä ässällä siinä toivossa, että tämä pakottaisi vastustajat pelaamaan pois saman maan kuninkaan tai kuningattaren, jolloin oma jätkä pääsisi ottamaan turvallisesti seuraavan tikin. Harvoin käy niin hyvä tuuri, että vastustajalla olisi tällainen jalaton kuvakortti (ts. kuvakortti, jonka lisäksi ei ole saman maan pieniä kortteja, jotka voisi laittaa tikkiin kuvakortin sijasta). Toisaalta juuri tällaisten tilanteiden välttämiseksi ei käteen yleensä kierroksen alkuvaiheissa kannata jättää jalatonta kuvakorttia.

Pelaajat voivat kommunikoida keskenään myös sakkuukorteilla. Jotakin maata olevan kortin sakkaaminen on ramissa merkki partnerille siitä, ettei kyseisen maan pelaaminen jatkossa ole toivottavaa. Pelaajan on siis lähdettävän maan valitsemisen onnistumiseksi pidettävä mielessä sekä maat, joita partneri ja vastustajat ovat pyytäneet, sekä maat, joita kukin pelaaja on sakannut.

Nolopelissä on hyväksyttävä, ettei jokaista tikkiä yleensä pääse antamaan vastustajalle, vaan osan joutuu ottamaan itsekin. Siksi pitäisi pyrkiä viisaasti ottamaan ne tikit, joiden ottamisesta on itselle eniten hyötyä. Alkupelissä kannattaa jopa kylmästi lähteä oman hyvin lyhyen maan isolla kortilla (puhdistaminen), erityisesti jos kyseinen kortti on jalaton. Tavoitteena on päästä kokonaan eroon jostakin maasta, jotta kyseistä maata jatkossa pelattaessa pääsee sakkaamaan turvallisesti jonkin toisen maan ison kortin pois. Jos partneri on jo ottanut tikin, niin tikin viimeisen pelaajan kannattaa ottaa tikki vielä isommalla kortilla.

Nolossa on pyrittävä välttämään tilannetta, jossa vuoro jää itselle siten, että kädessä on vain sellaisia kortteja, jotka tuovat niillä lähdettäessä varmasti tikin, eli joiden alle jokainen muu pelaaja pääsee. Kädessä on pyrittävä pitämään riittävästi ulosantokortteja (kortteja, joilla lähdettäessä joku toinen pelaaja ottaa tikin) niin, ettei tällaista tilannetta pääse syntymään. Jos kädessä on isojen korttien määrään nähden liian vähän ulosantokortteja, kannattaa ensin puhdistaa niiden maiden isoilla korteilla, joita ulosantokortin ottava vastustaja seuraavaksi pelaisi, ja käyttää vasta sitten ulosantokortti. Tämä estää vastustajalta saatavan ikävän paluupostin.

Toteutettava agentti

Harjoitustyössä kirjoitetaan tuppea pelaava agentti. Jotta agentteja voitaisiin peluuttaa vastakkain tätä varten rakennetussa testipenkissä, vaaditaan agentin toteutuskielen olevan ANSI-standardin mukainen C++. Standard Libraryn valmiita tietorakenteita ja algoritmeja saa tässä harjoitustyössä käyttää täysin vapaasti. Agentti kirjoitetaan luokaksi, joka periytetään tiedostossa ~ai/tuppi/Pelaaja.h annetusta abstraktista kantaluokasta. Luokan nimen on ehdottomasti oltava muotoa t123456, jossa t-kirjaimen perään on laitettu oma opiskelijanumero. Harjoitustyössä palautetaan asiaankuuluvat ja hyvin kommentoidut kooditiedostot, joista käy ilmi palauttajan nimi ja sähköpostiosoite, ja Makefile, joka tuottaa palautetuista tiedostoista kasan objektitiedostoja.

Luokka on kirjoitettava sellaiseksi, että siitä voidaan tehdä useita ilmentymiä eli olioita ilman, että oliot ovat mitenkään tietoisia toistensa olemassaolosta. Tästä syystä globaalien ja staattisten muuttujien käyttö on kielletty ja nimikonfliktien estämiseksi kaikki funktiot, luokat ja muut pitää esitellä oman luokkansa sisällä. Kuitenkaan mitään erityistä "oliomaisuutta" ei vaadita, joten pääluokkansa sisäisen toiminnan voi tehdä kuinka haluaa, kuitenkin hyvien ohjelmointitapojen mukaisesti. Samoin jos ei pidä esim. structista Kortti, ei sitä tarvitse sisäisesti käyttää, kunhan kommunikointi simulaattorin kanssa sujuu. Näiden sääntöjen rikkominen, agentin tekemät laittomat toiminnot (segmentation fault jne.) sekä laittomat pelisiirrot (sakkaaminen vaikka kädessä on lähdettyä maata, sellaisen kortin pelaaminen jota agentilla ei ole tai jonka agentti on jo pelannut jne.) johtavat harjoitustyön palaamiseen tekijälleen bumerangina.
 

Simulaattori

Tuppisimulaattori, jolla agentteja tullaan testaamaan ja jolla olisi itsekin syytä hieman testata, löytyy Lintulan hakemistosta ~ai/tuppi. Lopulta kaikkien agenttien pitää toimia Lintulassa, mutta jos joku haluaa kehittää omassa ympäristössään, niin simulaattorin lähdekoodikin on samassa hakemistossa. Sieltä erityisen koodausmieliset voivat myös kaivaa pelin kulun formaalisti.

Simulaattori käyttää kahta Pelaaja-luokasta periytettyä (eri) luokkaa, joista molemmista tehdään kaksi oliota. Saman luokan oliot pelaavat parina, joten agentilla on etukäteen tiedossa kuinka hänen parinsa pelaa, mikä lisää taktikointimahdollisuuksia huomattavasti.  Simulaattori peluuttaa näitä pareja niin kauan kunnes tuppi on saavutettu ennalta määrätty määrä kertoja ja raportoi tuloksista.

Simulaattorin luonti tietyille luokille käyttö tapahtuu teetuppi-skriptillä, joka ottaa parametreinaan kaksi luokan nimeä. teetuppi pitää ajaa hakemistossa, jonka alihakemistoissa ovat annettujen luokkien lähdekoodi ja Makefile ja näiden hakemistojen pitää olla samannimisiä kuin niissä olevat luokat. Tuloksena syntyy ohjelma tuppisim. Esimerkiksi ~ai/tuppi hakemiston tuppisim on tehty ajamalla tässä hakemistossa

teetuppi RandomPelaaja t999999

Palautus

Itse palautus tapahtuu sähköpostitse osoitteeseen ai@cs.tut.fi,subjectina ai palautus 1, ja itse tiedostot tar-paketoituna ja mime-koodattuna, esimerkiksi näin:

tar cf - t999999.h t999999.cc Makefile | mimencode | elm -s"ai palautus 1" ai@cs.tut.fi

Onnistuneesta harjoitustyön palautuksesta tulee nopeasti automaattinen vastaus. Tämä vastaus siis tarkoittaa, että harjoitustyö on saapunut perille, ei suinkaan hyväksymistä. Hyväksymis- ja hylkäystuomioita tulee hieman hitaampaa tahtia, mutta vasteaika ei nouse yli viikkoon ja useimmiten muutama päivä riittänee. Varsinaiset harjoitustyöstä saatavat pisteet julkistetaan dediksen jälkeen, kun keskinäinen paremmuusvertailu on suoritettu. Hyväksytystä työstä ei voi lähettää paranneltua versiota, kun taas hylätystä työstä täytyy lähettää hyväksyttävä versio entiseen deadlineen mennessä.

Deadlinen jälkeen ei oteta vastaan bumerangejakaan, joten palauta ajoissa!

Yleisiä ongelmia

Kaikkein yleisin hylkäämiseen johtava syy näyttää olevan se, että agentin koodi ei käänny testitilanteessa. Tälle on kaksi tavallista syytä. Joko makefilen VPATH osoittaa esim hakemistoon .. tai johonkin omaan hakemistoon, kun sen melkein poikkeuksetta pitäisi olla /home/ai/tuppi.Toinen mahdollinen tilanne missä polut voivat mennä vikaan on #includet. Ohjelmat eivät saa olettaa että Kortti.h ja Pelaaja.h löytyvät hakemistosta .. tai yleensäkään minkään suhteellisen polun päästä. Jälleen oikea tapa on käyttää absoluuttista polkua /home/ai/tuppi

Arvostelu

Harjoitustyö hyväksytään, jos agentti on palautettu deadlineen mennessä ohjeiden mukaisesti, täyttää ylläolevat vaatimukset ja on selvästi parempi kuin ahne agentti t999999. Harjoitustyöstä saatavat pisteet määräytyvät pääasiassa agentin pelitaidon mukaan, joskin myös suunnittelun, toteutuksen ja dokumentoinnin laadulla on sanansa sanottavana. Agentin pelitaitoja mitataan järjestämällä turnaus hyväksyttyjen agenttien kesken. Turnauksen voittajan tekijä saa suoraan tentistä maksimipisteet, ja muut saavat pisteitä sijoituksensa mukaan. Koska satunnaisuus on merkittävä tekijä, pyritään pelien määrä saamaan riittävän suureksi tuurin merkityksen vähentämiseksi.

Linkkejä