Et spill blir skapt (del 1)

Jeg lager spill for en datamaskin fra 1982 – og forklarer alt jeg gjør underveis.

I sommer kjøpte jeg en Sharp MZ-80A, og i tiden som har gått etter innkjøpet, har jeg brukt litt i overkant mye tid på å leke meg med den gamle datamaskinen. Det betyr for eksempel at min teknofobe far vet mer om Pokemon Go enn det jeg gjør, og at jeg ikke har hatt anledning til å klage over været. Annet enn når det tordner, og jeg må trekke ut strømkontakten. Men da har jeg en brukerhåndbok å lese.

Min Sharp MZ-80A.
Min Sharp MZ-80A.

En av tingene jeg har gjort med mitt nyinnkjøpte vidunder, er merkelig nok å programmere. Og det har vist seg å være skikkelig moro. Resultatene er så som så, men det er, som en venn av meg sa, ikke nytelsen av det ferdige bildet som er belønningen når man legger puslespill.

I denne artikkelserien vil jeg fortelle om det prosjektet som startet det hele – et enkelt arkadespill der spilleren skal skyte ned UFO-er før de får landet. Jeg innser at dette er veldig enkle greier for de som faktisk kan programmering, men forhåpentligvis klarer jeg å gi et lite inntrykk av hvorfor det faktisk er moro å holde på med dette.

Det begynte med et mysterium

Da jeg opprinnelig hadde min Sharp MZ-80A på åttitallet, hadde jeg bare fire spill. En eldre venn, som eide Commodore 64 og etter hvert skulle bli en ganske habil koder i den norske demoscenen, syntes åpenbart den lille Sharpen min var ganske fascinerende, så han kom opp til meg, og programmerte to nye spill som jeg kunne leke meg med. Disse spillene, som selvsagt er veldig enkle saker, virker utrolig nok fortsatt. Du kan se dem her:

Det var da jeg skulle vise det ene av disse spillene til en kamerat av meg at mysteriet oppsto. Spillet er en slags Breakout-klone programmert i plattformens offisielle BASIC-variant (SA-5510), og vi snakket om at det nok var mye man kunne gjøre for å forbedre det. Bare for å gjøre noe, bestemte vi oss for å bytte ut ballen med noe annet. En UFO, for eksempel.

Tastaturet lar deg lage mange spesialtegn – men ikke noen UFO.
Tastaturet lar deg lage mange spesialtegn, som du kan se på forsiden av tastene – men ikke noen UFO.

En litt kul greie med mange mikrodatamaskiner fra sytti- og åttitallet, inkludert Sharp MZ-80A, er at tegnsettet inneholder en bråte spesialtegn, som man kan bruke til å lage «grafikk» med. På denne plattformen er altså et av tegnene en knøttliten UFO. De fleste spesialtegnene kan man lage ved å trykke «GRPH»-knappen, som sender maskinen over i en slags grafikkmodus. Da har hver tast to tegn som den kan produsere (med og uten skift), som du kan se på bildet til høyre.

Men UFO-en og en håndfull andre kule tegn kan ikke lages på denne måten. Jeg husker at dette var et stort mysterium for meg på åttitallet, og at jeg veldig, veldig gjerne ville finne en måte å tegne UFO-en på.

Nå som jeg har litt erfaring med BASIC-programmering på Commodore 64, og i tillegg kan engelsk, tenkte jeg at det måtte være en smal sak å få til. Jeg fant frem oversikten over tegnsettet i manualen, og etter litt knoting frem og tilbake skjønte jeg at ASCII-koden for å lage UFO-en er 01100000 – som oversatt til vårt tallsystem er 96.

Ballen var lagt inn i koden som en enkel variabel som ble skrevet på skjermen ved hjelp av PRINT-kommandoen. Du kan tenke deg en variabel som en navngitt boks med innhold i – innhold som jeg bestemmer. Variabler kan inneholde tall (og dermed regnes med), eller strenger (altså tekst og andre tegn). A=3 betyr at variabelen A representerer tallet 3. A$=«Spillhistorie er best» betyr at variabelen A$ representerer teksten «Spillhistorie er best». Skriver jeg PRINT A$, svarer datamaskinen altså med å skrive «Spillhistorie er best».

Dermed skulle det være enkelt å endre ballen til en UFO – alt jeg trengte å gjøre burde være å endre denne variabelen til UFO-ens ASCII-kode. Altså, endre B$=«o» til B$=CHR$(96). Denne kommandoen sier at ASCII-tegnet som representeres av tallet i parentes skal puttes inn i variabelen B$. Så skal det bare være å skrive PRINT B$, for å «skrive» dette tegnet på skjermen.

Oversikt over maskinens tegnsett.
Oversikt over maskinens tegnsett. Sharp tenkte åpenbart at alle skjønte hva for eksempel 11001110 betyr.

Men det funket ikke. Da jeg endret koden, endte jeg bare opp med en helt usynlig ball i spillet (det samme gjorde jeg da jeg prøvde CHR$(096)). Dermed fulgte en lang periode der jeg prøvde meg på alt mulig rart, for å se om det var noe jeg hadde misforstått. Men ingen ting funket. Til slutt begynte det å ane meg at kameraten min hadde mistet interessen for lenge siden, så jeg ga opp.

Etter at han hadde gått for dagen, vendte jeg imidlertid tilbake til mysteriet. Jeg tok kontakt med engelske Ben Coffer, som driver nettstedet MZ-80A Secrets, og som jeg kan takke for at jeg nå har en Sharp å leke med i utgangspunktet. Jeg forklarte hva jeg hadde prøvd, og lurte på hva jeg hadde gjort galt. Ingenting, sa han. Han fant frem manualen for å sjekke om jeg hadde feil ASCII-kode, men alt så rett ut. Så prøvde han selv – og til sin store overraskelse, oppdaget han at han fikk samme resultat som meg. UFO-en ville ikke vises. Hvorfor ante han ikke – kanskje det rett og slett var en «bug» i BASIC-systemet?

POKE-kommandoen kunne hjelpe

Men han visste råd. I stedet for å bruke PRINT og variabler, burde jeg bare bruke kommandoen POKE for å sette inn skjermkoden for en UFO i minneaddressen for den ruten på skjermen som jeg ville ha UFO-en i. Det ville dessuten være mye, mye raskere. Aller helst burde jeg selvsagt skrive ting i maskinkode, mente han (og han er dermed den andre personen til å fortelle meg dette i løpet av kort tid), men om jeg skulle bruke BASIC, så var det POKE-kommandoen som var den beste løsningen uansett.

I Matte på Commodore 64, brukte jeg POKE for å lage munnvikene og forandre farger på teksten RIKTIG.
I Matte på Commodore 64, brukte jeg POKE for å lage munnvikene og forandre farger på teksten RIKTIG.

Altså: Minnet til Sharp MZ-80A er delt inn i addresser, der hver representerer én byte. Dette inkluderer maskinens 2 kilobytes med skjermminne. Addressene fra og med 53248 til og med 54247 er dedikert til de 1000 forskjellige tekstposisjonene på skjermen (skjermminnet går faktisk helt til 55247, men det kan vi ta senere). Ved å skrive POKE 53248,199, putter jeg verdien 199 inn i minneaddressen for tekstposisjonen i det øverste, venstre hjørnet. 199 er skjermkoden for UFO-en, og datamaskinen vil umiddelbart respondere med å tegne en UFO på skjermen.

Merk at skjermkoden for UFO-en og ASCII-koden for UFO-en altså er to forskjellige tall. Fra nå av ignorerer vi ASCII-koden.

Det fine med POKE-kommandoen, er at jeg kan bytte tallene ut med en variabel. Dette betyr at jeg kan skrive POKE A,B. Om variabelen A er satt til 53248 og variabelen B er satt til 199, får jeg samme resultat som over. Dermed kan én enkelt POKE-kommando brukes til å gjøre alt mulig rart – helt avhengig av hvilke tall som til en hver tid er puttet inn i variablene.

Og siden dette er tallvariabler, kan jeg regne med dem. Hvis A allerede er 53248 og jeg sier A=A+1, så bestemmer jeg at A-verdien nå skal være den gamle verdien pluss 1. Altså 53249. Det betyr at neste gang jeg bruker POKE-kommandoen, vil UFO-en min tegnes ett hakk til høyre for den opprinnelige posisjonen. Jeg kan også bruke minus for å flytte den andre veien. Og siden det er 40 tegn på hver linje, kan jeg bruke A=A+40 for å flytte UFO-en ett hakk ned, og eventuelt A=A-40 for å flytte den ett hakk opp igjen.

gotoLinjenummer og GOTO

Gamle BASIC-versjoner bruker linjenummer for at datamaskinen skal forstå hvilken rekkefølge koden man har skrevet inn skal utføres. Den begynner på det laveste linjenummeret, og arbeider seg oppover linje for linje.

Det er vanlig å lage litt «plass» mellom hvert linjenummer, slik at det blir lettere å legge til ting senere, ved behov. Så i stedet for å bruke 1,2,3,4 og så videre, bruker man gjerne 10,20,30,40 og så videre.

Hvis datamaskinen var låst til å følge linjenummerne slavisk, ville programmene raskt bli veldig begrenset. Heldigvis finnes det flere måter å få den til å få den til å bryte ut av den ordinære flyten på, og den enkleste er kommandoen GOTO. Hvis jeg i linje nummer 20 kriver GOTO 80, så hopper datamaskinen rett til linje 80 og ignorerer alt som ligger mellom, frem til den eventuelt sendes dit senere.

I mitt program bruker jeg GOTO-kommandoen til å få samme ting til å gjentas om og om igjen. Hver gang programmet har tegnet en UFO, og er ferdig med jobben, sender GOTO-kommandoen datamaskinen tilbake for å tegne en ny UFO.

Enkel animasjon

Det vil si: Ikke «flytte». Den originale UFO-en vil fortsatt stå tegnet opp der jeg satte den første gang jeg brukte POKE. Men her kommer det smarte: Ved å sette inn skjermkoden for et mellomrom (som er 0) på den originale posisjonen, kan jeg viske UFO-en bort igjen. Dermed kan jeg, om dette foregår raskt nok, skape illusjonen av bevegelse – animasjon.

Dette er ting jeg nå beskriver med en viss grad av selvfølgelighet, som om det er noe jeg har visst i mange år. Men slik er det egentlig ikke. Da jeg lagde mitt eminente (les: elendige) mattespill på Commodore 64 tidligere i år, var jeg så vidt innom det å POKE ting inn på skjermen. Hvis du ser videoen, ser du at ansiktet kan smile eller være surt – jeg endrer «munnvikene» ved hjelp av POKE. Jeg bruker også POKE til å få ulike deler av teksten til å endre farge. Men dette er også det mest avanserte jeg noengang har gjort ved hjelp av å POKE verdier inn i minnet til en datamaskin.

Så da jeg satte meg ned med min Sharp, etter å ha snakket med Ben Coffer, var målet fortsatt bare å få den fordømte UFO-en til å dukke opp på skjermen. Og takket være min nye kunnskap, klarte jeg dette. Siden det fortsatt var mye kveld igjen, og jeg akkurat hadde skrudd maskinen på, fant jeg ut at jeg ville leke meg litt. Ved hjelp av den A=A+1-greia jeg nevnte tidligere og kommandoen GOTO, lagde jeg et lite program der skjermen gradvis ble fylt med UFO-er. Heftig!

Så gikk jeg ett hakk videre, ved å først POKE inn UFO-en, så POKE inn mellomromstegnet, før jeg gikk videre til neste skjermposisjon og gjorde det samme der. Men siden POKE fungerer veldig raskt, resulterte ikke dette i noen skikkelig, synlig animasjon. UFO-en blinket ut og inn så kjapt at den nesten var umulig å se. Siden jeg ikke hadde så veldig lyst til å bruke tid på å lære meg hvordan jeg skulle forsinke bevegelsene, gjorde jeg det enkleste jeg kunne tenke meg: Jeg brukte maskinens MUSIC-kommando til å spille en note mellom hver flytting av UFO-en.

MUSIC-kommandoen i BASIC-versjonen for Sharp MZ-80A er et eksempel på hvor rett-frem denne maskinen er å bruke. Den fungerer rett og slett på samme måte som PRINT-kommandoen skriver tegn på skjermen, bare at resultatet er lyd. Så koden MUSIC «A» gir et enkelt lite pip som skal representere noten A (hvor lenge det piper bestemmer du med TEMPO-kommandoen, men det visste ikke jeg på dette tidspunktet).

Denne koden får UFO-en til å flytte seg over skjermen.
Denne koden får UFO-en til å flytte seg over skjermen.

Som resultat hadde jeg nå en UFO som flyttet seg gradvis over skjermen, mens datamaskinen lagde kule pipelyder. Hver gang den forlot høyre ende av skjermen, kom den inn til venstre i linjen under. Wow!

Hva med et lite landskap?

På dette tidspunktet lente jeg meg godt tilbake, og var egentlig temmelig fornøyd med det jeg hadde gjort ved hjelp av noen linjer med kode. Men så kom jeg på en annen kommando jeg liker med BASIC for Sharp MZ-80A, nemlig SET.

Sharp MZ-80A har i utgangspunktet ikke ekte «grafikk», på den måten at du kan styre hver enkelt piksel på skjermen slik du kan med for eksempel en Commodore 64. Men SET-kommandoen gir den en form for pseudo-grafikk. Den deler hver tegnposisjon i fire mindre ruter, og lar deg skru hver av disse rutene «på» individuelt. RESET-kommandoen lar deg skru dem «av» igjen. Med andre ord kan vi «late» som dette er veldig store piksler på skjermen, som kan tegnes eller viskes bort ved hjelp av SET og RESET. Hvis jeg skriver SET A,B , så representerer A-tallet en horisontal posisjon (som kan være fra 0 til 79) og B-tallet en vertikal posisjon (fra 0 til 49). SET 0,0 setter altså inn en bitteliten firkant øverst på venstre side av skjermen. Det er fire ganger så mange slike pseudografikk-ruter som det er tegnposisjoner.

Her er alle tegnene som kan POKEs frem. I F-kolonnen ser du spesialtegnene som pseudografikksystemet benytter seg av.
Her er alle tegnene som kan POKEs frem. I F-kolonnen ser du spesialtegnene som pseudografikksystemet benytter seg av.

I virkeligheten styrer SET-kommandoen unike tegn som skrives på skjermen etter behov, og det er ett slikt tegn for hver enkelt mulige kombinasjon av et kvadrat delt inn i fire ruter som kan være lyse eller mørke – som du ser av bildet på siden. Det er rett og slett bare en smart og veldig enkel snarvei til noe man kan gjøre ved hjelp av POKE. Du kan se disse tegnene i bildet til høyre.

Det jeg nå innså at jeg kunne gjøre ved hjelp av SET, og noen ganske enkle regnestykker, var å lage et lite landskap som UFO-en min kunne fly over.

Jeg begynner med å tegne opp en prikk på den andre kolonnen i rad nummer 35 på skjermen. Så legger jeg inn en RND-kommando, som lar datamaskinen trekke ut et tilfeldig tall fra 1 til og med 3. Hvis resultatet av dette lotteriet blir 1, bestemmer jeg at variabelen som bestemmer den vertikale posisjonen til neste prikk, skal reduseres med én. Hvis resultatet blir 2, vil jeg at den skal forbli den samme. Hvis resultatet blir 3, øker jeg den med én. Så øker jeg variabelen som representerer den horisontale posisjonen med én, tegner neste prikk, og gjentar prosessen.

Det som da skjer, er at hver gang SET-kommandoen brukes, så skal den sette inn en prikk ett hakk til høyre for den forrige prikken – men ikke nødvendigvis i samme høyde. Den kan enten være ett hakk opp, ett hakk ned, eller ett hakk rett til høyre for den forrige prikken, avhengig av hva RND-kommandoen trakk ut. Ved å gjenta dette helt til jeg når enden av skjermen, vil jeg dermed lage et tilfeldig «bakgrunnslandskap». Egentlig bare en strek som går litt opp og ned – men det funker. Bare se her:

Hele listingen akkurat nå. Ja, jeg burde brukt en FOR-NEXT-sløyfe for å tegne landskapet.
Hele listingen akkurat nå. Ja, jeg burde brukt en FOR-NEXT-sløyfe for å tegne landskapet.

Da jeg var ferdig med dette programmet, var jeg egentlig temmelig fornøyd. Det var dessuten blitt sent, så etter å ha sett UFO-en fly over landskapet mitt flere ganger enn jeg tør innrømme, lagret jeg programmet på en kassett, skrudde av datamaskinen og gikk og la meg. Og som vanlig, når man går og legger seg etter å ha holdt på med noe kreativt, begynte jeg å få ideer. Det jeg hadde laget, så jo faktisk litt ut som et spill. Så hvorfor ikke faktisk gjøre det til et spill?

Dermed var neste dags aktivitet bestemt. Og det får du lese mer om i neste artikkel.

Mens du venter på del 2, kan du jo lese om hvordan jeg skrev inn spillkoden til Sharp-spillet Cavern 2160 fra 1980, som vi nå har gjort tilgjengelig for nedlasting med originalskaper Takaya Aritas velsignelse.

2 kommentarer om “Et spill blir skapt (del 1)”

  1. Kan jo legge til en beskrivelse av den nederste programlistingen, linje for linje (følte det ble litt overkill i artikkelen). I tilfelle noen er interessert. :)

    Linje 5 – Her bruker jeg PRINT-kommandoen for å skrive et spesialtegn som rett og slett bare blanker skjermen.
    Linje 6 – Her bruker jeg GOTO for å hoppe over alle linjer før 50. I linje 50 begynner nemlig tegningen av landskapet, som var det siste jeg laget men det første som skal kjøres i programmet.
    Linje 10 – Her velger jeg at variabelen A skal ha verdien 53248, som altså refererer til skjermminneaddressen øverst i venstre hjørne. Variabelen A bestemmer UFO-ens posisjon.
    Linje 20 – Her bruker jeg POKE for å skrive UFO-tegnet inn i A-posisjonen.
    Linje 24 – Her bruker jeg MUSIC-kommandoen til å lage et lite pip, og samtidig også en pause i tegningen.
    Linje 25 – Her styrker jeg UFO-en fra A-posisjonen, ved å tegne et mellomrom der i stedet.
    Linje 30 – Her økes A-posisjonen med én. Første gang dette kjøres, får den da verdien 53249. Neste gang blir det 53250. Og så videre.
    Linje 40 – Her sendes programmet tilbake til linje 20, der UFO-en nå skrives i den nye A-posisjonen, og så videre. Det er ikke lagt inn noen avslutning i programmet enda, så det vil fortsette å gå fra 20 til 40 helt til det kommer en feilmelding (fordi POKE-kommandoen refererer til en minneaddresse den ikke kan referere til).
    Linje 50 – Denne linjen kjøres altså før linjene 10-20, og markerer starten på tegningen av landskapet. Her legger jeg inn tallet 1 i variabelen B, og 35 i variabelen C.
    Linje 60 – Her tegnes en prikk basert på variablene i B og C. B representerer altså prikkens horisontale posisjon, mens C representerer den vertikale.
    Linje 70 – Dette er en litt vanskelig kodebit, som rett og slett bare sier at variabelen D skal ha et tilfeldig generert tall som kan være 1, 2 eller 3.
    Linje 80 – Hvis det tilfeldige tallet er 1, trekker jeg fra 1 fra variabelen C. Det betyr at neste gang linje 60 kjøres, vil prikken tegnes ett hakk høyere.
    Linje 90 – Her gjør jeg bare det motsatte av det jeg gjorde i linje 80. Merk at det ikke er noen kode for den tredje muligheten; om den trekkes, forblir C-variabelen det samme som den var.
    Linje 100 – Her øker jeg variabelen B med én. Det betyr at neste gang linje 60 kjøres, vil prikken tegnes ett hakk til høyre. Nå har altså begge variablene som tegner prikkens nye posisjon blitt bestemt.
    Linje 110 – Her sier jeg at hvis variabelen B har nådd 80, så skal programmet hoppe ut av landskapstegningen, og gå til linje 10 for å starte UFO-moroa. Hvis variabelen B ikke er 80 enda, så går programmet videre til linje 120.
    Linje 120 – Her hopper programmet til linje 60 for å tegne neste prikk.

    Svar

Legg inn en kommentar

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