Looking for example tsdl games

This one is for the search engines…

I was looking for a way to setup a “proper” game loop, functional style.

Looking at the tsdl repo, I noticed the examples in the test folder make heavy use of match expressions. Replicating that style quickly led me to a “callback hell” type of problem though.

Anyhow, this is my take on setting up a basic game loop:

(*
 * dune exec --display=quiet bin/main.exe
 *)

module Sdl = Tsdl.Sdl
module Window = Sdl.Window
module Event = Sdl.Event

let ( let* ) = Result.bind

type error_ctx =
  | Partial of { window : Sdl.window }
  | Full of { window : Sdl.window; renderer : Sdl.renderer }

(* Attach extra context to the original error.
 * Used for resource cleanup on program exit.
 *)
let with_err_ctx ctx = Result.map_error (fun err -> (err, ctx))

let init_window () =
  let* () = Sdl.init Sdl.Init.(audio + video) in
  let* window =
    Sdl.create_window "OCaml/TSDL: CHANGE_ME" ~x:Window.pos_centered
      ~y:Window.pos_centered ~w:640 ~h:480 Window.shown
  in

  Ok window
;;

let render_frame renderer (x, y) =
  let* () = Sdl.set_render_draw_color renderer 255 127 40 255 in
  let* () = Sdl.render_clear renderer in
  let* () = Sdl.set_render_draw_color renderer 255 0 0 255 in
  let* () =
    Sdl.render_fill_rect renderer (Some (Sdl.Rect.create ~x ~y ~w:70 ~h:70))
  in
  ()
  ; Sdl.render_present renderer
  ; Ok ()
;;

type state = { quit : bool; pos : int * int }

let initial_state = { quit = false; pos = (0, 0) }

let poll evt state =
  let (x, y) = state.pos in
  let default = { state with pos = (x + 2, y + 1) } in

  if Sdl.poll_event (Some evt) then
    match Event.(enum (get evt typ)) with
    | `Quit -> { state with quit = true }
    | _ -> default
  else
    default
;;

let run_game_loop renderer =
  let rec loop evt state =
    if not state.quit then (
      let state = poll evt state in
      let* () = render_frame renderer state.pos in
      ()
      ; Sdl.delay 17l (* approx 60 FPS *)
      ; loop evt state
    ) else
      Ok ()
  in

  let evt = Event.create () in
  loop evt initial_state
;;

let start_game () =
  let* window = init_window () |> with_err_ctx None in
  let* renderer =
    Sdl.create_renderer window |> with_err_ctx (Some (Partial { window }))
  in
  let* () =
    run_game_loop renderer |> with_err_ctx (Some (Full { window; renderer }))
  in

  Ok (window, renderer)
;;

let rec destroy_resources = function
  | Some (Full { window; renderer }) ->
      ()
      ; Sdl.destroy_renderer renderer
      ; destroy_resources @@ Some (Partial { window })
  | Some (Partial { window }) ->
      ()
      ; Sdl.destroy_window window
  | None -> ()
;;

let () =
  match start_game () with
  | Ok (window, renderer) ->
      ()
      ; Sdl.destroy_renderer renderer
      ; Sdl.destroy_window window
      ; Sdl.quit ()
      ; exit 0
  | Error (`Msg msg, ctx) ->
      ()
      ; Sdl.log "Error: %s" msg
      ; destroy_resources ctx
      ; Sdl.quit ()
      ; exit 1
;;

It took a bit of fiddling before finding something I was happy with, I’ll gladly take any criticism/feedback :slight_smile:

If you have made a game or project with tsdl, please do tell us about it!

(personally, I’m going through the “Nature of Code” book and want to implement the exercises in many languages/libraries)

1 Like