Unaloműző online játék újratöltve

Fórumok

Talán emlékeztek még páran arra a régi DOS-os logikai játékra, amiben kockákat kellett tologatni, hogy kijusson a sárkány a barlangból.

Stone Age

Újraírtam nulláról, a legmodernebb eszközökkel, Linux-ra, Windows-ra és persze web-re is. Nem kell telepíteni és DLL-ek sem kellenek neki, simán futtatható (portable executable), de persze a webes játék elinditásához elég a fenti linkre kattintani. Forrás mellékelve a repóban, Szabad és Nyílt Forráskódú, GPL v3+ (a szprájtok ugyan pixelre ugyanazok, de az eredeti játékot készítő cég 20 éve becsődölt, nem hiszem, hogy túlzottan izgatná őket, már ha élnek egyáltalán még).

A játék kapcsán:
- mind a 100 eredeti pálya játszható
- de van beépítve pályaszerkesztő is
- kilépés F10 (vagy ALT+Q)
- teljesképernyő F11 (vagy ALT+Enter)
- jelszóbekérőnél van "autocompletition" Tab-ra: a legutóbb játszott pálya kódjára egészít ki
- a menü (és a pályaszerkesztő) egérrel is irányítható

Jó unaloműzést!

Hozzászólások

Nekem a 3. pályán szétesett. sidula jelszóval amire lépsz: Felmegyek a jobbranyíl dobozba, megnyomom és csapdába esek a pálya szélén. Megnyomom a jobbra gombot még egyszer, és kimegyek a pályáról!

2 nap alatt dobtam össze, és minden igyekezetem ellenére maradhattak még benne bugok. A játék mindesetre garantáltan végigjátszható, azt kipróbáltam.
Megnézem!

Szerk: sikerült reprodukálnom a hibát, máris javítom!
Szerk2: Javítva! Azért nyúzom még, hátha más kombóval is akad probléma. Ha te futnál bele újabb hibába, akkor kérlek jelezd!
Szerk3: egész pontosan most javítva! Volt mégegy eset, aminél kellett egy plusz ellenőrzés.

Alapból egyszerű a dolog, a ts változó mondja meg, hogy aktív-e a tile selector, a dt pedig a sárkány alatti tile. Ezeknek négy kombinációja lehet:
1. dt == 0 és ts == 0: ilyenkor a sárkány mozog, max 1 kockát, csak bizonyos tile-okra, üresre nem
2. dt != 0 és ts == 0: ilyenkor a sárkány együtt mozog a tile-lal, azaz a tile mozgatás szabályai érvényesek, nem a sárkányé
3. dt == 0 és ts ==1: ilyenkor a kijelölő mozog, nincs semmiféle akadály (csak a képernyő széle)
4. dt != 0 és ts ==1: ilyenkor a kiválasztott tile mozog a sárkány nélkül, csak az üres kockák jönnek szóba

Namost a problémát az adja, hogy ezeknek van speciális esete. Például a 2.-nél ha nem lehet mozogni a tile-lal, attól még lehetséges, hogy a sárkánnyal igen (ez a leszáll róla), illetve a 4. pontnál meg van egy olyan speciális eset, hogyha a kiválasztott tile semerre nem tud mozogni, akkor vissza kell állni az 1. esetre. Namost a bug az volt, hogyha a legutolsó esetnél a sárkány egy másik mozgatható tile-on állt, ilyenkor a dt helyett a sárkány koordinátáján lévő tile-t kell nézni (a dt ugye a kijelölő alatti ilyenkor, de a sárkány alatti tile-ra vonatkozó szabály kell). Úgy látszik ezt csak úgy teszteltem eddig, hogy a sárkány mindig nem mozgatható tile-on állt közben.

Hogyan lehet a zenét kikapcsolni?

Még nincs aláírásom.

F12 az opciómenü, és ott húzd le a hangerőt nullára.

A legutolsó commit-ba beraktam, hogy fele hangerővel induljon a zene, úgy talán nem annyira zavaró, míg letekered. Natív alkalmazás egyébként fájlba is lementi a konfigot, így az megjegyzi, mit állítottál be, de a webes csak addig, míg újra nem indítod a böngészőt.

Szerkesztve: 2025. 09. 05., p – 08:20

Egy kis update:

1. játékmenübe is bekerült a hangerőállítás (azaz hogy az eredeti játékból visszakerült a "Sound options")
2. megoldottam, hogy a webes verzió is elmentse a beállított értéket (JavaScript localStorage, így a sütik törlése után is megmarad)
3. találtam egy bugot: csak ha nyertél akkor adott a high score-hoz, ha vesztettél, akkor nem kerülhettél fel. Javítva.
4. apró igazítások a felületen, pl. a pályaszerkesztőnél halványan látszik a lerakandó elem, meg ilyenek.

Tegnap késő estig jó sokat nyüstöltem, asszem most már bugmentes (persze csak a halál biztos). Szóval ha esetleg valaki találna még valami hibát, kérem jelezze és javítom!

Jó szórakozást!

Mekkora macera lenne a webes változatban megoldani, hogy az URL-ből vegye a pályakódot? Amire gondolok:

  • játszok a motyóval
  • elhaladok pár pályát, ilyenkor ugye emlékezni kéne a pályakódra, vagy letölteni valahonnét a listát :)
  • ehelyett eltenném bookmarkba (én úgy szoktam, hogy a bookmark toolbarra húzom azt, amit majd később elő akarok venni)
  • ezért jó lenne ha:
    • frissítené a saját .location.href-ét, hogy benne legyen a pályakód
    • induláskor ezt figyelembe venné

Persze ez csak amolyan cicoma, de én örülnék neki, viszont sík hülye vagyok az efféle fejlesztésekhez.

frissítené a saját .location.href-ét

Ez nem megoldható, reklamációkat tessék a wasm fejlesztőinek címezni.

Az a baj ugyanis, ha megváltozik a location.href, akkor újratölti a böngésző az oldalt, ami wasm esetében azt is jelenti, hogy a komplett állapot és addigi memória megy a kukába.

Azaz az emcc által kigenerált 200K-nyi glue JS kódot újra be kell tölteni, újra le kell futtatni, annak a 2.5M-nyi wasm binárist újra fetch()-elni kell, azt újra parszeolni és linkelni kell, stb. Majd miután elindult végre, annak újra fel kell töltenie a memóriáját, a png-t és az ogg-ot dekódolni, a GL contextet újrainicializálni, stb. stb. stb. Ráadásul JS alatt képtelenség bináris buffereket permanensen (oldaltöltések között) tárolni, szóval a cachelés sem megoldható. Magyarán minden pályalépés jó 10-15 másodperc lenne az újratöltés miatt...

Egy kis rant: mi a jó fittyfenének kell 200K-nyi glue JS kódot generálni a wasm bináris mellé? Miért csak szerverről, fetch()-el lehet hivatkozni a wasm-ot, miért nem simán, mint egy js lib-et, css-t vagy képet vagy bármi mást? Miért nincsenek a DOM interfészek kivezetve a wasm által hívható emscripten API-ra és csók a családnak? Nemcsak tisztább, szárazabb érzés lenne, de könnyebb, jobb és gyorsabb is, és úgy elképzelhető lenne a location href (ami igazából egy DOM attribute) változtatása a wasm újratöltése és újrafuttatása nélkül is. De nem, a trágya fetch() és a DOM miatt okádék glue JS tenger van csak... (A fetch()-el az a baj, hogy az valójában egy átkeresztelt XMLHttpRequest, azaz mindenképp szerverkapcsolat szükséges hozzá, míg mondjuk egy <script src=> simán tud relatív elérési úttal lokális fájlt is hivatkozni.) Na, rant vége.

Nem, teljesen jó ötlet, és van benne ráció. Nem mi tehetünk róla, hogy ilyen fostalicska az egész és képtelenség hatékonyan megoldani benne egy tökre reális kérést. Igazából nem is magával a wasm-al van baj, hanem az emscripten által a köré pakolt sallaggal, meg ezzel az idióta böngészőbeáagyazással.

Egyébként az url query sztringet átpasszolni nem ügy, használom is pl. a MEG-4-nál, meg más projekteknél, pl. ez magyar, ez meg angol nyelven indítja az Időrégészt, de még itt is érződik, idő, amíg a böngésző berugdossa a wasm-ot (aztán ha már inicializált, akkor jól fut, csak hát az indulás, na az valami baromi lassú, leginkább a fetch() miatt, mert azt a böngésző nem képes cachelni). Ez az Időrégész azonban csak 900K, és direkt úgy írtam meg, hogy induláskor kicsomagol egy memdump-ot, és kész, szóval piszok gyors. A StoneAge ezzel szemben jelentős időt tölt induláskor az OGG Vorbis dekódolással (ami 1.6M), ha kicsomagolva WAV-ban tartanám, majdnem 20M lenne csak a zene.

Szerintem meg lehet csinálni, de van benne szívás rendesen. Mostanában wasm-oztam egy kicsit Tsoding wasm témájú videóin felbuzdulva.

Az emscripten tényleg iszonyú bloat, de lehet anélkül is élni. Tsodingon felbuzdulva csináltam ilyet, hogy minden illesztőt magam írtam meg, és tök jó tisztább szárazabb érzés. Persze az elején kicsit melós, de szerintem megtérül azon, hogy nincs egy bloat alattad, amit nem értesz, hogy mit csinál. Bár nekem sose nem térült meg, mert egyetlen projektet se fejeztem még be ezzel a technológiával, csak kipróbálgattam és elment vele jópár napom. Szóval megérte :-)

Van még egy dolog, amit megcsinál az emscripten: csinál egy transzformációt, amivel continuitássá alakítja az összes kódodat, és ezáltal lehetővé teszi látszólag a blokkolást. Enélkül az átalakítás nélkül a WASM-nak követnie kell a JS non-blocking logikáját, csak callbackeken működhet a program. Az átalakítás egyébként a WASM asszemblin működik, és a függvényeket átalakítja úgy, hogy bele lehet hívni a közepükbe és a blokkolási pontokról vissza tudnak azzal térni, hogy én most blokkolódok, hívjatok később! Elég kemény elméleti és gyakorlati probléma, de valaki megvalósította!

A WASM sajnos nem tud több "szálat" - azaz végrehajtási állapotot - kezelni, emiatt nem lehet se blokkolást, se green threadeket se implementálni benne triviális módon. Pedig ha egyszerre csak egy stack lehet aktív, akkor nem okoz nemdeterminizmust se, és nagyon egyszerű megvalósítani is. Igazán beletehették volna. Talán rajta van a listán, hogy meg akarják ezt valósítani, de sajnos nem tudom mikorra lesz alap az összes böngészőben.

Ez a blokkolhatóvá átalakítás is bloat SZVSZ, és mivel én 0-ról terveztem amit csináltam nekem nem volt gond elkerülni. Egy olyasmi keretet csináltam a programomnak, mint egy Arduino: van egy setup és egy loop, amit hívogat a keret. És minden "file" IO-hoz aszinkron callbackes API-t kell tervezni C-ben is. Ezt csak akkor teheted meg, ha zöldmezős a programot, ha tele van blocking IO logikával, akkor át kell mindent alakítani. Egész jó szorakozás, mondom megérte :-)

Ahogy meg lehet csinálni ezeket amiket fent írtál:

 * Lehet saját függvényeket delegálni a WASM-ba, amit JS-ben valósítasz meg. Ilyenen keresztül a teljes DOM manipuláció is megoldható például. Én az OGL->webGL hívások egy részét csináltam meg a játszós projektemben persze példából kiindulva, de bele is kellett nyúlkálnom. Szívás, hogy stringeket memória pointerrel tudod kiadni, és JS-ben meg kell csinálni az ASCII/UTF-8 -> String konverziót, de hát a jó dolgokért szenvedni kell. Ezzel minden JS funkció elérhetővé tehető WASM-ból is.

 * Az URL-t át lehet írni újratöltés nélkül is. A Single Page Application eszközei között van ez a megoldás. Lásd: https://stackoverflow.com/questions/3338642/updating-address-bar-with-n… https://developer.mozilla.org/en-US/docs/Web/API/History_API Egy projekten már használtam, tökéletesen működik a modern browserekben. Igaz nem WASM-os oldallal, de működött ez nekem jól, a WASM nem lehet akadály.

 * A WASM cache-elés szerintem ServiceWorker segítségével is megoldható kell hogy legyen. Ezzel szerintem kell, hogy működjön az oldal akár offline is, együtt tud működni a WASM és a Web Application technológia. Igaz ezt még nem próbáltam.

 * A WASM betöltéshez is van valami cache technológia a Chrome-ban legalábbis olvastam róla régebben. Tehát a JIT lépést nem csinálja újra a browser ha megbizonyosodik róla, hogy ugyanazt töltöd be újra.

 * Szerintem memória tartalmat is el lehet tenni localStorage-be, vagy a ServiceWorker segítségével is el lehet valahogy tárolni. És akkor megoldottá válik a bufferek visszatöltése. Igaz, a konverziók miatt nem biztos, hogy hatékony is lesz. A WASM-ban egyébként egészen egzakt a memória tartalma, szerintem meg lehet csinálni, hogy kisorosítod a teljes memóriaképet, és egy másik példányt azzal indítva a programodnak folytatódni kell akár egy másik gépen is. Lényegében az egyetlen külső állapot a memória növekedése, amit újra el lehet játszani az új példányban, vagy pedig ha fixre állítod a memória méretet, akkor nem is kell törődni vele.

 

Amennyire gyűlöltem régen a webes technológiákat, a WASM annyira lenyűgözött, és megláttam benne a szépet. Persze a hiányosságait is, de az egy másik történet... Már várom, hogy adódjon belőle projekt amiért fizetnek is, akkor majd elmondhatom, hogy tényleg megérte foglalkozni vele.

Hát ez az, mindezt végigszívod, és nem is biztos, hogy aztán minden böngészőn menni fog, lehet csak chrome only lesz... Vagy ha mégis megy, akkor a konverziók miatt a béka segge alatt lesz a teljesítménye.

A wasm magában egyébként nem rossz, egy szekció konténerformátum nem túl rosszul definiált bájtkóddal. Nem egy egetrengető, de igazából nem is szúrtak el benne semmit nagyon. A gond ott kezdődik, amikor illeszteni akarod valamivel, az ABI-ja egy kalap szar. Iszonyat gáz az emscripten és a WASI is, bár az tény, hogy utóbbi még mindig fényévekkel jobb, de böngészőkben nem elérhető.

Ha kihajítod ezt a komplett illesztéses faszságát, és csak az ABI illesztést megírod magad, akkor az önmagában nem is túl nagy meló (kb. 2000 SLoC, de ebben már a VM is benne van) és úgy egyébként nagyon faja kis cucc tud lenni. Piszok nagy előnye, hogy nyelvfüggetlen és sima fordítóproggival előállítható (Clang-ban csak újabb target, nem kell hozzá semmi se).

Amikor még ismerkedtem vele, akkor nekem ez a leírás volt nagyon hasznos (bűn rusnya az oldal, de értékes infó). Az is tanulságos, hogy emscripten nélkül is fut wasm a böngészőkben (persze ilyenkor csak paramétereket tudsz átvenni meg visszaadni, semmilyen funkcionalitás vagy függvényhívás sem elérhető).

>nem is biztos, hogy aztán minden böngészőn menni fog, lehet csak chrome only lesz...

Erről nekem az a tapasztalatom, hogy egész jók a nagyobb böngészők, lehet hinni a caniuse.com-nak és tényleg működnek.

Ezeken szoktam megnézni a saját weboldalaimat, és ezek teljesen jók szoktak lenni:

Linux: Chrome, Chromium, Firefox, Brave, Android

Android: Chrome, Firefox

A wasm+webgl demo kezdeményem például mindegyiken jól működött.

A fent említett feature-ök mindegyike elég elterjedt: https://caniuse.com/mdn-api_history https://caniuse.com/wasm https://caniuse.com/webgl https://caniuse.com/serviceworkers

A fent említett feature-ök mindegyike elég elterjedt

Persze nem is ezekkel van gond, hanem az olyanokkal, amiket fentebb is soroltál:

- "A WASM sajnos nem tud több "szálat" - azaz végrehajtási állapotot - kezelni," igazából tudja. Semmi nincs a specifikációban, ami tiltaná, hogy több vermes VM-et implementálj. Egyébként volt is, de amire az emscripten fordítja a pthread hívásokat, azt a fícsört pont, hogy épp kivezetik a böngészőkből, azért nincs. De egyébként is döcögött caniuse?=search=SharedArrayBuffer (ez pl zöldnek jelzi chrome-on, de valójában le van tiltva alapból, parancssori kapcsolóval lehet csak engedélyezni; firefox-on is alapból le van tiltva és nem elérhető)
- "A WASM cache-elés szerintem ServiceWorker segítségével is megoldható kell hogy legyen." tökéletes példa, mondjuk jól feljött, amióta legutóbb néztem: caniuse?search=serviceworker
- "Szerintem memória tartalmat is el lehet tenni localStorage-be [...] Igaz, a konverziók miatt nem biztos, hogy hatékony is lesz." Pontosan. Amikor pár éve írtam a webes Időrégészt, még gondot okozott az UTF-8 (TextEncoder) és a btoa() is. De még ha vannak is, akkor is hazavágják a teljesítményt. Most komolyan, minden sztringkonverziónál létrehozni egy TextEncoder class instancet, mennyivel több erőforrást igényel már ez, mint egy mbtowcs() függvényhívás?
- "A WASM-ban egyébként egészen egzakt a memória tartalma, szerintem meg lehet csinálni, hogy kisorosítod a teljes memóriaképet" Meg lehetne, csak a böngészők nem tudnak ilyent. És valószínű, biztonsági okokból soha nem is fogják ezt tudni.

>Meg lehetne, csak a böngészők nem tudnak ilyent. És valószínű, biztonsági okokból soha nem is fogják ezt tudni.

Tuti? A JS illesztőnél megkapod a teljes memóriát, mint egy arrayt. Az arrayt szerintem el is posztolhatod a szerverednek úgy ahogy van. Oké, darabolva, mert egy bináris websocket üzenetnek túl nagy lenne.

Szerk.: megnéztem a saját wasm projektemet, ezzel érem el a teljes memóriát:

instance = (await WebAssembly.instantiateStreaming(fetchAsm, importObject)).instance;

WASM_MEMORY=instance.exports.memory;

A WASM pointereket ha átadod a JS-nek, akkor ezen a memory-n belül értendőek. A teljes memória elérhető JS-ből. Például ezzel a segédfüggvénnyel csinálok JS stringet a C stringből:

    obj.memory=new Uint8Array( instance.exports.memory.buffer);
	this.buf2str=function( ptr, len ){
	    let arr = obj.memory.subarray( ptr, ptr+len );
	    return obj.utf8decoder.decode( arr );
	}

(Látszik, hogy nem a 0 terminálásra épít, hanem a hosszat is átadjuk, így gyorsabb és egyszerűbb a JS oldal.)

Tuti? A JS illesztőnél megkapod a teljes memóriát, mint egy arrayt.

A gondok a visszatöltéssel vannak, ráadásul több memóriablokkod is lehet, nemcsak egy, szóval több array is lehetséges.

Látszik, hogy nem a 0 terminálásra épít, hanem a hosszat is átadjuk, így gyorsabb és egyszerűbb a JS oldal.

Az én megoldásom erre az, hogy hozzábiggyesztem azt a nullát:

const buf = Module._malloc(data.length + 1);
Module.HEAPU8.set(new TextEncoder("utf-8").encode(data + String.fromCharCode(0)), buf);

De az alap problémán, hogy csúnya és rohadtul nem hatékony az egész JS-esdi, persze ez sem segít. Eleve az a gond, hogy konvertálgatni kell jobbra-balra, és ha mondjuk 128 M-nyi memóriád van (tipikus), akkor az overhead biza' combos tud már lenni.

Nagyon jó játék, jó ötlet volt épp ezt felújítani! Nem ismertem. Érdekes egyébként, hogy azt a "modern" módszert használja, hogy minden feature-t bevezet egy triviális példával, amit könnyű megérteni, és mindig csak 1-1 új dolgot ad. Plusz minden pályán kell valami új trükk is amin gondokodni kell. Nagyon jó!

Szerkesztve: 2025. 09. 06., szo – 11:12

Fisher kollégának:

elhaladok pár pályát, ilyenkor ugye emlékezni kéne a pályakódra, vagy letölteni valahonnét a listát :)

Sokat gondolkodtam, hogy lehetne ezt megoldani, ha már URL-esdi nincs, és az eredeti játékhoz is minnél hűbb akarok maradni. Sok játéknál ilyenkor megjelenik a menüben egy "Continue" opció, de ezt nem akartam, mert az eredetiben sincs ilyen. Úgyhogy amivel előrukkoltam helyette, az ez:

Ha az "Enter magic word" oldalon leütöd a TAB-ot, akkor az "autocompletition" automatikusan beírja annak a pályának a kódját, amin legutóbb játszottál. Megoldottam azt is, hogy ezt a web-es verzió is mentse, akárcsak a hiscore-t meg a módosított pályákat (hasonlóan a config-hoz, localStorage-ba).

Remélem segítettem! Már fel is töltöttem ezt az új verziót, kipróbálható. Egy pulzáló "TAB" felirat jelenik meg a jelszómező jobb sarkában, ha van korábbi pályakód, amit beilleszthet.

Először is, köszi, hogy foglalkoztál vele.

Másodszor viszont a probléma az, hogy négy böngészőt használok különböző gépeken. Persze így is sokkal kényelmesebb, legfeljebb pár pályát újra kell csinálni.

Most azt csinálom, hogy szinkronizált jegyzetbe felírom, hogy hol tartok :)

Először is, köszi, hogy foglalkoztál vele.

Igazán nincs mit, szívesen!

Most azt csinálom, hogy szinkronizált jegyzetbe felírom, hogy hol tartok :)

Tipp: jobb egérgomb a pályaszerkesztőben a betöltés gombon. (Bal egérgomb a beírt kódhoz tartozó pályát tölti be, a jobbklikk a rákövetkező pályát.)

Szóval addig nyomogatod a jobbklikket, amíg fel nem ismered azt a pályát, amit keresel, és a 🔒 mellett látni fogod a kódját.