summaryrefslogtreecommitdiff
path: root/Djup/src/Program.fs
blob: 45dea914dde125600f71fb19f9e830e525229fa7 (plain)
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
module Djup.Program

open FSharp.NativeInterop
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input
open Mibo.Elmish
open Mibo.Elmish.Graphics2D
open Mibo.Input
open Djup.Native

type GameAction =
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown

let inputMap =
    InputMap.empty
    |> InputMap.key MoveLeft Keys.Left
    |> InputMap.key MoveLeft Keys.A
    |> InputMap.key MoveRight Keys.Right
    |> InputMap.key MoveRight Keys.D
    |> InputMap.key MoveUp Keys.Up
    |> InputMap.key MoveUp Keys.W
    |> InputMap.key MoveDown Keys.Down
    |> InputMap.key MoveDown Keys.S

type Model =
    { PhysicsContext: nativeint
      CurrentSnapshot: nativeint
      PlayerId: int }

type Msg =
    | PhysicsTick of float32
    | Tick of GameTime
    | InputChanged of ActionState<GameAction>

let init (_: GameContext) : struct (Model * Cmd<Msg>) =
    LibDjup.log_set_output (
        LoggerDelegate(fun level source line message ->
            let level =
                match level with
                | LogLevel.Trace -> "TRACE"
                | LogLevel.Debug -> "DEBUG"
                | LogLevel.Info -> "INFO "
                | LogLevel.Warn -> "WARN "
                | LogLevel.Error -> "ERROR"
                | LogLevel.Fatal -> "FATAL"
                | _ -> sprintf "UNKNOWN(%d)" (int level)

            printfn "libdjup %s %s:%d: %s" level source line message)
    )

    let physics = LibDjup.physics_context_create (2, 1, 1000)

    if physics = 0 then
        failwith "Failed to allocate physics context"

    let snapshot = LibDjup.physics_context_get_snapshot physics

    let putPlayer playerId player =
        use ptr = fixed &player
        LibDjup.snapshot_put_player (snapshot, playerId, ptr)

    let playerId = 0

    match putPlayer playerId (Player()) with
    | 0 -> ()
    | errorCode -> failwithf "Failed to put player (error code %d)" errorCode

    let model =
        { PhysicsContext = physics
          CurrentSnapshot = snapshot
          PlayerId = playerId }

    model, Cmd.none

let update (msg: Msg) (model: Model) : struct (Model * Cmd<Msg>) =
    match msg with
    | PhysicsTick delta ->
        let next = LibDjup.physics_context_get_snapshot model.PhysicsContext

        match LibDjup.physics_tick (model.CurrentSnapshot, double delta, next) with
        | 0 ->
            LibDjup.physics_context_return_snapshot (model.PhysicsContext, model.CurrentSnapshot)
            { model with CurrentSnapshot = next }, Cmd.none
        | errorCode ->
            printfn "Physics tick failed, code %d" errorCode
            LibDjup.physics_context_return_snapshot (model.PhysicsContext, next)
            model, Cmd.none
    | InputChanged actionState ->
        // TODO: Actions get applied for a long time, a small press causes the
        // player to slide in the pressed direction for a while
        let mutable input = 0uy
        let isHeld action = actionState.Held.Contains action

        if isHeld MoveUp then
            input <- input ||| byte PlayerInput.Up

        if isHeld MoveDown then
            input <- input ||| byte PlayerInput.Down

        if isHeld MoveLeft then
            input <- input ||| byte PlayerInput.Left

        if isHeld MoveRight then
            input <- input ||| byte PlayerInput.Right

        let ret =
            LibDjup.snapshot_set_player_input (model.CurrentSnapshot, model.PlayerId, input)

        match ret with
        | 0 -> ()
        | errorCode -> printfn "Failed to set player input (error code %d)" errorCode

        model, Cmd.none
    | Tick _ -> model, Cmd.none

let view (ctx: GameContext) (model: Model) (buffer: RenderBuffer<RenderCmd2D>) =
    // Draw player (using a 1x1 pixel texture if no asset loaded, or load one)
    let pixel =
        Assets.getOrCreate
            "pixel"
            (fun gd ->
                let t = new Texture2D(gd, 1, 1)
                t.SetData [| Color.White |]
                t)
            ctx

    let snapshot: Snapshot =
        let ptr = model.CurrentSnapshot |> NativePtr.ofNativeInt
        NativePtr.get ptr 0

    // TODO: Snapshot.Players should be active players
    for p in snapshot.Players do
        Draw2D.sprite pixel (Rectangle(int p.Position.X, int p.Position.Y, 32, 32))
        |> Draw2D.withColor Color.Red
        |> Draw2D.submit buffer


[<EntryPoint>]
let main _ =
    let program =
        Program.mkProgram init update
        |> Program.withAssets
        |> Program.withRenderer (fun g -> Batch2DRenderer.create g view)
        |> Program.withInput
        |> Program.withSubscription (fun ctx _ -> InputMapper.subscribeStatic inputMap InputChanged ctx)
        |> Program.withFixedStep
            { StepSeconds = 1f / 60f
              MaxStepsPerFrame = 5
              MaxFrameSeconds = ValueNone
              Map = PhysicsTick }
        |> Program.withTick Tick
        |> Program.withConfig (fun (game, graphics) ->
            game.Content.RootDirectory <- "Content"
            game.Window.Title <- "Djup"
            game.IsMouseVisible <- true
            graphics.PreferredBackBufferWidth <- 800
            graphics.PreferredBackBufferHeight <- 600)

    use game = new ElmishGame<Model, Msg>(program)
    game.Run()
    0