ocamlconways-game-of-life

Building my first generation of game of life with OCaml but my board is returned empty


I am currently trying to build a game of life with OCaml but I encountered a problem when trying to apply the rules on the first generation, my board is always returned empty when I apply next_generation and I can't find a way to apply the first generation by going through my board and then return the modified board.

When I apply next_generation I expect my function to go through the lists that compose my board and to apply rules0 to each cell then return my board with the first generation applied but I always get an empty board at the end.

here is my code:

#use "topfind";;
#require "graphics";;
open Graphics ;;
open Random ;;

let put_list v I list =
  if i < 0 then 
    invalid_arg "index must be a positive"
  else
    let rec put_list_rec v i list = 
      match list with
        [] -> []
      | e::t -> 
        if i = 0 then v::t
        else e::put_list_rec v (i-1) t
    in 
    put_list_rec v i list;;

let rec put_cell v (x,y) board = 
  match board with
    []   -> board
  | e::l -> 
    if x = 0 then (put_list v y e)::l
    else e::put_cell v ((x-1),y) l;;

let board = 
  [[1;1;1;1;1;1;1;1;1;1]; 
   [0;0;0;0;0;0;0;0;0;0]; 
   [1;0;1;0;1;0;1;0;1;0];
   [0;1;0;1;0;1;0;1;0;1]; 
   [0;0;0;0;0;0;0;0;0;0]; 
   [1;1;1;1;1;1;1;1;1;1]; 
   [0;0;0;0;0;0;0;0;0;0]; 
   [1;0;1;0;1;0;1;0;1;0]; 
   [0;1;0;1;0;1;0;1;0;1]; 
   [0;0;0;0;0;0;0;0;0;0]];;

let cell_color = function
  | 0 -> white
  | _ -> black;;

let grey = rgb 127 127 127;;

let draw_cell (x,y) size color =
  if size <= 0 then 
    invalid_arg " size must be positive"
  else 
    let color = set_color color in 
    let draw_cell_rec (x,y) size color =
      fill_rect x y size size;
      set_color grey;
      draw_rect x y size size
    in 
    draw_cell_rec (x,y) size color;;

let draw_board board cellsize =
  if cellsize <= 0 then 
    invalid_arg " cellsize must be positive"
  else 
    let rec draw_board_rec (x,y) board cellsize = 
      match board with
        []                     -> ()
      | (e::l)::t when l <> [] -> 
        draw_cell (x,y) cellsize (cell_color e);
        draw_board_rec (x,y+cellsize) (l::t) cellsize
      | (e::l)::t when l = []  -> draw_board_rec (x+cellsize, 0) t cellsize
    in 
    draw_board_rec (0,0) board cellsize;;

let rules0 cell near =
  if cell < 0 || cell > 1 then 
    invalid_arg "cell must be 0 or 1" 
  else if near < 0 || near > 8 then 
    invalid_arg "near must be between 0 and 8" 
  else
    match cell with
      0 -> if near = 3 then 1 else 0
    | _ -> if (near = 3) || (near = 2) then 1 else 0;;

let count_neighbours (x,y) board size =
  if board = [] then 
    invalid_arg "board must not be empty"
  else 
    let rec count_neighbours_rec (x,y) board = 
      match (x,y) with
        (x,y) when y < 0 || y >= size || x < 0 || x >= size -> 0
      | (_,_) -> get_cell(x,y) board
    in 
      count_neighbours_rec (x-1,y-1) board + 
      count_neighbours_rec (x-1,y) board + 
      count_neighbours_rec (x-1,y+1) board + 
      count_neighbours_rec (x,y+1) board + 
      count_neighbours_rec (x,y-1) board + 
      count_neighbours_rec (x+1,y+1) board + 
      count_neighbours_rec (x+1,y) board + 
      count_neighbours_rec (x+1,y-1) board;;

let next_generation board size =
  if size < 0 then 
    invalid_arg "size must be positive"
  else 
    let bis = board in 
    let rec next_generation_rec bis (x,y) = 
      match bis with
        []        -> bis
      | []::t     -> next_generation_rec t (x+1,0)
      | (e::l)::t -> 
        put_cell (rules0 e (count_neighbours (x,y) board size)) (x,y) bis;
        next_generation_rec (l::t) (x,y+1)
    in 
    next_generation_rec bis (0,0);;

here is an exemple of what I get:

next_generation board 10;;

;;
- : int list list = []

Solution

  • I suspect your problem arises from thinking board is mutable and that the put_cell function will modify it.

    But lists in OCaml are not mutable. You can work on a list to generate a new list and then pass that along to another function.

    Ignoring the return value of put_cell as you have in next_generation is a sure sign that something is amiss, and OCaml should warn you about it. See the below very simple example where the value of the expression 1 + 2 is never used.

    utop # let foo () = 1 + 2; print_endline "foo";;
    Line 1, characters 13-18:
    Warning 10 [non-unit-statement]: this expression should have type unit.
    val foo : unit -> unit = <fun>
    

    You either need to rework your code to account for this immutability, or switch to using a mutable data structure, like an array.

    let board = 
      [| [| 1;1;1;1;1;1;1;1;1;1 |]; 
         [| 0;0;0;0;0;0;0;0;0;0 |]; 
         [| 1;0;1;0;1;0;1;0;1;0 |];
         [| 0;1;0;1;0;1;0;1;0;1 |]; 
         [| 0;0;0;0;0;0;0;0;0;0 |]; 
         [| 1;1;1;1;1;1;1;1;1;1 |]; 
         [| 0;0;0;0;0;0;0;0;0;0 |]; 
         [| 1;0;1;0;1;0;1;0;1;0 |]; 
         [| 0;1;0;1;0;1;0;1;0;1 |]; 
         [| 0;0;0;0;0;0;0;0;0;0 |] |]
    

    Where you could modify position (1, 2) in board with:

    board.(1).(2) <- 1