./jerome{m}

~/Experiments/Flappy Bird in Haskell.md · experiment · 2 min · updated

Flappy Bird in Haskell

2015 · Haskell · Yampa · SDL2 · Source

Flappy Bird clone running in Haskell

Functional Reactive Programming (FRP)11Wikipedia · Functional reactive programming simplifies real-time application development by modeling systems with continuous time. I wanted to make another graphical app with Haskell and decided to make a Flappy Bird clone. Since Haskell is purely functional, I wanted to go down the rabbit hole and avoid handling the game state/inputs with dirty IO Monads as much as possible. After benchmarking several libraries, I decided to go with Yampa, which works well with SDL2.

The Basics of FRP in Yampa

FRP allows us to model the game world as a series of signals that change over time. In Yampa, we use signal functions to manage these signals, defining relationships between inputs (like user commands) and outputs (game state changes). This declarative approach contrasts with traditional imperative loops, providing clearer and more expressive code.

Key Concepts

  1. Signal Functions: Core components that transform input signals into output signals over time.
  2. Event Handling: Managing discrete events (e.g., user inputs) within the continuous signal framework.
  3. Integration: Handling the accumulation of changes, such as the bird's position based on velocity and gravity.

Creating Flappy Bird

To create Flappy Bird, we need to define several components:

  1. Bird Motion: Model the bird’s vertical position and velocity, affected by gravity and player inputs.
  2. Obstacle Generation: Generate pipes at regular intervals, each with varying heights.
  3. Collision Detection: Check if the bird collides with pipes or the ground.
  4. Score Calculation: Derive the player’s score from elapsed time.

There are also a few other little things, like playing a point sound when a pipe passes the bird.

Bird Motion

For instance, here is the signal corresponding to the bird moving forward and being affected by gravity.

haskell
fallingBird :: Bird -> SF a Bird
fallingBird (Bird y0 v0 s0) = proc _ -> do
  v <- imIntegral v0 -< 250
  -- ^ velocity
  y <- imIntegral y0 -< (v*2)
  -- ^ position
  s <- imIntegral s0 -< 15
  -- ^ to handle bird sprite animation state
  p <- time >>^ ((6 *) . sin . ((2 * pi) *)) -< ()
  -- ^ this will make the bird fly more "naturally" (like a wave)
  returnA -< Bird (y+p) v s

As you can see, only time is affecting this signal.

Events from input

haskell
flapTrigger :: SF AppInput (Event ())
flapTrigger = proc input -> do
  mouseTap <- lbp -< input
  spacebarTap <- keyPressed ScancodeSpace -< input
  returnA -< mouseTap `lMerge` spacebarTap

flapVelocity :: Double
flapVelocity = -100.0

flappingBird :: Bird -> SF AppInput Bird
flappingBird bird0 = switch sf cont
  where sf = proc input -> do
           b <- fallingBird bird0 -< ()
           flap <- flapTrigger -< input
           returnA -< (b, flap `tag` b)
        cont (Bird y _ s) = flappingBird $ Bird y flapVelocity s

Here flapTrigger returns an Event when the mouse or space bar is tapped. We compose it with fallingBird to make the complete flappingBird signal. When an event is detected, the continuation resets the bird’s Y velocity to the fixed flapVelocity, making it rise again.

Source code: github.com/Rydgel/flappy-haskell


Back to Experiments · read the FRP retrospective · see also Game of Life in Haskell · Home.

notes

  1. Wikipedia · Functional reactive programming↩