İçindekiler
Yılan oyunu, küçük görünmesine rağmen oyun programlamasının en önemli sorularını barındırır: Zaman nasıl ilerler, klavye girdisi nasıl alınır, rastgele yem nereye çıkar ve ekran nasıl çizilir? Haskell bu sorulara ilginç bir ayrım getirir: Oyunun kuralları saf fonksiyonlarda yaşar; klavye, saat, rastgelelik ve terminal gibi dış dünya ayrıntıları ise kontrollü biçimde IO içinde tutulur. Böylece test edilebilir, tahmin edilebilir ve genişletilebilir bir oyun döngüsü elde ederiz.
``
Önce durum: Oyunun tamamı bir değer
Saf fonksiyonel tasarımda gizli, değiştirilebilir global değişkenler yerine oyunun tüm anlık bilgisi tek bir veri yapısında tutulur. Her karede eski durum alınır, yeni durum üretilir. Matematiksel olarak oyun adımı şöyle düşünülebilir:
\[S_{t+1} = \operatorname{update}(I_t, R_t, S_t)\]Burada $S_t$ oyun durumu, $I_t$ kullanıcı girdisi ve $R_t$ ise rastgelelikten gelen bilgidir. Kritik nokta şudur: update, dış dünyaya dokunmadan sonuç üretirse aynı girdiler için her zaman aynı sonucu verir.
type Pos = (Int, Int)
data Direction = Up | Down | Left | Right
deriving (Eq, Show)
data Game = Game
{ snake :: [Pos]
, direction :: Direction
, food :: Pos
, alive :: Bool
} deriving Show
Bu modelde listenin başı yılanın kafasıdır. Game değeri, ekran çizimi için gereken her şeyi taşır; dolayısıyla bir hata oluştuğunda belirli bir durumu kaydedip sonradan yeniden oynatmak da mümkündür.
| Yaklaşım | Durum nerede yaşar? | Test etme | Hata ayıklama |
|---|---|---|---|
| Değişken tabanlı döngü | Global veya mutable nesnelerde | Zor | Geçmiş durum kaybolabilir |
| Saf durum dönüşümü | Game değerinde |
Kolay | Durumlar kaydedilebilir |
Saf hareket ve çarpışma kuralları
Yılanın bir hücre ilerlemesi, tamamen saf bir dönüşümdür. Yem yenirse kuyruk korunur; yenmezse son eleman atılır. Bu kararın içinde ne terminal ne de IO bulunur.
nextPos :: Direction -> Pos -> Pos
nextPos Up (x, y) = (x, y - 1)
nextPos Down (x, y) = (x, y + 1)
nextPos Left (x, y) = (x - 1, y)
nextPos Right (x, y) = (x + 1, y)
step :: Game -> Game
step game = game { snake = newSnake, alive = safe }
where
head' = nextPos (direction game) (head $ snake game)
ateFood = head' == food game
body' = if ateFood then snake game else init (snake game)
newSnake = head' : body'
safe = head' `notElem` body' && insideBoard head'
insideBoard :: Pos -> Bool
insideBoard (x, y) = x >= 0 && x < 30 && y >= 0 && y < 20
step fonksiyonunun güzelliği, örnek bir Game verip doğrudan sonuç bekleyebilmemizdir. Örneğin “duvara yaklaşınca alive yanlış olur mu?” sorusu terminal açmadan test edilir. Fonksiyonel programlamadaki referans şeffaflığı tam olarak bu rahatlıktır.
Monadlar: Saf çekirdek ile dış dünyayı ayırmak
Peki yem için rastgele koordinat nasıl üretilecek? Rastgelelik dış dünyaya ait olduğundan IO kullanabiliriz. Ancak IOyu oyun kurallarına yaymak yerine yalnızca sınırda tutmak iyi bir mimaridir.
spawnFood :: [Pos] -> IO Pos
spawnFood occupied = do
x <- randomRIO (0, 29)
y <- randomRIO (0, 19)
let candidate = (x, y)
if candidate `elem` occupied
then spawnFood occupied
else pure candidate
loop :: Game -> IO ()
loop game = do
render game
input <- readDirection
let moved = step (game { direction = input })
next <- if head (snake moved) == food game
then do f <- spawnFood (snake moved)
pure moved { food = f }
else pure moved
when (alive next) $ loop next
do gösterimi sihir değildir; IO eylemlerini sıraya koyan monadik bir bağlama biçimidir. render, readDirection ve spawnFood etkili işlemler yaparken, step saf kalır. Daha gelişmiş bir sürümde rastgelelik State StdGen ile taşınabilir; terminal erişimi için de ReaderT benzeri katmanlar kurulabilir.
| Katman | Sorumluluk | Örnek |
|---|---|---|
| Saf çekirdek | Kurallar ve çarpışma | step, nextPos |
| Etki sınırı | Girdi, çizim, zaman | IO |
| Durum monadı | Ek oyun verisini taşıma | State Game |
Sonuçta Haskell ile yılan oyunu, “durumu değiştir” yaklaşımından “yeni bir dünya üret” yaklaşımına geçiştir. Bu ayrım yalnızca şık değildir: testleri kolaylaştırır, tekrar oynatma özelliğini mümkün kılar ve oyunun büyümesini daha az korkutucu hale getirir.
Yorumlar