- 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 = '_'
- }