Ana fikirler
- ROM → .gbc: pret/pokered disassembly, rgbds 0.9.x ile doğrulanmış bir Game Boy kartuş imajı oluşturur.
- WASM paketi: binjgb (C emülatörü) Emscripten ile derlenir ve sekiz fonksiyon dışa aktarır; wrapper.c kaplama katmanı kare-mükemmel bir canvas döngüsü ekler.
- Sıra sistemi: Railway'deki bir WebSocket sunucusu kumandayı kimin elinde olduğuna karar verir — 5 saniye hareketsizlik sırayı geçer; Redis (Upstash) kayıt durumunu oturumlar arasında saklar.
- AI botu: autoplay.js, ekran görüntüsü analizi yapmadan Game Boy RAM adreslerini doğrudan okuyarak hem overworld'de gezinir hem de savaşları otomatik yönetir.
- Oyuncuyu tek bir iframe ile sitenize gömebilir ya da 30 dakikadan kısa sürede tüm sistemi kendi altyapınızda çalıştırabilirsiniz.
🎮 Canlı oyna → — tek bir örnek, herkes birlikte oynuyor.
fikir basitti aslında: siteyi ziyaret eden insanlar Pokémon Red’i birlikte oynayabilse, kumandayı aralarında geçirseler — Twitch Plays benzeri ama bir blog üzerine gömülü, düzgün kayıt durumu olan ve maliyeti yok denecek kadar az bir şey olsa nasıl olurdu?
bu, o yapının tam inşa günlüğü.
ne yapıyor
tarayıcıda tek bir Pokémon Red kopyası çalışıyor. sayfayı açan herkes katılabiliyor. bir seferde tek oyuncu kumandayı elinde tutuyor; beş saniye hareketsizlik sonrası sıra kuyruktaki bir sonraki kişiye geçiyor. etrafta kimse yoksa bir AI botu devreye giriyor, overworld’de dolaşıyor ya da savaşıyor. oyunun yanında canlı bir sohbet var. kayıt durumu oturumlar arası korunuyor: sekmeyi kapatın, yarın geri gelin — oyun bıraktığınız yerde.
| katman | teknoloji | görevi |
|---|---|---|
| ROM kaynağı | pret/pokered | orijinal Game Boy assembly |
| derleme | rgbds 0.9.x | doğrulanmış .gbc imajı oluşturur |
| emülatör | nicknassar/binjgb | WASM’a derlenen C emülatörü |
| derleme aracı | Emscripten (emsdk) | C’den WebAssembly’ye |
| kabuk | vanilla HTML/CSS/JS | canvas döngüsü, ses, klavye |
| sunucu | Node.js / Railway | WebSocket arbitrasyonu |
| durum | Upstash Redis | oyuncular arası kayıt saklama |
| AI bot | autoplay.js | Game Boy RAM’ini doğrudan okur |
adım 1 — ROM’u kaynaktan derleme
her şey pret/pokered ile başlıyor; bu, Pokémon Kırmızı ve Mavi’nin tamamen tersine mühendislikle elde edilmiş disassembly’si. onlarca yıllık topluluk emeğiyle 1 MB’lık bir Game Boy kartuşu okunabilir, açıklamalı assembly’ye dönüştürüldü.
git clone https://github.com/pret/pokered.git
cd pokered
make
# pokered.gbc üretilir
sha1sum pokered.gbc # roms.sha1 ile doğrula
rgbds (≥0.9) gerekiyor. assembler, linker ve fix aracının tamamı dahil. make, pokered.gbc üretiyor — doğrulanmış bir kartuş imajı. SHA1, roms.sha1’de sabitleniyor; bu ROM’u kamuya açık biçimde dağıtacaksanız tekrarlanabilir derleme önemli.
adım 2 — emülatörü WebAssembly’ye derleme
binjgb, C ile yazılmış, döngü doğruluklu bir Game Boy emülatörü. küçük, iyi yapılandırılmış ve Emscripten derlemesi için doğru kancaları sunuyor.
git clone https://github.com/nicknassar/binjgb.git
source ~/emsdk/emsdk_env.sh
emcc binjgb/src/emulator.c binjgb/src/joypad.c binjgb/src/apu.c \
web/binjgb/wrapper.c \
-o dist/binjgb.js \
-s EXPORTED_FUNCTIONS=@web/binjgb/exported.json \
-s MODULARIZE=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-O3
web/binjgb/exported.json, tarayıcı kabuğunun ihtiyaç duyduğu sekiz C fonksiyonunu listeliyor. wrapper.c ise binjgb kaynağını değiştirmeden kare-mükemmel 60fps canvas döngüsü ekleyen ve ses tamponunu JavaScript’e açan bir kaplama katmanı. derleme çıktısı iki dosya: binjgb.js (çalışma zamanı ve bağlantı kodu) ile binjgb.wasm (asıl bytecode).
adım 3 — tarayıcı kabuğunu bağlama
web/player.html bağımsız bir HTML sayfası. çerçeve yok, paket yöneticisi yok. binjgb.js’yi yükleyip emülatörü ROM baytlarıyla başlatıyor, sonra requestAnimationFrame döngüsünde ilerlettiriyor.
klavye girişi, player.js içindeki ince bir dağıtıcıdan geçiyor; bu dağıtıcı DOM KeyboardEvent kodlarını sekiz Game Boy düğmesine eşleyip Module._joypad_set’i çağırıyor. canvas, piksel sanatının net kalması için en-yakın-komşu interpolasyonuyla 3x büyütülüyor.
adım 4 — çok oyunculu sıra sistemi
en ilginç mühendislik, sıra sistemi. iki dosyada yaşıyor: web/multiplayer.js (tarayıcı istemcisi) ve Railway’de çalışan server/server.js (Node.js WebSocket sunucusu).
protokol minimal:
| mesaj | yön | anlamı |
|---|---|---|
join | istemci → sunucu | oyuncu lakabını bildiriyor |
input | istemci → sunucu | düğmeye basma |
state_sync | sunucu → istemci | katılımda tam kayıt blob’u |
turn_granted | sunucu → istemci | kumanda sende |
turn_revoked | sunucu → istemci | sırayı ver |
chat | her iki yön | canlı sohbet mesajı |
ai_granted | sunucu → istemci | AI botu şu an oynuyor |
ai_revoked | sunucu → istemci | AI botu bitti |
sunucu tek bir activeMode takip ediyor: idle, human veya ai. yalnızca o anki mod sahibinin girişleri emülatöre iletiliyor. diğer herkes izliyor.
adım 5 — Redis ile kayıt durumunu kalıcı hale getirme
kalıcılık olmadan oyun, sunucu her yeniden başladığında ya da son oyuncu ayrıldığında sıfırlanırdı. çözüm: her sıra devri sırasında emülatörün tam RAM’ini serileştirmek ve Redis’e kaydetmek.
// sıra devri sırasında (server.js)
const stateBlob = emulator.serializeState(); // ~8 KB base64
await redis.set('pokemon:state', stateBlob);
// yeni oyuncu katıldığında
const saved = await redis.get('pokemon:state');
if (saved) ws.send(JSON.stringify({ type: 'state_sync', state: saved }));
upstash redis sunucusuz: kalıcı bağlantı yok, provizyon yok. REST API’si Railway’in geçici konteyner modeliyle mükemmel uyuşuyor. ücretsiz katman günlük ~10.000 istek kapsıyor — hobi ölçeği için fazlasıyla yeterli.
adım 6 — AI botu
autoplay.js, bouletmarc/PokeBot projesinin basitleştirilmiş bir uyarlaması. bot ekran görüntüsü analizi yapmıyor; emülatörün dışa aktarılan bellek işaretçisi üzerinden Game Boy RAM adreslerini doğrudan okuyor.
// savaşta olup olmadığını kontrol et
const BATTLE_ACTIVE = 0xD057;
const inBattle = readRam(BATTLE_ACTIVE) !== 0;
if (inBattle) {
battleRoutine(); // FIGHT seç, en yüksek PP'li hamleyi al
} else {
overworldWalk(); // duvar algılama ile rastgele yürüyüş
}
bot multiplayerAllowed koşuluna bağlı: yalnızca sunucu ai_granted mesajıyla sıra verdiğinde etkinleşiyor. bir insan kumandayı tutarken asla devreye girmiyor.
adım 7 — gömme ya da kendi altyapınızda çalıştırma
paylaşımlı arcade’i gömün (tek satır):
<iframe src="https://yigitkonur.github.io/wasm-pokemon-red/"
width="100%" height="640"
style="border:none;border-radius:12px"
allow="autoplay">
</iframe>
tüm sistemi kendi altyapınızda çalıştırın:
- yigitkonur/wasm-pokemon-red reposunu klonlayın ve ROM ile WASM’ı derleyin (
make) server/server.js’yi Railway’e deploy edin —railway.tomlile tek tıklama- Upstash Redis örneği oluşturun,
UPSTASH_REDIS_REST_URLveUPSTASH_REDIS_REST_TOKENdeğerlerini Railway ortam değişkenlerine ekleyin web/multiplayer.js’dekiWS_URLdeğerini kendi Railway deployment’ınıza güncelleyinweb/klasörünü herhangi bir statik barındırma servisine gönderin (GitHub Pages, Netlify, Vercel, S3)
hobi ölçeğinde toplam bulut maliyeti: $0.
bundan sonra ne var
mantıksal bir sonraki adım daha fazla ROM. binjgb emülatörü herhangi bir Game Boy kartuşunu çalıştırıyor; ROM dosyasını değiştirip yeniden deploy etmek beş dakikalık bir işlem. sıra sistemi, Redis kalıcılığı ve AI botu tamamen ROM’dan bağımsız.
ilginç tasarım sorusu şu: her ROM ayrı bir sayfa ve ayrı bir sunucu olarak mı çalışmalı, yoksa sunucuyu her odanın bir ROM olduğu çok odalı bir modele mi genişletmeli? ikincisi daha zarif ve ziyaretçilerin oyunlar arasında geçiş yapmasına — küçük bir arcade gibi — izin verir.
Tetris, açık ilk ek. Sonra Link’s Awakening. Sonunda ana sayfada bir lobi: bir oyun seç, kuyruğa gir, beş saniyen için oyna.
bağlantılar
- canlı arcade: yigitkonur.github.io/wasm-pokemon-red
- kaynak kod: github.com/yigitkonur/wasm-pokemon-red
- pret/pokered: github.com/pret/pokered
- binjgb: github.com/nicknassar/binjgb
- PokeBot: github.com/bouletmarc/PokeBot
Kaynaklar
- pret/pokered — Pokémon Kırmızı/Mavi disassembly
- nicknassar/binjgb — Game Boy emülatörü (C)
- Emscripten — C'den WebAssembly'ye araç zinciri
- rgbds — Game Boy assembler ve linker
- bouletmarc/PokeBot — RAM okuyan Pokémon botu
- Upstash Redis — sunucusuz Redis
- Railway — WebSocket sunucusunu deploy etmek için
- Kaynak kod ve self-host kılavuzu
Sorular ve yanıtlar
Kayıt durumu oyuncular arasında nasıl kalıcı hale geliyor?
Her sıra devri sırasında sunucu, emülatörün tam RAM'ini base64 blob olarak serileştirir ve Upstash Redis'e kaydeder. Katılan bir sonraki oyuncu bu blobu çeker ve emülatörü bu noktadan başlatır; böylece oyun hiç sıfırlanmaz.
AI botu ne zaman oynamaya başlıyor?
Sunucu activeMode değerini (idle / human / ai) takip eder. Beş saniye boyunca hiçbir insan katılmazsa sunucu AI'ya sıra verir. Bot, piksel yerine doğrudan Game Boy bellek adreslerini okur; bu sayede savaş bayrağını RAM'den kontrol ederek savaşları algılar.
Bunu kendi siteme gömebilir miyim?
Evet. Paylaşımlı deneyim için yigitkonur.github.io/wasm-pokemon-red/ adresini işaret eden tek bir iframe ekleyin. Ya da repo'yu klonlayıp ROM'u değiştirin ve sunucuyu yaklaşık 20 dakikada Railway'e deploy edin.
Çalıştırma maliyeti nedir?
Hobi ölçeğinde esasen sıfır. GitHub Pages statik dosyaları barındırır, Railway'in ücretsiz katmanı WebSocket sunucusunu karşılar ve Upstash Redis'in günlük 10.000 istek ücretsiz kotası var. Ciddi trafik alana kadar toplam maliyet: $0.