Tic-tac-toe in Erlang — top-level loop

This is part of an Erlang tutorial built around a tic-tac-toe program. The program is stuffed into one file, called tic.erl and available here. The source file and this tutorial are organized as follows:

Top-level loop

This is the top-level loop that plays the tic-tac-toe game. It is a common pattern, a read-eval-print loop that reads the user’s request, carries out the user command, displays the board, and repeats.

Interface

The top-level loop section implements play(), the exported function that plays the game of tic-tac-toe.

play( )
Plays tic-tac-toe with the user. Exported from tic module.

Source code

Here’s the Erlang source code for the top-level loop section of the tic.erl file.

% --------------------------------------------------------------
% Export Function - play()
% --------------------------------------------------------------
% play( )
%
%   Plays a game of tic-tac-toe, printing the board and reading
%   user instructions to/from standard IO.
%
%   This could print some final statistics.

play( ) ->

  % We use random:uniform(N) to break ties.
  % Take this out (or replace it with something that sets the
  % seed) if you want exact reproducability.
  randomize_random_seed( ),

  % The opening message.
  write_line( ),
  write_line( "Welcome to tic-tac-toe, the game that predicts"),
  write_line( "the outcomes of every move and lets you erase"),
  write_line( "X's and O's and skip turns."),

  % Play until the user quits.
  play_new_game( init_game( )),

  % The closing message.
  write_line( ),
  write_line( "Thanks for playing!"),
  write_line( ),
  ok.

% --------------------------------------------------------------
% play_new_game( Game )
%
%   Plays a new game created by the caller.

play_new_game( Game ) ->
  write_line( ),
  write_line( "Starting a new game."),
  case catch game_loop( Game) of
    quit -> ok;
    {new_game, Game_final} ->
      play_new_game( init_game( Game_final))
  end.

% --------------------------------------------------------------
% game_loop( Game )
%
%   Displays the board, gets user input, changes the board,
%   and calls itself tail-recursivly with the changed board.

game_loop( Game ) ->
  print_board( Game),
  game_loop(
    case get_next_move( Game) of
      quit     -> throw( quit);
      new_game -> throw( {new_game, Game});

      Option when
          Option == simple;
          Option == number;
          Option == predict ->
        set_game_print_option( Game, Option);

      skip ->
        skip_game_next_turn( Game);
      automatic ->
        automatic_game_next_turn( Game);
      {mark, Pos} ->
        mark_game_next_turn( Game, Pos);
      {erase, Pos} ->
        erase_game_next_turn( Game, Pos)
    end).

% --------------------------------------------------------------
% skip_game_next_turn( Game )
%
%   Called when the user asks to skip a turn.
%   Never called on a cat game or a game that is already won.
%
%   Returns a new #game{} object where the state is flipped
%   between "it is X's turn" and "it is O's turn".
%   May also calculate new predicted outcomes for the board.

skip_game_next_turn( Game ) ->
  set_game_state_board(
    Game,
    case get_game_state( Game) of
      x_next -> o_next;
      o_next -> x_next
    end,
    get_game_board( Game)).

% --------------------------------------------------------------
% automatic_game_next_turn( Game )
%
%   Called when the user asks the computer to choose the
%   next move.
%
%   Returns a #game{} with the new move recorded.

automatic_game_next_turn( Game ) ->
  mark_game_next_turn( Game,
    get_board_best_predicted_position(
      get_game_board( Game))).

% --------------------------------------------------------------
% mark_game_next_turn( Game, Position )
%
%   Called when the user selects a board position to mark
%   for the next move.
%
%   Returns a re-calculated #game{}.

mark_game_next_turn( Game, Pos ) ->
  State_old = get_game_state( Game),
  Board_old = clear_board_predicted_outcomes(
                get_game_board( Game)),
  ?m_assert( (State_old == x_next) or (State_old == o_next)),
  ?m_assert( get_board_mark( Board_old, Pos) == empty),

  { Xo, State_win, State_flip } =
    case State_old of
      x_next -> { x_mark, x_winner, o_next };
      o_next -> { o_mark, o_winner, x_next }
    end,

  Board_marked = mark_board( Board_old, Pos, Xo),
  set_game_state_board(
    Game,
    case is_board_won( Board_marked, Xo) of
      true  -> State_win;
      false ->
        case is_board_full( Board_marked) of
          true  -> cat_game;
          false -> State_flip
        end
    end,
    Board_marked).

% --------------------------------------------------------------
% erase_game_next_turn( Game, Position )
%
%   Erases the X or O mark on the board at position.
%
%   Returns a re-calculated #game{}.

erase_game_next_turn( Game, Pos ) ->
  State_old = get_game_state( Game),
  Board_old = clear_board_predicted_outcomes(
                get_game_board( Game)),
  Xo_old    = get_board_mark( Board_old, Pos),
  ?m_assert( (Xo_old == x_mark) or (Xo_old == o_mark)),

  Board_erased = mark_board( Board_old, Pos, empty),
  set_game_state_board(
    Game,
    case {State_old, Xo_old} of
      {cat_game, x_mark} -> x_next;
      {cat_game, o_mark} -> o_next;
      {x_winner, o_mark} -> x_winner;
      {o_winner, x_mark} -> o_winner;
      {x_next  , x_mark} -> x_next;
      {o_next  , x_mark} -> x_next;
      {x_next  , o_mark} -> o_next;
      {o_next  , o_mark} -> o_next;
      {x_winner, x_mark} ->
        case is_board_won( Board_erased, x_mark) of
          true  -> x_winner;
          false -> x_next
        end;
      {o_winner, o_mark} ->
        case is_board_won( Board_erased, o_mark) of
          true  -> o_winner;
          false -> o_next
        end
    end,
    Board_erased).

Comments

Leave a Reply