=> Tehtävä 1 <= call-with-current-continuation-koodin analysointia -------------------------------------------------- Alla on kolme saman asian tekevää Scheme-proseduuria. Proseduurit ottavat argumentikseen listan ja palauttavat siitä kopion, kuitenkin siten että jos listassa esiintyy symboli del, sitä edeltävää listan tavallista alkiota ei oteta mukaan kopioon. Esimerkkiajoja: (delcopy1 '(1 2 3)) ; => (1 2 3) (delcopy1 '(1 2 del 3)) ; => (1 3) (delcopy1 '(1 del 2 del 3 del)) ; => () (delcopy1 '(1 2 3 del del 4)) ; => (1 4) (delcopy1 '(1 2 del del 3)) ; => (3) (delcopy1 '(1 2 del del 3 del)) ; => () (delcopy1 '(1 2 3 4 del del 5 del 6 del del)) ; => (1) (delcopy1 '(1 2 del del 3 4 del 5 6 del 7)) ; => (3 5 7) Argumenttilista ei saa olla sellaista muotoa, että del-symbolilla ei ole mitään poistettavaa, ts. kutsujen (delcopy1 '(del)) tai (delcopy1 '(1 del del)) ei tarvitse toimia. Toteutukset: (define (delcopy1 l) (define (iter rest copied ignore) (cond ((null? rest) copied) ((eq? (car rest) 'del) (iter (cdr rest) copied (+ ignore 1))) ((> ignore 0) (iter (cdr rest) copied (- ignore 1))) (else (iter (cdr rest) (cons (car rest) copied) ignore)))) (iter (reverse l) '() 0)) (define (delcopy2 l) (define (loop rest prevs) (cond ((null? rest) '()) ((eq? (car rest) 'del) ((car prevs) (loop (cdr rest) (cdr prevs)))) (else (call-with-current-continuation (lambda (k) (cons (car rest) (loop (cdr rest) (cons k prevs)))))))) (loop l (list (lambda (x) x)))) (define (delcopy3 l) (define (loop rest prev) (cond ((null? rest) '()) ((eq? (car rest) 'del) (prev (lambda () (cdr rest)))) (else (let ((r (call-with-current-continuation (lambda (k) (cons (car rest) (loop (cdr rest) k)))))) (if (procedure? r) (loop (r) prev) r))))) (loop l (lambda (x) '()))) Montako kertaa kukin näistä proseduureista kutsuu primitiiviä cons, kun ne saavat argumenttinaan listan, jossa on n tavallista alkiota ja d del-symbolia? (Esimerkiksi n=6 ja d=5 listassa (1 2 3 4 del del 5 del 6 del del).) Huomaa, että delcopy1:n käyttämä Scheme-primitiivi reverse (joka palauttaa kopion argumenttina annetusta listasta käännettynä lopusta alkuun, (reverse '(1 2 3)) => (3 2 1)) kutsuu consia yhtä monta kertaa kuin sen palauttamassa listassa on alkioita. Samoin delcopy2:ssa list kutsuu consia listansa pituuden verran; kaikki muut cons-kutsut näkyvät suoraan yllä olevassa koodissa. Palauta vastaus kolmena Scheme-proseduurina f1, f2 ja f3, jotka ottavat argumenteiksi n:n ja d:n ja palauttavat consin kutsujen määrän. Esimerkiksi jos olisit sitä mieltä että delcopy1 kutsuu consia 5*n+d kertaa, delcopy2 n*(n-1)-d kertaa ja delcopy3 n*d kertaa, palauttaisit rivit: (define (f1 n d) (+ (* 5 n) d)) (define (f2 n d) (- (* n (- n 1)) d)) (define (f3 n d) (* n d)) Vastauksen tulee olla vain edellämainitun kaltainen yksinkertainen lauseke, ei siis koodia, joka tekisi rekursiivista laskentaa tai yrittäisi suorittaa delcopy-toteutuksia. Vihjeitä: Muista, että cons-primitiiviä kutsutaan vasta kun sen molemmat argumentit on evaluoitu! Tyhjä lista ei ole pari, joten lauseketta '() evaluoitaessa ei kutsuta consia. => Tehtävä 2 <= Syntaksimuunnos omien proseduurien kutsujen laskemiseen ------------------------------------------------------- Alla oleva Scheme-ohjelma ottaa argumentiksi Scheme-koodia (listarakenteena) ja palauttaa saman Scheme-koodin muunnettuna siten, että jokaisen if-lauseen ehtoon lisätään tilastointiproseduurin kutsu. Esimerkiksi lauseke (if (< x 0) a b) muuttuu muotoon (if (predicate-stat 1 (< x 0)) a b) jossa 1 on jokin uniikki numero (tunniste juuri tälle ehdolle). Näin muunnettua koodia voitaisiin ajaa esimerkiksi seuraavanlaisella predicate-stat-funktion määritelmällä, joka tulostaa rivin aina kun ehto lasketaan ja kertoo oliko se tosi vai epätosi: (define (predicate-stat id val) (display "pred: ") (display id) (display " val: ") (display val) (newline)) Hienompi predicate-stat voisi esimerkiksi kerätä tilastoja siitä, mitkä ehdot olivat useimmiten tosia tai useimmiten epätosia, ja näitä tietoja voisi käyttää esimerkiksi kääntäjän tekemissä optimoinneissa. Edellä kuvatun muunnoksen tekevä ohjelma, joka tarvitsee lisäksi cond->if- ja tagged-list?-proseduurit SICP-kirjan kohdasta 4.1: (define predicate-id 0) (define (process exp) (cond ((tagged-list? exp 'quote) exp) ((tagged-list? exp 'set!) (list 'set! (cadr exp) (process (caddr exp)))) ((tagged-list? exp 'define) (cons 'define (cons (cadr exp) (map process (cddr exp))))) ((tagged-list? exp 'if) (set! predicate-id (+ predicate-id 1)) (cons 'if (cons (list 'predicate-stat predicate-id (process (cadr exp))) (map process (cddr exp))))) ((tagged-list? exp 'lambda) (cons 'lambda (cons (cadr exp) (map process (cddr exp))))) ((tagged-list? exp 'begin) (cons 'begin (map process (cdr exp)))) ((tagged-list? exp 'cond) (process (cond->if exp))) ((tagged-list? exp 'let) (cons 'let (cons (map (lambda (clause) (list (car clause) (process (cadr clause)))) (cadr exp)) (map process (cddr exp))))) ((pair? exp) (map process exp)) (else exp))) Tehtävä: Lisää tähän process-proseduuriin syntaksimuunnos, joka muokkaa jokaista annetussa lausekkeessa esiintyvää proseduurin määritelmää siten, että proseduurin rungon alkuun lisätään kutsu (procedure-stat 1) missä 1 on jokin uniikki numero (proseduurin tunniste). Näin muokattua koodia voisi ajaa halutulla procedure-stat-proseduurin määritelmällä, joka esimerkiksi tulostaisi tiedon proseduurikutsuista tai keräisi tilastoa siitä, mitä proseduureja kutsuttiin useimmin. Palauta vastauksena muokkaamasi process-proseduuri. (SICP-kirjan cond->if-muunnosproseduuria ei tarvitse palauttaa.) Muunnoksesi pitäisi muuttaa esimerkiksi argumenttina saatu koodi (begin (define a 3) (define (fib n) (if (< n 1) 1 (+ (fib (- n 1)) (fib (- n 2))))) (define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) (map (lambda (x) (fact (fib x))) '(1 2 3))) muotoon (begin (define a 3) (define (fib n) (procedure-stat 1) (if (predicate-stat 1 (< n 1)) 1 (+ (fib (- n 1)) (fib (- n 2))))) (define (fact n) (procedure-stat 2) (if (predicate-stat 2 (= n 0)) 1 (* n (fact (- n 1))))) (map (lambda (x) (procedure-stat 3) (fact (fib x))) '(1 2 3))) (joillakin id-numeroilla). Voit kopioida edellä olevan koodin ja tarvittavat osat kirjan kohdan 4.1 tulkista (eli cond->if ja sen vaatimat apuproseduurit) itsellesi evaluoimalla (copy-course-extension "syntax-process" "syntax-process.scm") Niksulan Scheme-tulkissa. Huomaa, että koodissa on kaksi eri versiota process-proseduurista (eri tehtäviä varten)! => Tehtävä 3 <= Syntaksimuunnos proseduurikutsujen luokitteluun ----------------------------------------------- Alla on edellisen tehtävän syntaksimuunnosproseduurin kaltainen ohjelma, joka ei tee edellisen tehtävän if-muunnosta (mutta tekee cond->if-muunnoksen). (define (process exp) (cond ((tagged-list? exp 'quote) exp) ((tagged-list? exp 'set!) (list 'set! (cadr exp) (process (caddr exp)))) ((tagged-list? exp 'define) (cons 'define (cons (cadr exp) (map process (cddr exp))))) ((tagged-list? exp 'if) (cons 'if (map process (cdr exp)))) ((tagged-list? exp 'lambda) (cons 'lambda (cons (cadr exp) (map process (cddr exp))))) ((tagged-list? exp 'begin) (cons 'begin (map process (cdr exp)))) ((tagged-list? exp 'cond) (process (cond->if exp))) ((tagged-list? exp 'let) (cons 'let (cons (map (lambda (clause) (list (car clause) (process (cadr clause)))) (cadr exp)) (map process (cddr exp))))) ((pair? exp) (map process exp)) (else exp))) Muokkaa tästä rungosta (ei edellisen tehtävän rungosta!) syntaksimuunnos, joka muuttaa koodin jokaisen proseduurikutsun joko muotoon (call proseduurin-nimi argumentit ...) tai muotoon (tailcall proseduurin-nimi argumentit ...) sen mukaan, onko kutsu häntärekursiivinen (tail-recursive) kutsu. Palauta vastauksena muokkaamasi process-proseduuri. (SICP-kirjan cond->if-muunnosproseduuria ei tarvitse palauttaa.) Muunnoksesi pitäisi muuttaa esimerkiksi argumenttina saatu koodi (begin (define (fib n) (if (< n 1) 1 (+ (fib (- n 1)) (fib (- n 2))))) (define (fact n) (define (iter p c m) (if (> c m) p (iter (* c p) (+ c 1) m))) (iter 1 1 n)) (map (lambda (x) (fact (fib x))) '(1 2 3))) muotoon (begin (define (fib n) (if (call < n 1) 1 (tailcall + (call fib (call - n 1)) (call fib (call - n 2))))) (define (fact n) (define (iter p c m) (if (call > c m) p (tailcall iter (call * c p) (call + c 1) m))) (tailcall iter 1 1 n)) (tailcall map (lambda (x) (tailcall fact (call fib x))) '(1 2 3))) Voit kopioida edellä olevan koodin ja tarvittavat osat kirjan kohdan 4.1 tulkista (eli cond->if ja sen vaatimat apuproseduurit) itsellesi evaluoimalla (copy-course-extension "syntax-process" "syntax-process.scm") Niksulan Scheme-tulkissa. Huomaa, että koodissa on kaksi eri versiota process-proseduurista (eri tehtäviä varten)! => Tehtävä 4 <= Tehtävä 5.13 ------------ Tee kirjan tehtävä 5.13. Palauta kaikki määrittelyt, joita olet muokannut kirjan rekisterikonesimulaattorista. Koneen tulee toimia ilman että rekisterilistaa annetaan make-machinelle argumentiksi. Esimerkki: (define gcd-machine (make-machine (list (list 'rem remainder) (list '= =)) '(test-b (test (op =) (reg b) (const 0)) (branch (label gcd-done)) (assign t (op rem) (reg a) (reg b)) (assign a (reg b)) (assign b (reg t)) (goto (label test-b)) gcd-done))) Voit ladata rekisterikoneen tulkkiin evaluoimalla (load-course-extension "regsim") tai kopioida simulaattorin itsellesi evaluoimalla (copy-course-extension "regsim" "regsim.scm"). => Tehtävä 5 <= find-variable ------------- Tee kirjan tehtävässä 5.41 määritelty find-variable funktio. Mikäli muuttujaa ei löydy käännösaikaisesta ympäristöstä, tulee palauttaa symboli not-found. => Tehtävä 6 <= Tehtävä 5.9 ----------- Tee kirjan tehtävä 5.9. Palauta kaikki määrittelyt, joita olet muokannut kirjan rekisterikonesimulaattorista. Lausekkeita käsittelevien proseduurien tulee antaa virheilmoitus error-primitiiviä käyttäen. (Varsinaisella virheilmoitustekstillä ei ole merkitystä, riittää että virheilmoitus aiheutetaan error-primitiivillä.) Esimerkki: > (define x (make-machine '() (list (list '= =)) '(label-1 (test (op =) (label label-1))))) *** ERROR IN make-test, -- Invalid inputs -- ASSEMBLE Voit ladata rekisterikoneen tulkkiin evaluoimalla (load-course-extension "regsim") tai kopioida simulaattorin itsellesi evaluoimalla (copy-course-extension "regsim" "regsim.scm"). => Tehtävä 7 <= eceval-cond ----------- Tee kirjan tehtävä 5.24. Toteuta cond-primitiivi uutena erikoismuotona eksplisiittisen kontrollin rekisterikonetulkkiin. Kirjan ec-eval-totetuksen voi ladata kurssin tulkkiin evaluoimalla (load-course-extension "eceval"), jonka jälkeen tulkin voi käynnistää evaluoimalla (start eceval). Esimerkki tulkin käytöstä: > (load-course-extension "eceval") > (start eceval) ;;; EC-Eval input: (+ 1 2) (total-pushes = 8 maximum-depth = 5) ;;; EC-Eval value: 3 ;;; EC-Eval input: (if (= 1 2) 3 4) (total-pushes = 11 maximum-depth = 8) ;;; EC-Eval value: 4 Tehtävässä tulee käyttää valmiiksi annettua runkoa, jonka voi kopioida itselleen evaluoimalla (copy-course-extension "eceval-cond" "eceval-cond.scm") kurssin tulkissa. Tässä rungossa ei ole kirjassa esitettyä read-eval-print-silmukkaa vaan tulkki voidaan käynnistää yhden lauseen evaluointiin kutsumalla proseduuria eval-expr. Esimerkiksi: > (copy-course-extension "eceval-cond" "eceval-cond.scm") > (load "eceval-cond.scm") > (eval-expr '(+ 1 2)) 3 Voit lisätä uusia operaatioita eceval-operations-listaan ja palauttaa niitä vastaavat Scheme-määrittely. Lisäksi voit muokata eceval- rekisterikoneen käskymäärittelyä, mutta älä muuta sitä muilta osin, kuin mitä condin toteuttamiseen uutena erikoismuotona on tarpeen. Palauta muokkaamasi tiedosto eceval-cond.scm. => Tehtävä 8 <= CPS-tyylin käyttöä: deep-copy-if-positive ----------------------------------------- Kolmannen luennon kalvoissa esiintyi esimerkki call/cc:n käytöstä: (define (copy-list-if-positive l) (call-with-current-continuation (lambda (k) (define (loop x) (cond ((null? x) '()) ((<= (car x) 0) (k '())) (else (cons (car x) (loop (cdr x)))))) (loop l)))) Proseduuri tekee kopion argumenttinaan annetusta listasta, paitsi jos lista sisältää yhdenkin negatiivisen arvon, jolloin proseduuri palauttaa tyhjän listan. Esimerkiksi: (copy-list-if-positive '(1 2 3 4 5)) => (1 2 3 4 5) (copy-list-if-positive '(1 2 -3 4 5)) => () Jos ei halua käyttää call/cc:tä (tai vaikkapa ohjelmoi kielellä jossa sitä ei ole), saman laskennan voi tehdä muuntamalla proseduurin continuation passing style -muotoon esimerkiksi näin: (define (copy-list-if-positive-k l k) (define (loop x k2) (cond ((null? x) (k2 '())) ((<= (car x) 0) (k '())) (else (loop (cdr x) (lambda (res) (k2 (cons (car x) res))))))) (loop l k)) (define (copy-list-if-positive l) (copy-list-if-positive-k l (lambda (res) res))) Alla on edellisen esimerkin kaltainen call/cc:tä käyttävä proseduuri, joka kopioi myös mahdolliset alilistat ja etsii niistäkin negatiivisia lukuja: (define (deep-copy-if-positive l) (call-with-current-continuation (lambda (k) (define (loop x) (cond ((null? x) '()) ((pair? x) (cons (loop (car x)) (loop (cdr x)))) ((<= x 0) (k '())) (else x))) (loop l)))) Tämä toimii näin: (deep-copy-if-positive '(1 2 3 4 5)) => (1 2 3 4 5) (deep-copy-if-positive '(1 2 -3 4 5)) => () (deep-copy-if-positive '(1 (2 3) 4 5)) => (1 (2 3) 4 5) (deep-copy-if-positive '(1 (2 -3) 4 5)) => () (deep-copy-if-positive '(1 2 (3 4) (5 6 (7 8) 9) 10)) => (1 2 (3 4) (5 6 (7 8) 9) 10) (deep-copy-if-positive '(1 2 (3 4) (5 6 (7 -8) 9) 10)) => () Tehtävä: Tee deep-copy-if-positive-proseduuri käyttämättä call/cc:tä muuntamalla edellä oleva koodi CPS-muotoon yllä olevan esimerkin kaltaisesti. Saman laskennan voisi toki tehdä muullakin tavalla - esimerkiksi käymällä argumentti läpi ensin negatiivisia lukuja etsien ja sitten vasta kopiota tehden - mutta tässä tehtävässä on tarkoitus harjoitella CPS-tyyliä, joten pyri tekemään tehtävä muuntamalla yllä olevaa koodia eikä kokonaan omalla ratkaisulla. Oikein tehdyssä ratkaisussa pitäisi olla vastaava loop-apuproseduuri, jossa käydään läpi samat kolme ehtoa ja päätetään niiden perusteella mitä tehdään. Tee koodi funktionaalisesti, siis ilman set!iä, set-car!:ia tai set-cdr!:ää. => Tehtävä 9 <= preserving ---------- Tee kirjan tehtävä 5.37. Toteuta tehtävänannossa määritelty preserving-funktio ja palauta pelkästään se vastauksenasi. Mieti, miten voit testata preserving-funktiota. Miten testaat sitä yksinkertaisilla käskysekvensseillä? Voit myös verrata tehtävässä toteutetun preserving-funktion avulla generoitua koodia kirjan preserving-funktion koodiin joillakin yksinkertaisilla esimerkeillä, kuten tehtävänannossa kehoitetaan, mutta vastausta tähän ei tule palauttaa. Kirjan kääntäjätoteutuksen voi ladata kurssin tulkkiin evaluoimalla (load-course-extension "eceval-compiler"), jonka jälkeen koodin voi kääntää SICPin luvussa 5.5.5 esitetyllä tavalla kutsumalla compile-proseduuria: (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) Kirjan luvussa 5.5.7 esitellään compile-and-go-proseduuri, jonka avulla koodin voi kääntää ja ladata suoraan eksplisiittisen kontrollin tulkkiin: (compile-and-go '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n)))) ;;; EC-Eval value: ok ;;; EC-Eval input: (factorial 5) ;;; EC-Eval value: 120 Voit kopioida alkuperäisen preserving-funktion määrittelyn evaluoimalla (copy-course-extension "preserving" "preserving.scm") kurssin tulkissa. => Tehtävä 10 <= Ristinollapeli tallennuksella ----------------------------- Tämä tehtävä antaa esimerkin hieman suuremmasta tavanomaisesta Scheme-ohjelmasta (ei siis tulkista tai kääntäjästä), johon tehdään tehtävässä muutoksia. Ristinolla on yksinkertainen kahden pelaajan lautapeli, jossa pelaajat asettavat vuorotellen X- ja O-merkkejä 3x3-kokoisen laudan ruutuihin. Jos pelaaja saa kolme omaa merkkiänsä riviin (vaakariviin, pystyriviin tai vinottain), hän voittaa pelin. Jos laudan kaikki yhdeksän ruutua tulevat täyteen ilman voittorivejä, pelistä tulee tasapeli. Peli voisi kulkea esimerkiksi näin: | | | |O | |O |O|O X|O|O -+-+- -+-+- -+-+- -+-+- -+-+- |X| |X| |X| |X| |X| (X voittaa) -+-+- -+-+- -+-+- -+-+- -+-+- | | | | | |X | |X | |X Pelistä on tässä tehtävässä tehty valmiiksi kaikki muu paitsi voiton tarkistaminen sekä pelitilanteen tallennus ja lataus. Voit kopioida valmiin koodin itsellesi evaluoimalla esim. (copy-course-extension "oandx" "oandx.scm"), joka kopioi koodin tiedostoon oandx.scm Scheme-tulkin nykyiseen hakemistoon. Pelin voi käynnistää lataamalla koodin Scheme-tulkkiin ja evaluoimalla (play). Valmis koodi toimii muuten, mutta se ei huomaa, että toinen pelaaja voittaa, vaan jatkaa peliä, kunnes lauta on täynnä (jolloin se väittää pelin olevan tasapeli), eikä tallennus ja lataus toimi. Tehtäväsi on tehdä valmiiksi: a) Make-board-proseduurin sisällä oleva move-wins?-proseduuri. Tämä proseduuri saa argumentteinaan laudan koordinaatit x ja y (molemmat 0, 1 tai 2), jotka kertovat edellisen siirron sarakkeen ja rivin, sekä edellisen siirron tehnyttä pelaajaa kuvaavan symbolin (joko x tai o). Proseduuri palauttaa totuusarvon, joka kertoo, aiheuttiko argumentteina annettu siirto voiton (tosi) vai ei (epätosi). Proseduuria kutsuttaessa argumentteina annettuihin koordinaatteihin tehty siirto on jo merkitty laudalle. b) Pelitilanteen tallennus ja lataus eli play-a-game-proseduurin sisällä olevat save-game ja load-game. Tätä varten saatat myös joutua muuttamaan make-board-proseduuria. Tallennetun pelin lataamisen jälkeen pelin tulee jatkua samasta tilanteesta (sama lauta, sama pelaaja seuraavana vuorossa - lataamisen jälkeen kysytään siis tallennuksen tehneen pelaajan seuraavaa siirtoa). Laudan tila on make-boardin paikallisessa muuttujassa board, jossa on kolmen listan lista. Kussakin sisemmässä listassa on yksi laudan rivi. Listan alkioissa on joko #f (jos laudan vastaava ruutu on tyhjä) tai jompaa kumpaa pelaajaa kuvaava symboli (joko x tai o). Esimerkiksi lista ((x o o) (#f x o) (x #f #f)) kuvaa seuraavaa lautaa: X|O|O -+-+- |X|O -+-+- X| | Vasemmalla alhaalla olevaa X:ää kuvaisivat koordinaatit x=0 ja y=2, eli jos tämä olisi ollut edellinen siirto, siirron jälkeinen move-wins?:n kutsu olisi (move-wins? 0 2 'x), ja move-wins?:n tulisi palauttaa #f, koska siirto ei aiheuttanut voittoa. Tee load-game ja save-game koodissa annettuihin runkoihin. Load-gamessa käytetty Scheme-primitiivi with-input-from-file suorittaa argumenttinaan saamansa proseduurin niin, että sen sisällä olevat read-kutsut lukevat tiedostosta oandx.savegame. Samoin save-gamessa oleva with-output-from-file suorittaa argumenttinaan saamansa proseduurin niin, että sen sisällä olevat write-, display- ja newline-kutsut kirjoittavat tiedostoon. Jos tiedosto loppuu kesken, read palauttaa arvon v, jolle primitiivi (eof-object? v) on tosi. Write-primitiivi on suositeltavampi tällaiseen tallennukseen kuin display: write tulostaa tekstinsä sellaisessa muodossa, että kun tulosteen antaa readille, yksinkertaisista Scheme-tietotyypeistä (listat, symbolit, numerot jne., ei yleensä kuitenkaan esimerkiksi proseduureja) saa samanlaisen olion kuin mikä kirjoitettiin. (Tätä Schemen ominaisuutta sanotaan write/read invarianceksi.) Palauta vastauksena koko muokkaamasi ohjelma. Tarkistinta varten: älä muuta announce-game-end-proseduuria äläkä tapaa, jolla sitä kutsutaan (tarkistin käyttää sitä). Älä myöskään muuta pelin käyttöliittymää esimerkiksi lisäämällä uusia ask-for-choice-kysymyksiä. Käytä tiedostojen käsittelyyn vain em. primitiivejä read, write, display, newline ja eof-object? annetun rungon (eli with-input-from-file ja with-output-to-file) sisällä. Esimerkiksi edellä olevan pelin voisi pelata syöttämällä pelille (play):n jälkeen vastaukset (n x 2 2 1 3 3 3 1 2 1 1 n) järjestyksessä. Viimeisen 1:n jälkeen peli kertoisi että x on voittanut ja kysyisi, halutaanko pelata uudelleen. Latausta ja tallennusta voit kokeilla syötteellä (n x 2 2 3 1 3 3 s y n o 2 2 1 1 1 2 q y y 2 1 1 1 n), jonka lopussa x:n pitäisi voittaa peli. (Kokeile tätä tehtyäsi tehtävän.)