2013-11-29 13:21:58 +04:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
2013-10-25 01:40:48 +04:00
|
|
|
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)
|
2013-10-25 01:40:48 +04:00
|
|
|
import Data.Word
|
|
|
|
|
|
|
|
import Control.Applicative
|
2013-11-29 13:21:58 +04:00
|
|
|
import Control.Lens hiding (Level)
|
2013-10-25 01:40:48 +04:00
|
|
|
import Control.Monad
|
|
|
|
import Control.Monad.RWS
|
|
|
|
import Control.Monad.Writer
|
|
|
|
|
|
|
|
import System.IO
|
|
|
|
import System.Random
|
|
|
|
|
2013-11-20 23:43:13 +04:00
|
|
|
data Dude = Dude
|
|
|
|
{ dude_x :: Int
|
|
|
|
, dude_y :: Int
|
|
|
|
} deriving (Show,Eq)
|
2013-10-25 01:40:48 +04:00
|
|
|
|
|
|
|
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.
|
|
|
|
, geo_image :: Image
|
2013-10-25 01:40:48 +04:00
|
|
|
}
|
|
|
|
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
|
2013-11-25 07:48:40 +04:00
|
|
|
level_0 <- mkLevel 1
|
2013-10-25 01:40:48 +04:00
|
|
|
let world_0 = World (Dude (fst $ start level_0) (snd $ start level_0)) level_0
|
2013-11-29 13:21:58 +04:00
|
|
|
(_final_world, ()) <- execRWST (play >> update_display) vty world_0
|
2013-10-25 01:40:48 +04:00
|
|
|
shutdown vty
|
|
|
|
|
2013-11-25 07:48:40 +04:00
|
|
|
mkLevel difficulty = do
|
|
|
|
let size = 80 * difficulty
|
|
|
|
[level_width, level_height] <- replicateM 2 $ randomRIO (size,size)
|
|
|
|
let randomP = (,) <$> randomRIO (2, level_width-3) <*> randomRIO (2, level_height-3)
|
|
|
|
start <- randomP
|
|
|
|
end <- randomP
|
2013-10-26 20:43:16 +04:00
|
|
|
-- first the base geography: all rocks
|
|
|
|
let base_geo = array ((0,0), (level_width, level_height))
|
|
|
|
[((x,y),Rock) | x <- [0..level_width-1], y <- [0..level_height-1]]
|
2013-11-20 23:43:13 +04:00
|
|
|
-- 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 (add_room level_width level_height) base_geo (start : end : centers)
|
2013-11-21 03:53:15 +04:00
|
|
|
return $ Level start end geo (build_geo_image geo)
|
2013-11-20 23:43:13 +04:00
|
|
|
|
2013-11-25 07:48:40 +04:00
|
|
|
add_room level_width level_height geo (center_x, center_y) = do
|
2013-11-20 23:43:13 +04:00
|
|
|
size <- randomRIO (5,15)
|
2013-11-20 23:56:27 +04:00
|
|
|
let x_min = max 1 (center_x - size)
|
|
|
|
x_max = min (level_width - 1) (center_x + size)
|
|
|
|
y_min = max 1 (center_y - size)
|
|
|
|
y_max = min (level_height - 1) (center_y + size)
|
|
|
|
let room = [((x,y), EmptySpace) | x <- [x_min..x_max - 1], y <- [y_min..y_max - 1]]
|
|
|
|
return (geo // room)
|
2013-10-25 01:40:48 +04:00
|
|
|
|
|
|
|
image_for_geo EmptySpace = char (def_attr `with_back_color` green) ' '
|
2013-11-25 07:48:40 +04:00
|
|
|
image_for_geo Rock = char def_attr 'X'
|
2013-10-25 01:40:48 +04:00
|
|
|
|
2013-11-25 07:48:40 +04:00
|
|
|
pieceA = def_attr `with_fore_color` blue `with_back_color` green
|
2013-10-25 01:40:48 +04:00
|
|
|
dumpA = def_attr `with_style` reverse_video
|
|
|
|
|
|
|
|
play = do
|
2013-11-29 13:21:58 +04:00
|
|
|
update_display
|
2013-10-25 01:40:48 +04:00
|
|
|
done <- process_event
|
|
|
|
unless done play
|
|
|
|
|
|
|
|
process_event = do
|
|
|
|
k <- ask >>= liftIO . next_event
|
|
|
|
if k == EvKey KEsc []
|
|
|
|
then return True
|
|
|
|
else do
|
|
|
|
case k of
|
2014-01-19 12:19:56 +04:00
|
|
|
EvKey (KChar 'r') [MCtrl] -> ask >>= liftIO . refresh
|
2013-10-25 01:40:48 +04:00
|
|
|
EvKey KLeft [] -> move_dude (-1) 0
|
|
|
|
EvKey KRight [] -> move_dude 1 0
|
|
|
|
EvKey KUp [] -> move_dude 0 (-1)
|
|
|
|
EvKey KDown [] -> move_dude 0 1
|
|
|
|
_ -> return ()
|
|
|
|
return False
|
|
|
|
|
|
|
|
move_dude dx dy = do
|
|
|
|
vty <- ask
|
|
|
|
world <- get
|
|
|
|
let Dude x y = dude world
|
2013-11-20 23:56:27 +04:00
|
|
|
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 ()
|
2013-10-25 01:40:48 +04:00
|
|
|
|
2013-11-29 13:21:58 +04:00
|
|
|
update_display :: Game ()
|
|
|
|
update_display = do
|
2013-11-20 23:43:13 +04:00
|
|
|
let info = string def_attr "Move with the arrows keys. Press ESC to exit."
|
|
|
|
-- determine offsets to place the dude in the center of the level.
|
2013-12-25 11:03:22 +04:00
|
|
|
(w,h) <- asks output_iface >>= liftIO . display_bounds
|
2013-11-20 23:43:13 +04:00
|
|
|
the_dude <- gets dude
|
|
|
|
let ox = (w `div` 2) - dude_x the_dude
|
2013-11-25 07:48:40 +04:00
|
|
|
oy = (h `div` 2) - dude_y the_dude
|
2013-11-20 23:43:13 +04:00
|
|
|
-- translate the world images to place the dude in the center of the level.
|
|
|
|
world' <- map (translate ox oy) <$> world
|
|
|
|
let pic = pic_for_layers $ info : world'
|
|
|
|
vty <- ask
|
|
|
|
liftIO $ update vty pic
|
|
|
|
|
|
|
|
world :: Game [Image]
|
|
|
|
world = do
|
|
|
|
the_dude <- gets dude
|
2013-10-25 01:40:48 +04:00
|
|
|
the_level <- gets level
|
2013-11-20 23:43:13 +04:00
|
|
|
let dude_image = translate (dude_x the_dude) (dude_y the_dude) (char pieceA '@')
|
2013-11-21 03:53:15 +04:00
|
|
|
return [dude_image, geo_image the_level]
|
|
|
|
|
|
|
|
build_geo_image geo =
|
|
|
|
let (geo_width, geo_height) = snd $ bounds geo
|
|
|
|
-- seems like a the repeated index operation should be removable. This is not performing random
|
|
|
|
-- access but (presumably) access in order of index.
|
|
|
|
in vert_cat [ geo_row
|
|
|
|
| y <- [0..geo_height-1]
|
|
|
|
, let geo_row = horiz_cat [ i
|
|
|
|
| x <- [0..geo_width-1]
|
|
|
|
, let i = image_for_geo (geo ! (x,y))
|
|
|
|
]
|
|
|
|
]
|