brick/programs/Rogue.hs
2015-06-24 20:58:34 -07:00

154 lines
4.9 KiB
Haskell

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Graphics.Vty
import Data.Array
import Data.Default (def)
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import System.Random
import System.Exit
import Brick.Main
import Brick.Core
import Brick.Widgets.Core
import Brick.Widgets.Border
import Brick.Widgets.Center
data Player = Player
{ playerCoord :: Coord
} deriving (Show,Eq)
data World = World
{ player :: Player
, level :: Level
}
deriving (Show,Eq)
data Level = Level
{ levelStart :: Coord
, levelEnd :: Coord
, levelGeo :: Geo
-- building the geo image is expensive. Cache it. Though VTY should go
-- through greater lengths to avoid the need to cache images.
, levelGeoImage :: Image
}
deriving (Show,Eq)
data LevelPiece
= EmptySpace
| Rock
deriving (Show, Eq)
type Geo = Array Coord LevelPiece
type Coord = (Int, Int)
main :: IO ()
main = do
level0 <- mkLevel 1
let world0 = World (Player (levelStart level0)) level0
app = def { appDraw = updateDisplay
, appHandleEvent = processEvent
, appChooseCursor = neverShowCursor
}
defaultMain app world0
-- |Generate a level randomly using the specified difficulty. Higher
-- difficulty means the level will have more rooms and cover a larger area.
mkLevel :: Int -> IO Level
mkLevel difficulty = do
let size = 80 * difficulty
[levelWidth, levelHeight] <- replicateM 2 $ randomRIO (size,size)
let randomP = (,) <$> randomRIO (2, levelWidth-3) <*> randomRIO (2, levelHeight-3)
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
-- 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)
-- |Add a room to a geography and return a new geography. Adds a
-- randomly-sized room centered at the specified coordinates.
addRoom :: Int
-> Int
-- ^The width and height of the geographical area
-> Geo
-- ^The geographical area to which a new room should be added
-> Coord
-- ^The desired center of the new room.
-> IO 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)
pieceA, dumpA :: Attr
pieceA = defAttr `withForeColor` blue `withBackColor` green
dumpA = defAttr `withStyle` reverseVideo
processEvent :: Event -> World -> EventM World
processEvent k world = do
case k of
EvKey KEsc [] -> liftIO exitSuccess
EvKey KLeft [] -> return $ movePlayer world (-1) 0
EvKey KRight [] -> return $ movePlayer world 1 0
EvKey KUp [] -> return $ movePlayer world 0 (-1)
EvKey KDown [] -> return $ movePlayer world 0 1
_ -> return world
movePlayer :: World -> Int -> Int -> World
movePlayer world dx dy = do
let Player (x, y) = player world
x' = x + dx
y' = y + dy
case levelGeo (level world) ! (x',y') of
EmptySpace -> world { player = Player (x',y') }
_ -> world
updateDisplay :: World -> [Widget]
updateDisplay world = [ info, playerLayer, geoLayer ]
where
info = vBox [ (hCenter $ txt "Move with the arrows keys. Press ESC to exit.", High)
, (hBorder, High)
]
(px, py) = playerCoord $ player world
playerLoc = Location (px, py)
theLevel = level world
playerLayer = center $ raw (char pieceA '@')
geoLayer = centerAbout playerLoc $ raw $ levelGeoImage theLevel
imageForGeo :: LevelPiece -> Image
imageForGeo EmptySpace = char (defAttr `withBackColor` green) ' '
imageForGeo Rock = char defAttr 'X'
buildGeoImage :: Geo -> Image
buildGeoImage geo =
let (geoWidth, geoHeight) = snd $ bounds geo
in vertCat [ geoRow
| y <- [0..geoHeight-1]
, let geoRow = horizCat [ i
| x <- [0..geoWidth-1]
, let i = imageForGeo (geo ! (x,y))
]
]
playerX :: Player -> Int
playerX = fst . playerCoord
playerY :: Player -> Int
playerY = snd . playerCoord