game.erl implements the game logic using gen_server, accepting {guess, <char>}, replying with the progress of the word, until the whole word is completed. game_server.erl is responsible for launching various games, returning a registered name so that players could interact with that game.

Demo

Running server part on trygger:

1
2
3
4
5
6
7
8
9
username@trygger:~/erlang$ erlc game*.erl ; erl -setcookie magic -name server
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:10] [kernel-poll:false]

Eshell V9.1 (abort with ^G)
(server@trygger.it.uu.se)1> game_server:start_link().
{ok,<0.71.0>}
(server@trygger.it.uu.se)2> gen_server:call({global, game_server}, start_game).
{ok,game_0}
(server@trygger.it.uu.se)3>

Running player part on hedenius:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
username@hedenius:~/erlang$ erlc game*.erl ; erl -setcookie magic -name player -eval "net_adm:ping('server@trygger.it.uu.se')"
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V9.1 (abort with ^G)
(player@hedenius.it.uu.se)1> nodes().
['server@trygger.it.uu.se']
(player@hedenius.it.uu.se)2> gen_server:call({global, game_0}, {guess, $a}).
{guessedright,"a _ a _ _ _ _ _ a _ _"}
(player@hedenius.it.uu.se)3> gen_server:call({global, game_0}, {guess, $d}).
{guessedright,"a d a _ _ _ _ _ a _ _"}
(player@hedenius.it.uu.se)4> gen_server:call({global, game_0}, {guess, $l}).
{guessedright,"a d a l _ _ _ l a _ _"}
(player@hedenius.it.uu.se)5> gen_server:call({global, game_0}, {guess, $o}).
{guessedright,"a d a l o _ _ l a _ _"}
(player@hedenius.it.uu.se)6> gen_server:call({global, game_0}, {guess, $v}).
{guessedright,"a d a l o v _ l a _ _"}
(player@hedenius.it.uu.se)7> gen_server:call({global, game_0}, {guess, $e}).
{guessedright,"a d a l o v e l a _ e"}
(player@hedenius.it.uu.se)8> gen_server:call({global, game_0}, {guess, $c}).
{youwin,"a d a l o v e l a c e"}
(player@hedenius.it.uu.se)9>

Source code

game.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-module(game).

-behaviour(gen_server).

-export([game/2]).

%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).

-record(state, {word::[{char(), boolean()}]}).

game(Name, Word) ->
gen_server:start({global, Name}, ?MODULE, Word, []).

init(Word) ->
ProcessedWord = lists:map(fun(Char) -> {Char, false} end, Word),
{ok, #state{word = ProcessedWord}}.

handle_call({guess, Letter}, _From, State = #state{word = Word}) ->
Result = lists:keyfind(Letter, 1, Word),

case Result of
false ->
{reply, {guessedwrong, formatWord(Word)}, State};
{Letter, true} ->
{reply, {alreadyguessed, formatWord(Word)}, State};
{Letter, false} ->
NewWord = lists:map(
fun
({Char, false}) when (Char =:= Letter) -> {Char, true};
(Anything) -> Anything
end, Word),

GameEnd = lists:all(fun
({_Anything, Boolean}) -> Boolean
end, NewWord),

case GameEnd of
true ->
{stop, normal, {youwin, formatWord(NewWord)}, State#state{word = NewWord}};
false ->
{reply, {guessedright, formatWord(NewWord)}, State#state{word = NewWord}}
end
end;
handle_call(_Request, _From, State) ->
{reply, ignored, State}.

handle_cast(_Msg, State) ->
{noreply, State}.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%% Internal functions
formatWord(Word) ->
Encrypted = lists:map(
fun
({Letter, true}) -> Letter;
({_Letter, false}) -> $_
end, Word),
Space = $\s,
lists:join(Space, Encrypted).

game_server.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-module(game_server).

-behaviour(gen_server).

-export([start_link/0, game_count/0]).

-define(GAME_MODULE, game).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).

-record(state, {words, games = []}).

start_link() ->
gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).

games() ->
["adalovelace", "hello", "world"].
init([]) ->
{ok, #state{words = games(), games = games()}}.

handle_call(start_game, _From, State = #state{games = []}) ->
{reply, empty, State};
handle_call(start_game, _From, State = #state{games = [G|Gs]}) ->
Id = length(State#state.words) - length(State#state.games),
Name = game_name(Id),
game:game(Name, G),
{reply, {ok, Name}, State#state{games = Gs}};
handle_call(game_left, _From, State) ->
{reply, length(State#state.games), State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.

handle_cast(_Msg, State) ->
{noreply, State}.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.
game_name(Id) ->
list_to_atom(lists:concat(["game_", Id])).
game_count() ->
gen_server:call(?MODULE, game_left).