SET til Elm

Projekt24

1 Bordet

I dette kapitel skal vi sørge for, at SET-bordet bliver sat rigtigt op, så det kan vise 12 kort. Med andre ord skal vi nu implementere det vi har lært om HTML og CSS ved hjælp af Elm.

Inden vi går i gang skal I hente en template at arbejde i. Akkurat, som vi gjorde med talspillet. Templaten kan hentes her: Link.

1.1 Det første kort

I første omgang er målet at få browseren til at vise ét SET-kort. Kortet skal se sådan her ud:

Det betyder, at vi skal have browseren til at vise følgende HTML:

HTML
<div class="card"> <div class="symbol red solid squiggle"></div> <div class="symbol red solid squiggle"></div> </div>

Det føles rart at bygge tingene op fra bunden, så vi ved, at det hele virker. Vi begynder derfor med at skrive kortet direkte ind i funktionen view.

Øvelse

Brug Html.div og Attributes.class direkte i view-funktionen for at få vist kortet i browseren.

Hint! Din kode kunne fx begynde sådan her:
Html.main_ [] 
    [ Html.div [ Attributes.class "card" ]
    	[ Html.div [Attributes.class "symbol", Attributes.class "red", **HER MANGLER NOGET**][]
    	, **HER MANGLER OGSÅ NOGET**
    	]
    ]

Det bliver et kæmpe arbejde, hvis vi skal skrive hvert eneste SET-kort ind i koden som ovenfor - og det er heller ikke særligt fleksibelt, når vi gerne vil bygge et spil, hvor man skal kunne interagere med kortene. Vi vil derfor følge en anden strategi.

Målet er i stedet at lave en funktion, der tager et Card som input og giver os den korrekte Html Msg som output. Så kan vi nemlig let vise et hvilket som helst kort uden at redigere direkte i vores view-funktion. Vores mål i dette afsnit er derfor at definere en funktion viewCard:

Elm (viewCard)
viewCard : Card -> Html Msg viewCard card = **DET FUNKTIONEN GØR**

For at kunne gøre det, har vi brug for at kunne oversætte værdier af de tre typer Color, Shape og Shading til de relevante classes. Det kan for eksempel gøres således:

Elm (colorToClass)
colorToClass : Color -> Attribute Msg colorToClass color = case color of Green -> Attributes.class "green" Purple -> Attributes.class "purple" Red -> Attributes.class "red"

Øvelse

Definer først tilsvarende funktioner shapeToClass og shadingToClass. Overvej derefter, hvorfor numberToClass ikke kan defineres på samme måde (vent med at definere numberToClass!).

Som du måske kom frem til i øvelsen ovenfor, så skal numberToClass ikke have en værdi af typen Attribute Msg som output, fordi antallet af symboler på et kort ikke dikteres af en HTML-class. Vi vender tilbage til at definere numberToClass om lidt, men først skal vi have det, vi har lavet indtil nu, til at fungere.

Vi skal nu lave første udgave af viewCard-funktionen, som kan bruges til at vise et kort. Som diskuteret ovenfor, så har vi lidt udfordringer med antallet af symboler, men farve, fyld og form skulle være på plads.

Øvelse

Definer funktionen viewCard. Brug hjælpefunktionerne colorToClass, shapeToClass og shadingToClass. Du kan for eksempel begynde sådan her:

Elm (viewCard)
viewCard: Card -> Html Msg viewCard card = **DET HER SKAL DU LAVE NU**
Outputtet af viewCard skal være en Html Msg, der producerer følgende HTML:
HTML
<div class="card"> <div class="symbol red solid squiggle"></div> </div>

Nu er vi klar til at vise et kort, der ser således ud:

Bemærk, at der stadig kun er ét symbol på kortet!

Øvelse

Implementér viewCard i view, hvor du bruger exampleCard som input til viewCard.

(I filen Template.elm er der et afsnit, der hedder -- CONSTANTS. Her har vi defineret et kort, som du kan bruge som eksempel: exampleCard.)

Hint!
Html.main_ [] 
    [ **Her skal du bruge viewCard** 
    ]

Hvis alt er gået godt, kan du nu se dette kort i browseren:

Næste skridt er at få vist flere symboler på samme kort.

Det er lidt anderledes at arbejde med antallet af symboler på kortet, for Number skal, som vi allerede har bemærket, ikke være en HTML-class men derimod antallet af <div></div>-elementer på kortet. Derfor giver det mening at definere en hjælpefunktion numberToInt, der har et heltal som output. Husk, at et heltal har typen Int i Elm.

Øvelse

Definer funktionen numberToInt som:

Elm (numberToInt)
numberToInt: Number -> Int numberToInt number = **DET HER SKAL DU LAVE NU**
Hint!

Søg inspiration i colorToClass, men husk at output skal være tallene 1, 2 eller 3.

Nu er vi klar til at rette viewCard, så vi viser det korrekte antal symboler.

Øvelse

Brug funktionen List.repeat til at vise det ønskede antal symboler med viewCard-funktionen.

Hint!

Brug List.repeat til at lave en liste af typen List (Html Msg). Der skal være et element på listen for hvert symbol.

1.2 En række af kort

I dette afsnit skal vi udvide vores view-funktion, så den viser en række af kort. Vejen frem minder rigtig meget om den, som vi fulgte i foregående afsnit: Vi vil definere en funktion viewRow, som skal kalde viewCard. Der skal være tre kort på en række, så følgende definition virker fornuftig:

Elm (viewRow)
viewRow: Card -> Card -> Card -> Html Msg viewRow firstCard secondCard thirdCard = **DET FUNKTIONEN GØR**

Målet er at få følgende vist i bowseren:

Og det svarer til, at vores viewRow-funktion skal producerer en Html Msg, som bliver til følgende HTML i browseren:
<div class="row">
	<div class="card">
		<div class="symbol red solid squiggle"></div>
		<div class="symbol red solid squiggle"></div>
	</div>
	<div class="card">
		<div class="symbol red solid squiggle"></div>
		<div class="symbol red solid squiggle"></div>
	</div>
	<div class="card">
		<div class="symbol red solid squiggle"></div>
		<div class="symbol red solid squiggle"></div>
	</div>
</div>
Øvelse

Definer funktionen viewRow ved at kalde viewCard tre gange. Kald derefter viewRow i view-funktionen for at vise tre kort i browseren.

Elm (viewRow)
viewRow: Card -> Card -> Card -> Html Msg viewRow firstCard secondCard thirdCard = **DET FUNKTIONEN GØR**
Hint!

Din kode kunne for eksempel begynde sådan her...

viewRow : Card -> Card -> Card -> Html Msg
viewRow firstCard secondCard thirdCard =
    Html.div [Attributes.class "row"]
        [ viewCard firstCard
        , **HER MANGLER NOGET**
        ]

Det hele fungerer, men viewRow gentager en del af koden, når vi kalder viewCard hele tre gange. Tænk engang, hvis der skulle være flere kort i en række, for eksempel 10 kort, så skulle vi kalde viewCard 10 gange! Vi vil nu forsøge at omskrive viewRow, så der ikke bliver gentaget kode. Det kan gøres ved, at viewRow i stedet for de tre enkelte kort tager en samlet lsite af kort som input. Med andre ord betyder det, at viewRow skal tage en liste af typen List Card som input!

Øvelse
Du skal modificere funktionen viewRow, så den tager input af typen List Card, men output skal fortsat være det samme. Brug List.map til at genere en liste af typen List (HTML Msg).
Elm (viewRow)
viewRow: List Card -> Html Msg viewRow cards = Html.div [Attributes.class "row"] (**HER SKAL List.map BRUGES**)

Modificer derefter viewRow i view-funktionen, så den tager exampleRow (en konstant) som input og tjek at det hele virker i browseren.

Hint!

Funktionen, der skal bruges i List.map er viewCard.

Øvelse
Prøv nu at ændre antallet af kort i exampleRow. Virker viewRow også hvis der for eksempel er 7 kort i en række? Hvordan kan det være?

1.3 Hele bordet

Nu er det tid til at lave hele bordet! På et SET-bord ligger der normalt 12 kort. Du har sikkert allerede gættet, at vi skal lave en funktion viewTable, og at den skal tage en list af typen List Card som input. Vi kaster os ud i det, og et bud på en definition af viewTable kunne være:

Elm (viewTable)
viewTable: List Card -> Html Msg viewTable cards = Html.div [Attributes.class "table"] (List.map viewRow cards)

Øvelse
Implementer viewTable som ovenfor, og kald den i view-funktionen. Hvorfor virker det ikke?

Problemet er at listen exampleTable er en liste af enkelte kort. Du kan tænke på det sådan her:

exampleTable == [kort1, kort2, kort3, kort4, kort5, kort6, kort7, kort8, kort9, kort10, kort 11, kort12]
Nu forsøger List.map at bruge viewRow på hvert eneste element i listen, og her går det galt! Det går galt fordi viewRow forventer at få en liste af kort (fx 3 kort) som input, men den får kun ét enkelt kort og ikke en liste!

Vi kan godt fikse det, og vi behøver faktisk ikke lave ret meget om. Lad os overveje, hvordan exampleTable skulle se ud, hvis vi gerne vil bruge List.map og viewRow som ovenfor. I så fald skal de 12 kort i exampleTable ordnes i lister - én for hver række! Vi har altså brug for en liste af lister, som ser sådan her ud:

[[kort1, kort2, kort3], [kort4, kort5, kort6], [kort7, kort8, kort9] , [kort10, kort 11, kort12]]
Læg mærke til, at den yderste liste har fire elementer, som hver især er lister. Disse lister har hver tre elementer, som er kort. Listen af lister har altså typen List (List Card).

Vi skal nu lave en funktion buildRows, der inddeler exampleTable i lister med tre kort, som ovenfor.

Øvelse

Definer funktionen buildRows og brug case of på følgende måde:

Elm (buildRows)
buildRows: List Card -> List (List Card) buildRows cards = case cards of x :: y :: z :: rest -> **LAV EN SMART REKURSION HER** rest -> []
Hint!

Rekursionen kan for eksempel laves således:

[x,y,z] :: (buildRows rest)

Øvelse

Brug buildRows i viewTable og tjek at du får 12 kort vist i browseren.

Hint!

Din viewTable skulle nu se således ud:

viewTable : List Card -> Html Msg
viewTable cards =
    Html.div [ Attributes.class "table"] (List.map viewRow ( buildRows cards))

Nu er der kun tilbage, at vi skal have vist noget, der er lidt mere interessant end 12 ens kort! Heldigvis kommer Template.elm med endnu en konstant myTable, som består af 12 tilfældige SET-kort.

Øvelse

Brug myTable som input til viewTable, så du få vist 12 tilfædige kort.

Det sidste vi mangler er at gøre bordet til en del af modellen i elm arkitekturen, så vi ikke bare loader 12 kort, men i stedet får mulighed for at bruge hele elm arkitekturen.

Øvelse

Opret et felt i typen Model, der hedder "table", og brug init til at loade myTable ind. Modificer view-funktionen, så den kalder viewTable med model.table som input. Tjek at du får det rigtige output :)