(toiminnot)

hwechtla-tl: Merkkikorvauspelin säännöt

Kierre.png

Mikä on WikiWiki?
nettipäiväkirja
koko wiki (etsi)
viime muutokset


(nettipäiväkirja 03.07.2015) Keksin mahtavan uuden pelin! (Ja 04.07.2015 ehdittiin jopa testipelata se ja tilkitä muutamia porsaanreikiä...)

Lyhyt tiivistelmä

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.

Esimerkkipeli

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.

Säännöt tiiviisti

  1. Kaikki pelaajat valitsevat kolmen kirjaimen merkkijonon (variantti: säännöllisen lausekkeen, jossa vähintään kolme kirjainta metamerkkejä laskematta), kirjoittavat sen paperille, ja jättävät piiloon. Tämä paperilla oleva merkkijono on se, johon pelaaja pyrkii.
  2. Pöydällä olevalle paperille (näkyviin) kirjoitetaan merkkijono, jossa on valitun aakkoston jokainen kirjain kerran. Jos esimerkiksi on sovittu, että pelissä käytetään kirjaimia a, t, c ja g, kirjoitetaan "atcg".
  3. Kaikki pelaajat kirjoittavat piiloon paperille uuden uudelleenkirjoitussäännön (eli mikä merkkijono korvataan millä), ja paljastavat ne yhtaikaa. Esimerkiksi "at => ccca" on uudelleenkirjoitussääntö, joka ilmaisee, että kaikki "at"-osumat korvataan kirjaimilla "ccca". Uudelleenkirjoitussäännössä oleva korvattava merkkijono ei saa olla täsmälleen sama, kuin pöydän paperin merkkijono, eikä myöskään tyhjä merkkijono (eli ei voi tehdä sääntöä, joka sopii joka kohtaan merkkijonoa). Laajenne sen sijaan saa olla tyhjä merkkijono (en ole ihan varma, pitäisikö tämä kieltää). Laajenne ei saa sisältää pelaajan omaa voittomerkkijonoa (1. kohdassa paperille kirjoitettua salaista merkkijonoa).
  4. Otetaan nämä uudelleenkirjoitussäännöt ja sovelletaan niitä siihen merkkijonoon, joka viimeksi on kirjoitettu pöydällä olevaan paperiin. Sääntöjen soveltamisessa edetään merkkijonon alusta loppuun, siten että laajenteita ei uudelleenkirjoiteta uudelleen (eli kukin merkki tulee korvatuksi joko 1 tai 0 säännöllä). Jos samaan kohtaan merkkijonoa täsmää useampi sääntö, valitaan se, jonka korvattava osuma on pisin. Jos molemmissa säännöissä on sama merkkijono korvattavaksi, valitaan sääntö, jonka laajenne on lyhin. Jos tämäkään ei auta, ei toteuteta kumpaakaan (tai mitään) näistä säännöistä.
  5. Kun kaikki korvaukset on suoritettu, kirjoitetaan lopputulos paperille seuraavalle riville.
  6. Otetaan aikaisemman kierroksen uudelleenkirjoitussäännöt ja jos sellaiset on olemassa, toistetaan kohdasta 4 näillä aiemmilla uudelleenkirjoitussäännöillä. (Kaikki koko pelin aikana julkaistut uudelleenkirjoitussäännöt siis käydään viimeisistä ensimmäisiin.)
  7. Toistetaan kohdasta 3 kaksi kertaa. (Variantti: kolme/neljä kertaa)
  8. Paljastetaan 1. kohdassa kirjoitetut ja piiloon jätetyt voittokriteerit. Lasketaan kaikkien osumat paperin viimeisestä merkkijonosta.
  9. Se pelaaja voittaa, jolla on eniten osumia. Jos osumia on sama määrä (esim. 0), voittaa se pelaaja, jonka uudelleenkirjoitussääntöä sovellettiin viimeksi.

Muutamia huomioita strategiasta

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.

Uudelleenkirjoitusten toteuttaminen tietokoneella

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:


kommentoi (viimeksi muutettu 06.01.2018 21:40)