I wrote this up for fun, ended up teaching myself the importance of applicative functors in the process
First you have your components.
type Position = Float
type Velocity = Float
type Name = String
Now, your systems.
position :: Position -> Velocity -> Position
position x v = x + v
velocity :: Velocity -> Velocity
velocity v = v - 9.81
draw :: Position -> Name -> String
draw x n = n ++ "@" ++ show x
All pretty basic. Next we add our entity type as well as a couple of convenience functions.
data Entity = Entity {
x :: Maybe Position,
v :: Maybe Velocity,
n :: Maybe Name
}
makeStatic :: Position -> Name -> Entity
makeStatic x n = Entity (Just x) Nothing (Just n)
makeDynamic :: Position -> Velocity -> Name -> Entity
makeDynamic x v n = Entity (Just x) (Just v) (Just n)
Hopefully this is still pretty straightforward. Here's where things start to get a bit hairy.
First, I made a convenience operator that basically works the same as
or in Lua. My Haskell standard library knowledge is very shallow so I may have accidentally used a name that another module defines.
(<?>) :: Maybe a -> Maybe a -> Maybe a
Just x <?> _ = Just x
Nothing <?> x = x
I then broke the tick function down into two discrete parts.
tick :: Entity -> IO Entity
tick e = do
render e
return (update e)
The update function is pure and makes the changes to each component out-of-order. In a real implementation you would probably want to bucket things to reduce latency.
update :: Entity -> Entity
update e = Entity
(position <$> (x e) <*> (v e) <?> (x e))
(velocity <$> (v e) <?> (v e))
(n e)
Here you can see the usage of the operator I defined. Let's analyze the calculation of the position. It basically reads: "If both the position and velocity are
Just values, make the new position the result of the
position system function. Otherwise, copy over the old position." The render function is a bit simpler.
render :: Entity -> IO ()
render e = maybe (return ()) putStrLn (draw <$> (x e) <*> (n e))
If it has both a position and a name, it prints a line. If not, it doesn't do anything.
I didn't bother to make an actual game loop, but it should be pretty straightforward. You would just map the tick function over a list of entities to get the next frame's entity list.
Here's the full, runnable source file if you're interested.
http://hastebin.com/fusenunawa.hs