package o1.peeveli import GameState.Unrevealed import o1._ /** Kukin luokan `GameState` ilmentymä kuvaa yhtä Peeveli-hirsipuupelin peliGameStatetta: * Minkä näköinen on guessLetterjalle näytettävä (osin paljastettu) visibleWord? Montako * arvausta on jäljellä? Mitä arvauksia on jo tehty? Sekä huijaavalle Peevelille * tärkeä tieto: Mitkä kaikki sanat ovat edelleen mahdollisia vastauksia? * * Peeveli-pelin toiminta on selitetty tarkemmin kurssimateriaalin luvussa 10.1. * * Vaikka pelin aikana peliGameState vaihteleekin, on kukin `GameState`-olio tilaltaan täysin * muuttumaton. Uusi peliGameState muodostetaan uutena `GameState`-oliona kutsumalla vanhalle * tilanteelle `guessLetter`-metodia. (Tämä luokka edustaa funktionaalista ohjelmointityyliä.) * * @param missesAllowed se määrä vääriä arvauksia, joka vielä sallitaan ennen pelin päättymistä. * Negatiivinen luku tarkoittaa, että peli on päättynyt. * @param previousGuesses merkkijono, joka sisältää järjestyksessä kaikki toistaiseksi previousGuesses merkit * @param visibleWord merkkijono, jossa on visibleWordn guessLetterjalle näkyvä muoto. Pelin alussa * visibleWordssa on vain piilossa olevia merkkejä (ks. []]), * mutta merkkejä paljastuu vähitellen. * @param possibleSolutions kaikki ne käytetyn vocabularyn sanat, jotka sopivat yhteen `visibleWord`-parametrin * kanssa eli ovat mahdollisia oikeita vastauksia */ class GameState(val missesAllowed: Int, val previousGuesses: String, val visibleWord: String, val possibleSolutions: Vector[String]) { /** Luo uuden `GameState`-olion, joka kuvaa juuri alkaneen uuden `Peeveli`-pelin tilaa. * Pelin alkaessa koko visibleWord on vielä piilossa ja kaikki annetun vocabularyn sopivan * mittaiset sanat ovat mahdollisia oikeita ratkaisuja. * * Lisätieto opiskelijoille: Huomaa, miten tämä '''toinen konstruktori''' on määritelty * annetussa ohjelmakoodissa. Tällä tavoin voidaan määritellä vaihtoehtoinen tapa luoda * `GameState`-olio sen "oletustavan" lisäksi, joka on määritelty luokan otsikkorivillä. * Voidaan siis luoda GameStateolio joko käskyllä `new GameState(arvauksia, previousGuesses, visibleWord, sopivat)` * (oletustapa) tai käskyllä `new GameState(arvauksia, length, vocabulary)`. * * @param missesAllowed se määrä vääriä arvauksia, joka yhteensä sallitaan ennen pelin päättymistä * @param length uuden visibleWordn length * @param vocabulary vocabulary, jonka `length`-mittaiset sanat ovat mahdollisia oikeita vastauksia */ def this(missesAllowed: Int, length: Int, vocabulary: Vector[String]) = { // Seuraava tarkoittaa: luo olio käyttäen "oletustapaa" ja antaen seuraavat konstruktoriparametrit: this(missesAllowed, "", Unrevealed.toString * length, vocabulary.map( _.toUpperCase )) } private def eiPiilokirjaimia: Boolean = { this.visibleWord.find { x => x == Unrevealed }.isEmpty } /** Palauttaa visibleWordn pituuden. */ def wordLength = this.visibleWord.length /** Palauttaa niiden käytetystä vocabularysta löytyvien sanojen lukumäärän, jotka ovat * (edelleen) mahdollisia ratkaisuja visibleWordan. */ def numberOfSolutions = this.possibleSolutions.size /** Palauttaa `true` jos guessLetterja on arvannut väärin jo enemmän kertoja kuin sallittiin * ja on siis hävinnyt pelin; palauttaa `false`, jos näin ei ole. */ def isLost = if(this.missesAllowed < 0) true else false /** Palauttaa `true`, jos guessLetterja on voittanut pelin eli ei ole arvannut liian monta kertaa väärin * ja kaikki visibleWordn kirjaimet ovat näkyvissä; muutoin palauttaa `false`. */ def isWon = if(!this.isLost && eiPiilokirjaimia ) true else false /** Palauttaa visibleWordsta sellaisen version, josta on paljastettu osoitetut merkit. Esimerkiksi * jos visibleWord on `"K___A"` ja parametrimerkkijono on `"__SS_"`, palauttaa `"K_SSA"`. */ private def reveal(paljastettavat: String) = { var uusivisibleWord = "" for (indeksi <- this.visibleWord.indices) { if (paljastettavat(indeksi) != Unrevealed) { uusivisibleWord += paljastettavat(indeksi) } else { uusivisibleWord += this.visibleWord(indeksi) } } uusivisibleWord } /** Palauttaa uuden pelitilanteen, joka seuraa nykyisestä, kun guessLetterja guessLetter annetun merkin. * Uusi peliGameState valitaan periaatteella, joka on selostettu kurssimateriaalin luvussa 10.1. * Uudessa tilanteessa on ainakin yksi tehty arvaus enemmän kuin nykyisessä; lisäksi siinä saattaa * olla enemmän näkyviä merkkejä visibleWordssa, vähemmän vääriä arvauksia jäljellä ja/tai vähemmän * mahdollisia oikeita vastauksia. * @param arvaus viimeksi arvattu merkki; voi olla iso tai pieni kirjain, mutta tulkitaan aina isoksi. * @return uusi peliGameState */ def guessLetter(arvaus: Char) = { import scala.collection.mutable.Buffer val arvausIsona = arvaus.toUpper //Kertoo arvatun kirjaimen indeksit sanassa def indeksit(sana: String): Vector[Int] = { var indeksit: Buffer[Int] = Buffer() var indeksi = sana.indexOf(arvausIsona) while(indeksi >= 0){ indeksit += indeksi indeksi = sana.indexOf(arvausIsona, indeksi + 1 ) } indeksit.toVector } val(sisaltaaKirjaimen, eiKirjainta) = this.possibleSolutions.partition( _.contains(arvausIsona)) //Palauttaa sanalistan, jossa previousGuesses kirjaimet ensin jaotellaan sanalistoihin sen mukaan, montako kertaa niissä //esiintyy arvattu kirjain. Listoista valitaan suurin. var isoinLista = this.possibleSolutions.groupBy(x => x.count(_ == arvausIsona) ).values.maxBy( _.size) //Järjestää sanalistan indeksien mukaan, valitsee joukon, joka on suurin val isoinSijainninMukaan = isoinLista.groupBy(x => indeksit(x) ).values.maxBy(_.size) val ratkaisu: String= { if(isoinSijainninMukaan.size > eiKirjainta.size) { var sana = isoinSijainninMukaan.head var sijainnit = indeksit(sana) var paljastettavat = visibleWord var test = paljastettavat.toBuffer var paljastettavaSana = "" for(i <- sijainnit){ test(i) = arvausIsona } for(k <- 0 until test.size){ paljastettavaSana += test(k) } paljastettavaSana }else{ visibleWord } } isoinLista = if(isoinSijainninMukaan.size > eiKirjainta.size) isoinSijainninMukaan else eiKirjainta if(isoinSijainninMukaan.size > eiKirjainta.size){ new GameState(this.missesAllowed, this.previousGuesses + arvausIsona, ratkaisu, isoinLista) }else{ new GameState(this.missesAllowed - 1, this.previousGuesses + arvausIsona, ratkaisu, isoinLista) } } /** Palauttaa tekstimuotoisen kuvauksen tästä pelitilanteesta. */ override def toString = this.visibleWord + ", " + "vääriä sallitaan vielä: " + this.missesAllowed + ", " + "previousGuesses: " + (if (this.previousGuesses.isEmpty) "ei ole" else this.previousGuesses) + ", " + "vaihtoehtoja: " + this.numberOfSolutions } /** Tämä `GameState`-luokan kumppaniolio vain tarjoaa yhden vakioarvon. * @see [[GameState]]-luokka */ object GameState { /** merkki, jota käytetään piilossa olevien kirjainten merkitsemiseen Peeveli-pelissä */ val Unrevealed = '_' }