4 people like it.

Learning Flappy Bird in F# with Nephew

Learning F# with my 11 year old nephew, he is loving it, swapping out assets and creating new ones, adjusting gravity to what feels right to him and putting in very basic collision detection. Going to keep expanding on it over the year.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
#if INTERACTIVE
#I @"../packages/MonoGame.Framework.WindowsDX.3.4.0.459/lib/net40/"
//#I @"../packages/MonoGame.Framework.WindowsGL.3.4.0.459/lib/net40/"
#r "MonoGame.Framework.dll"
#endif

/// Bird type
type Bird = { X:float; Y:float; VY:float; mutable IsAlive:bool; mutable Rectangle:Microsoft.Xna.Framework.Rectangle }
/// Respond to flap command
let flap (bird:Bird) = { bird with VY = -2.11}
/// Applies gravity to bird
let gravity (bird:Bird) = { bird with VY = bird.VY + 0.1  }
/// Applies physics to bird
let physics (bird:Bird) = { bird with Y = bird.Y + bird.VY }
/// Updates bird with gravity & physics
let update = gravity >> physics
 
/// Generates the level's tube positions
let generateLevel n =
   let rand = System.Random()
   [for i in 1..n -> 50+(i*150), 32+rand.Next(160)]

open System.IO
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input

let loadImage (device:GraphicsDevice) file =
   let path = Path.Combine(__SOURCE_DIRECTORY__, file)
   use stream = File.OpenRead(path)
   let texture = Texture2D.FromStream(device, stream)
   let textureData = Array.create<Color> (texture.Width * texture.Height) Color.Transparent
   texture.GetData(textureData)
   texture

type FlappyBird() as this =
   inherit Game()
   do this.Window.Title <- "Flappy Callum"
   let graphics = new GraphicsDeviceManager(this)
   do graphics.PreferredBackBufferWidth <- 288
   do graphics.PreferredBackBufferHeight <- 440
   let mutable spriteBatch : SpriteBatch = null
   let mutable bg : Texture2D = null
   let mutable ground : Texture2D = null
   let mutable tube1 : Texture2D = null
   let mutable tube2 : Texture2D = null
   let mutable bird_sing : Texture2D = null
   let mutable gameover : Texture2D = null
   let mutable lastKeyState = KeyboardState()
   let mutable lastMouseState = MouseState()
   let level = generateLevel 10
   let mutable flappy = { X = 30.0; Y = 150.0; VY = 0.0; IsAlive=true; Rectangle = Microsoft.Xna.Framework.Rectangle()}
   let flapMe () = if flappy.IsAlive then flappy <- flap flappy
   let mutable scroll = 0

   override this.LoadContent() =
      spriteBatch <- new SpriteBatch(this.GraphicsDevice)
      let load = loadImage this.GraphicsDevice
      bg <- load "bg.png"
      ground <- load "beartraps2.png"
      tube1 <- load "tube1.png"
      tube2 <- load "tube2.png"
      gameover <- load "gameover.png"
      bird_sing <- load "bird_sing.png"
      flappy.Rectangle <- bird_sing.Bounds
   
   override this.Update(gameTime) =
      let currentKeyState = Keyboard.GetState()
      let currentMouseState = Mouse.GetState()
      let isKeyPressedSinceLastFrame key =
         currentKeyState.IsKeyDown(key) && lastKeyState.IsKeyUp(key)
      let isMouseClicked () =
         currentMouseState.LeftButton = ButtonState.Pressed &&
         lastMouseState.LeftButton = ButtonState.Released
      if flappy.IsAlive
      then
        scroll <- scroll - 1      
        if isKeyPressedSinceLastFrame Keys.Space || isMouseClicked () 
        then flapMe ()
        flappy <- update flappy
        lastKeyState <- currentKeyState
        lastMouseState <- currentMouseState     
      else
        if isKeyPressedSinceLastFrame Keys.Space || isMouseClicked () 
        then
            scroll <- 0
            flappy <- { X = 30.0; Y = 150.0; VY = 0.0; IsAlive=true; Rectangle = Microsoft.Xna.Framework.Rectangle()}
        
   
   override this.Draw(gameTime) =
      this.GraphicsDevice.Clear Color.White
      spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied)
      let draw (texture:Texture2D) (x,y) =
         spriteBatch.Draw(texture, Rectangle(x,y,texture.Width,texture.Height), Color.White)      
      draw bg (0,0)
      flappy.Rectangle <- Rectangle(int flappy.X, int flappy.Y, bird_sing.Width, bird_sing.Height)
      draw bird_sing (int flappy.X,int flappy.Y)
      for (x,y) in level do
         let x = x+scroll         
         draw tube1 (x,-320+y)
         draw tube2 (x,y+100)
      draw ground (0,360)
      if flappy.Rectangle.Intersects(Rectangle(0, 360, ground.Width, ground.Height))
      then 
        flappy.IsAlive <- false
        draw gameover (-40, 150)     
      spriteBatch.End()
      

do
   use game = new FlappyBird()
   game.Run()
type Bird =
  {X: float;
   Y: float;
   VY: float;
   mutable IsAlive: bool;
   mutable Rectangle: obj;}

Full name: Script.Bird


 Bird type
Bird.X: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
Bird.Y: float
Bird.VY: float
Bird.IsAlive: bool
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
Bird.Rectangle: obj
namespace Microsoft
val flap : bird:Bird -> Bird

Full name: Script.flap


 Respond to flap command
val bird : Bird
val gravity : bird:Bird -> Bird

Full name: Script.gravity


 Applies gravity to bird
val physics : bird:Bird -> Bird

Full name: Script.physics


 Applies physics to bird
val update : (Bird -> Bird)

Full name: Script.update


 Updates bird with gravity & physics
val generateLevel : n:int -> (int * int) list

Full name: Script.generateLevel


 Generates the level's tube positions
val n : int
val rand : System.Random
namespace System
Multiple items
type Random =
  new : unit -> Random + 1 overload
  member Next : unit -> int + 2 overloads
  member NextBytes : buffer:byte[] -> unit
  member NextDouble : unit -> float

Full name: System.Random

--------------------
System.Random() : unit
System.Random(Seed: int) : unit
val i : int
System.Random.Next() : int
System.Random.Next(maxValue: int) : int
System.Random.Next(minValue: int, maxValue: int) : int
namespace System.IO
val loadImage : device:'a -> file:string -> 'b

Full name: Script.loadImage
val device : 'a
val file : string
val path : string
type Path =
  static val DirectorySeparatorChar : char
  static val AltDirectorySeparatorChar : char
  static val VolumeSeparatorChar : char
  static val InvalidPathChars : char[]
  static val PathSeparator : char
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member GetDirectoryName : path:string -> string
  static member GetExtension : path:string -> string
  static member GetFileName : path:string -> string
  ...

Full name: System.IO.Path
Path.Combine([<System.ParamArray>] paths: string []) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val stream : FileStream
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...

Full name: System.IO.File
File.OpenRead(path: string) : FileStream
val texture : 'b
val textureData : obj
module Array

from Microsoft.FSharp.Collections
val create : count:int -> value:'T -> 'T []

Full name: Microsoft.FSharp.Collections.Array.create
Multiple items
type FlappyBird =
  inherit obj
  new : unit -> FlappyBird
  override Draw : gameTime:'a -> 'b
  override LoadContent : unit -> 'a
  override Update : gameTime:'a -> 'b

Full name: Script.FlappyBird

--------------------
new : unit -> FlappyBird
val this : FlappyBird
override FlappyBird.LoadContent : unit -> 'a

Full name: Script.FlappyBird.LoadContent
override FlappyBird.Update : gameTime:'a -> 'b

Full name: Script.FlappyBird.Update
override FlappyBird.Draw : gameTime:'a -> 'b

Full name: Script.FlappyBird.Draw
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val game : FlappyBird
Raw view Test code New version

More information

Link:http://fssnip.net/rO
Posted:8 years ago
Author:John Nolan, Callum Simpson
Tags: f# , flappy bird clone , 11 year old