How to implement multiple levels in Gloss Haskell?

225 Views Asked by At

I am trying to implement a game with multiple levels in Haskell. Each level has different but generalized game state. We can define the game state like the following-

type Player = (Float, Float) -- Player coordinates
data State a = State Player a Bool

Here, a is the environment. In one level it is defined as String and in another level it is defined as ([Int], [Int]). And the Bool determines if level is complete or not.

I have two different functions implemented that uses play from Graphics.Gloss.Interface.Pure.Game. I can inject any of this functions to the main and play them individually. For example-

lv1 = play window black 90 (State (0,0) "Hello" False) drawLv1 handleLv1 updateLv1
lv2 = play window black 90 (State (0,0) ([1,2,3,4], [0,0,0,0]) False) drawLv2 handleLv2 updateLv2

main :: IO()
main = lv1
-- main = lv2

However, I want to go from lv1 to lv2 somehow. I know that Haskell functions can be passed through as values and there is obviously a way to change the main function from lv1 to lv2 but I cannot figure that out.

A solution or maybe an idea on how the problem can be generalized and handled properly, would be very nice.

2

There are 2 best solutions below

0
pptx704 On BEST ANSWER

I decided to write how I finally solved the problem since others might face the same problem as me-

First of all, I changed the State data to make a generalized system for better transition between different levels. So instead of current State a data, I now have State but with a new parameter Level

type Player = (Float, Float) -- Player coordinates
-- data State a = State Player a Bool
data State = State {
    getPlayer :: Player,
    getLevel :: Level,
    levelCompleted :: Bool
}
data Level = Lv1 String | Lv2 ([Int], [Int])

Then I needed to fix the level specific functions accordingly. For example-

updateLv1 :: Float -> State -> State
-- If com is true then level is complete
updateLv1 _ (State player (Lv1 st) com) =
    if com then State (0,0) ([1,2,3,4], [0,0,0,0]) False
    else
        ...
        ...

updateLv1 time state = commonUpdater time state

Same goes for drawing and event handlers.

Finally I had to fix the common handlers-

commonUpdater time state = case getLevel state of
    Lv1 _ = updateLv1 time state
    Lv2 _ = updateLv2 time state

I had to implement the commonHandler and commonDrawer too. The process is almost the same.

Finally the play function was easy. I just had to initialize the Lv1 state and rest were to be handled by common functions-

game :: IO()
game = play window black 90 (State (0,0) (Lv2 "Hello") False) commonDrawer commonHandler commonUpdater

main :: IO()
main = game

That's all!

2
Paul Johnson On

When you say that the levels can have different types, somehow your game code that plays the levels must be interpreting and modifying this state value. So there must be functions from a String to some types in your game, and equivalent function from ([Int], [Int]) to the same types.

If so then you need to put these functions in a class:

class MyGameState a where
   someFunction :: a -> Int
   someOtherFunction :: a -> Thing
   updateGameState :: Int -> a -> a

instance MyGameState String where
   someFunction str = .....
   someOtherFunction str = ......
   updateGameState n oldState = ......

And likewise for instance MyGameState ([Int],[Int]). Then you can define your game play function using someFunction and someOtherFunction.