(nettipäiväkirja 03.07.2015) Keksin mahtavan uuden pelin! (Ja 04.07.2015 ehdittiin jopa testipelata se ja tilkitä muutamia porsaanreikiä...)
Tässä pelissä on tarkoitus muuttaa pöydällä oleva merkkijono mieleisekseen. Pelaajat eivät tiedä, mikä on muiden pelaajien päämäärä. Merkkijonoa muutetaan säännöillä (kuten "korvaa jokainen 'poks' merkkijonolla 'jee'"), ja pelaajien luomat säännöt vaikuttavat yhtaikaa. Kaikki säännöt jäävät voimaan, joten pelissä tulee kierros kierrokselta lisää sääntöjä.
Pelin voittaa kahdella tavalla: joko saamalla lopputulosmerkkijonoon omia tavoitemerkkijonojaan, tai tekemällä ensimmäisellä kierroksella uudelleenkirjoitussäännön, joka todennäköisesti toteutuu viimeisellä kierroksella (hankalaa suunnitella?) ja yrittämällä estää muita pelaajia saamasta lopputulosmerkkijonoon omia tavoitemerkkijonojaan.
Tässä pelissä on 5 pelaajaa (merkitään kirjaimilla H, Y, I, G ja M), jotka ensin valitsevat itselleen tavoitteet. Kukin pelaaja kirjoittaa oman tavoitteensa lapulle, jonka jättää piiloon. Pelin merkkijonot rakentuvat merkeistä a, t, c ja g. Tavoitteet ovat seuraavat (mutta pelaajat eivät tiedä vielä toistensa tavoitteita):
H: aag Y: acc I: ctc G: acg M: tca
Pelin aluksi pöydällä on merkkijono "atcg", ja nyt pelaajat tekevät ensimmäisen sääntöjoukon. Kukin pelaaja tekee yhden säännön ja säännöt näytetään, kun kaikki ovat kirjoittaneet omansa. Tuloksena on seuraavat säännöt:
1. H: tcg => ag Y: t => ac I: g => tc G: at => ta M: g => at
Muutamia huomioita näistä säännöistä: ensinnäkään laajenteessa (eli säännön oikeassa puolessa) ei saa olla pelaajan salaista tavoitetta. Siksi kaikki pelaajat tekevät epäsuorasti omaa tavoitettaan. Esimerkiksi G haluaa korvata "atcg":stä "at":n "ta":lla, koska tulos on "tacg" joka sisältää G:n tavoitteen "acg". Y haluaa korvata "atcg":stä "t":n "ac":llä, koska tulos on "aaccg" joka sisältää Y:n tavoitteen "acc".
Pöydän merkkijono prosessoidaan alusta loppuun. Ensin siis katsotaan, onko merkkijonon alkuun täsmäävää sääntöä. Sääntö "at => ta" täsmää "atcg":n alkuun ja tulee siis sovelletuksi. Tulos alkaa siis "ta". Tämän jälkeen merkkijonosta on jäljellä "cg". Mitään sääntöä, joka korvaisi mitään c-alkuista, ei ole, joten c siirtyy tulokseen sellaisenaan ja tulos alkaa siis "tac".
Sitten merkkijonosta pitää prosessoida vielä "g". Tähän soveltuu kaksi sääntöä, "g => tc" ja "g => at". Kun kahdella säännöllä on täsmälleen sama vasen puoli, valitaan se, jonka laajenne (oikea puoli) on lyhempi. Tässä tapauksessa ne ovat yhtä pitkät, jolloin säännöistä ei suoriteta kumpaakaan. I:n ja M:n säännöt siis estävät toisiaan tulemasta valituiksi koskaan. "g" tulee sellaisenaan läpi, ja tulos on kokonaisuudessaan "tacg".
Nyt pöydällä on uusi merkkijono ("tacg") ja pelaajat luovat uudet säännöt. Tällä kertaa lopputulos on tällainen:
2. H: ac => aa Y: tac => atc I: ta => ct G: c => ac M: t => tc
Nyt merkkijonon alkuun täsmääviä sääntöjä on peräti kolme ("tac=>atc", "ta=>ct" ja "t=>tc") ja niistä valitaan se, jossa korvattava merkkijono on pisin eli "tac". Tällöin lopputulos alkaa "atc". Jäljellä oleva osa, "g", ei täsmää mihinkään sääntöön ja lopputulos näistä uudelleenkirjoituksista on siis kokonaisuudessaan "atcg".
Muutamia huomioita. Uudelleenkirjoitussäännön vasen puoli ei saa olla täsmälleen sama, kuin pöydällä oleva merkkijono. Tällä estetään pelaajia pystymästä varmasti kirjoittamaan koko merkkijonon mieleisekseen (korvauksissahan suositaan sääntöjä, jotka korvaavat pidemmän pätkän).
Mutta tämä kierros ei ole vielä ohi. Uuden säännöstön jälkeen merkkijonoon sovelletaan vielä aiempienkin kierrosten säännöstöjä viimeisestä ensimmäiseen. 2. säännöstön lopputulos, "atcg", ajetaan siis vielä 1. säännöstön läpi, ja lopputulos on (kuten ensimmäiselläkin kierroksella) "tacg". Tämä on harvinainen tilanne: yleensä 2. kierroksen uudelleenkirjoitussäännöstö ei jätä jälkeensä samaa lopputulosta kuin edellisen kierroksen lähtökohta. Mutta tällä kertaa näin tapahtui, joten pöydällä oleva merkkijono on jälleen "tacg". Tämä siis johtuu siitä, että 2. säännöstö muuttaa "tacg":n muotoon "atcg" ja 1. säännöstö muuttaa "atcg":n takaisin muotoon "tacg".
Peliä on vielä yksi kierros jäljellä. Tällä kertaa pelaajat valitsevat seuraavat säännöt:
3. H: cg => gg Y: cg => tctc I: g => tc G: tac => tac M: acg => ca
"tacg"-merkkijonon alkuun täsmää tasan yksi sääntö, "tac=>tac". Niinpä lopputulos alkaa "tac". Mutta mitä tapahtuu "g":lle? I:n säännön vuoksi se muuttuu muotoon "tc", jolloin tämän säännöstön koko lopputulos on "tactc".
"tactc" menee seuraavaksi uudelleenkirjoitettavaksi 2. säännöstölle. Alku "tac" täsmää edelleen sääntöön "tac=>atc" ja lopputulos alkaa siis "atc". Sitten "t" täsmää sääntöön "t=>tc" ja lopputuloksen alku tämän korvauksen jälkeen on "atctc". Vielä "c" täsmää sääntöön "c=>ac" ja koko säännöstön lopputulos on siis "atctcac".
"atctcac" menee vielä 1. säännöstölle uudelleenkirjoitettavaksi. "at=>ta" täsmää taas, lopputulos alkaa "ta" mutta "ctcac" on vielä jäljellä prosessoitavaksi. c-kirjaimeen täsmääviä sääntöjä ei ole, eli se siirtyy lopputulokseen sellaisenaan: "tac", ja vielä "tcac" pitää prosessoida. "t":hen täsmää Y:n sääntö "t=>ac", joten lopputulos alkaa nyt "tacac" ja "cac" pitää vielä prosessoida. c-kirjaimen sääntöjähän ei ollut, samoin ei pelkkään a-kirjaimeen (jota ei siis seuraa t), joten loppuosaan ei täsmää mikään sääntö ja lopullinen lopputulos on "tacaccac".
Nyt paljastetaan tavoitteet. Ainoa pelaaja, jonka tavoite on lopputuloksessa, on Y ("tacACCac") ja niinpä Y voittaa saamalla 1 osuman, kun muut saivat 0. Jos tätä osumaa ei olisi ollut, voittaja olisi tullut sen mukaan, kenen keksimää sääntöä sovellettiin viimeksi. Tällä kertaa viimeksi sovellettu sääntö oli Y:n "t=>ac", joten Y olisi voittanut tälläkin tavalla.
Pelissä on monia tasapainotettavia asioita. Mitä pidempi on sääntösi korvattava merkkijono, sitä preferoidumpi sääntö on kilpatilanteissa, mutta sitä vähemmän todennäköistä on, että sääntöön täsmäävää merkkijonoa on olemassa. Mitä pidempi on laajenne, sen enemmän pystyy laittamaan asioita mieleisikseen, mutta mahdollisuudet säännön valituksi tulemiseen kilpatilanteessa laskevat.
Ensimmäisellä kierroksella voi valita ainakin kolmesta lähestymistavasta: siitä, että tekee säännön, joka toteutuu _viimeisellä_ kierroksella mahdollisimman todennäköisesti (ja parantaa mahdollisuuksiaan voittaa tasaosumatilanteessa, joka on varsin yleinen), siitä, että tekee säännön, joka auttaa omien osumien syntymisessä, tai siitä, että tekee säännön, joka täsmää mahdollisimman varmasti ja antaa sinulle ensi kierrokselle jotain mieleistä.
Kun valitsee, minkä osan pöydällä olevasta merkkijonosta haluaa korvata, alkuun kohdistuvat säännöt toteutuvat todennäköisemmin ja loppuun kohdistuvat säännöt lisäävät todennäköisyyttä, että niiden toteutuessa viimeinen aktivoitu uudelleenkirjoitussääntö on oma, mikä on tärkeää tasaosumatilanteissa.
Pelaajat, jotka valitsevat samat strategiat, häiritsevät toisiaan olennaisesti ja niinpä kannattaa usein yrittää tehdä jotain muuta kuin mitä tähän mennessä on tyypillisesti tehty (tai siis: jotain arvaamatonta).
Kaikkein varmin uudelleenkirjoitus on se, joka kirjoittaa merkkijonon alusta kaiken paitsi viimeisen kirjaimen. Vastaavasti viimeisen kirjaimen pääsee sitten usein kirjoittamaan rauhassa (koska tuo varma uudelleenkirjoitus jättää sen rauhaan).
Siinäkin on valinnanvaraa, yrittääkö piilotella tavoitteitaan ensimmäisillä kierroksilla, tehdä niihin liittymättömiä sääntöjä ja vasta viimeisellä kierroksella kirjoittaa kaiken mieleisekseen, vai laittaa jo ensimmäisellä kierroksella "pesämunaksi" hyödyllisiä sääntöjä ja pedata niiden toteutuminen myöhempien kierrosten säännöillä. Voi siis yrittää optimoida sitä, mihin suuntaan pöydällä oleva merkkijono kehittyy, tai panostaa kaiken viimeiseen kierrokseen ja tehdä jo 1. kierroksella sääntöjä, joiden hyöty on vasta viimeisellä kierroksella.
Pelissä saa koko ajan lisää informaatiota, ja myöhempien kierrosten säännöt tehdään paremman tiedon perusteella mutta niistä on vähemmän hyötyä, sillä edellisten kierrosten säännöt suoritetaan aina niiden jälkeen.
Yleensä uudelleenkirjoitukset ovat ihan tarpeeksi helppoja tehdä käsin, mutta tietyillä säännöillä (esim. paljon sääntöjä, jotka korvaavat yhden merkin kahdella tai kolmella) pöydällä oleva merkkijono kasvaa tosi pitkäksi ja uudelleenkirjoitusten tekeminen virheettömästi on vaikeaa. Omaa vaivaa voi säästää, jos teettää uudelleenkirjoitushommat tietokoneella.
Kirjoitin tähän tarkoitukseen lyhyen Haskell-ohjelman: http://members.sange.fi/~atehwa/vc/r+d/rewrite-game/rewrite.hs
Unixissa on melko hyvin hommaan sopiva hyötyohjelma, sed. sed-ohjelmalla pystyy korvaamaan merkkijonoja toisilla. Ongelma on vain se, että sed ei katso merkkijonon joka kohdasta vuorollaan, mikä korvaus siihen sopii, vaan tekee ensin kaikki yhdenlaiset korvaukset ja sitten kaikki toisenlaiset korvaukset. Niinpä sitä pitää erikseen estää uudelleenkorvaamasta laajenteita. Helpoin tapa tähän lienee se, että merkitään laajenteet eri tavalla (esim. isoilla kirjaimilla) ja muutetaan ne takaisin tavallisiksi, kun kaikki korvaukset on tehty.
Esimerkiksi säännöstö { s=>pu, su=>ss, pu=>su } voidaan toteuttaa seuraavanlaisena sed-ohjelmana:
s/su/SS/g s/pu/SU/g s/s/PU/g y/PUS/pus/
Spesifimmät säännöt (ne, joilla on pidempi vasen puoli) sijoitetaan korvauksissa ensiksi, sillä ne estävät vähemmän spesifejä toteutumasta. Nyt jos tämä sed-ohjelma on talletettu tiedostoon saannosto1.sed, se voidaan ajaa seuraavasti:
$ echo uuspusu | sed -f saannosto1.sed uupususs
Mutta. Tässä on yksi gotcha. Vaikka sääntö olisi vähemmän spesifi, se voi estää spesifimpää sääntöä toteutumasta jos sen osumakohta on alummassa merkkijonoa mutta päällekkäin spesifimmän säännön osumakohdan kanssa (tällöinhän sääntö ehtii toteutua ennen spesifimpää koska merkkijonoa käydään lopusta alkuun). Esimerkiksi säännöstö { pupu=>su, pupusu=>ssp, su=>puupuu } tuottaa naivisti koodattuna väärän tuloksen syötteelle "pupupusu" (pitäisi olla "supupuupuu"):
$ echo pupupusu | sed 's/pupusu/SSP/g;s/pupu/SU/g;s/su/PUUPUU/g;y/PUS/pus/' pussp
Oikeasti pupu-säännön pitäisi täsmätä ennen pupusu-sääntöä, koska heti merkkijonon alussa on "pupu", jota ei seuraa "su". Tällainen ongelma on mahdollinen aina, kun lyhyemmän säännön vasen puoli loppuu samoihin merkkeihin kuin millä pidemmän säännön vasen puoli alkaa. Ongelman voi korjata etsimällä erikseen tilanteet, joissa lyhempi sääntö estää pidempää toteutumasta, lisäämällä säännöstöön vielä tarkempi sääntö tilanteelle, jossa pidemmän säännön osuma on osana lyhyemmän säännön osumaa:
$ echo pupupusupupusu | \ > sed 's/pupupusu/SUpusu/g;s/pupusu/SSP/g;s/pupu/SU/g;s/su/PUUPUU/g;y/PUS/pus/' supupuupuussp
Tällä kertaa tulos on oikein.
Pikalinkit: