13 Interaktive historier

I dette kapitlet lager du den interaktive historien Siste ferje, der valgene til leseren bestemmer hvordan historien ender. Lagre alt du lager i dette kapitlet i mappen kap13-historier.

13.1 Historien som flytdiagram

flowchart TD
  start["start<br>Den siste ferja har gått"]
  ring["ring<br>Mobilen dør"]
  kiosken["kiosken<br>Kiosken stenger snart"]
  kajakk["kajakk<br>En rød kajakk på stranda"]
  sluttNatt(["sluttNatt<br>En kald natt på brygga"])
  sluttTelefon(["sluttTelefon<br>Taxibåten henter deg"])
  sluttPadle(["sluttPadle<br>Redningsskøyta"])
  start -- Ring mamma --> ring
  start -- Gå til kiosken --> kiosken
  ring -- Vent på brygga --> sluttNatt
  ring -- Gå til kiosken --> kiosken
  kiosken -- Lån telefonen --> sluttTelefon
  kiosken -- Lån en kajakk --> kajakk
  kajakk -- Padle over --> sluttPadle
  kajakk -- Gå tilbake til brygga --> start

En interaktiv historie består av scener. Et flytdiagram viser scenene som bokser og valgene som piler. Sluttscenene har runde hjørner. Før du skriver kode, tegner du flytdiagrammet.

  1. Les flytdiagrammet. Hvor mange scener har historien, og hvor mange av dem er slutter?
  2. Følg pilene fra start til en slutt. Hvor mange ulike veier finnes det, når du ikke går i ring?
  3. Hvilke scener kan leseren komme til fra mer enn én annen scene?
  4. Hva skjer hvis leseren velger «Gå tilbake til brygga» i scenen kajakk? Kan historien da vare for alltid?
  5. Tegn flytdiagrammet av på papir. Legg til en ny scene, for eksempel at leseren møter noen på brygga, med to valg og en ny slutt.

Gi hver scene et kort navn uten mellomrom og uten æ, ø og å, som kiosken og sluttNatt. Navnet blir navnet på funksjonen i 13.2.

Hver scene med to valg dobler antall veier gjennom historien. Med fem valg etter hverandre blir det 32 veier, og alle må skrives og testes. Begynn med en liten historie, og la noen av valgene føre til den samme scenen.

Spør KI

Jeg skal lage en interaktiv historie om [skriv temaet]. Ikke skriv historien for meg. Still meg spørsmål som hjelper meg å finne en start, to gode valg og tre ulike slutter.

13.2 Valg som fører videre

siste-ferje/index.html
<body>
  <h1>Siste ferje</h1>
  <p id="txtTekst"></p>
  <button id="btnA"></button>
  <button id="btnB"></button>

  <script src="skript.js"></script>
</body>
siste-ferje/skript.js
function visStart() {
  txtTekst.textContent = "Klokka er 23.10. Du står alene på brygga på Langøya, og den siste ferja har akkurat gått. Mobilen har bare 2 prosent strøm igjen.";
  btnA.textContent = "Ring mamma";
  btnA.onclick = visRing;
  btnB.textContent = "Gå til kiosken";
  btnB.onclick = visKiosken;
}

function visRing() {
  txtTekst.textContent = "Mamma rekker bare å si «Vent der, så …» før mobilen dør. Nå er det helt stille på brygga.";
  btnA.textContent = "Vent på brygga";
  btnA.onclick = visSluttNatt;
  btnB.textContent = "Gå til kiosken";
  btnB.onclick = visKiosken;
}

function visKiosken() {
  txtTekst.textContent = "Kiosken på kaia stenger om fem minutter. Ekspeditøren ser trøtt ut, men smiler når du kommer inn.";
  btnA.textContent = "Spør om å låne telefonen";
  btnA.onclick = visSluttTelefon;
  btnB.textContent = "Lån en kajakk ved stranda";
  btnB.onclick = visKajakk;
}

visStart();

Hver scene er en funksjon, som viser teksten og bestemmer hva knappene skal gjøre. btnA.onclick = visRing; betyr «når noen trykker på knapp A, skal visRing kalles».

I 10.2 skrev du onclick i HTML-koden. Når det en knapp gjør, skal endre seg underveis, setter du onclick i JavaScript i stedet.

  1. Les koden ovenfor. Hva står det på siden når den lastes inn? Hva skjer når du trykker på Gå til kiosken?
  2. Lag mappen siste-ferje med filene index.html og skript.js. Skriv en hel nettside med tittelen Siste ferje, og legg koden ovenfor inn i <body>. Skriv av skript.js, og test de to knappene på startsiden.
  3. Last inn siden på nytt, og trykk på Ring mamma. Hva skjer på siden? Åpne konsollen. Hvilken feilmelding står der, og hvorfor?
  4. Teksten til de fire siste scenene står i tekstfilen nedenfor. Lag én funksjon for hver scene, og kopier teksten fra filen inn i funksjonene. Sluttscenene skal ha én knapp, Start på nytt, som kaller visStart.
  5. Fjern den siste kodelinja i skript.js, visStart();. Hva skjer når siden lastes? Legg den tilbake.
scener.txt
kajakk
Ved stranda ligger en rød kajakk. Det blåser opp, og lysene på fastlandet er to kilometer unna.
Valg A: Padle over → sluttPadle
Valg B: Gå tilbake til brygga → start

sluttNatt
Du venter og venter, men ingen kommer. Du sover på en benk og tar den første ferja klokka seks. Slutt.

sluttTelefon
Ekspeditøren ringer taxibåten for deg. Turen koster 600 kroner, men du er hjemme før midnatt. Slutt.

sluttPadle
Bølgene blir høyere, og du må snu. Redningsskøyta plukker deg opp, kald og våt. Det gikk bra, men det var ikke lurt. Slutt.

Sluttscenene trenger bare én knapp. Med btnB.hidden = true; skjuler du knapp B.

  1. Skjul knapp B i alle sluttscenene. Spill historien til en slutt, og trykk på Start på nytt. Hvor mange knapper er det på startsiden nå? Hva må du legge til i visStart for å rette feilen?

Nedenfor ser du fire kodelinjer som skal få knapp A til å kalle visRing. Bare én av dem virker.

Kode A
btnA.onclick = visRing();
Kode B
btnA.onclick = "visRing";
Kode C
btnA.onclick = visRing;
Kode D
btnA.onClick = visRing;
  1. Finn kodelinja som virker. Prøv de tre andre i visStart, én om gangen. Hva skjer når siden lastes, og når du trykker på knappen? Står det noe i konsollen?

Skriv og test én scene om gangen. Når en ny scene virker, går du videre til den neste.

13.3 Variabler som husker valgene

siste-ferje/skript.js
let harStrom = false;

function visLader() {
  harStrom = true;
  txtTekst.textContent = "Ekspeditøren lar deg lade mobilen bak disken i ti minutter. Nå har den 30 prosent strøm.";
  btnA.textContent = "Gå tilbake til brygga";
  btnA.onclick = visStart;
  btnB.hidden = true;
}

function visRing() {
  if (harStrom) {
    txtTekst.textContent = "Mamma svarer med en gang. «Bli der du er», sier hun. «Naboen henter deg med båten.»";
    btnA.textContent = "Vent på naboen";
    btnA.onclick = visSluttHjem;
    btnB.hidden = true;
  } else {
    txtTekst.textContent = "Mamma rekker bare å si «Vent der, så …» før mobilen dør. Nå er det helt stille på brygga.";
    btnA.textContent = "Vent på brygga";
    btnA.onclick = visSluttNatt;
    btnB.textContent = "Gå til kiosken";
    btnB.onclick = visKiosken;
  }
}

Så langt glemmer historien hva leseren har gjort. Med en variabel kan historien huske et valg til senere. Nå kan det samme valget, Ring mamma, føre til to ulike scener.

  1. Les koden ovenfor. Når fører Ring mamma til at naboen kommer?
  2. Legg til variabelen og funksjonen visLader øverst i skript.js, og erstatt visRing med koden ovenfor. Endre valg A i kiosken til «Spør om å lade mobilen», som kaller visLader.
  3. Lag funksjonen visSluttHjem med en egen sluttekst.
  4. Test to veier: Ring mamma med en gang, og gå først til kiosken og lad mobilen. Får du ulike svar?
  5. Spill til en slutt, trykk på Start på nytt, og ring mamma med en gang. Hvorfor kommer naboen, selv om du ikke har ladet mobilen denne gangen?
  6. Hvorfor står let harStrom = false; utenfor funksjonene?
  7. Hva tror du if (harStrom) betyr? Skriv vilkåret om med ===, og test.

For å rette feilen må harStrom bli false igjen når historien starter på nytt.

siste-ferje/skript.js
function startPaNytt() {
  harStrom = false;
  visStart();
}
  1. Legg til funksjonen ovenfor, og la knappen Start på nytt i alle sluttscenene kalle startPaNytt. Hvorfor kan du ikke bare skrive harStrom = false; i visStart?
  2. Tegn flytdiagrammet på nytt. Hvordan viser du at scenen ring kan føre til to ulike scener?

Gi variabler som er true eller false, navn som er et spørsmål med ja eller nei som svar, som harStrom, harNokkel eller erRedd.

Spør KI

Jeg lager en interaktiv historie i JavaScript, og har variabelen harStrom utenfor alle funksjonene. Forklar hvorfor variabelen må stå utenfor funksjonene, og hva som ville skjedd hvis den sto inne i en funksjon. Ikke skriv kode for meg.

13.4 Bilder og lyd

siste-ferje/index.html
<h1>Siste ferje</h1>
<img id="imgScene" src="bilder/brygga.jpg" alt="En tom brygge i mørket. Ferja er på vei bort.">
<p id="txtTekst"></p>
<button id="btnA"></button>
<button id="btnB"></button>
<audio id="lydBolger" src="lyd/bolger.mp3"></audio>
siste-ferje/skript.js
function visKajakk() {
  imgScene.src = "bilder/kajakk.jpg";
  imgScene.alt = "En rød kajakk på en steinstrand. Det er store bølger på sjøen.";
  lydBolger.play();
  txtTekst.textContent = "Ved stranda ligger en rød kajakk. Det blåser opp, og lysene på fastlandet er to kilometer unna.";
  btnA.textContent = "Padle over";
  btnA.onclick = visSluttPadle;
  btnB.textContent = "Gå tilbake til brygga";
  btnB.onclick = visStart;
}

Et bilde byttes ved å gi src en ny adresse. play() starter en lyd, og pause() stopper den. Id-er til bilder begynner med img, og id-er til lyder med lyd.

  1. Les koden ovenfor. Hvilke to egenskaper ved bildet endres? Hvorfor må begge endres?
  2. Lag mappen bilder med ett bilde for hver scene. Bruk bilder du har tatt selv, eller som du har lov til å bruke. Legg til <img> i index.html, og bytt bilde og alternativ tekst i hver scene.
  3. Glem å endre alt i én av scenene. Inspiser bildet i den scenen. Hva vil en skjermleser lese opp?
  4. Lag mappen lyd, og finn en lyd av bølger med en lisens som tillater at du bruker den. Spill lyden i visKajakk.
  5. Trykk på Gå tilbake til brygga før lyden er ferdig. Hva skjer? Stopp lyden med lydBolger.pause(); i visStart.
  6. Prøv å spille en lyd i visStart når siden lastes inn. Hva skjer, og hva står i konsollen?
  7. Hvorfor ser du ikke <audio>-elementet på siden? Legg til attributtet controls, og se hva som skjer.

Nettleseren spiller ikke av lyd før brukeren har trykket på noe på siden. Derfor kan ikke historien starte med lyd. Lyd må heller aldri være den eneste måten å få vite noe på. Det lyden forteller, må også stå i teksten.

Gi alle bildene den samme formen, for eksempel 4 : 3, slik at knappene ikke hopper opp og ned når bildet byttes. Du lærte å bruke aspect-ratio og object-fit i 9.4.

13.5 Teste historien på andre

siste-ferje/skript.js
function visStart() {
  lydBolger.pause();
  imgScene.src = "bilder/brygga.jpg";
  imgScene.alt = "En tom brygge i mørket. Ferja er på vei bort.";
  txtTekst.textContent = "Klokka er 23.10. Du står alene på brygga på Langøya, og den siste ferja har akkurat gått. Mobilen har bare 2 prosent strøm igjen.";
  btnA.textContent = "Ring mamma";
  btnA.onclick = visRing;
  btnB.textContent = "Gå til kiosken";
  btnB.onclick = visKiosken;
}

Det er lett å glemme en vei gjennom historien. Derfor tester du hver pil i flytdiagrammet. En pil som aldri er testet, kan ha en feil.

Fra Valg Forventet Resultat
start Ring mamma ring, uten strøm
start Gå til kiosken kiosken
ring Vent på brygga sluttNatt
kiosken Spør om å lade mobilen lader
lader Gå tilbake til brygga start, med to knapper
  1. Les koden ovenfor. Hva skjer på startsiden når leseren kommer tilbake fra scenen lader?
  2. Skriv av testtabellen, og legg til én rad for hver pil i flytdiagrammet ditt. Husk at ring har ulike piler med og uten strøm, og at Start på nytt også er en pil.
  3. Gå gjennom alle radene, og fyll inn resultatet. Rett feilene du finner, og test hele tabellen på nytt.
  4. Lukk utviklerverktøyene, og bruk historien bare med tastaturet. Trykk Tab for å gå til en knapp, og Enter for å trykke på den. Kan du spille hele historien?

Knapper laget med <button> virker med tastatur og skjermleser uten at du gjør noe ekstra. Bruk derfor alltid <button> til valgene, og ikke et avsnitt eller et bilde med onclick.

Til slutt skal noen som ikke har laget historien, teste den. Be testpersonen tenke høyt: si hva hun leser, hva hun tror skjer, og hvorfor hun velger som hun gjør.

  1. La en medelev spille historien mens du ser på. Ikke hjelp til. Skriv ned hvor hun ble usikker, og hvilken slutt hun fikk. Ville hun spille en gang til?
  2. Når teksten byttes, merker ikke en skjermleser alltid det. Legg til aria-live="polite" på <p id="txtTekst">. Slå på skjermleseren, og spill historien. Hva er forskjellen?
Spør KI

Her er scenene og valgene i historien min: [lim inn scenene og valgene]. Lag en liste over alle veiene fra start til en slutt, slik at jeg kan bruke den som testtabell. Ikke endre historien, og ikke skriv kode.

Oppsummering

I dette kapitlet har du planlagt en interaktiv historie med et flytdiagram, laget en funksjon for hver scene, husket valg i variabler, byttet bilder og lyd og testet alle veiene.

En historie med en variabel

kort-historie.html
<p id="txtTekst" aria-live="polite"></p>
<button id="btnA"></button>
<button id="btnB"></button>

<script>
  let harStrom = false;

  function visStart() {
    txtTekst.textContent = "Den siste ferja har gått. Mobilen er nesten tom.";
    btnA.textContent = "Ring mamma";
    btnA.onclick = visRing;
    btnB.textContent = "Lad mobilen i kiosken";
    btnB.onclick = visLader;
    btnB.hidden = false;
  }

  function visLader() {
    harStrom = true;
    txtTekst.textContent = "Nå har mobilen 30 prosent strøm.";
    btnA.textContent = "Gå tilbake til brygga";
    btnA.onclick = visStart;
    btnB.hidden = true;
  }

  function visRing() {
    if (harStrom) {
      txtTekst.textContent = "Mamma svarer. Naboen henter deg med båten. Slutt.";
    } else {
      txtTekst.textContent = "Mobilen dør. Du sover på en benk på brygga. Slutt.";
    }
    btnA.textContent = "Start på nytt";
    btnA.onclick = startPaNytt;
    btnB.hidden = true;
  }

  function startPaNytt() {
    harStrom = false;
    visStart();
  }

  visStart();
</script>
Nettleser

Merk deg følgende:

  • Tegn flytdiagrammet før du skriver kode. Hver boks blir en funksjon, og hver pil blir et valg.
  • Hver scene viser teksten, setter tekst på knappene og bestemmer hvilken funksjon hver knapp skal kalle.
  • En variabel utenfor funksjonene husker valg. Gi den startverdien igjen når historien starter på nytt.
  • Test hver pil i flytdiagrammet, også med tastaturet.

Ny kode

Kode Betyr
btnA.onclick = visRing; knappen kaller visRing når noen trykker på den
btnB.hidden = true; skjuler knappen
btnB.hidden = false; viser knappen igjen
if (harStrom) det samme som if (harStrom === true)
imgScene.src = "bilder/kajakk.jpg"; bytter bildet
imgScene.alt = "En rød kajakk"; bytter den alternative teksten
lydBolger.play(); spiller lyden
lydBolger.pause(); stopper lyden
aria-live="polite" skjermleseren leser opp teksten når den endres

Oppgaver

Oppgave 13.1

oppg1-rett-feil/skript.js
function visStart() {
  txtTekst.textContent = "Du står foran en gammel dør.";
  btnA.textContent = "Åpne døra";
  btnA.onclick = visRommet();
  btnB.textcontent = "Gå videre i gangen";
  btnB.onclick = visGangen;
}

function visRommet() {
  txtTekst.textContent = "Rommet er tomt, bortsett fra en nøkkel på gulvet.";
}

function visGangen() {
  txtTekst.textContent = "Gangen ender i en trapp ned i mørket.";
}

Koden ovenfor har tre feil. Den ene er at noe mangler helt til slutt. Lag mappen oppg1-rett-feil med index.html og skript.js, og rett feilene.

Oppgave 13.2

  • btnA.onclick = startPaNytt;
  • function visSluttSkatt() {
  • btnB.hidden = true;
  • }
  • txtTekst.textContent = "Du fant skatten! Slutt.";
  • btnA.textContent = "Start på nytt";

Sett sammen kodelinjene til en sluttscene.

Oppgave 13.3

Historien nedenfor er skrevet som vanlig tekst. Tegn et flytdiagram for historien. Gi hver scene et kort navn, og skriv valgene på pilene.

kantina.txt
Du kommer for sent til lunsj, og det er lang kø i kantina. Du kan stille deg i køen, eller gå til butikken over veien.

Står du i køen, er pizzaen utsolgt når det blir din tur. Da kan du kjøpe en salat, eller gå sulten tilbake til klassen.

Går du til butikken, må du velge mellom å kjøpe en baguette eller en pakke boller. Uansett hva du velger, kommer du fem minutter for sent til timen.

Velger du salat, sitter du mett og fornøyd i timen. Går du sulten tilbake, klarer du ikke å følge med.

Oppgave 13.4

Lag historien fra oppgave 13.3 i mappen oppg4-kantina, med en funksjon for hver scene. Lag testtabellen før du tester.

Oppgave 13.5

Forklar forskjellen på btnA.onclick = visRommet; og btnA.onclick = visRommet();.

B-oppgaver

Oppgave 13.6

Legg til variabelen penger med startverdien 300 i Siste ferje. Vis hvor mye penger leseren har i et eget avsnitt under teksten. Taxibåten koster 600 kroner, så leseren må finne en måte å få nok penger på, eller velge noe annet. Husk startPaNytt.

Oppgave 13.7

Lag en quiz som en historie i mappen oppg7-quiz. Hvert spørsmål er en scene med to svar. Variabelen poeng øker når leseren svarer riktig. Sluttscenen skal gi ulike tekster for ulike poengsummer.

Oppgave 13.8

Gjør Siste ferje så tilgjengelig som du klarer. Sjekk at alle bildene har en god alternativ tekst, at historien kan spilles med tastaturet, at kontrasten er god nok og at teksten kan forstørres til 200 prosent. Test historien med skjermleser, og skriv en kort rapport om hva du fant og rettet.

Oppgave 13.9

Undersøk hva et objekt er i JavaScript. Lagre alle scenene i Siste ferje i ett objekt, der hver scene har en tekst, et bilde og to valg. Lag én funksjon, visScene(navn), som kan vise alle scenene. Hva er fordelen med denne måten å lage historien på, når den får mange scener?