Benjamin Kovach
Benjamin Kovach

Reputation: 3260

How to avoid operating in the IO monad for this whole program?

I'm writing a Sokoban program in Haskell using the Gloss library, and I'm reaching the point where I'd like to, when the player beats a level, load in a new level from a text file and have the program continue on.

I'm having a little bit of difficulty with this because of Gloss's limitations -- the play function to set up a game and have it continuously update looks like this:

play    :: forall world
        .  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 ()

(Copied directly from http://hackage.haskell.org/packages/archive/gloss/1.7.4.1/doc/html/Graphics-Gloss-Interface-Pure-Game.html)

Here's the world type I'm using:

data Game = Game
  { levelNumber  :: Int,
    currentLevel :: Level Square,
    won          :: Bool }

where Levels contain the blocks in the current level. I'm reading in Games using something like this (haven't actually made a generalized one yet, but this is essentially all it would be with a filename argument):

startGame = do
  lvl <- readFile "levels/level001.lvl"
  let lvl' = parseLevel lvl
  return $ Game 1 lvl' False

So, my difficulty is arising because of the update functions in play. I can easily take a Game and produce a Picture (and a Game, etc) without having to read any data in from the file system if I'm just operating on a single level, but since I'm loading levels from files during the middle of the game, I don't know how to avoid making all of my Games IO Games. Maybe this isn't possible in this circumstance, and maybe that's for a good reason? I will always be operating on a Game pulled from a file but I don't know if it's avoidable at any given point, and if it is, I'd like to avoid it.

Upvotes: 6

Views: 950

Answers (1)

Benjamin Kovach
Benjamin Kovach

Reputation: 3260

I ended up using Gloss's playIO from Graphics.Gloss.Interface.IO.Game. I needed to change my a couple of my functions to operate on pure data types and output them wrapped in the IO monad. Here are the types I had to change:

worldToPicture :: World -> IO Picture
eventHandler   :: Event -> World -> IO Picture
stepWorld      :: Float -> World -> IO World 

For the most part, this only resulted in adding some returns to my currently existing functions, but it ended up adding a LOT of functionality (like saving on the fly, loading new levels, using BMP files for graphics, etc). I was also able to keep almost all of my currently existing code free from IO since the new functions still took pure data types as parameters. It ended up being a really easy refactor and solved my problem perfectly.

Upvotes: 3

Related Questions