Contents

Getting Started With Incr_dom - Part 3

Contents

Note: Incr dom is superceded by a new, much easier to use library called Bonsai

In this installment of the tutorial we’ll get the basics of the Tetris game working. We’ll create a 10x24 space board and use the standard pieces. There will be a preview box, score, and line clear animation.

Adding A Board

So we don’t clutter the main tetris.ml, let’s create a new file lib/board.ml to store the board model. This will encapsulate both the shapes and the board operations. To start create a type for the cells:

module Cell = struct
  type t = S | Z | T | L | I | O | J | Empty
  [@@deriving sexp, compare]
end

Fairly straight forward, we put it in a module to keep the helper functions namespaced. Now we need to create our board representation. We’ll use an immutable map because OCaml arrays are mutable. We’d have to copy the array each time we made and update, but the map will automatically share as much as possible.

module IntPair = struct
  module T = struct
    type t = int * int
    [@@deriving compare, sexp]
  end

  include T 
  include Comparable.Make(T)
end

type t = Cell.t IntPair.Map.t
[@@deriving sexp, compare]

We wrap up the int * int type in a module so we can derive the Map module without having to deal with all the type variables. Now that we have our board, we need an initial empty board. Let’s create a variable to store that:

let empty_board =
  let board = ref IntPair.Map.empty in
  for x=0 to 9 do
    for y=0 to 23 do
      board := IntPair.Map.set !board ~key:(x, y) ~data:Cell.Empty
    done
  done;
  !board
;;

Pretty simple, take an empty map and fill it with an entry for every coordinate in our board mapping to the empty cell. Let’s now add this to the overall model and try to show it on the New Game screen. In lib/tetris.ml a couple updates are needed. First the Model.t type needs to have a board field. The Model.init function needs to set the board field to Board.empty_board. Lastly, when we ChangePage to the NewGame page we need to reset the board state.

module Model = struct
  (* ... *)
  type t = {
    current_page : page;
    board : Board.t;
  }
  [@@deriving sexp, compare]

  let init = { 
    current_page = MainMenu;
    board = Board.empty_board;
  }
  (* ... *)
end
(* ... *)
let apply_action (model : Model.t) (* ... *) =
  match action with
  | ChangePage NewGame ->
    Model.{ board = Board.empty_board; current_page = NewGame }

  | ChangePage new_page ->
    Model.{ model with current_page = new_page }
;;

But, the board still needs to be drawn on screen. To do that we need to update the new_game_page function to take a board and render the appropriate HTML.

Under construction…