vty/test/Rogue.hs

148 lines
4.7 KiB
Haskell
Raw Normal View History

2013-11-29 13:21:58 +04:00
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Graphics.Vty
import Data.Array
import qualified Data.ByteString as B
2014-01-27 01:25:13 +04:00
import Data.Default (def)
import Data.Word
import Control.Applicative
2013-11-29 13:21:58 +04:00
import Control.Lens hiding (Level)
import Control.Monad
import Control.Monad.RWS
import Control.Monad.Writer
import System.IO
import System.Random
data Dude = Dude
{ dudeX :: Int
, dudeY :: Int
} deriving (Show,Eq)
data World = World
{ dude :: Dude
, level :: Level
}
deriving (Show,Eq)
data Level = Level
{ start :: (Int, Int)
, end :: (Int, Int)
, geo :: Array (Int, Int) LevelPiece
2013-11-21 03:53:15 +04:00
-- building the geo image is expensive. Cache it. Though VTY should go through greater lengths
-- to avoid the need to cache images.
, geoImage :: Image
}
deriving (Show,Eq)
data LevelPiece
= EmptySpace
| Rock
deriving (Show, Eq)
type Game = RWST Vty () World IO
main = do
2014-01-27 01:25:13 +04:00
vty <- mkVty def
level0 <- mkLevel 1
let world0 = World (Dude (fst $ start level0) (snd $ start level0)) level0
(_finalWorld, ()) <- execRWST (play >> updateDisplay) vty world0
shutdown vty
2013-11-25 07:48:40 +04:00
mkLevel difficulty = do
let size = 80 * difficulty
[levelWidth, levelHeight] <- replicateM 2 $ randomRIO (size,size)
let randomP = (,) <$> randomRIO (2, levelWidth-3) <*> randomRIO (2, levelHeight-3)
2013-11-25 07:48:40 +04:00
start <- randomP
end <- randomP
-- first the base geography: all rocks
let baseGeo = array ((0,0), (levelWidth-1, levelHeight-1))
[((x,y),Rock) | x <- [0..levelWidth-1], y <- [0..levelHeight-1]]
-- next the empty spaces that make the rooms
2013-11-25 07:48:40 +04:00
-- for this we generate a number of center points
centers <- replicateM (2 ^ difficulty + difficulty) randomP
-- generate rooms for all those points, plus the start and end
geo <- foldM (addRoom levelWidth levelHeight) baseGeo (start : end : centers)
return $ Level start end geo (buildGeoImage geo)
addRoom levelWidth levelHeight geo (centerX, centerY) = do
size <- randomRIO (5,15)
let xMin = max 1 (centerX - size)
xMax = min (levelWidth - 1) (centerX + size)
yMin = max 1 (centerY - size)
yMax = min (levelHeight - 1) (centerY + size)
let room = [((x,y), EmptySpace) | x <- [xMin..xMax - 1], y <- [yMin..yMax - 1]]
return (geo // room)
imageForGeo EmptySpace = char (defAttr `withBackColor` green) ' '
imageForGeo Rock = char defAttr 'X'
pieceA = defAttr `withForeColor` blue `withBackColor` green
dumpA = defAttr `withStyle` reverseVideo
play = do
updateDisplay
done <- processEvent
unless done play
processEvent = do
k <- ask >>= liftIO . nextEvent
if k == EvKey KEsc []
then return True
else do
case k of
EvKey (KChar 'r') [MCtrl] -> ask >>= liftIO . refresh
EvKey KLeft [] -> moveDude (-1) 0
EvKey KRight [] -> moveDude 1 0
EvKey KUp [] -> moveDude 0 (-1)
EvKey KDown [] -> moveDude 0 1
_ -> return ()
return False
moveDude dx dy = do
vty <- ask
world <- get
let Dude x y = dude world
let x' = x + dx
y' = y + dy
-- this is only valid because the level generation assures the border is always Rock
case geo (level world) ! (x',y') of
EmptySpace -> put $ world { dude = Dude x' y' }
_ -> return ()
updateDisplay :: Game ()
updateDisplay = do
let info = string defAttr "Move with the arrows keys. Press ESC to exit."
-- determine offsets to place the dude in the center of the level.
(w,h) <- asks outputIface >>= liftIO . displayBounds
theDude <- gets dude
let ox = (w `div` 2) - dudeX theDude
oy = (h `div` 2) - dudeY theDude
-- translate the world images to place the dude in the center of the level.
world' <- map (translate ox oy) <$> world
let pic = picForLayers $ info : world'
vty <- ask
liftIO $ update vty pic
world :: Game [Image]
world = do
theDude <- gets dude
theLevel <- gets level
let dudeImage = translate (dudeX theDude) (dudeY theDude) (char pieceA '@')
return [dudeImage, geoImage theLevel]
2013-11-21 03:53:15 +04:00
buildGeoImage geo =
let (geoWidth, geoHeight) = snd $ bounds geo
2013-11-21 03:53:15 +04:00
-- seems like a the repeated index operation should be removable. This is not performing random
-- access but (presumably) access in order of index.
in vertCat [ geoRow
| y <- [0..geoHeight-1]
, let geoRow = horizCat [ i
| x <- [0..geoWidth-1]
, let i = imageForGeo (geo ! (x,y))
]
]