Joey Hess: 7drl 2015 day 1 groundwork
First, some groundwork. I'm writing Scroll in Haskell, so let's get the core data types and monads and IO squared away. Then I can spend days 2-7 writing entirely pure functional code, in the Haskell happy place. To represent the current level, I'm using a Vector of Vectors of Chars. Actually, MVectors, which can be mutated safely by pure code running inside the ST monad, so it's fast and easy to read or write any particular location on the level.
-- Writes a Char to a position in the world.
writeWorld :: Pos -> Char -> M ()
writeWorld (x, y) c = modWorld $ \yv -> do
xv <- V.read yv y
V.write xv x c
showPlayer :: M ()
showPlayer = writeWorld (5,8) '@'
type M = StateT S (ST RealWorld)
. (It could be
forall s. StateT S (ST s)
, but I had some trouble getting that to type
check, so I fixed s
to RealWorld
, which is ok since it'll be run using
stToIO
.
Next, a concept of time, and the main event loop. I decided to use a
continutation passing style, so the main loop takes
the current continuation, and runs it to get a snapshot of the state to
display, and a new continutation. The advantage of using continuations
this way is that all the game logic can be handled in the pure code.
I should probably be using the Cont monad in my monad stack, but
I've not learned it and lack time. For now I'm handling the
continuations by hand, which seems ok.
updateWorld :: Step
updateWorld (Just 'Q') = do
addMessage "Are you sure you want to quit? [yn]"
next $ \i -> case i of
Just 'y' -> quit
_ -> continue
updateWorld input = do
addMessage ("pressed " ++ show input)
continue
Start time: After midnight last night. Will end by midnight next Friday. Lines of code written today: 368 Craziest type signature today:
writeS :: forall a. ((Vec2 a -> ST RealWorld ()) -> M ()) -> Pos -> a -> M ()
By the way, there's a whole LambdaHack library for Haskell, targeted at just this kind of roguelike construction. It looks excellent. I'm not using it for two reasons:
- Scroll is going to be unusual in a lot of ways, and LambdaHack probably makes some assumptions that don't fit.
mainSer :: (MonadAtomic m, MonadServerReadRequest m) => [String] -> COps -> (m () -> IO ()) -> (COps -> DebugModeCli -> ((FactionId -> ChanServer ResponseUI RequestUI -> IO ()) -> (FactionId -> ChanServer ResponseAI RequestAI -> IO ()) -> IO ()) -> IO ()) -> IO ()
That's a lot of stuff to figure out! I only have a week, so it's probably easier to build my own framework, and this gives me an opportunity to learn more generally useful stuff, like how to use mutable Vectors.