All these other answers are built up upon functional programming, but make a lot of their own design decisions. One library that is built basically entirely out of functions and simple abstract data types is gloss. Here is the type for its play function from the source
-- | Play a game in a window. Like `simulate`, but you manage your own input events.
play :: Display -- ^ Display mode.
-> Color -- ^ Background color.
-> Int -- ^ Number of simulation steps to take for each second of real time.
-> world -- ^ The initial world.
-> (world -> Picture) -- ^ A function to convert the world a picture.
-> (Event -> world -> world)
-- ^ A function to handle input events.
-> (Float -> world -> world)
-- ^ A function to step the world one iteration.
-- It is passed the period of time (in seconds) needing to be advanced.
-> IO ()
As you can see, it works entirely by supplying pure functions with simple abstract types, that other libraries help you with.