Commodore blog

A Commodore Plus/4 Magyarország Facebook-csoportjában már futólag említettem, hogy elkezdtem dolgozni a The Pit Plus/4-es portján.

Felvezetésként idézem is az ott írottakat:

Hogy mihez kellenek a hangok, amit a korábbi posztban említettem? Nagy kedvencemhez, amely az első találkozásom volt a videojátékkal, már ha nem számítjuk annak a Videoton Tévéteniszt.

A nyolcvanas évek közepén a katonai lakótelep tisztiklubjában (!!!) valamiért volt egy pénzbedobós arcade-gép, amin a The Pit futott. Számtalan Kossuth-ötforintost dobáltunk bele, amíg rá nem jöttünk, hogy a "terem" kulcsa, amit külön el kellett kérni, ha játszani akartunk, egyben nyitja a gép kasszáját is.

Persze jórészt addig volt érdekes, amíg nem volt otthon saját számítógép, hiszen a folyton ismétlődő, bár az idő elteltével gyorsuló játékmenet messze nem volt olyan izgalmas, mint a magnó előtt várni az épp aktuális játék betöltését.

Bennem mindenesetre mély nyomot hagyott, és akkor jutott eszembe újra, amikor nemrég felfedeztem, hogy Doug Turner, az Icicle Works (és más bányászós remekművek) alkotója úgy öt éve kijött az új rilizzel: a The Pit-tel.


Ami amúgy egy egészen más játék, egészen más technikai tudással és színvonalon, mégis megvan az egyenes párhuzam. Doug inspirációja, a saját Icicle Works-je egy szabadon értelmezett Boulder Dash-klón, karácsonyi-téli témában. A Boulder Dash ihletője pedig az alkotó, Peter Liepa elmondása alapján egy, a jelenkor számára fent nem maradt BASIC-program volt Chris Gray-től. Chris programja sem volt azonban eredeti ötlet - egy játékteremben fogant meg egy arcade gépen játszva. Ez a gép a The Pit volt.

Megvan az analógia? Metszetben látjuk a földalatti kalandot, mint egy hangyafarmban, kövek esnek a fejünkre, satöbbi, satöbbi...

Micsoda szép kör, nem?

Így aztán a The Pit ösztönzött arra, hogy elkészítsem a The Pit Plus/4-es portját. Nem egy igazán embert és technikát próbáló feladat, de amennyi időm nekem van ilyesmire, illetve amennyire vissza kell rázódnom a kódolásba, számomra tökéletes feladat.

Csatoltam is három videót, ha valaki nem ismerné a játékot. Az első az eredeti Arcade-verzió, a második a C64-es port, a harmadik pedig a leendő Plus/4 port. Utóbbiról csak ízelítő, mert a folytatása még nagyon suta.

 

 

 

 

Azt viszont jól szemlélteti, hogy digihangokat beletenni erős túlzás lenne (hacsak nem négyszögjelet digizünk, de azt meg minek, ugye), és hangeffektből sem kell túl sok, ezért töröm a fejem még ennek a megoldásán (a rövid részletben TEDZakker szól).

Még egy, ami miatt jó hatásfokú megoldást keresek: szeretnék 16K-ban megállni. Az eredeti C64-verzió kártyás volt, a tört verzió is a kártya helyén fut, 16K-ban bőven elfér, rengeteg üres hellyel.

Ja, és mi lesz a címe? Nyilván The Pit, hiszen ez az eredeti is, de majd valamit kitalálok a megkülönböztetésre. Mondjuk The Pit Arcade, vagy hasonló.


Bár viszonylag kevés erre fordítható időm van (mint ahogyan általában lenni szokott felnőtt emberként), de azért szépen halad a projekt, belátható időn belül valószínűleg elkészül majd.

Ami miatt blogposzt lett belőle, az az, hogy viszonylagos egyszerűsége miatt elég sok speciális, megoldandó probléma került elő, még ha ennek egy részét én magam is okoztam.

Először is, az eredeti C64-es program (legalábbis az, ami hozzám került) cartridge-ból lett törve, és 16K-ban elfér úgy, hogy még maradt is hely bőven. Ezért az egyik cél az lett, hogy a Plus/4-es verzió is elférjen 16K-ban, sőt, fusson C16-on (a kettő nem ugyanaz!).

Másodszor: az eredetiben egy rakás sprite (a háttértől függetlenül, hardverból futó elem, mint a játékos, az ellenségek, stb...) van, ezeket Plus/4-en csak szoftveresen lehet megoldani, ami igen erőforrás-igényes. A játék jellegzetessége viszont, hogy ezek többsége kicsi (1x1 karakter), és viszonylag ritkán találkoznak a háttérrel vagy egymással, így a sprite maszkolása (a sprite és a háttér egymásra "fésülése") szinte teljesen elhagyható.

Ezért már a legelején kialakult, hogy a sprite-ok a következőképpen fognak működni:

1. Játékos: 1x1 karakteres figura, ami elméletileg egy 2x2 karakteres sprite-maszkon jelenhetne meg. Hogyan egyszerűsíthetnénk ezt le?
- marad a pixeles mozgás, de megállni csak karakterhatáron belül tud. Ez egy kicsit könnyít a játékon, az eredetiben ugyanis kifejezetten szívatós volt eltalálni a pixelhatárokat.
- A játékos, ha homokban halad, mindig kiás maga előtt egy karakternyi területet - emiatt viszont mindig üres, már kiásott helyen mozog. Ezért a háttérrel nem találkozik, nem kell maszkolni.
- Ha kincset vesz fel, megint nincs szükség maszkolásra - hiszen felveszi azt, ezzel itt is csak üres hely marad.
- Ha ellenséggel ütközik, nem kell maszkolni, hiszen HALÁL! HALÁL! HALÁL!, aminek az eredeti játékban is van egy fura, összegabalyodós animációja. Ez fix fázisokból előállítható, ráadásul karakterhatáron belül.
- A lövedékkel nem kell maszkolnia, hiszem azt maga elé lövi, és az távolodik tőle.
- Ha a szikla rázuhan, a helyzet ugyanaz, mint az ellenséggel, nincs extra tennivaló.
- Játékosunk ráadásul nem mozog átlósan, csak függőleges VAGY vízszintes irányban halad, egyszerre a kettő nem lehetséges.

A fentiek miatt a játékos mozgása és annak megjelenítése jelentősen leegyszerűsödik. Gyakorlatilag üres hely előtt kell mozognia, akár karakteresen is megjelenhetne, de így a mozgása nagyon darabos volna. A pixeles mozgás érdekében a következő megoldás született:
- Ha a játékos karakter-határon belül van, egyszerűen egy karakterként kitesszük, amibe belemásoljuk az akutális animációs fázist.
- Ha oldalra mozog, az aktuális pozíciótól BALRA dehogy, JOBBRA kap egy plusz karaktert, e kettőben toljuk el oldalirányban 1-7 pixellel, a 8. már egy karakterrel arrébb történik meg.
- Fel-le mozgásnál ugyanez történik, de az aktuális pozíciótól egy sorral lejjebb kerül ki a második karakter.
- A hátteret ezzel nem írjuk felül, mert még mozgás előtt megnézzük, a lehetséges négy irányban mi található, és csak üres hely esetén engedjük tovább. Ha valamelyik irányban homok az akadály, de mozogna a játékos, ásunk egyet - ezután már ott is üres hely lesz, mehet tovább.
- Mivel a játéktér a bejáraton kívül át nem járható akadályokkal körbe van zárva, az előzőek miatt nem szükséges a bejárható minimális és maximális pozíció ellenőrzése, hiszen az akadályok megfogják a játékost, mielőtt túlmenne a bejárható tartományon. A bejáratot (és a tank melletti kijáratot) egyébként $00 karakterek veszik körbe, amik pont ugyanolyan üresek, mint az átjárható $20 karakterek, de blokkolják a játékos továbbhaladását.

Külön bónusz a játéktér kialakítása, ami miatt a képernyő jobb oldala nem bejárható a játékosnak. Egy kis magyarázat erről:

A Commodore Plus/4 képernyője 25 sorban 40 karakter méretű, pixelben pedig 320x200. A függőleges pozíció (0-199, vagy hexadecimálisan $00-$C7) leírható egy byte-ban, a vízszintes viszont már nem, annak az értéke (ha a teljes képernyő bejárható) $0000-$013F lehet, amely már csak két byte-on tárolható. Még akkor is, ha a sprite pozíciója amúgy nem lehet $013F / $C7, hiszen akkor már csak egy pixel lenne látható a bal alsó sarokban.

Na de a The Pit esetében ilyen gond nincs, az eredeti pályát használva a játékos (és persze így az ellenségek is), beleszámítva a méretét is, maximum a $D0 / $B8 pozícióig mehet el. Ez már tárolható egy byte-on is. Ez a különbség nem tűnik jelentősnek, de így memóriát is takarítunk meg a rövidebb programmal, illetve processzoridőt az így szükségtelen +1 byte-tal való számolgatással.

2. Ellenségek: szinte minden igaz rájuk, ami a játékosra. Plusz bónusz, hogy mivel egymással igen ritkán találkoznak, ezzel sem szükséges mélyebben foglalkozni. Egymással ütközve egyszerűen felülírják majd egymást, ami a játék hevében nem lesz feltűnő, így zavaró sem.

3. Lövedékek: kétféle lövedék van, a játékosé és  tanké. Ebből utóbbi egyszerű karakteres megjelenítés lesz, nem igényel többet. A játékosé sem lesz bonyolultabb, hiszen üres háttér előtt mozog, akadályba ütközve megsemmisül (=egyszerűen csak eltűnik), ellenséggel ütközve pedig azzal együtt tűnik el.

4. Bombák az alsó barlanban: A lövedékhez hasonló a megjelenítésük, ráadásul egyetlen irányban mozognak, így még egyszerűbben kezelhetőek.

5. Szörny a savmedencében, tank, UFO: nincs igazán szerepük a játékban. Mindhárom egyszerű karakteres történettel megjeleníthető minimális területen mozogva. Egyedül a tank kap fél fázissal eltolt karakteres animációt a viszonylag hosszú, egyenesen megtett út miatt.

Ahhoz, hogy a 16K-ba beleférjek, nem kellett túl nagy erőfeszítéseket tennem, hiszen maga a játék nem túl komplikált. 

  • Az elérhető teljes karakterkészlet helyett csak egy felet használok, ez bőven elegendő a képernyő megjelenítéséhez.

 

A The Pit karakterkészlete
A The Pit karakterkészlete jelenlegi állásában. Ez nem a végleges, az animációs fázisok még kikerülnek belőle.

(A grafikához egyébként alapvetően a C-64-es verziót vettem alapul, de néhány részletet átvettem az eredeti, játéktermi verzióból)

  • A hangok sem túl komplikáltak az eredeti játéktermi verzióban sem, így elég könnyen és spórolósan előállíthatóak. Ebben némi keresgélés után Epy volt segítségemre. Az ő egyszólamú effekt-lejátszója nagyjából 256 byte, ehhez jönnek még az effektek definíciói, amik hasonló méretű helyet igényelnek majd, így fél kilobyte körül vesz csak el helyet a memóriából a hang. Epy lejátszóját egyébként majdnem változatlanul használtam fel, csak annyit változtattam rajta, hogy egy adott effektet loopolni (ismételni) lehessen. Így a tank és az UFO hangja viszonylag kevés, ismétlődő adatttal leírható lett.

 

Az UFO hangja
Az UFO hangja, 45 byte

 

  • A játék képernyője, azaz a szín- és karakteradatok összesen 2000 byte memóriát foglalnak. Ezekben viszonylag sok az ismétlődő adat, így elvileg egy egyszerű byte-tömörítéssel csökkenthető lenne a mérete. Az ehhez szükséges kicsomagoló rutin azonban plusz helyet foglalna a memóriában, így a csökkenés már nem volna olyan jelentős. Egyelőre maradt bőven hely, ha mégis fogytán lenne, akkor erre visszatérek.
  • A címképernyő viszont, ami elméletileg szintén 2000 byte helyet kérne, nincs tárolva. Mivel viszonylag kevés tartalom van rajta, így a gyári KERNAL rutinjait használva szöveges tartalomként kerül ki. Így $015C, azaz decimálisan 348 byte-ot foglal csupán.
  • A különböző állapotokat, címeket, időzítőket változóként tartalmazó memóriaterületek nagyobbrészt lekerültek részben a nulláslapra ($00-$FF), részben pedig a képernyőmemória alatti területre ($0800 alá). Ezek nem hoznak sokat, de mivel minden játékinduláskor újra fel kell tölteni őket adatokkal, értelmetlen volna a "rendes" memóriát koptatni velük, pláne nem sok értelme volna a kész programfájlban tárolni őket.
  • A billentyűzet és joystick lekérdezése táblázatból történik. 37 byte program végzi a lekérdezést, és billentyűnként 4-5 byte a táblázat mérete, ami alapján tudjuk, melyik gomb állapotát kell lekérdezni, és ha lenyomtuk, mit kell tenni vele. Ez kevés billentyű esetén még inkább hoz növekedést, de ha joystickról és billentyűzetről is irányítható a játék, plusz van még pause, kilépés és hasonlók, már nyerni lehet vele pár byte-ot.
  • Amennyire lehet, használom a gyári KERNAL rutinjait. Képernyőtörlés, billentyűzet-mátrix lekérdezése,  képernyőre írás - ezek egyesével nem mindig jelentenek komoly előnyt, de módszeres használatukkal már egész jól lehet spórolni.
  • Ebből adja magát az is, hogy nem kapcsolom ki a ROM-okat, még ideiglenesen sem. Nincs szükség az alattuk lévő RAM-ra, és így megspórolom az STA $FF3F / STA $FF3E hárombájtos utasításokat is. Ennek folyományaként a megszakítást sem $FFFE/FF címen irányítom át, hanem $0314/15-ön, megspórolva ezzel a regiszterek verembe pakolását és visszaolvasását is.
  • Bár a játékos pozícióját pixelben tároljuk, többször szükség van a karakteres, illetve a karakteren belüli pixeles pozíciójára is. Mozgás után utóbbiakat egy külön rutin képernyőciklusonként egyszer kiszámolja és le is tárolja. Így inkább foglalok még négy byte-ot a nulláslapon, de nem kell ezeket újra és újra AND-ekkel és biteltolásokkal kiszámolni futás közben.
  • A mozgó elemek képernyőre írásakor az elem pozícióját nulláslapi byte-párosba töltöm, és ha már ott van, ugyanebben a körben felhasználom ezt a byte-párost a környezet vizsgálatára is (pl. van-e a játékos előtt akadály, vagy az ellenség utolérte-e a játékost). Ezzel megspórolható a képernyő-pozíciók újra és újra kiszámítása. Kombinálva az előző ötlettel elég sok erőforrást meg lehet spórolni.
  • Számtalan apró megoldás van még kisebb bytespórolásokra - ilyen például az, hogy ha valahová be kell írnom egy adatot, és ezért azt regiszterbe töltöttem, akkor már igyekszem másra is felhasználni azt. Jellemző példa erre, hogy ha kikapcsolom a képernyőt, majd a keretet, hátteret feketére állítom, aztán telemásolom tartalommal, akkor ahhoz elég egyetlen LDY #$00, hiszen azt beírhatom sorban $FF06, $FF19, $FF15-be, és ugyanezzel indíthatom az Y-nal indexelt ciklust is, amivel a tartalmat feltöltöm. Ez a megoldás jellemzően utólag működik jól, hiszen a (majdnem) kész programban könnyebb észrevenni az ilyesmit - de előre gondolkodva nagyobb részét már meg lehet csinálni a korai fázisokban is.
  • ...és hát a helyspórolás egyik legjobb módja, hogy a beépített Monitor helyett crossplatform-assemblerben dolgozom. Így nem kell mindent jól megjegyezhető, nullára végződő címre pakolnom, mint a klasszikus időkben :)

A fentiek nagy része persze minimális helyet spórol meg csak, de hogy értsük, miért fontosak ezek a morzsák is, most lapozzunk vissza a fenti, látszólag minden ok nélkül zölddel írt szövegre - ennek a mérete kb. 4500 karakter, azaz kb. 4.4kB.

Ez a kis zöld szövegrész négyszer egymás után már nem fér be a 16K-ba!

Még egy apróság a végére: fejlesztés közben sokszor használok afféle fakocka-debug módszereket, amik aztán a végleges verzióból könnyen-gyorsan kidobhatóak. Ilyenkor a képernyő passzív, a program futása közben lényegtelen területein remekül megjeleníthetőek olyan információk, amik segítik a hibakeresést, vagy akár magát a fejlesztés folyamatát is. Ilyenek láthatóak ezen a képernyőképen is:

The Pit Arcade Commodore Plus/4
"Debug mód" a The Pit alatt

Jól látható egyrész a klasszikus "raszteridő-mérés", a megszakítás elején elállított, majd a végén visszaállított keretszín, másrészt a képernyő alján sorban a játékos X és Y pixeles koordinátái, az X és Y karakteresn belüli pixeles koordinátái, az X és Y karakteres koordinátái, az ásás-számláló (0-7, illetve ennek megfelelően @-G), illetve az aktuális mozgás iránya (vízszintes irányú, függőleges irányú vagy - jelen esetben - egyik sem).

A High Score listában a nevek helyett felül a játékost körbevevő akadályok a négy irányban, alatta az aktuális mozgás iránya.

Ezen kívül itt a debug módban a fentebb említett üres, de a szabad területtől eltérő $00, azaz @ karakter nem üres, tartalmaz egyetlen pixelt, hogy megkülönböztethető legyen a valóban üres, átjárható $20 (szóköz) karaktertől. Ezért látszik például az is, hogy az UFO mellett kijáratból csak lefelé távozhat a játékos.

Ezen kívül a debug-módban kikapcsolható (és itt ki is van kapcsolva) az ellenségek mozgása, a sziklák zuhanása, a tank lövöldözése, stb... Ez sokat segíthet olyankor, ha valamilyen hibát kell visszakeresni, aminek a forrása még teljes homályba vész.

Ez az egész debug-mód a kész verzió mentése előtt néhány ;-vel kikommentelhető, mentésnél pedig kihagyható. Esetünkben - épp a 16K-s cél miatt eleve $4000 felett helyezkedik el, így a rendelkezésre álló memóriában sem zavar be.

 

Nos, egyelőre ennyi, legközelebb - remélem - a kész The Pit-tel jelentkezem majd.