Et spill blir skapt (del 2)

Arkadespillet mitt begynner nå å ta form – men det er fortsatt langt fra ferdig.

Dette er andre del i min lille «spillutviklerdagbok» for et superenkelt arkadespill jeg har laget for Sharp MZ-80A. Den første leser du her.

For en person som aldri har eid en mikrodatamaskin fra det tidlige åttitallet, virker det kanskje litt rart at noe av det første jeg finner på når jeg sitter med min nyinnkjøpte Sharp MZ-80A, er å programmere. Men det er egentlig helt naturlig, og hva som var forventet av en typisk databruker på den tiden. Hvis vi i dag har behov for at datamaskinen, smartmobilen eller nettbrettet skal gjøre noe spesielt, laster vi ned et program (eller en «app») fra nettet. Tidlig på åttitallet, ville vi sannsynligvis ha vurdert å programmere denne appen selv. Det var ikke noe nett å laste ned ting fra, og butikker som forhandlet programvare for vår utvalgte plattform var få og ofte langt unna.

Manualen er svær, og er full av mer eller mindre artige illustrasjoner.
Manualen er svær, og er full av mer eller mindre artige illustrasjoner.

Men med mindre det vi trengte å gjøre var veldig komplisert, hadde vi en løsning. Vi kunne lage et program for å løse oppgaven selv.

Tykk manual

Derfor ble Sharp MZ-80A og andre datamaskiner fra samme tid levert med et programmeringspråk – som oftest en variant av BASIC – og en svær instruksjonsmanual. Den startet vanligvis med å forklare enkle ting som hvordan man skrur maskinen på og laster inn programvare, men gikk nesten umiddelbart over i å lære brukeren om programmering. I Sharp-manualens tilfelle, heter det første kapittelet i brukerhåndboken følgende: Your MZ-80A and BASIC Programming. Etter en kjapp introduksjon om maskinen, lærer man på side 5 å laste inn et program – og det programmet er, ikke overraskende, maskinens medfølgende BASIC-versjon. Så får vi noen sider hvor tastaturet beskrives, før manualen går i gang med BASIC-kurset for alvor på side 9.

En nyttig kompanjong til manualen.
En nyttig kompanjong til manualen.

Derfra lærer brukeren de aller fleste BASIC-kommandoene, med rikelig av eksempler man kan skrive inn for å se hvordan ting fungerer. Noen av eksemplene er kun ment som øvelse, men man får også det som må ha blitt oppfattet som matnyttige tips – hvordan man skal konstruere et program for å regne ut renters rente, hvordan man kan lagre og presentere skolekarakterer, hvordan man kan få datamaskinen til å lage multiplikasjonstabeller, og så videre. Manualen inneholder til og med koden til et par enkle spill.

Dette betyr at nesten all den informasjonen jeg trenger for å programmere et spill finnes i maskinens brukerhåndbok. Den øvrige informasjonen jeg bruker, finner jeg i ei lita bok som Ben Coffer var grei nok til å sende med maskinen min – Peeking and Pokeing the MZ-80A, av G. P. Ridley. Programmeringskurset i Sharp-manualen forteller deg nemlig ikke særlig mye om POKE-kommandoen, som er sentral i spillet mitt. Det er kanskje fordi dette er en rimelig avansert kommando, og man kan faktisk få BASIC til å krasje (eller oppføre seg på merkelige måter) ved å POKE inn tall i minneaddresser der de ikke bør være.

Ah, og boka har forresten en mye bedre oversikt over både skjermkoder og ASCII-koder for maskinens tegnsett enn det manualen har.

Slik så koden min ut da jeg sluttet å programmere dagen i forveien.
Slik så koden min ut da jeg sluttet å programmere dagen i forveien.

En plan er født

Da jeg lastet inn igjen UFO-programmet fra kvelden i forveien, hadde jeg en viss plan om hvordan spillet mitt skulle arte seg. Jeg hadde aldri noen illusjon om at dette spillet kom til å bli noe mer enn noe jeg neppe ville hatt veldig mye til overs for om det dukket opp i samlepakken 75 Spill fra Data-Tronic (som jeg brukte forskrekkelig mye tid på å pløye meg gjennom for et halvt års tid siden). Men til min fordel, skal det også sies at mulighetene man har til å lage imponerende spill på Sharp MZ-80A er langt mindre enn de er på Commodore 64, så sånn sett tenkte jeg at jeg skulle få til å lage noe som ikke ville vært sett på som helt elendig om jeg hadde laget det i 1982.

Planen var som følger: Spilleren skulle få kontroll over en stillestående kanon i bunnen av skjermen, og målet skulle være å skyte ned UFO-en, som gradvis arbeidet seg nedover linje for linje, før den nådde bakken. For å gjøre det litt mer interessant, ville jeg at UFO-en skulle hoppe noen hakk nedover på skjermen hver gang spilleren bommet. I ettertid tror jeg at jeg godt kunne vært litt mer ambisiøs, men på dette tidspunktet ville jeg bare få til noe som funket, og som kunne kalles et spill. Større ting kunne vente.

Du ser at dette er en kanon? Bra!
Du ser at dette er en kanon? Bra!

Kodingen fortsetter

Vel plassert foran datamaskinen, begynner jeg med å tegne en kanon i bunnen av skjermen. Dette gjør jeg ved hjelp av PRINT-kommandoen, og tastaturets «grafikkmodus». Jeg skrur på grafikkmodus på ved å trykke en knapp merket GRPH (som jeg var innom i forrige del). Alle tastene på maskinens tastatur kan, i tillegg til å lage store og små bokstaver, også lage to figurer. Jeg forklarte hvordan dette fungerer i en artikkel jeg skrev i forbindelse med Data-Tronic-prosjektet mitt, så jeg lenker like godt bare til den – det er riktignok Commodore 64 det er snakk om der, men det fungerer ikke helt ulikt selv om Commodore 64-brukere har et annet tegnsett å velge mellom.

Rent praktisk, «tegnet» jeg inn kanonen på en blank skjerm, før jeg i ettertid satte inn PRINT-kommandoer på de aktuelle linjene. Det er litt lettere å gjøre det slik, enn å prøve å lage «ASCII-kunst» i selve programlistingen. I alle fall når man ikke helt har planlagt hvordan det man tegner skal se ut på forhånd, og bare må eksperimentere.

Apropos «se ut» – den endelige kanonen ser egentlig mest ut som en merkelig radarantenne, men … jeg ble fornøyd likevel. Det kan jo være en slags strålekanon. Ved hjelp av CURSOR-kommandoen, som kan brukes sammen med koordinater (0-39 horisontalt og 0-23 vertikalt) til å plassere tekst på spesifikke posisjoner på skjermen, setter jeg kanonen opp omtrent midt på skjermen, i den nederste halvdelen.

Ja, de har skrevet GOSUB feil i manualen...GOSUB eller GOTO?

GOSUB betyr nesten det samme som GOTO, men det er en kjempeviktig forskjell. GOSUB er nemlig ment for subrutiner, altså deler av programmet som utfører en eller annen generell oppgave som kan hentes frem flere ganger i koden. Den fungerer i samarbeid med kommandoen RETURN, som sender programmet tilbake til kommandoen (ikke nødvendigvis linjen) etter GOSUB-kommandoen. Et kjapt eksempel:

10 GOSUB 40
20 PRINT «DER»
30 END
40 PRINT «HEI»
50 RETURN

Her vil resultatet når jeg kjører programmet være følgende:

HEI
DER

Det praktiske resultatet er med andre ord det samme som hvis jeg hadde skrevet GOTO 40 i linje 10, og GOTO 20 i linje 50. Men forskjellen er at hvis jeg utvider programmet mitt, så kan jeg hente frem HEI fra andre deler av koden, og takket være RETURN vil jeg da automatisk returnere dit i stedet for å returnere til linje 20, som jeg ville gjort med en enkel GOTO-kommando. Hendig! Men GOSUB og RETURN krever at man holder tunga rett i munnen, spesielt i avansert kode der man kanskje har flere nivåer av GOSUB og RETURN.

Forresten: Jeg kunne brukt CURSOR-kommandoen i kombinasjon med PRINT for å oppnå akkurat det samme som jeg gjør med POKE for å flytte UFO-en, men det hadde gått langsommere. Skal man lage spill på en plattform med en Z80-prosessor på 2Mhz, og i tillegg bruker programmeringsspråket BASIC, så må man bruke de raskeste metodene man kan. Det er derfor POKE egner seg så bra til spillprogrammering.

Jeg må gjøre det interaktivt

Nå ser i alle fall programmet mitt ut som et spill, men for at det faktisk skal bli et spill, må jeg nødvendigvis involvere brukeren. Siden dette er et typisk «one button game», er heldigvis ikke dette spesielt vanskelig. Jeg bruker kommandoen GET, for å lese inn tastetrykk fra tastaturet. Der kommandoer som INPUT venter på at brukeren skal skrive inn noe, og trykke Return eller Enter-knappen (på Sharp heter den CR, som står for «carriage return»), glir GET inn i bakgrunnen. Den registrerer eventuelle tastetrykk, men hindrer ikke programmet i å fortsette om brukeren ikke trykker på noe. Det er naturligvis litt greit om man prøver å lage et arkadespill.

Kommandoen GET K$ sier at et eventuelt tastetrykk skal registreres i variabelen K$. Så kan jeg bruke kommandoene IF og THEN for å gi datamaskinen en oppgave hvis variabelen K$ har riktig innhold. Altså: IF K$=«S» THEN GOTO 400 betyr at hvis GET K$ har registrert at brukeren trykket S-knappen skal programmet hoppe til linje 400 – der jeg har lagt inn hva som skal skje når kanonen skyter. Og, som sagt, hvis ikke S har blitt trykket, skal programmet fortsette som før. I dette tilfellet betyr det at UFO-en fortsetter sin flytur mot bakken. Man kan forøvrig også bruke GET for å si at noe spesifikt skal skje hvis ingen knapper registreres.

Det som faktisk skjer når brukeren trykker S, må også spesifiseres. I dette tilfellet, genererer jeg ved hjelp av POKE-kommandoen en ball, like over der jeg har tegnet kanonen min inn på skjermen. Jeg fortalte hvordan skjermminnet fungerer i forrige del, men skjermbildet er altså delt inn i 1000 «ruter», en for hver tegnposisjon, og hver slik rute har en minneadresse. De går fra 53248 i øverste, venstre hjørne til 54247 i nederste, høyre hjørne. Ved å POKE inn en verdi i disse minneadressene, vises tegnet som denne verdien representerer. Mens UFO-en har verdien 199, har ballen verdien 71.

Etter å ha POKEt ballen inn på skjermen, flytter jeg den i en rett linje oppover, på akkurat samme måte som jeg flyttet UFO-en bortover. UFO-en bruker altså en instruksjon av typen X=X+1 (der X representerer minneadressen), mens «kula» bruker en instruksjon av typen X=X-40. Siden det er 40 tegn per linje, betyr det at for hver gang kulen flytter seg, så flytter den seg nøyaktig ett hakk oppover på skjermen. I mellomtiden, står UFO-en rett og slett bare og venter på å bli truffet – dette er, som sagt, et enkelt spill.

Vi introduserer PEEK

Men vent, sier du kanskje – hvordan kan datamaskinen skjønne at kulen treffer UFO-en? Jo, det skal jeg forklare (enten du spør eller ikke – det er nemlig viktig). Jeg bruker POKE-kommandoens stallvenn, PEEK. Der POKE skriver til minneadresser, leser PEEK fra dem. Minneadressen som representerer den posisjonen UFO-en står på, vil inneholde tallet 199. Ingen andre minneposisjoner vil inneholde det tallet – faktisk vil adressene til alle unntatt én skjermposisjon i den banen kula beveger seg inneholde tallet 0, siden de er tomme. Unntaket er den posisjonen som inneholder akkurat den bittelille biten av bakgrunnslandskapet jeg tegnet ved hjelp av SET, som befinner seg rett over kanonen.

Spillets hovedperson flytter seg over skjermen.
Spillets hovedperson flytter seg over skjermen.

Så – når jeg tegner kulas gang oppover skjermen, kjører jeg rett og slett en liten sjekk for hvert hakk den går opp. Igjen, ved hjelp av IF og THEN. Det jeg sier er «Hvis den neste skjermminneposisjonen for kula inneholder verdien 199, så skal det registreres som et treff». Og det som da skjer er … vel, det er at programmet avsluttes. Yay!

Den første versjonen av spillet mitt er altså temmelig enkel. I tillegg til å tegne opp et tilfeldig landskap, og flytte UFO-en bortover skjermen, så har det en «kanon» i bunnen av skjermen, som skyter en kule når brukeren trykker S. S står for «skyt», selvsagt. Om ballen bommer, resulterer det at jeg flytter UFO-en noen hakk ned på skjermen – riktignok uten å slette den forrige versjonen av UFO-en, som blir stående igjen som et stillestående spøkelse på himmelen, men det er en enkel ting å fikse. Så fortsetter UFO-en sin ubønnhørlige gang mot bunnen av skjermen. Ballen og UFO-en har forøvrig akkurat samme hastighet, siden jeg nok en gang bruker MUSIC-kommandoen for å lage et lite pip for hver posisjon «kula» flyttes – og fortsatt ikke har oppdaget TEMPO-kommandoen.

Om resultatet av at kula treffer UFO-en er antiklimatisk, står det enda verre til med resultatet av tap. Jeg har faktisk ikke programmert inn noen måte å tape på i det hele tatt, så hvis man bommer mange nok ganger vil UFO-en etter hvert bevege seg over (og i prosessen overskrive) både landskapet og kanonen. Til slutt havner UFO-en under den posisjonen kulene skytes fra, og blir dermed udødelig. Så vil den fortsette nedover neste skjerm (Sharp MZ-80A har 2 kb med skjermminne, og kan dermed lagre to «skjermer» i minnet samtidig – den siste adressen i skjermminnet er faktisk 55247), helt til brukeren avslutter programmet eller det eventuelt oppstår en feil fordi tallet som POKE-kommandoen vil referere til slipper opp for skjermminneaddresser.

Så … dette er altså et ganske tragisk spill akkurat nå, men det er en begynnelse! Utviklingen fortsetter i del 3.

Relaterte innlegg

Legg inn en kommentar

Dette nettstedet bruker Akismet for å redusere spam. Lær om hvordan dine kommentar-data prosesseres.