aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2024-03-04 09:15:01 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2024-03-04 09:15:01 +0100
commitc34d7363b61a9e00c986b79793bf7cdc03e9ea99 (patch)
tree79c9121e2bff6bc938b7b36d1195e5759b163d2c
parent0be77ac09408c13cc12b5953b9ac7459b549c202 (diff)
downloaderlang-workshop-c34d7363b61a9e00c986b79793bf7cdc03e9ea99.tar.gz
erlang-workshop-c34d7363b61a9e00c986b79793bf7cdc03e9ea99.tar.bz2
erlang-workshop-c34d7363b61a9e00c986b79793bf7cdc03e9ea99.tar.xz
erlang-workshop-c34d7363b61a9e00c986b79793bf7cdc03e9ea99.zip
wip
-rw-r--r--tictactoe-2/.gitignore20
-rw-r--r--tictactoe-2/apps/tictactoe/src/tictactoe.app.src15
-rw-r--r--tictactoe-2/apps/tictactoe/src/tictactoe_app.erl18
-rw-r--r--tictactoe-2/apps/tictactoe/src/tictactoe_sup.erl35
-rw-r--r--tictactoe-2/apps/ttt/rebar.config (renamed from ttt/rebar.config)0
-rw-r--r--tictactoe-2/apps/ttt/rebar.lock (renamed from ttt/rebar.lock)0
-rw-r--r--tictactoe-2/apps/ttt/src/ttt.app.src (renamed from ttt/src/ttt.app.src)0
-rw-r--r--tictactoe-2/apps/ttt/src/ttt.erl (renamed from ttt/src/ttt.erl)0
-rw-r--r--tictactoe-2/apps/ttt/test/ttt_test.erl (renamed from ttt/test/ttt_test.erl)0
-rw-r--r--tictactoe-2/apps/ttt_server/.gitignore20
-rw-r--r--tictactoe-2/apps/ttt_server/rebar.config2
-rw-r--r--tictactoe-2/apps/ttt_server/rebar.lock1
-rw-r--r--tictactoe-2/apps/ttt_server/src/ttt_server.app.src14
-rw-r--r--tictactoe-2/apps/ttt_server/src/ttt_server.erl93
-rw-r--r--tictactoe-2/apps/ttt_server/test/server_tests.erl14
-rw-r--r--tictactoe-2/config/sys.config3
-rw-r--r--tictactoe-2/config/vm.args6
-rw-r--r--tictactoe-2/rebar.config32
-rw-r--r--tictactoe-2/rebar.lock1
-rw-r--r--tictactoe/ttt/rebar.config7
-rw-r--r--tictactoe/ttt/rebar.lock1
-rw-r--r--tictactoe/ttt/src/ttt.app.src13
-rw-r--r--tictactoe/ttt/src/ttt.erl51
-rw-r--r--tictactoe/ttt/test/ttt_test.erl18
24 files changed, 364 insertions, 0 deletions
diff --git a/tictactoe-2/.gitignore b/tictactoe-2/.gitignore
new file mode 100644
index 0000000..df53f7d
--- /dev/null
+++ b/tictactoe-2/.gitignore
@@ -0,0 +1,20 @@
+.rebar3
+_build
+_checkouts
+_vendor
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+.idea
+*.iml
+rebar3.crashdump
+*~
diff --git a/tictactoe-2/apps/tictactoe/src/tictactoe.app.src b/tictactoe-2/apps/tictactoe/src/tictactoe.app.src
new file mode 100644
index 0000000..ccce597
--- /dev/null
+++ b/tictactoe-2/apps/tictactoe/src/tictactoe.app.src
@@ -0,0 +1,15 @@
+{application, tictactoe,
+ [{description, "An OTP application"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {mod, {tictactoe_app, []}},
+ {applications,
+ [kernel,
+ stdlib
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache-2.0"]},
+ {links, []}
+ ]}.
diff --git a/tictactoe-2/apps/tictactoe/src/tictactoe_app.erl b/tictactoe-2/apps/tictactoe/src/tictactoe_app.erl
new file mode 100644
index 0000000..8829237
--- /dev/null
+++ b/tictactoe-2/apps/tictactoe/src/tictactoe_app.erl
@@ -0,0 +1,18 @@
+%%%-------------------------------------------------------------------
+%% @doc tictactoe public API
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(tictactoe_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+start(_StartType, _StartArgs) ->
+ tictactoe_sup:start_link().
+
+stop(_State) ->
+ ok.
+
+%% internal functions
diff --git a/tictactoe-2/apps/tictactoe/src/tictactoe_sup.erl b/tictactoe-2/apps/tictactoe/src/tictactoe_sup.erl
new file mode 100644
index 0000000..c506caf
--- /dev/null
+++ b/tictactoe-2/apps/tictactoe/src/tictactoe_sup.erl
@@ -0,0 +1,35 @@
+%%%-------------------------------------------------------------------
+%% @doc tictactoe top level supervisor.
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(tictactoe_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%% sup_flags() = #{strategy => strategy(), % optional
+%% intensity => non_neg_integer(), % optional
+%% period => pos_integer()} % optional
+%% child_spec() = #{id => child_id(), % mandatory
+%% start => mfargs(), % mandatory
+%% restart => restart(), % optional
+%% shutdown => shutdown(), % optional
+%% type => worker(), % optional
+%% modules => modules()} % optional
+init([]) ->
+ SupFlags = #{strategy => one_for_all,
+ intensity => 0,
+ period => 1},
+ ChildSpecs = [],
+ {ok, {SupFlags, ChildSpecs}}.
+
+%% internal functions
diff --git a/ttt/rebar.config b/tictactoe-2/apps/ttt/rebar.config
index 59700d5..59700d5 100644
--- a/ttt/rebar.config
+++ b/tictactoe-2/apps/ttt/rebar.config
diff --git a/ttt/rebar.lock b/tictactoe-2/apps/ttt/rebar.lock
index 57afcca..57afcca 100644
--- a/ttt/rebar.lock
+++ b/tictactoe-2/apps/ttt/rebar.lock
diff --git a/ttt/src/ttt.app.src b/tictactoe-2/apps/ttt/src/ttt.app.src
index 813faba..813faba 100644
--- a/ttt/src/ttt.app.src
+++ b/tictactoe-2/apps/ttt/src/ttt.app.src
diff --git a/ttt/src/ttt.erl b/tictactoe-2/apps/ttt/src/ttt.erl
index a9fc4d1..a9fc4d1 100644
--- a/ttt/src/ttt.erl
+++ b/tictactoe-2/apps/ttt/src/ttt.erl
diff --git a/ttt/test/ttt_test.erl b/tictactoe-2/apps/ttt/test/ttt_test.erl
index 95b6bf7..95b6bf7 100644
--- a/ttt/test/ttt_test.erl
+++ b/tictactoe-2/apps/ttt/test/ttt_test.erl
diff --git a/tictactoe-2/apps/ttt_server/.gitignore b/tictactoe-2/apps/ttt_server/.gitignore
new file mode 100644
index 0000000..b0d808b
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/.gitignore
@@ -0,0 +1,20 @@
+_build/default/lib/.rebar3
+_build
+_checkouts
+_vendor
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+.idea
+*.iml
+rebar3.crashdump
+*~
diff --git a/tictactoe-2/apps/ttt_server/rebar.config b/tictactoe-2/apps/ttt_server/rebar.config
new file mode 100644
index 0000000..2656fd5
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/rebar.config
@@ -0,0 +1,2 @@
+{erl_opts, [debug_info]}.
+{deps, []}.
diff --git a/tictactoe-2/apps/ttt_server/rebar.lock b/tictactoe-2/apps/ttt_server/rebar.lock
new file mode 100644
index 0000000..57afcca
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/rebar.lock
@@ -0,0 +1 @@
+[].
diff --git a/tictactoe-2/apps/ttt_server/src/ttt_server.app.src b/tictactoe-2/apps/ttt_server/src/ttt_server.app.src
new file mode 100644
index 0000000..d3de90f
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/src/ttt_server.app.src
@@ -0,0 +1,14 @@
+{application, ttt_server,
+ [{description, "An OTP library"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache-2.0"]},
+ {links, []}
+ ]}.
diff --git a/tictactoe-2/apps/ttt_server/src/ttt_server.erl b/tictactoe-2/apps/ttt_server/src/ttt_server.erl
new file mode 100644
index 0000000..62979e8
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/src/ttt_server.erl
@@ -0,0 +1,93 @@
+-module(ttt_server).
+
+-export([
+ start/0,
+ start_local/0,
+ stop/0,
+ stop/2,
+ loop/1,
+
+ start_game/1,
+ dump/1]).
+
+-import(ttt, [move/4, who_wins/1, format/1, empty_board/0]).
+
+-record(ttt_game, {
+ ref = make_ref(),
+ board,
+ player1,
+ player2}).
+
+-record(ttt_state, {
+ games = []}).
+
+-define(GLOBAL_NAME, ttt).
+
+loop(State) ->
+ _State =
+ receive
+ {Player1, start} when is_pid(Player1) ->
+ Board = ttt:empty_board(),
+ io:format("board:~n~s~n", [ttt:format(Board)]),
+ Game = #ttt_game{board = Board, player1 = Player1},
+ #ttt_state{games = [Game, State#ttt_state.games]},
+ Player1 ! {ok, Game#ttt_game.ref};
+ dump ->
+ io:format("Server state:~n~p~n", [State]),
+ State;
+ stop ->
+ io:format("Exiting~n", []),
+ exit(normal);
+ {stop, Pid} ->
+ Pid ! ok,
+ exit(normal);
+ X ->
+ io:format("unexpected message: ~p~n", [X]),
+ State
+ end,
+ case _State of
+ _ when _State =/= State ->
+ io:format("New State: ~p~n", [_State]);
+ _ -> _State
+ end,
+ ?MODULE:loop(_State).
+
+start_game(Server) ->
+ Server ! {self(), start},
+ receive
+ {ok, Game} ->
+ io:format("Game started: ~w~n", [Game]),
+ {ok, Game};
+ X ->
+ io:format("Game start failed: ~w~n", [X]),
+ X
+ after 100 ->
+ io:format("timeout~n", []),
+ {timeout}
+ end.
+
+dump(Server) ->
+ Server ! dump.
+
+start_local() ->
+ InitialState = #ttt_state{},
+ spawn(?MODULE, loop, [InitialState]).
+
+start() ->
+ Pid = start_local(),
+ case global:register_name(?GLOBAL_NAME, Pid) of
+ yes ->
+ io:format("Server started, pid=~p~n", [Pid]);
+ X ->
+ io:format("Register name failed: ~p~n", [X]),
+ Pid ! stop
+ end.
+
+stop(Server, Pid) ->
+ Server ! {stop, Pid}.
+
+stop() ->
+ case global:whereis_name(?GLOBAL_NAME) of
+ undefined -> io:format("Server not running~n");
+ Pid -> Pid ! {stop, self()}
+ end.
diff --git a/tictactoe-2/apps/ttt_server/test/server_tests.erl b/tictactoe-2/apps/ttt_server/test/server_tests.erl
new file mode 100644
index 0000000..322f9ed
--- /dev/null
+++ b/tictactoe-2/apps/ttt_server/test/server_tests.erl
@@ -0,0 +1,14 @@
+-module(server_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+simple_test() ->
+ Server = ttt_server:start_local(),
+ P1 = ttt_server:start_game(Server),
+ ?debugFmt("P1:~p~n", [P1]),
+ ttt_server:dump(Server),
+ ttt_server:stop(Server, self()),
+ ?assert(receive
+ ok -> true
+ after 100 -> false
+ end).
diff --git a/tictactoe-2/config/sys.config b/tictactoe-2/config/sys.config
new file mode 100644
index 0000000..33fcac5
--- /dev/null
+++ b/tictactoe-2/config/sys.config
@@ -0,0 +1,3 @@
+[
+ {tictactoe, []}
+].
diff --git a/tictactoe-2/config/vm.args b/tictactoe-2/config/vm.args
new file mode 100644
index 0000000..3b90361
--- /dev/null
+++ b/tictactoe-2/config/vm.args
@@ -0,0 +1,6 @@
+-sname tictactoe
+
+-setcookie tictactoe_cookie
+
++K true
++A30
diff --git a/tictactoe-2/rebar.config b/tictactoe-2/rebar.config
new file mode 100644
index 0000000..e08a4e2
--- /dev/null
+++ b/tictactoe-2/rebar.config
@@ -0,0 +1,32 @@
+{erl_opts, [debug_info]}.
+{deps, []}.
+
+{relx, [{release, {tictactoe, "0.1.0"},
+ [tictactoe,
+ sasl]},
+
+ {mode, dev},
+
+ %% automatically picked up if the files
+ %% exist but can be set manually, which
+ %% is required if the names aren't exactly
+ %% sys.config and vm.args
+ {sys_config, "./config/sys.config"},
+ {vm_args, "./config/vm.args"}
+
+ %% the .src form of the configuration files do
+ %% not require setting RELX_REPLACE_OS_VARS
+ %% {sys_config_src, "./config/sys.config.src"},
+ %% {vm_args_src, "./config/vm.args.src"}
+]}.
+
+{profiles, [{prod, [{relx,
+ [%% prod is the default mode when prod
+ %% profile is used, so does not have
+ %% to be explicitly included like this
+ {mode, prod}
+
+ %% use minimal mode to exclude ERTS
+ %% {mode, minimal}
+ ]
+ }]}]}.
diff --git a/tictactoe-2/rebar.lock b/tictactoe-2/rebar.lock
new file mode 100644
index 0000000..57afcca
--- /dev/null
+++ b/tictactoe-2/rebar.lock
@@ -0,0 +1 @@
+[].
diff --git a/tictactoe/ttt/rebar.config b/tictactoe/ttt/rebar.config
new file mode 100644
index 0000000..59700d5
--- /dev/null
+++ b/tictactoe/ttt/rebar.config
@@ -0,0 +1,7 @@
+{erl_opts, [debug_info]}.
+{deps, [
+]}.
+
+{shell, [
+ {apps, [ttt]}
+]}.
diff --git a/tictactoe/ttt/rebar.lock b/tictactoe/ttt/rebar.lock
new file mode 100644
index 0000000..57afcca
--- /dev/null
+++ b/tictactoe/ttt/rebar.lock
@@ -0,0 +1 @@
+[].
diff --git a/tictactoe/ttt/src/ttt.app.src b/tictactoe/ttt/src/ttt.app.src
new file mode 100644
index 0000000..813faba
--- /dev/null
+++ b/tictactoe/ttt/src/ttt.app.src
@@ -0,0 +1,13 @@
+{application, ttt,
+ [{description, "TTT app"},
+ {vsn, "0.1.0"},
+ {registered, []},
+% {mod, {myapp_app, []}},
+ {applications,
+ [kernel, stdlib]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache-2.0"]},
+ {links, []}
+ ]}.
diff --git a/tictactoe/ttt/src/ttt.erl b/tictactoe/ttt/src/ttt.erl
new file mode 100644
index 0000000..a9fc4d1
--- /dev/null
+++ b/tictactoe/ttt/src/ttt.erl
@@ -0,0 +1,51 @@
+-module(ttt).
+
+-export_type([
+ player/0,
+ square/0,
+ board/0,
+ game_result/0]).
+
+-export([
+ who_wins/1,
+ empty_board/0,
+ move/4,
+ format/1]).
+
+-type player() :: 'X' | 'O'.
+-type square() :: player() | 'E'.
+-type board() :: list(square()).
+-type game_result() :: player() | 'draw' | 'running'.
+
+-define(EMPTY_BOARD, ['E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E']).
+
+-spec empty_board() -> board().
+empty_board() -> ?EMPTY_BOARD.
+
+-spec who_wins(Board :: board()) -> game_result().
+who_wins([A, A, A, _, _, _, _, _, _]) when A == 'X' orelse A == 'O' -> A;
+who_wins([_, _, _, A, A, A, _, _, _]) when A == 'X' orelse A == 'O' -> A;
+who_wins([_, _, _, _, _, _, A, A, A]) when A == 'X' orelse A == 'O' -> A;
+who_wins([A, _, _, A, _, _, A, _, _]) when A == 'X' orelse A == 'O' -> A;
+who_wins([_, A, _, _, A, _, _, A, _]) when A == 'X' orelse A == 'O' -> A;
+who_wins([_, _, A, _, _, A, _, _, A]) when A == 'X' orelse A == 'O' -> A;
+who_wins([A, _, _, _, A, _, _, _, A]) when A == 'X' orelse A == 'O' -> A;
+who_wins([_, _, A, _, A, _, A, _, _]) when A == 'X' orelse A == 'O' -> A;
+who_wins(Board) ->
+ case lists:member('E', Board) of
+ true -> running;
+ false -> draw
+ end.
+
+move(_, _, Row, _) when Row < 0 orelse Row > 2 -> {bad_arg};
+move(_, _, _, Col) when Col < 0 orelse Col > 2 -> {bad_arg};
+move(_, Move, _, _) when not (Move == 'X') -> {bad_arg};
+move(Board, Move, Row, Col) ->
+ I = Row * 3 + Col,
+ Updated = lists:sublist(Board, I) ++ [Move] ++ lists:nthtail(I + 1, Board),
+ {ok, Updated}.
+
+format(Board) when length(Board) == 9 ->
+ B = lists:map(fun(C) -> case C of 'E' -> ' '; _ -> C end end, Board),
+ io:format("+---+~n|~s~s~s|~n|~s~s~s|~n|~s~s~s|~n+---+~n", B);
+format(_) -> "bad board".
diff --git a/tictactoe/ttt/test/ttt_test.erl b/tictactoe/ttt/test/ttt_test.erl
new file mode 100644
index 0000000..95b6bf7
--- /dev/null
+++ b/tictactoe/ttt/test/ttt_test.erl
@@ -0,0 +1,18 @@
+-module(ttt_test).
+
+-include_lib("eunit/include/eunit.hrl").
+
+empty_board_test() ->
+ E = ttt:empty_board(),
+ ?assertEqual(E, ttt:empty_board()).
+
+moves_board_test() ->
+ B0 = ttt:empty_board(),
+ {ok, B1} = ttt:move(B0, 'X', 0, 0),
+ {ok, B2} = ttt:move(B1, 'X', 0, 1),
+ {ok, B3} = ttt:move(B2, 'X', 0, 2),
+ ?assertEqual(['X', 'X', 'X', 'E', 'E', 'E', 'E', 'E', 'E'], B3).
+
+simple_test() ->
+ R = ttt:who_wins(['X', 'X', 'X', 'E', 'E', 'E', 'E', 'E', 'E']),
+ ?assertEqual('X', R).