So, small confession, and a bit of history. I actually wrote this code about three years ago, over a couple evenings, in a hotel in New Delhi. It was the seed of the whole project actually, after a colleague told me he’d write tic-tac-toe to learn a new language.

So, impressions. After I wrote this, I started acutely noticing the absence of type inference and, especially, pattern matching in the rest of my life. It pushes your thinking to the start of the project in a very elegant and rewarding way. The win-condition code especially drove this home, because even though it was a bit more verbose than other methods, it was easy to make patterns that obviously, visually depicted what I was trying to do.

I did spend a fair bit of time fighting with the compiler, but by the time I satisfied it, damned if the thing didn’t just work on the first try. (I had that impression coming to Java from C many years ago, so who can say if that means anything…) I can also imagine working against some deadline and having the language force me into doing things The Right Way instead of the expedient, hacky, get-it-done-NOW way being pretty frustrating. Does the extra discipline mean you find yourself in those kinds of predicaments less often? Would love to hear from a real Ocamler on the subject.

As my computer languages professor said about SML, “its concrete syntax is not the most attractive.” I actually kind of like how it flows on the screen, but figuring out the rules about when expressions terminate, and when to use “;;” and did involve some head-scratching.

Update: Commenters on reddit showed me the error of my ways, and the dreaded “;;”s are gone.

The code (For ocaml version 3.11.1):

open List open String open Printf (******************) (* Representation *) (******************) (* Individual TTT square. Also represents the player. *) type square = X | O | Empty of int (* Game Result *) type result = Continue of square | Win of square | Draw (* Initial Empty Board *) let starting_grid = [[ Empty 1; Empty 2; Empty 3]; [ Empty 4; Empty 5; Empty 6]; [ Empty 7; Empty 8; Empty 9]] (* X always goes first *) let starting_player = X (* Return new square if current matches the target slot *) let update_square replacement target_slot_no current = match current with | Empty slot_no when slot_no = target_slot_no -> replacement | current -> current (* Replace the given slot in the grid with the new square *) let update_grid grid replacement target_slot_no = map (map (update_square replacement target_slot_no)) grid (* Toggle X and O *) let swap square = match square with X -> O | O -> X | square -> square (* Determine win/lose/draw for a grid. *) let result_of_grid current_player grid = let is_empty square = match square with Empty _ -> true | square -> false in let not_full grid = exists is_empty (flatten grid) in let next_player grid = if ((List.length (filter is_empty (flatten grid))) mod 2) = 1 then starting_player else swap starting_player in match grid with | [[a; _; _]; (* Diagonals *) [_; b; _]; [_; _; c]] when a = b && b = c -> Win a | [[_; _; a]; [_; b; _]; [c; _; _]] when a = b && b = c -> Win a | [[a; b; c]; (* Horizontals *) [_; _; _]; [_; _; _]] when a = b && b = c -> Win a | [[_; _; _]; [a; b; c]; [_; _; _]] when a = b && b = c -> Win a | [[_; _; _]; [_; _; _]; [a; b; c]] when a = b && b = c -> Win a | [[a; _; _]; (* Verticals *) [b; _; _]; [c; _; _]] when a = b && b = c -> Win a | [[_; a; _]; [_; b; _]; [_; c; _]] when a = b && b = c -> Win a | [[_; _; a]; [_; _; b]; [_; _; c]] when a = b && b = c -> Win a (* If there's no winner, but empty squares remain, keep playing. *) | grid when not_full grid -> Continue (next_player grid) (* Otherwise, it must be a draw. *) | grid -> Draw (* XXX There's probably a better way to do this. XXX *) (**********) (* Output *) (**********) (* String Representations *) let x_mark_str = "X" let o_mark_str = "O" let wall_str = "|" let floor_str = "

---+---+---

" (* Convert a square type to its string representation *) let string_of_square square = match square with | Empty n -> string_of_int n | X -> x_mark_str | O -> o_mark_str (* Output the concrete grid representation of a square *) let concrete square = match square with | Empty n -> sprintf "(%d)" n | square -> sprintf " %s " (string_of_square square) (* Convert a whole grid to its string representation *) let concrete_grid grid = map (map concrete) grid (* Print the converted grid *) let print_grid grid = let rows = map (concat wall_str) (concrete_grid grid) in printf "

%s

" (concat floor_str rows) (* String representation of result type *) let string_of_result result = match result with | Continue player -> sprintf "Select a square, %s: " (string_of_square player) | Win player -> sprintf "%s Wins!" (string_of_square player) | Draw -> "It's a Draw!" (*********) (* Input *) (*********) (* Read a number from standard input *) let read_slot_no result = printf "

%s" (string_of_result result); try int_of_string (read_line()) with Failure("int_of_string") -> -1 (* Main Game Loop *) let rec game_loop current_player grid = let result = result_of_grid current_player grid in let next_grid player = update_grid grid player (read_slot_no result) in print_grid grid; match result with | Continue player -> game_loop player (next_grid player) | result -> result let () = printf "

%s

" (string_of_result (game_loop starting_player starting_grid))