6 people like it.
Like the snippet!
Flappy bird with sequence expressions
This is a somewhat sensible solution to the flappy bird dojo by Phil Trelford. It uses sequence expressions eliminate some mutation of the flappy bird. To play this sample on Windows simply copy the code from the raw view and execute in F# interactive, for cross platform implementations check out: http://trelford.com/prognet15.zip
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:
|
(Standard flappy bird setup)
// In this version, we use the F# sequence to represent the flappy
// bird game logic. Each time the screen updates, we call MoveNext
// on the workflow and so we can nicely keep state. We still need
// one mutable variable for the flappy bird, because he's also
// mutated by the keyboard & click events.
let flappy = ref { X = 30.0; Y = 150.0; VY = 0.0; IsAlive=true }
let rec starting () = seq {
for i in 0 .. 100 do yield ()
yield! running (0) }
and running (scroll) = seq {
flappy := update !flappy
let bird = !flappy
move bird_sing (bird.X, bird.Y)
for ((x,y),tube1,tube2) in tubes do
let scrollX = float (x + scroll)
if scrollX > bird.X - 10.0 && scrollX < bird.X + 10.0 then
if (bird.Y + bird_sing.Height > float y + 100.0) ||
(bird.Y < float y - 320.0 + tube1.Height) then
yield! dead()
move tube1 (float (x + scroll),float (y-320))
move tube2 (float (x + scroll),float (y+100))
yield ()
yield! running (scroll - 1) }
and dead () = seq {
// Just loop forever in the dead state
yield ()
yield! dead () }
// The user interaction - this is where we still have a bit of mutation
// because we are changing the flappy bird
let flapme () = if flappy.Value.IsAlive then flappy := flap !flappy
let window = Window(Title="Flap me",Width=288.0,Height=440.0)
window.Content <- canvas
window.MouseDown.Add(fun _ -> flapme())
window.KeyDown.Add(fun args -> if args.Key = Key.Space then flapme())
window.Show()
let game = starting().GetEnumerator()
CompositionTarget.Rendering.Add(fun _ ->
game.MoveNext() |> ignore
)
|
#if INTERACTIVE
#r "PresentationCore.dll"
#r "PresentationFramework.dll"
#r "System.Xaml.dll"
#r "UIAutomationTypes.dll"
#r "WindowsBase.dll"
#endif
open System
open System.IO
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Imaging
/// Bird type
type Bird = { X:float; Y:float; VY:float; IsAlive:bool }
/// Respond to flap command
let flap (bird:Bird) = { bird with VY = - System.Math.PI }
/// 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)]
let level = generateLevel 10
/// Converts specified bitmap to an image
let toImage (bitmap:#BitmapSource) =
let w, h = float bitmap.PixelWidth, float bitmap.PixelHeight
Image(Source=bitmap,Stretch=Stretch.Fill,Width=w,Height=h)
/// Loads image from file if it exists or the url otherwise
let load file url =
let path = Path.Combine(__SOURCE_DIRECTORY__, file)
let uri =
if File.Exists(path)
then Uri(path, UriKind.Relative)
else Uri(url, UriKind.Absolute)
BitmapImage(uri)
let bg =
load "bg.png" "http://flappycreator.com/default/bg.png"
|> toImage
let ground =
load "ground.png" "http://flappycreator.com/default/ground.png"
|> toImage
let tube1 = load "tube1.png" "http://flappycreator.com/default/tube1.png"
let tube2 = load "tube2.png" "http://flappycreator.com/default/tube2.png"
let bird_sing =
load "bird_sing.png" "http://flappycreator.com/default/bird_sing.png"
|> toImage
let canvas = Canvas()
let move image (x,y) =
Canvas.SetLeft(image, x)
Canvas.SetTop(image, y)
let add image (x,y) =
canvas.Children.Add(image) |> ignore
move image (float x, float y)
add bg (0,0)
add bird_sing (30,150)
// Level's tubes
let tubes =
[for (x,y) in level ->
let tube1 = toImage tube1
let tube2 = toImage tube2
add tube1 (x,y-320)
add tube2 (x,y+100)
(x,y), tube1, tube2]
add ground (0,360)
val flappy : Bird ref
Full name: Script.flappy
Multiple items
val ref : value:'T -> 'T ref
Full name: Microsoft.FSharp.Core.Operators.ref
--------------------
type 'T ref = Ref<'T>
Full name: Microsoft.FSharp.Core.ref<_>
Bird.X: float
Bird.Y: float
val starting : unit -> seq<unit>
Full name: Script.starting
Multiple items
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
val i : int
val running : scroll:int -> seq<unit>
Full name: Script.running
val scroll : int
val update : (Bird -> Bird)
Full name: Script.update
Updates bird with gravity & physics
val bird : Bird
val move : image:UIElement -> x:float * y:float -> unit
Full name: Script.move
val bird_sing : Image
Full name: Script.bird_sing
val x : int
val y : int
val tube1 : Image
val tube2 : Image
val tubes : ((int * int) * Image * Image) list
Full name: Script.tubes
val scrollX : float
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
property FrameworkElement.Height: float
val dead : unit -> seq<unit>
Full name: Script.dead
val flapme : unit -> unit
Full name: Script.flapme
property Ref.Value: Bird
Bird.IsAlive: bool
val flap : bird:Bird -> Bird
Full name: Script.flap
Respond to flap command
val window : Window
Full name: Script.window
Multiple items
type Window =
inherit ContentControl
new : unit -> Window
member Activate : unit -> bool
member AllowsTransparency : bool with get, set
member Close : unit -> unit
member DialogResult : Nullable<bool> with get, set
member DragMove : unit -> unit
member Hide : unit -> unit
member Icon : ImageSource with get, set
member IsActive : bool
member Left : float with get, set
...
Full name: System.Windows.Window
--------------------
Window() : unit
property ContentControl.Content: obj
val canvas : Canvas
Full name: Script.canvas
event UIElement.MouseDown: IEvent<MouseButtonEventHandler,MouseButtonEventArgs>
member IObservable.Add : callback:('T -> unit) -> unit
event UIElement.KeyDown: IEvent<KeyEventHandler,KeyEventArgs>
val args : KeyEventArgs
property KeyEventArgs.Key: Key
type Key =
| None = 0
| Cancel = 1
| Back = 2
| Tab = 3
| LineFeed = 4
| Clear = 5
| Return = 6
| Enter = 6
| Pause = 7
| Capital = 8
...
Full name: System.Windows.Input.Key
field Key.Space = 18
Window.Show() : unit
val game : Collections.Generic.IEnumerator<unit>
Full name: Script.game
type CompositionTarget =
inherit DispatcherObject
member Dispose : unit -> unit
member RootVisual : Visual with get, set
member TransformFromDevice : Matrix
member TransformToDevice : Matrix
static event Rendering : EventHandler
Full name: System.Windows.Media.CompositionTarget
event CompositionTarget.Rendering: IEvent<EventHandler,EventArgs>
Collections.IEnumerator.MoveNext() : bool
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
More information