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:
- Introduction
- Top-level loop (this page)
- User input
- Board display
- Board abstraction
- Game abstraction
- Next move calculations and predictions
- Predicted outcome abstraction
- Utilities to introduce randomness
- Macros for testing and debugging
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