keskiviikko 4. helmikuuta 2015

Koodauksen ABC: 11. oppitunti

Tällä kerralla laitoimme pacman:ille namut valmiiksi labyrinttiin. Koska oppilailla oli jo valmiina seinien sijainnit, pohdimme yhdessä miten saisimme kaikkiin muihin ruutuihin namuja. Päädyimme tekemään namut kaikkiin mahdollisiin ruutuihin ja poistamaan ne namut, jotka osuivat seinän kanssa samaan ruutuun.
Koska olimme tehneet jo pari rekursiivista funktiota, teimme tämänkin rekursiivisesti. Suunnitelma oli käydä pelikenttä läpi rivi kerrallaan ja tehdä kokonaisen rivin namut yhdellä iteraatiokierroksella ja samalla kerätä valmista listaa "paikat"-muuttujaan.

;; tee-namut : numero tyhjä-lista -> paikka-lista
(define (tee-namut rivi paikat)
  (if (> rivi PELIN-KORKEUS)
      paikat
      (tee-namut (add1 rivi) 

                 (append (map (tee-paikka rivi) leveys-lista)  
                          paikat)))) 

Tällaisia rekursiivisia funktioita olimme jo tehneet, mutta jotta saimme kätevästi koko rivillisen namuja, teimme sen suoraan käyttämällä map-funktiota (ks. koodin sininen osa). Map ottaa parametrina funktion tee-paikka ja ajaa sen jokaiselle leveys-listan alkiolle. Leveys-lista sisältää x-koordinaatit pelikentän leveydeltä, ja teimme sen näin:

(define leveys-lista (map add1 (build-list PELIN-LEVEYS values)))   

Tee-paikka - funktio oli myöskin uusi ilmestys, koska se ei ollutkaan ns. tavallinen funktio. Kun sitä kutsuu, se palauttaa funktion. Tämän selittäminen oppilaille olikin aika vaikeaa, mutta ilmeisesti ohjelmoinnissa on niin monta "outoa" asiaa, ettei tämä herättänyt sen kummempaa vastustusta.

;; tee-paikka : numero -> funktio
(define (tee-paikka y)
  (lambda (x) (make-paikka x y)))


Testasimme REPL:issä length:in avulla, että oikea määrä namuja syntyi (PELIN-LEVEYS * PELIN-KORKEUS):

> (length (tee-namut 1 '()))

Nyt poistimme tästä listasta ne, jotka osuivat seinän kanssa samaan ruutuun. Tähän käytimme remove*-funktiota.

(define namu-lista
  (remove* seinä-lista (tee-namut 1 '()) paikka=?))


Remove* - funktio tarvitsee parametrina apufunktion, joka kertoo milloin kaksi paikka ovat "samat". Jouduimme kirjoittamaan tätä varten paikka=? - funktion. Selitin, että tätä kutsutaan "predikaatiksi", mutta sillä ei ole äidinkielen predikaatin kanssa mitään tekemistä. Funktion toiminta oli helppo perustella: kaksi paikkaa ovat samat, jos niiden x-koordinaatit ovat samat JA niiden y-koordinaatit ovat samat. Tätä käyttäisimme myös jatkossa hyväksi kun testaamme onko pacman törmäämässä seinään tai syömässä namua.

;; paikka=? : paikka paikka -> boolean
(define (paikka=? p1 p2)
  (and (equal? (paikka-x p1)
               (paikka-x p2))
       (equal? (paikka-y p1)
               (paikka-y p2))))

 
Lopuksi teimme piirtofunktiot namujen piirtämiselle. Otimme mallia viime viikon vastaavista funktioista, joilla piirsimme seinäpalat pelipohjalle. 
  
(define SEINÄT (foldl piirrä-seinäpala PELIPOHJA SEINÄ-LISTA))

(define NAMU (circle (/ RUUTU 3) "solid" "red"))

;; piirrä-namupala : paikka kuva -> kuva
(define (piirrä-namupala p k)
  (place-image NAMU (skaalaa (paikka-x p)) (skaalaa (paikka-y p)) k))

(define TESTI (foldl piirrä-namupala SEINÄT namu-lista))  


 Ja tätä kun testasi niin voilà labyrintti täyttyi namuista!

Keltaiset namut
Punaiset namut
Tämän oppitunnin asiat olivat kyllä jokseenkin vaikeita, enkä tiedä kunka moni oikeasti ymmärsi mistä oli kyse kun käytimme map, foldl ja remove* - funktioita saati sitten lambdaa. Kun joululomalla itse tein pacman-pelin tein sen aivan eri tavalla. Nyt yritin tehdä sen yksinkertaisemmin ja välttelin sisäkkäistä rekursiota viimeiseen asti (rekursiivista funktiota, joka kutsuu toista rekursiivista funktiota). En nyt lopultakaan tiedä, oliko tämä valinta sitten yhteään parempi, koska jouduin vetämään esiin map:in ja lambdan. Ensi viikolla pacman pääsee syömään herkkuja, eli yritämme integroida syksyllä tehdyn koodin kevään koodiin. Katsotaan miten siinä käy.

Ei kommentteja:

Lähetä kommentti