2012-10-13

Seven Languages in Seven Weeks

I started learning Erlang by way of Bruce Tate’s book from Pragmatic Programmers, Seven Languages in Seven Weeks. I’m sure it’s not my original inspiration, because I skipped a few languages to reach it, but at this point I’m not sure what was.

I set the book aside once I reached the end of Day 2, because I couldn’t figure out how to do the bonus problem in a clean manner, and I’m easily frustrated. (Not a good characteristic for a programmer, I’ll readily admit.)

After reading Solving Embarrassingly Obvious Problems In Erlang, a blog post from Garrett Smith on refactoring/simplifying Erlang, I decided I should take another look at it.

Problem description

Take a list or tuple representing a tic-tac-toe board and determine who, if anyone, has won the game. Straightforward enough.

The atom cat is used as a return value indicate that the board is fully occupied but no one has won; the atom no_winner means no one has won but open spots remain.

I chose to use undef as the atom to indicate an open spot on the board, and x and o are used as pieces.

Solving it

Pattern matching makes this a fairly straightforward task, but it’s very repetitive code. Again, though, after reading Garrett’s post, I’m no longer afraid of making long code, so long as the functions themselves are very short.

And, in fact, these functions are remarkably short. Not a single function is longer than one line of code.

I think the code speaks for itself.

The code

-module(ttt).
-include_lib("eunit/include/eunit.hrl").
-export([ttt/1]).

%% Throughout this I use S for side

ttt(List) ->
    check_open_unless(List,
                      check_diag_unless(List,
                                        check_vertical_unless(List,
                                                              check_horiz(List)))).

check_horiz([]) ->
    { no };
check_horiz([S, S, S | _Tail]) when S =/= undef ->
    { yes, S };
check_horiz([_, _, _| Tail]) ->
    check_horiz(Tail).

check_vertical_unless(_List, {yes, S}) ->
    { yes, S };
check_vertical_unless(List, {no}) ->
    check_vert(List).

check_vert([ S, _, _, S, _, _, S, _, _]) when S =/= undef ->
    { yes, S };
check_vert([ _, S, _, _, S, _, _, S, _]) when S =/= undef ->
    { yes, S };
check_vert([ _, _, S, _, _, S, _, _, S]) when S =/= undef ->
    { yes, S };
check_vert(_List) ->
    { no }.

check_diag_unless(_List, {yes, S}) ->
    { yes, S };
check_diag_unless(List, {no}) ->
    check_diag(List).

check_diag([ S, _, _, _, S, _, _, _, S ]) when S =/= undef ->
    { yes, S };
check_diag([ _, _, S, _, S, _, S, _, _]) when S =/= undef ->
    { yes, S };
check_diag(_List) ->
    { no }.

check_open_unless(_List, {yes, S}) ->
    { yes, S };
check_open_unless(List, {no}) ->
    check_open(List).

%%
%% Note that this function will return cat if there are no open
%% squares, regardless of whether there's a winner.
check_open([]) ->
    cat;
check_open([undef | _Tail]) ->
    no_winner;
check_open([_H | Tail]) ->
    check_open(Tail).


%%
%% Test functions

no_horiz_test() ->
    { no } = check_horiz([x, o, o, o, x, x, o, x, o]).

horiz_1_test() ->
    { yes, o } = check_horiz([x, o, o, x, o, x, o, o, o]).

horiz_2_test() ->
    { yes, x } = check_horiz([x, o, o, x, x, x, o, x, o]).

no_diag_test() ->
    { no } = check_diag([x, o, o, x, o, o, x, o, o]).

diag_1_test() ->
    { yes, o } = check_diag([x, o, o, x, o, o, o, x, o]).

diag_2_test() ->
    { yes, x } = check_diag([x, o, x, o, x, o, x, x, o]).

no_vert_test() ->
    { no } = check_vert([x, o, o, x, o, o, o, x, x]).

vert_1_test() ->
    { yes, o } = check_vert([x, o, o, x, o, o, o, x, o]).

vert_2_test() ->
    { yes, x } = check_vert([x, o, x, o, x, x, o, o, x]).

undef_1_test() ->
    { no } = check_vert([x, o, undef, o, x, undef, o, o, undef]).

done_test() ->
    cat = check_open([x, o, x, o, x, x, o, o, x]).

open_test() ->
    no_winner = check_open([x, o, x, o, undef, x, o, o, x]).


full_open_test() ->
    no_winner = ttt([undef, o, x, o, undef, x, o, undef, o]).

full_done_test() ->
    cat = check_open([x, o, x, o, x, x, o, x, o]).

full_vert_test() ->
    { yes, o } = ttt([x, o, o, x, o, o, o, x, o]).
erlang 7langs
Previous post
Chessboard: Day 13 Stay on target. Stay on target!
Next post
Game of Twenty-One MIT 6.001 in action, with edoc and type specs