├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── ct.spec
├── doc
├── README.md
├── edoc-info
├── erlang.png
├── nuk_app.md
├── nuk_game.md
├── nuk_game_coinflip.md
├── nuk_game_engine.md
├── nuk_game_engine_state.md
├── nuk_game_server.md
├── nuk_game_session.md
├── nuk_game_session_storage.md
├── nuk_game_session_store_server.md
├── nuk_game_sessions.md
├── nuk_game_state.md
├── nuk_game_storage.md
├── nuk_game_store_server.md
├── nuk_game_store_sup.md
├── nuk_game_sup.md
├── nuk_games.md
├── nuk_sup.md
├── nuk_user.md
├── nuk_user_server.md
├── nuk_user_session.md
├── nuk_user_session_storage.md
├── nuk_user_session_store_server.md
├── nuk_user_sessions.md
├── nuk_user_storage.md
├── nuk_user_store_server.md
├── nuk_user_store_sup.md
├── nuk_user_sup.md
├── nuk_users.md
└── stylesheet.css
├── erlang.mk
├── guide
├── developer-overview.md
├── guide.md
└── implementing-a-game.md
├── relx.config
├── src
├── nuk.app.src
├── nuk_app.erl
├── nuk_game.erl
├── nuk_game_coinflip.erl
├── nuk_game_engine.erl
├── nuk_game_engine_state.erl
├── nuk_game_server.erl
├── nuk_game_session.erl
├── nuk_game_session_storage.erl
├── nuk_game_session_store_server.erl
├── nuk_game_sessions.erl
├── nuk_game_state.erl
├── nuk_game_storage.erl
├── nuk_game_store_server.erl
├── nuk_game_store_sup.erl
├── nuk_game_sup.erl
├── nuk_games.erl
├── nuk_sup.erl
├── nuk_user.erl
├── nuk_user_server.erl
├── nuk_user_session.erl
├── nuk_user_session_storage.erl
├── nuk_user_session_store_server.erl
├── nuk_user_sessions.erl
├── nuk_user_storage.erl
├── nuk_user_store_server.erl
├── nuk_user_store_sup.erl
├── nuk_user_sup.erl
└── nuk_users.erl
└── test
├── ct.cover.spec
├── nuk_game_SUITE.erl
└── nuk_user_SUITE.erl
/.gitignore:
--------------------------------------------------------------------------------
1 | *.coverdata
2 | *.kdev4
3 | *.plt
4 | .erlang.mk
5 | _rel
6 | cover
7 | deps
8 | ebin
9 | erl_crash.dump
10 | logs
11 | nuk.d
12 | relx
13 | test/*.beam
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT = nuk
2 | COVER = 1
3 | DOC_DEPS = edown
4 | EDOC_OPTS += '{doclet,edown_doclet}'
5 |
6 | include erlang.mk
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | nuk
2 | =====
3 |
4 | nuk is a generic turn based game server written in Erlang.
5 |
6 | Features
7 | --------
8 |
9 | - provides framework for registering games, users and controlling their sessions
10 | - allows you to write and plug in your own turn based game engine using behavior callbacks
11 | - allows to use custom external storage using simple behaviors
12 |
13 | Additional info
14 | ---------------
15 |
16 | - [guide](guide/guide.md)
17 | - [developer overview](guide/developer-overview.md)
18 | - [implementing a game](guide/implementing-a-game.md)
19 | - [reference documentation](doc/README.md)
20 |
21 | what nuk is *not*:
22 | ------------------
23 |
24 | - client or frontend (it's a server)
25 | - web server: it does not provide one, although I might consider adding this in the future
26 |
--------------------------------------------------------------------------------
/ct.spec:
--------------------------------------------------------------------------------
1 | {alias, root, "./test/"}.
2 | {logdir, "./logs/"}.
3 | {suites, root, all}.
4 | {cover, "./test/ct.cover.spec"}.
5 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # The nuk application #
4 |
5 |
6 | ## Modules ##
7 |
8 |
9 |
38 |
39 |
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | %% encoding: UTF-8
2 | {application,nuk}.
3 | {modules,[nuk_app,nuk_game,nuk_game_coinflip,nuk_game_engine,
4 | nuk_game_engine_state,nuk_game_server,nuk_game_session,
5 | nuk_game_session_storage,nuk_game_session_store_server,
6 | nuk_game_sessions,nuk_game_state,nuk_game_storage,
7 | nuk_game_store_server,nuk_game_store_sup,nuk_game_sup,nuk_games,
8 | nuk_sup,nuk_user,nuk_user_server,nuk_user_session,
9 | nuk_user_session_storage,nuk_user_session_store_server,
10 | nuk_user_sessions,nuk_user_storage,nuk_user_store_server,
11 | nuk_user_store_sup,nuk_user_sup,nuk_users]}.
12 |
--------------------------------------------------------------------------------
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix1/nuk/ad771c8b164c305408d8076627228024c4955ec1/doc/erlang.png
--------------------------------------------------------------------------------
/doc/nuk_app.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_app #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_app` module.
9 |
10 | __Behaviours:__ [`application`](application.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This starts the nuk application.
16 |
17 | ## Function Index ##
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## Function Details ##
26 |
27 |
28 |
29 | ### get_storage_module/1 ###
30 |
31 |
32 | get_storage_module(Type::atom()) -> atom()
33 |
34 |
35 |
36 | Get storage module
37 |
38 | Returns the storage module associated with the specified storage type. If
39 | none specified, nuk default is returned.
40 |
41 |
42 |
43 | ### start/2 ###
44 |
45 | `start(StartType, StartArgs) -> any()`
46 |
47 |
48 |
49 | ### stop/1 ###
50 |
51 | `stop(State) -> any()`
52 |
53 |
--------------------------------------------------------------------------------
/doc/nuk_game.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_game` module.
10 |
11 |
12 |
13 | ## Description ##
14 | This module is used to operate on the [`nuk_game:game()`](nuk_game.md#type-game) data type.
15 | This data type is used during game registration in nuk - i.e. when calling
16 | [`nuk_games:register/1`](nuk_games.md#register-1), [`nuk_games:get/1`](nuk_games.md#get-1),
17 | [`nuk_games:list/0`](nuk_games.md#list-0).
18 |
19 |
20 | ## Data Types ##
21 |
22 |
23 |
24 |
25 | ### game() ###
26 |
27 |
28 | __abstract datatype__: `game()`
29 |
30 | Data type used to register games in nuk. Use functions in this module to
31 | operate on this data type.
32 |
33 |
34 |
35 | ## Function Index ##
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | ## Function Details ##
44 |
45 |
46 |
47 | ### get_max_players/1 ###
48 |
49 |
50 | get_max_players(Game::game()) -> integer()
51 |
52 |
53 |
54 | Get maximum number of players
55 |
56 | Extract maximum number of players from [`nuk_game:game()`](nuk_game.md#type-game) data type.
57 |
58 |
59 |
60 | ### get_min_players/1 ###
61 |
62 |
63 | get_min_players(Game::game()) -> integer()
64 |
65 |
66 |
67 | Get minimum number of players
68 |
69 | Extract minimum number of players from [`nuk_game:game()`](nuk_game.md#type-game) data type.
70 |
71 |
72 |
73 | ### get_module/1 ###
74 |
75 |
76 | get_module(Game::game()) -> atom()
77 |
78 |
79 |
80 | Get module name
81 |
82 | Extract module name from [`nuk_game:game()`](nuk_game.md#type-game) data type.
83 |
84 |
85 |
86 | ### get_name/1 ###
87 |
88 |
89 | get_name(Game::game()) -> string()
90 |
91 |
92 |
93 | Get game name
94 |
95 | Extract game name from [`nuk_game:game()`](nuk_game.md#type-game) data type.
96 |
97 |
98 |
99 | ### new/4 ###
100 |
101 |
102 | new(GameName::string(), Module::atom(), MinPlayers::integer(), MaxPlayers::integer) -> game()
103 |
104 |
105 |
106 | Create a new [`nuk_game:game()`](nuk_game.md#type-game) data type
107 |
108 | `GameName` is a string used to identify the game during registration.
109 | `Module` is the name of the module implementing the
110 | [`nuk_game_engine`](nuk_game_engine.md) behavior. `MinPlayers` and `MaxPlayers` are integers
111 | used for game registration. They are used by nuk to validate correct
112 | number of players before starting a game.
113 |
114 |
--------------------------------------------------------------------------------
/doc/nuk_game_coinflip.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_coinflip #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_coinflip_multi` module.
9 |
10 | __Behaviours:__ [`nuk_game_engine`](nuk_game_engine.md).
11 |
12 |
13 |
14 | ## Function Index ##
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ## Function Details ##
23 |
24 |
25 |
26 | ### finish/2 ###
27 |
28 | `finish(State, NukState) -> any()`
29 |
30 |
31 |
32 | ### initialize/2 ###
33 |
34 |
35 | initialize(User::nuk_user:user(), OptionsOverride::list()) -> {error, invalid_options, string()} | {ok, nuk_game_engine_state:state()}
36 |
37 |
38 |
39 |
40 |
41 | ### player_join/3 ###
42 |
43 | `player_join(User, State, NukState) -> any()`
44 |
45 |
46 |
47 | ### player_leave/3 ###
48 |
49 | `player_leave(User, State, NukState) -> any()`
50 |
51 |
52 |
53 | ### start/2 ###
54 |
55 | `start(State, NukState) -> any()`
56 |
57 |
58 |
59 | ### turn/4 ###
60 |
61 | `turn(User, Turn, State, NukState) -> any()`
62 |
63 |
--------------------------------------------------------------------------------
/doc/nuk_game_engine.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_engine #
4 | * [Description](#description)
5 |
6 | `nuk_game_engine` module.
7 |
8 |
9 |
10 | ## Description ##
11 | This is a behavior that all game engines must implement. It is also the only
12 | logic that game engines need implement. All callback function returns allow
13 | engines to: (1) set the arbitrary state that's relevant to the game being
14 | implemented, (2) get callbacks for important game events with the arbitrary
15 | state previously set.
--------------------------------------------------------------------------------
/doc/nuk_game_engine_state.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_engine_state #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_game_engine_state` module.
10 |
11 |
12 |
13 | ## Description ##
14 | This module is used to operate on [`nuk_game_engine_state:state()`](nuk_game_engine_state.md#type-state) data
15 | type. This data type is used when retrieving game engine's state from
16 | [`nuk_game_session:get_nuk_state/1`](nuk_game_session.md#get_nuk_state-1).
17 |
18 |
19 | ## Data Types ##
20 |
21 |
22 |
23 |
24 | ### state() ###
25 |
26 |
27 | __abstract datatype__: `state()`
28 |
29 | Data type containing game engine's game state. This is part of
30 | [`nuk_game_session:session()`](nuk_game_session.md#type-session) data type. Functions in this module can
31 | be used to operate on the following data:
32 | - `private`: part of the state that stays private to the game engine; it is
33 | never shared with any players
34 | - `public`: part of the state that is public - it is shared with all players
35 | - `players`: a map of players usernames to the data that will be shared to
36 | those specific players only
37 | All above data is of type `term()` - i.e. it's up to the game engine
38 |
39 |
40 |
41 | ## Function Index ##
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ## Function Details ##
50 |
51 |
52 |
53 | ### get_all/1 ###
54 |
55 |
56 | get_all(State::state()) -> list()
57 |
58 |
59 |
60 | Get all components of engine state
61 |
62 | Returns a list containing all - private, public and players - states.
63 |
64 |
65 |
66 | ### get_player/2 ###
67 |
68 |
69 | get_player(State::state(), Username::string()) -> term()
70 |
71 |
72 |
73 | Get player specific state
74 |
75 | Gets state specific to the given player.
76 |
77 |
78 |
79 | ### get_players/1 ###
80 |
81 |
82 | get_players(State::state()) -> #{}
83 |
84 |
85 |
86 | Get a map of all player states
87 |
88 | Gets a map of player states with player usernames as keys.
89 |
90 |
91 |
92 | ### get_private/1 ###
93 |
94 |
95 | get_private(State::state()) -> term()
96 |
97 |
98 |
99 | Get private state
100 |
101 | Gets game engine's private state.
102 |
103 |
104 |
105 | ### get_public/1 ###
106 |
107 |
108 | get_public(State::state()) -> term()
109 |
110 |
111 |
112 | Get public state
113 |
114 | Gets game engine's public state.
115 |
116 |
117 |
118 | ### new/3 ###
119 |
120 |
121 | new(Private::term(), Public::term(), Players::#{}) -> state()
122 |
123 |
124 |
125 | Create new a new [`state()`](#type-state) data type
126 |
127 | Creates a new state with specified values.
128 |
129 |
130 |
131 | ### put_player/3 ###
132 |
133 |
134 | put_player(State::state(), Username::string(), Player::term()) -> state()
135 |
136 |
137 |
138 | Put a state for a new or existing player
139 |
140 | Sets a state for a specific player; if the Username doesn't exist, it is
141 | added; if it exists its data is overwritten.
142 |
143 |
144 |
145 | ### remove_player/2 ###
146 |
147 |
148 | remove_player(State::state(), Username::string()) -> state()
149 |
150 |
151 |
152 | Remove player from players state
153 |
154 | Completely removes a player from the map of player states
155 |
156 |
157 |
158 | ### set_all/4 ###
159 |
160 |
161 | set_all(State::state(), Private::term(), Public::term(), Players::#{}) -> state()
162 |
163 |
164 |
165 | Set all components of the engine state
166 |
167 | Sets all - private, public and players - states
168 |
169 |
170 |
171 | ### set_player/3 ###
172 |
173 |
174 | set_player(State::state(), Username::string(), Player::term()) -> state()
175 |
176 |
177 |
178 | Set a state for an existing player
179 |
180 | Sets a state for a specific existing player.
181 |
182 |
183 |
184 | ### set_players/2 ###
185 |
186 |
187 | set_players(State::state(), Players::#{}) -> state()
188 |
189 |
190 |
191 | Set all players state
192 |
193 | Sets a map of states for all players.
194 |
195 |
196 |
197 | ### set_private/2 ###
198 |
199 |
200 | set_private(State::state(), Private::term()) -> state()
201 |
202 |
203 |
204 | Set private state
205 |
206 | Sets game engine private session state.
207 |
208 |
209 |
210 | ### set_public/2 ###
211 |
212 |
213 | set_public(State::state(), Public::term()) -> state()
214 |
215 |
216 |
217 | Set public state
218 |
219 | Sets game engine public session state.
220 |
221 |
--------------------------------------------------------------------------------
/doc/nuk_game_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_server #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_game_server` module.
10 |
11 | __Behaviours:__ [`gen_server`](gen_server.md).
12 |
13 |
14 |
15 | ## Description ##
16 |
17 | When a nuk game session is created, a new process is spawned that keeps the
18 | general nuk state and the arbitrary game engine state. It is also
19 | responsible for processing significant events during the lifetime of the
20 | game, triggering appropriate [`nuk_game_engine`](nuk_game_engine.md) callbacks, and
21 | processing their results. This is the `gen_server` module that accomplishes
22 | the above.
23 |
24 | For public API to accessing this functionality use the [`nuk_games`](nuk_games.md)
25 | module. Do not call the functions of this module directly.
26 |
27 |
28 | ## Data Types ##
29 |
30 |
31 |
32 |
33 | ### state() ###
34 |
35 |
36 |
37 | state() = #{session_id => string(), session => nuk_game_session:session()}
38 |
39 |
40 |
41 |
42 | ## Function Index ##
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## Function Details ##
51 |
52 |
53 |
54 | ### code_change/3 ###
55 |
56 | `code_change(OldVersion, State, Extra) -> any()`
57 |
58 |
59 |
60 | ### create/3 ###
61 |
62 |
63 | create(User::nuk_user:user(), GameName::string(), Options::list()) -> {ok, GameSessionId::string()} | {error, invalid_game_name, Extra::string()} | {error, invalid_options, Extra::string()}
64 |
65 |
66 |
67 | Create a new game session
68 |
69 | Given a user, name of the game and list of options, creates a new game
70 | session. This function does 2 things:
71 | - starts a new `nuk_game_server` child via [`nuk_game_sup`](nuk_game_sup.md)
72 | - sends itself an `initialize` message to invoke the game engine to
73 | obtain the initial game state
74 |
75 | Calling this function triggers the [`nuk_game_engine:initialize/2`](nuk_game_engine.md#initialize-2)
76 | callback.
77 |
78 | For public API [`nuk_games:create/2`](nuk_games.md#create-2) or [`nuk_games:create/3`](nuk_games.md#create-3) must
79 | be used.
80 |
81 |
82 |
83 | ### get_session/2 ###
84 |
85 |
86 | get_session(Pid::pid(), User::nuk_user:user()) -> nuk_game_session:session()
87 |
88 |
89 |
90 | Get a snapshot of game session
91 |
92 | This is a function powering the implementation of
93 | [`nuk_games:get_game_session/2`](nuk_games.md#get_game_session-2). It returns the current snapshot of
94 | the general nuk game session state and arbitrary game engine state.
95 |
96 | For public API [`nuk_games:get_game_session/2`](nuk_games.md#get_game_session-2) must be used.
97 |
98 |
99 |
100 | ### handle_call/3 ###
101 |
102 | `handle_call(X1, From, State) -> any()`
103 |
104 |
105 |
106 | ### handle_cast/2 ###
107 |
108 | `handle_cast(Msg, State) -> any()`
109 |
110 |
111 |
112 | ### handle_info/2 ###
113 |
114 | `handle_info(Msg, State) -> any()`
115 |
116 |
117 |
118 | ### init/1 ###
119 |
120 |
121 | init(GameName::[GameName::string()]) -> {ok, State::state()}
122 |
123 |
124 |
125 |
126 |
127 | ### join/2 ###
128 |
129 |
130 | join(Pid::pid(), User::nuk_user:user()) -> ok | {error, user_already_joined, Extra::string()} | {error, max_users_reached, Extra::string()}
131 |
132 |
133 |
134 | Join a user to the game session
135 |
136 | This is a function powering the implementation of [`nuk_games:join/2`](nuk_games.md#join-2).
137 | It adds the given user to the current game session after validating that
138 | - user hasn't already joined the game
139 | - maximum number of users allowed by the game wouldn't be exceeded
140 |
141 | Calling this function triggers the [`nuk_game_engine:player_join/3`](nuk_game_engine.md#player_join-3)
142 | callback.
143 |
144 | For public API [`nuk_games:join/2`](nuk_games.md#join-2) must be used.
145 |
146 |
147 |
148 | ### leave/2 ###
149 |
150 |
151 | leave(Pid::pid(), User::nuk_user:user()) -> ok | {error, user_not_in_game, Extra::string()}
152 |
153 |
154 |
155 | Remove a user from the game session
156 |
157 | This is a function powering the implementation of [`nuk_games:leave/2`](nuk_games.md#leave-2).
158 | It removes a given user from the current game session after validating that
159 | user is in the current game session.
160 |
161 | Calling this function triggers the [`nuk_game_engine:player_leave/3`](nuk_game_engine.md#player_leave-3)
162 | callback.
163 |
164 | For public API [`nuk_games:leave/2`](nuk_games.md#leave-2) must be used.
165 |
166 |
167 |
168 | ### start/2 ###
169 |
170 |
171 | start(Pid::pid(), User::nuk_user:user()) -> ok | {error, user_not_in_game, Extra::string()}
172 |
173 |
174 |
175 | Start a game
176 |
177 | This is a function powering the implementation of [`nuk_games:start/2`](nuk_games.md#start-2).
178 | It starts the current game session after validating that the user requesting
179 | the action is in the current game session.
180 |
181 | Calling this function triggers the [`nuk_game_engine:start/2`](nuk_game_engine.md#start-2) callback.
182 |
183 | For public API [`nuk_games:start/2`](nuk_games.md#start-2) must be used.
184 |
185 |
186 |
187 | ### start_link/1 ###
188 |
189 | `start_link(GameName) -> any()`
190 |
191 |
192 |
193 | ### terminate/2 ###
194 |
195 | `terminate(Reason, State) -> any()`
196 |
197 |
198 |
199 | ### turn/3 ###
200 |
201 |
202 | turn(Pid::pid(), User::nuk_user:user(), Turn::term()) -> ok | {error, user_not_in_game, Extra::string()} | {error, bad_turn_order, Extra::string()} | {error, invalid_turn, Extra::string()}
203 |
204 |
205 |
206 | Process player's turn
207 |
208 | This is a function powering the implementation of [`nuk_games:turn/3`](nuk_games.md#turn-3).
209 | It takes and processes a turn for a given player after verifying that the
210 | given player may make a turn at current stage of the game.
211 |
212 | Calling this function triggers the [`nuk_game_engine:turn/4`](nuk_game_engine.md#turn-4) callback.
213 | The game engine may return the `invalid_turn` error if the turn data is
214 | not acceptable.
215 |
216 | For public API [`nuk_games:turn/3`](nuk_games.md#turn-3) must be used.
217 |
218 |
--------------------------------------------------------------------------------
/doc/nuk_game_session.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_session #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_game_session` module.
10 |
11 |
12 |
13 | ## Description ##
14 | This module is used to operate on [`nuk_game_session:session()`](nuk_game_session.md#type-session) data
15 | type. This data type is used when retrieving the game session state from
16 | [`nuk_games:get_game_session/2`](nuk_games.md#get_game_session-2). It tracks the following data:
17 | - Game [`nuk_game:game()`](nuk_game.md#type-game) which this session is for
18 | - nuk's general game session state
19 | - Game engine's arbitrary state
20 |
21 |
22 | ## Data Types ##
23 |
24 |
25 |
26 |
27 | ### session() ###
28 |
29 |
30 | __abstract datatype__: `session()`
31 |
32 | Data type used to represent a game session state. Use functions in this
33 | module to operate on this data type. It contains the following:
34 | - `game`: [`nuk_game:game()`](nuk_game.md#type-game) data type, use [`get_game/1`](#get_game-1) to
35 | extract
36 | - `nuk_state`: [`nuk_game_state:state()`](nuk_game_state.md#type-state) data type, use functions in
37 | this module to extract specific values from this state
38 | - `game_state`: [`nuk_game_engine_state:state()`](nuk_game_engine_state.md#type-state) data type containing
39 | game engine specific state; use [`nuk_game_engine_state`](nuk_game_engine_state.md) functions
40 | to operate on this data
41 | information from this data type
42 |
43 |
44 |
45 | ## Function Index ##
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ## Function Details ##
54 |
55 |
56 |
57 | ### add_player/2 ###
58 |
59 |
60 | add_player(Session::session(), Player::nuk_user:user()) -> session()
61 |
62 |
63 |
64 | Add a player to the game session
65 |
66 | Whenver a players joins a game, nuk uses this function to add that player to
67 | the game session.
68 |
69 |
70 |
71 | ### get_game/1 ###
72 |
73 |
74 | get_game(Session::session()) -> nuk_game:game()
75 |
76 |
77 |
78 | Get game
79 |
80 | Returns the [`nuk_game:game()`](nuk_game.md#type-game) data type to which this session belongs.
81 |
82 |
83 |
84 | ### get_game_state/1 ###
85 |
86 |
87 | get_game_state(Session::session()) -> nuk_game_engine_state:state()
88 |
89 |
90 |
91 | Get game engine arbitrary state
92 |
93 | Returns game engine specific state of the session. Refer to
94 | [`nuk_game_engine_state`](nuk_game_engine_state.md) module to operate on this data type.
95 |
96 |
97 |
98 | ### get_nuk_state/1 ###
99 |
100 |
101 | get_nuk_state(Session::session()) -> nuk_game_state:state()
102 |
103 |
104 |
105 | Get general nuk game state
106 |
107 | Returns the general nuk state in the form of [`nuk_game_state:state()`](nuk_game_state.md#type-state)
108 | data type. Use [`nuk_game_state`](nuk_game_state.md) module to operate on this data type.
109 |
110 |
111 |
112 | ### get_players/1 ###
113 |
114 |
115 | get_players(Session::session()) -> [nuk_user:user()]
116 |
117 |
118 |
119 | Get players currently in the game session
120 |
121 | Returns a list of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a list
122 | of players currently joined to this game session.
123 |
124 |
125 |
126 | ### get_players_count/1 ###
127 |
128 |
129 | get_players_count(Session::session()) -> non_neg_integer()
130 |
131 |
132 |
133 | Get number of players currently in the game session
134 |
135 | Returns number of players currently in this game session.
136 |
137 |
138 |
139 | ### get_players_turn/1 ###
140 |
141 |
142 | get_players_turn(Session::session()) -> [nuk_user:user()]
143 |
144 |
145 |
146 | Get players whose turn it is next
147 |
148 | Returns a list of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a list
149 | of players who the game engine is expecting to make the turn(s) next. i.e.
150 | the answer to "whose turn is it?" question.
151 |
152 |
153 |
154 | ### get_status/1 ###
155 |
156 |
157 | get_status(Session::session()) -> nuk_game_state:status()
158 |
159 |
160 |
161 | Get game session status
162 |
163 | Returns an atom status of the game session
164 |
165 |
166 |
167 | ### get_turn_number/1 ###
168 |
169 |
170 | get_turn_number(Session::session()) -> non_neg_integer()
171 |
172 |
173 |
174 | Get turn number
175 |
176 | Every time any player makes a turn nuk increments an internal turn counter.
177 | This returns the current turn number from the game session.
178 |
179 |
180 |
181 | ### get_winners_losers/1 ###
182 |
183 |
184 | get_winners_losers(Session::session()) -> {Winners::[nuk_user:user()], Losers::[nuk_user:user()]}
185 |
186 |
187 |
188 | Get winners and losers lists
189 |
190 | Returns two lists of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a
191 | list of players who have won and who have lost the game. This is only
192 | relevant once the game has completed. In all other cases these lists will
193 | be empty.
194 |
195 |
196 |
197 | ### has_player/2 ###
198 |
199 |
200 | has_player(Session::session(), Player::nuk_user:user()) -> boolean()
201 |
202 |
203 |
204 | Is a player a member of this game session?
205 |
206 | This is useful for checking whether a given user is a player in this game
207 | session.
208 |
209 |
210 |
211 | ### increment_turn_number/1 ###
212 |
213 |
214 | increment_turn_number(Session::session()) -> session()
215 |
216 |
217 |
218 | Increments the internal turn number
219 |
220 | nuk uses an internal turn counter every time any player makes a turn. This
221 | is used to increment that turn counter.
222 |
223 |
224 |
225 | ### is_players_turn/2 ###
226 |
227 |
228 | is_players_turn(Session::session(), Player::nuk_user:user()) -> boolean()
229 |
230 |
231 |
232 | Is it a given player's turn?
233 |
234 | This is useful to checking whether it is OK for a given player to make a
235 | turn.
236 |
237 |
238 |
239 | ### new/1 ###
240 |
241 |
242 | new(Game::nuk_game:game()) -> session()
243 |
244 |
245 |
246 | Create a new [`session()`](#type-session) data type.
247 |
248 | `Game` is a [`nuk_game:game()`](nuk_game.md#type-game) data type which is stored inside the
249 | session. All other values are set to their defaults.
250 |
251 |
252 |
253 | ### remove_player/2 ###
254 |
255 |
256 | remove_player(Session::session(), Player::nuk_user:user()) -> session()
257 |
258 |
259 |
260 | Remove a player from the game session
261 |
262 | Whenver a player leaves a game, nuk uses this function to remove that player
263 | from the game session.
264 |
265 |
266 |
267 | ### set_game_state/2 ###
268 |
269 |
270 | set_game_state(Session::session(), GameState::term()) -> session()
271 |
272 |
273 |
274 | Sets game engine state in the session
275 |
276 | With every successful [`nuk_game_engine`](nuk_game_engine.md) callback the game engine
277 | returns its new state. This function is used to then store that state in the
278 | game session.
279 |
280 |
281 |
282 | ### set_players/2 ###
283 |
284 |
285 | set_players(Session::session(), Players::[nuk_user:user()]) -> session()
286 |
287 |
288 |
289 | Set a list of players to the current game session
290 |
291 | nuk uses this function to set the initial players to the game session.
292 |
293 |
294 |
295 | ### set_players_turn/2 ###
296 |
297 |
298 | set_players_turn(Session::session(), Players::[nuk_user:user()]) -> session()
299 |
300 |
301 |
302 | Set players whose turn it is next
303 |
304 | In cases where [`nuk_game_engine`](nuk_game_engine.md) callback returns the players whose
305 | turn it is next, nuk uses this function to update them in its session state.
306 |
307 |
308 |
309 | ### set_status/2 ###
310 |
311 |
312 | set_status(Session::session(), Status::nuk_game_state:status()) -> session()
313 |
314 |
315 |
316 | Set game session status
317 |
318 | nuk uses this function to update the general game status.
319 |
320 |
321 |
322 | ### set_turn_number/2 ###
323 |
324 |
325 | set_turn_number(Session::session(), TurnNumber::non_neg_integer()) -> session()
326 |
327 |
328 |
329 | Set turn number
330 |
331 | Sets turn number to a specified integer.
332 |
333 |
334 |
335 | ### set_winners_losers/3 ###
336 |
337 |
338 | set_winners_losers(Session::session(), Winners::[nuk_user:user()], Losers::[nuk_user:user()]) -> session()
339 |
340 |
341 |
342 | Set winners and losers
343 |
344 | nuk uses this function to set winners and losers in the game session. This
345 | is used once the game has completed.
346 |
347 |
--------------------------------------------------------------------------------
/doc/nuk_game_session_storage.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_session_storage #
4 | * [Description](#description)
5 |
6 | `nuk_game_session_storage` module.
7 |
8 |
9 |
10 | ## Description ##
11 | This is a behavior that allows to extend the game session ID to game process
12 | ID map. The default simple proof of concept implementation is provided in
13 | [`nuk_game_session_store_server`](nuk_game_session_store_server.md).
--------------------------------------------------------------------------------
/doc/nuk_game_session_store_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_session_store_server #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_session_store_server` module.
9 |
10 | __Behaviours:__ [`nuk_game_session_storage`](nuk_game_session_storage.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This is an implementation of [`nuk_game_session_storage`](nuk_game_session_storage.md) behavior. It
16 | is meant for testing and proof of concept purposes only.
17 |
18 | ## Function Index ##
19 |
20 |
21 | delete/1 | Delete a session ID mapping. |
get_pid/1 | Get game session process ID. |
put/1 | Create a new game session identifier. |
22 |
23 |
24 |
25 |
26 | ## Function Details ##
27 |
28 |
29 |
30 | ### delete/1 ###
31 |
32 |
33 | delete(SessionId::string()) -> ok
34 |
35 |
36 |
37 | Delete a session ID mapping
38 |
39 | Given a session identifier string, deletes its mapping to the game session
40 | process ID, so that next call to [`nuk_game_session_store_server:get/1`](nuk_game_session_store_server.md#get-1)
41 | results in `game_session_not_found` response.
42 |
43 |
44 |
45 | ### get_pid/1 ###
46 |
47 |
48 | get_pid(SessionId::string()) -> {ok, pid()} | {error, game_session_not_found, Extra::string()}
49 |
50 |
51 |
52 | Get game session process ID
53 |
54 | Given the session identifier string, returns the game session `pid()` which
55 | then can be used to interface with [`nuk_game_server`](nuk_game_server.md) functions.
56 |
57 |
58 |
59 | ### put/1 ###
60 |
61 |
62 | put(Pid::pid()) -> SessionId::string()
63 |
64 |
65 |
66 | Create a new game session identifier
67 |
68 | Given a game session process ID, creates a new session identifier string.
69 |
70 |
--------------------------------------------------------------------------------
/doc/nuk_game_sessions.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_sessions #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_sessions` module.
9 |
10 |
11 |
12 | ## Description ##
13 |
14 | This module should be used as an API for mapping game session identifiers
15 | to process IDs:
16 | - given a game process `pid()` create a new unique session identifier
17 | - translate a given unique session identifier to the game session `pid()`
18 |
19 | The backend implementation of this is swappable. See
20 | [`nuk_game_session_storage`](nuk_game_session_storage.md) behavior and
21 | [`nuk_game_session_store_server`](nuk_game_session_store_server.md) default implementation.
22 |
23 | ## Function Index ##
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ## Function Details ##
32 |
33 |
34 |
35 | ### delete/1 ###
36 |
37 |
38 | delete(SessionId::string()) -> ok
39 |
40 |
41 |
42 | Delete a session
43 |
44 | Delete the session associated with the given session identifier.
45 |
46 |
47 |
48 | ### get_pid/1 ###
49 |
50 |
51 | get_pid(SessionId::string()) -> {ok, Pid::pid()} | {error, game_session_not_found, Extra::string()}
52 |
53 |
54 |
55 | Get a process ID
56 |
57 | Given a previously created session identifier, retrieve a process ID.
58 |
59 |
60 |
61 | ### put/1 ###
62 |
63 |
64 | put(Pid::pid()) -> SessionId::string()
65 |
66 |
67 |
68 | Create a new session
69 |
70 | Given a process ID, create a new unique session identifier.
71 |
72 |
--------------------------------------------------------------------------------
/doc/nuk_game_state.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_state #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_game_state` module.
10 |
11 |
12 |
13 | ## Description ##
14 | This module is used to operate on [`nuk_game_state:state()`](nuk_game_state.md#type-state) data type.
15 | This data type is used when retrieving nuk's general game session state from
16 | [`nuk_game_session:get_nuk_state/1`](nuk_game_session.md#get_nuk_state-1).
17 |
18 |
19 | ## Data Types ##
20 |
21 |
22 |
23 |
24 | ### state() ###
25 |
26 |
27 | __abstract datatype__: `state()`
28 |
29 | Data type containing nuk's general game state. This is part of
30 | [`nuk_game_session:session()`](nuk_game_session.md#type-session) data type. Functions in this module can
31 | be used to operate on the following data:
32 | - `status`: game session status, default `nil`, use [`get_status/1`](#get_status-1) to
33 | extract
34 | - `turn_number`: current turn number, default `0`, use
35 | [`get_turn_number/1`](#get_turn_number-1) to extract
36 | - `players`: list of players currently in the game session, default `[]`,
37 | use [`get_players/1`](#get_players-1) to extract
38 | - `players_turn`: list of players who should make turn(s) next,
39 | default `[]`, use [`get_players_turn/1`](#get_players_turn-1) to extract
40 | - `players_winners`: list of players who won the game, only populated
41 | after the game completes, default `[]`, use [`get_winners_losers/1`](#get_winners_losers-1)
42 | to extract
43 | - `players_losers`: list of players who lost the game, only populated
44 | after the game completes, default `[]`, use [`get_winners_losers/1`](#get_winners_losers-1)
45 | to extract
46 |
47 |
48 |
49 | ### status() ###
50 |
51 |
52 |
53 | status() = nil | initialized | await_turn | complete
54 |
55 |
56 | General game session status tracked by nuk.
57 |
58 |
59 |
60 | ## Function Index ##
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ## Function Details ##
69 |
70 |
71 |
72 | ### get_players/1 ###
73 |
74 |
75 | get_players(State::state()) -> [nuk_user:user()]
76 |
77 |
78 |
79 | Get players currently in the game session
80 |
81 | Returns a list of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a list
82 | of players currently joined to this game session.
83 |
84 |
85 |
86 | ### get_players_turn/1 ###
87 |
88 |
89 | get_players_turn(State::state()) -> [nuk_user:user()]
90 |
91 |
92 |
93 | Get players whose turn it is next
94 |
95 | Returns a list of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a list
96 | of players who the game engine is expecting to make the turn(s) next. i.e.
97 | the answer to "whose turn is it?" question.
98 |
99 |
100 |
101 | ### get_status/1 ###
102 |
103 |
104 | get_status(State::state()) -> status()
105 |
106 |
107 |
108 | Get game session status
109 |
110 | Returns an atom status of the game session.
111 |
112 |
113 |
114 | ### get_turn_number/1 ###
115 |
116 |
117 | get_turn_number(State::state()) -> non_neg_integer()
118 |
119 |
120 |
121 | Get turn number
122 |
123 | Every time any player makes a turn nuk increments an internal turn counter.
124 | This returns the current turn number from the game session.
125 |
126 |
127 |
128 | ### get_winners_losers/1 ###
129 |
130 |
131 | get_winners_losers(State::state()) -> {Winners::[nuk_user:user()], Losers::[nuk_user:user()]}
132 |
133 |
134 |
135 | Get winners and losers lists
136 |
137 | Returns two lists of [`nuk_user:user()`](nuk_user.md#type-user) data types that represent a
138 | list of players who have won and who have lost the game. This is only
139 | relevant once the game has completed. In all other cases these lists are
140 | likely to be empty.
141 |
142 |
143 |
144 | ### new/0 ###
145 |
146 |
147 | new() -> state()
148 |
149 |
150 |
151 | Create a new [`state()`](#type-state) data type.
152 |
153 | Creates a new state with default values.
154 |
155 |
156 |
157 | ### set_players/2 ###
158 |
159 |
160 | set_players(State::state(), Players::[nuk_user:user()]) -> state()
161 |
162 |
163 |
164 | Set a list of players to the current game session
165 |
166 | Useful for setting initial set of players for the game session.
167 |
168 |
169 |
170 | ### set_players_turn/2 ###
171 |
172 |
173 | set_players_turn(State::state(), Players::[nuk_user:user()]) -> state()
174 |
175 |
176 |
177 | Set players whose turn it is next
178 |
179 | Sets a new list of players, replacing an existing list.
180 |
181 |
182 |
183 | ### set_status/2 ###
184 |
185 |
186 | set_status(State::state(), Status::status()) -> state()
187 |
188 |
189 |
190 | Set status
191 |
192 | Sets a new status in the game state.
193 |
194 |
195 |
196 | ### set_turn_number/2 ###
197 |
198 |
199 | set_turn_number(State::state(), TurnNumber::non_neg_integer()) -> state()
200 |
201 |
202 |
203 | Set turn number
204 |
205 | Sets turn number to a specified integer.
206 |
207 |
208 |
209 | ### set_winners_losers/3 ###
210 |
211 |
212 | set_winners_losers(State::state(), Winners::[nuk_user:user()], Losers::[nuk_user:user()]) -> state()
213 |
214 |
215 |
216 | Set winners and losers
217 |
218 | This is typically used once the game has completed.
219 |
220 |
--------------------------------------------------------------------------------
/doc/nuk_game_storage.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_storage #
4 | * [Description](#description)
5 |
6 | `nuk_game_storage` module.
7 |
8 |
9 |
10 | ## Description ##
11 | This behavior allows to extend the storage service for registered games.
12 | When a new game engine is registered with nuk via
13 | [`nuk_games:register/1`](nuk_games.md#register-1) it is stored internally by the system.
14 | Implementing this behavior allows a custom storage backend to be defined.
15 | The default simple implementation is provided with
16 | [`nuk_game_store_server`](nuk_game_store_server.md).
--------------------------------------------------------------------------------
/doc/nuk_game_store_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_store_server #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_store_server` module.
9 |
10 | __Behaviours:__ [`gen_server`](gen_server.md), [`nuk_game_storage`](nuk_game_storage.md).
11 |
12 |
13 |
14 | ## Description ##
15 |
16 | This is an implementation of [`nuk_game_storage`](nuk_game_storage.md) behavior. It is meant
17 | for testing and proof of concept purposes only.
18 |
19 | This is a `gen_server` that's started by the [`nuk_game_store_sup`](nuk_game_store_sup.md)
20 | supervisor. It provides storage interface to registered games. For public
21 | API the [`nuk_games`](nuk_games.md) module should be used which, in turn, will use the
22 | appropriate storage backend.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### code_change/3 ###
37 |
38 | `code_change(OldVersion, State, Extra) -> any()`
39 |
40 |
41 |
42 | ### delete/1 ###
43 |
44 |
45 | delete(GameName::string()) -> ok
46 |
47 |
48 |
49 | Delete a game from registry
50 |
51 | Deletes a game by its name from the game registration database.
52 |
53 |
54 |
55 | ### get/1 ###
56 |
57 |
58 | get(GameName::string()) -> {ok, nuk_game:game()} | {error, game_not_found, string()}
59 |
60 |
61 |
62 | Get a game
63 |
64 | Retrieves a game registration by its name from the database.
65 |
66 |
67 |
68 | ### handle_call/3 ###
69 |
70 | `handle_call(X1, From, State) -> any()`
71 |
72 |
73 |
74 | ### handle_cast/2 ###
75 |
76 | `handle_cast(Msg, State) -> any()`
77 |
78 |
79 |
80 | ### handle_info/2 ###
81 |
82 | `handle_info(Msg, State) -> any()`
83 |
84 |
85 |
86 | ### init/1 ###
87 |
88 | `init(X1) -> any()`
89 |
90 |
91 |
92 | ### list/0 ###
93 |
94 |
95 | list() -> [nuk_game:game()]
96 |
97 |
98 |
99 | List all games
100 |
101 | Lists all registered games from the registration database.
102 |
103 |
104 |
105 | ### put/1 ###
106 |
107 |
108 | put(Game::nuk_game:game()) -> ok
109 |
110 |
111 |
112 | Create or replace a game
113 |
114 | If a game by the name is already registered, replaces that registration;
115 | otherwise creates a new registration.
116 |
117 |
118 |
119 | ### start_link/0 ###
120 |
121 | `start_link() -> any()`
122 |
123 |
124 |
125 | ### terminate/2 ###
126 |
127 | `terminate(Reason, State) -> any()`
128 |
129 |
--------------------------------------------------------------------------------
/doc/nuk_game_store_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_store_sup #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_store_sup` module.
9 |
10 | __Behaviours:__ [`supervisor`](supervisor.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This supervisor is started by [`nuk_sup`](nuk_sup.md) top level supervisor. It
16 | supervises [`nuk_game_store_server`](nuk_game_store_server.md).
17 |
18 | ## Function Index ##
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Function Details ##
27 |
28 |
29 |
30 | ### init/1 ###
31 |
32 | `init(X1) -> any()`
33 |
34 |
35 |
36 | ### start_link/0 ###
37 |
38 | `start_link() -> any()`
39 |
40 |
--------------------------------------------------------------------------------
/doc/nuk_game_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_game_sup #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_game_sup` module.
9 |
10 | __Behaviours:__ [`supervisor`](supervisor.md).
11 |
12 |
13 |
14 | ## Description ##
15 |
16 | This supervisor is started by [`nuk_sup`](nuk_sup.md) top level supervisor.
17 |
18 | Whenever a new game session is created, nuk spawns a new
19 | [`nuk_game_server`](nuk_game_server.md). This module is a `simple_one_for_one` supervisor
20 | that supervises those servers.
21 |
22 | ## Function Index ##
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## Function Details ##
31 |
32 |
33 |
34 | ### init/1 ###
35 |
36 | `init(X1) -> any()`
37 |
38 |
39 |
40 | ### start_link/0 ###
41 |
42 | `start_link() -> any()`
43 |
44 |
--------------------------------------------------------------------------------
/doc/nuk_games.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_games #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_games` module.
9 |
10 |
11 |
12 | ## Description ##
13 |
14 | This module should be used as an API to interacting with all game related
15 | actions. There are 2 types of actions: (1) game registration, and (2) game
16 | flow.
17 |
18 | The game registration functions are:[`register/2`](#register-2),
19 | [`unregister/1`](#unregister-1), [`get/1`](#get-1), [`list/0`](#list-0).
20 |
21 | The game flow functions are: [`create/2`](#create-2), [`create/3`](#create-3),
22 | [`join/2`](#join-2), [`leave/2`](#leave-2), [`start/2`](#start-2),
23 | [`get_game_session/2`](#get_game_session-2), [`turn/3`](#turn-3).
24 |
25 | ## Function Index ##
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ## Function Details ##
34 |
35 |
36 |
37 | ### create/2 ###
38 |
39 |
40 | create(UserSessionId::string(), GameName::string()) -> {ok, GameSessionId::string()} | {error, invalid_user_session, Extra::string()} | {error, invalid_game_name, Extra::string()}
41 |
42 |
43 |
44 | Equivalent to [`create(UserSessionId, GameName, [])`](#create-3).
45 |
46 | Create a new game session with default options
47 |
48 |
49 |
50 | ### create/3 ###
51 |
52 |
53 | create(UserSessionId::string(), GameName::string(), Options::[tuple()]) -> {ok, GameSessionId::string()} | {error, invalid_user_session, Extra::string()} | {error, invalid_game_name, Extra::string()}
54 |
55 |
56 |
57 | Create a new game with options
58 |
59 | Using a logged in user session, create a new game by supplying its
60 | registered name. This does not start a game, but merely creates a new
61 | session allowing other players to join. Game session must be created before
62 | it can be started.
63 |
64 | Calling this function triggers the [`nuk_game_engine:initialize/2`](nuk_game_engine.md#initialize-2)
65 | callback.
66 |
67 |
68 |
69 | ### get/1 ###
70 |
71 |
72 | get(GameName::string()) -> {ok, nuk_game:game()} | {error, game_not_found, Extra::string()}
73 |
74 |
75 |
76 | Get a game by its name
77 |
78 | This can be used to look up any registered game metadata for a specific game
79 | engine. Given a game name, get a [`nuk_game:game()`](nuk_game.md#type-game) data type. Then use
80 | [`nuk_game`](nuk_game.md) module functions to extract needed information.
81 |
82 |
83 |
84 | ### get_game_session/2 ###
85 |
86 |
87 | get_game_session(GameSessionId::string(), UserSessionId::string()) -> {ok, nuk_game_session:session()} | {error, game_session_not_found, Extra::string()} | {error, user_session_not_found, Extra::string()} | {error, user_not_in_game, Extra::string()}
88 |
89 |
90 |
91 | Get game session containing nuk and game engine states
92 |
93 | Returns the current snapshot of the game session state. The
94 | [`nuk_game_session`](nuk_game_session.md) module functions should be used to extract needed
95 | data from the returned [`nuk_game_session:session()`](nuk_game_session.md#type-session) data type. This is
96 | useful for players to get a new game session state during the game.
97 |
98 | Note that [`nuk_game_session:get_game_state/1`](nuk_game_session.md#get_game_state-1) can be used to extract
99 | [`nuk_game_engine_state`](nuk_game_engine_state.md) set by the specific [`nuk_game_engine`](nuk_game_engine.md).
100 |
101 |
102 |
103 | ### join/2 ###
104 |
105 |
106 | join(GameSessionId::string(), UserSessionId::string()) -> ok | {error, game_session_not_found, Extra::string()} | {error, user_session_not_found, Extra::string()} | {error, user_already_joined, Extra::string()} | {error, max_users_reached, Extra::string()}
107 |
108 |
109 |
110 | Join a player to a game session
111 |
112 | Joins a given logged in user session to an existing game session. Game
113 | session must be created first, see [`create/2`](#create-2) and [`create/3`](#create-3).
114 |
115 | Calling this function triggers the [`nuk_game_engine:player_join/3`](nuk_game_engine.md#player_join-3)
116 | callback.
117 |
118 |
119 |
120 | ### leave/2 ###
121 |
122 |
123 | leave(GameSessionId::string(), UserSessionId::string()) -> ok | {error, game_session_not_found, Extra::string()} | {error, user_session_not_found, Extra::string()} | {error, user_not_in_game, Extra::string()} | {error, game_already_started, Extra::string()}
124 |
125 |
126 |
127 | Remove a player from a game session
128 |
129 | This does the opposite of [`join/2`](#join-2) - it allows a player to leave an
130 | existing game session that the player has already joined.
131 |
132 | Calling this function triggers the [`nuk_game_engine:player_leave/3`](nuk_game_engine.md#player_leave-3)
133 | callback.
134 |
135 |
136 |
137 | ### list/0 ###
138 |
139 |
140 | list() -> [nuk_game:game()]
141 |
142 |
143 |
144 | List all registered games
145 |
146 | Produces a list of all registered games in the system. The return is a list
147 | of [`nuk_game:game()`](nuk_game.md#type-game) data types. Use [`nuk_game`](nuk_game.md) module
148 | functions to extract needed information from each game element.
149 |
150 |
151 |
152 | ### register/1 ###
153 |
154 |
155 | register(Game::nuk_game:game()) -> ok
156 |
157 |
158 |
159 | Register a game engine
160 |
161 | This registers a game engine with nuk. After creating a new game engine
162 | by implementing the [`nuk_game_engine`](nuk_game_engine.md) behavior, create a
163 | [`nuk_game:game()`](nuk_game.md#type-game) data type and use it here to register that game.
164 | A game engine must be registered before it can be played.
165 |
166 |
167 |
168 | ### start/2 ###
169 |
170 |
171 | start(GameSessionId::string(), UserSessionId::string()) -> ok | {error, game_session_not_found, Extra::string()} | {error, user_session_not_found, Extra::string()} | {error, min_users_not_met, Extra::string()} | {error, user_not_in_game, Extra::string()}
172 |
173 |
174 |
175 | Start a game
176 |
177 | This starts an existing game session. In general, at this point all players
178 | wishing to participate should have already joined the game via
179 | [`join/2`](#join-2).
180 |
181 | Calling this function triggers the [`nuk_game_engine:start/2`](nuk_game_engine.md#start-2) callback.
182 |
183 |
184 |
185 | ### turn/3 ###
186 |
187 |
188 | turn(GameSessionId::string(), UserSessionId::string(), Turn::term()) -> ok | {error, game_session_not_found, Extra::string()} | {error, user_session_not_found, Extra::string()} | {error, user_not_in_game, Extra::string()} | {error, bad_turn_order, Extra::string()} | {error, invalid_turn, Extra::string()}
189 |
190 |
191 |
192 | Make a player turn
193 |
194 | This function should be used when it's time for a specific player to make a
195 | turn. The `Turn` argument is an arbitrary term that is expected by the game
196 | engine. It is not validated by nuk and is passed to the game engine
197 | directly.
198 |
199 | Calling this function triggers the [`nuk_game_engine:turn/4`](nuk_game_engine.md#turn-4) callback.
200 |
201 |
202 |
203 | ### unregister/1 ###
204 |
205 |
206 | unregister(GameName::string()) -> ok
207 |
208 |
209 |
210 | Unregister a game engine
211 |
212 | Unregistering has the opposite effect of registering via [`register/1`](#register-1).
213 | It makes nuk forget about the registered game.
214 |
215 |
--------------------------------------------------------------------------------
/doc/nuk_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_sup #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_sup` module.
9 |
10 | __Behaviours:__ [`supervisor`](supervisor.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This is a top level `one_for_one` nuk supervisor started by the nuk
16 | application. It, in turn, starts the following supervisors under it:
17 | - [`nuk_user_sup`](nuk_user_sup.md)
18 | - [`nuk_user_store_sup`](nuk_user_store_sup.md)
19 | - [`nuk_game_sup`](nuk_game_sup.md)
20 | - [`nuk_game_store_sup`](nuk_game_store_sup.md)
21 |
22 | ## Function Index ##
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## Function Details ##
31 |
32 |
33 |
34 | ### init/1 ###
35 |
36 | `init(X1) -> any()`
37 |
38 |
39 |
40 | ### start_link/0 ###
41 |
42 | `start_link() -> any()`
43 |
44 |
--------------------------------------------------------------------------------
/doc/nuk_user.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_user` module.
10 |
11 |
12 |
13 | ## Description ##
14 | This module is used to operate on [`nuk_user:user()`](nuk_user.md#type-user) data type. This
15 | data type is used when dealing with users in nuk, for example, when calling
16 | [`nuk_users:put/1`](nuk_users.md#put-1) and [`nuk_users:get/1`](nuk_users.md#get-1).
17 |
18 |
19 | ## Data Types ##
20 |
21 |
22 |
23 |
24 | ### user() ###
25 |
26 |
27 | __abstract datatype__: `user()`
28 |
29 | Data type used to operate on users in nuk. Use functions in this module to
30 | operate on this data type.
31 |
32 |
33 |
34 | ## Function Index ##
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ## Function Details ##
43 |
44 |
45 |
46 | ### check_password/2 ###
47 |
48 |
49 | check_password(X1::user(), EnteredPassword::string()) -> boolean()
50 |
51 |
52 |
53 | Verify user's password
54 |
55 | This function is only here for testing and proof of concept purposes. In
56 | production scenarios where users are stored in external
57 | [`nuk_user_storage`](nuk_user_storage.md) implementations the authentication should be
58 | performed by that system, and this function should not be used.
59 |
60 |
61 |
62 | ### get_username/1 ###
63 |
64 |
65 | get_username(X1::user()) -> string()
66 |
67 |
68 |
69 | Get username
70 |
71 | Extract username from [`nuk_user:user()`](nuk_user.md#type-user) data type.
72 |
73 |
74 |
75 | ### new/2 ###
76 |
77 |
78 | new(Username::string(), Password::string()) -> user()
79 |
80 |
81 |
82 | Create a new [`nuk_user:user()`](nuk_user.md#type-user) data type
83 |
84 | `Username` is a string that uniquely identifies a user in the user storage.
85 | `Password` is a string used to validate user for login. Note that the
86 | password is here for proof of concept and testing purposes only. It does not
87 | provide hashing or secure password storage. When using
88 | [`nuk_user_storage`](nuk_user_storage.md) implementations for the hashed password is likely
89 | stored there, so this password shouldn't be used in that case.
90 |
91 |
--------------------------------------------------------------------------------
/doc/nuk_user_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_server #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_server` module.
9 |
10 | __Behaviours:__ [`gen_server`](gen_server.md).
11 |
12 |
13 |
14 | ## Description ##
15 |
16 | When a user logs in, a new process is spawned that keeps the session of the
17 | user. This is a `gen_server` that keeps the session state and provides
18 | interface to its manipulation.
19 |
20 | For public API to accessing this functionality use the [`nuk_users`](nuk_users.md) and
21 | [`nuk_user_sessions`](nuk_user_sessions.md) modules. Do not call the functions of this module
22 | directly.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### code_change/3 ###
37 |
38 | `code_change(OldVersion, State, Extra) -> any()`
39 |
40 |
41 |
42 | ### get_session/1 ###
43 |
44 |
45 | get_session(Pid::pid()) -> nuk_user_session:session()
46 |
47 |
48 |
49 | Get logged in user session
50 |
51 | Gets the [`nuk_user_session:session()`](nuk_user_session.md#type-session) data type for the given logged
52 | in user session. Use [`nuk_user_session`](nuk_user_session.md) module to operate on the
53 | returned data type.
54 |
55 |
56 |
57 | ### handle_call/3 ###
58 |
59 | `handle_call(X1, From, State) -> any()`
60 |
61 |
62 |
63 | ### handle_cast/2 ###
64 |
65 | `handle_cast(Msg, State) -> any()`
66 |
67 |
68 |
69 | ### handle_info/2 ###
70 |
71 | `handle_info(Msg, State) -> any()`
72 |
73 |
74 |
75 | ### init/1 ###
76 |
77 | `init(X1) -> any()`
78 |
79 |
80 |
81 | ### login/3 ###
82 |
83 |
84 | login(Username::string(), Password::string(), StorageModule::atom()) -> {ok, string()} | {error, wrong_password | user_not_found, string()}
85 |
86 |
87 |
88 | Log in a user
89 |
90 | Attempts to log a user in given the username and password. Upon successful
91 | login a new process is spawned and the string session identifier returned.
92 |
93 |
94 |
95 | ### logout/1 ###
96 |
97 |
98 | logout(Pid::pid()) -> ok
99 |
100 |
101 |
102 | Log out a user
103 |
104 | Logs a user session out - i.e. stops the process that was keeping the logged
105 | in user state.
106 |
107 |
108 |
109 | ### start_link/0 ###
110 |
111 | `start_link() -> any()`
112 |
113 |
114 |
115 | ### terminate/2 ###
116 |
117 | `terminate(Reason, State) -> any()`
118 |
119 |
--------------------------------------------------------------------------------
/doc/nuk_user_session.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_session #
4 | * [Description](#description)
5 | * [Data Types](#types)
6 | * [Function Index](#index)
7 | * [Function Details](#functions)
8 |
9 | `nuk_user_session` module.
10 |
11 |
12 |
13 | ## Description ##
14 |
15 | This module is used to operate on [`nuk_user_session:session()`](nuk_user_session.md#type-session) data
16 | type. This data type is used when dealing with user sessions in nuk, for
17 | example, when calling [`nuk_user_sessions:get/1`](nuk_user_sessions.md#get-1).
18 |
19 | For public API the [`nuk_user_sessions`](nuk_user_sessions.md) module should be used which
20 | already provides convenience functions for extracting information from
21 | user sessions.
22 |
23 |
24 | ## Data Types ##
25 |
26 |
27 |
28 |
29 | ### session() ###
30 |
31 |
32 | __abstract datatype__: `session()`
33 |
34 |
35 |
36 | ## Function Index ##
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ## Function Details ##
45 |
46 |
47 |
48 | ### get_user/1 ###
49 |
50 |
51 | get_user(X1::session()) -> nuk_user:user()
52 |
53 |
54 |
55 | Get user
56 |
57 | Extracts a [`nuk_user:user()`](nuk_user.md#type-user) stored in the session.
58 |
59 |
60 |
61 | ### new/0 ###
62 |
63 |
64 | new() -> session()
65 |
66 |
67 |
68 | Create a new [`nuk_user_session:session()`](nuk_user_session.md#type-session) data type
69 |
70 | Creates a new data type with default values.
71 |
72 |
73 |
74 | ### set_user/2 ###
75 |
76 |
77 | set_user(Session::session(), User::nuk_user:user()) -> session()
78 |
79 |
80 |
81 | Set user
82 |
83 | Set a specified [`nuk_user:user()`](nuk_user.md#type-user) in the session.
84 |
85 |
--------------------------------------------------------------------------------
/doc/nuk_user_session_storage.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_session_storage #
4 | * [Description](#description)
5 |
6 | `nuk_user_session_storage` module.
7 |
8 |
9 |
10 | ## Description ##
11 | This is a behavior that allows to extend the user session ID to user process
12 | ID map. The default simple proof of concept implementation is provided in
13 | [`nuk_user_session_store_server`](nuk_user_session_store_server.md).
--------------------------------------------------------------------------------
/doc/nuk_user_session_store_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_session_store_server #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_session_store_server` module.
9 |
10 | __Behaviours:__ [`nuk_user_session_storage`](nuk_user_session_storage.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This is an implementation of [`nuk_user_session_storage`](nuk_user_session_storage.md) behavior. It
16 | is meant for testing and proof of concept purposes only.
17 |
18 | ## Function Index ##
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Function Details ##
27 |
28 |
29 |
30 | ### delete/1 ###
31 |
32 |
33 | delete(SessionId::string()) -> ok
34 |
35 |
36 |
37 | Delete a session ID mapping
38 |
39 | Given a session identifier string, deletes its mapping to the user session
40 | process ID, so that next call to [`nuk_user_session_store_server:get/1`](nuk_user_session_store_server.md#get-1)
41 | results in `game_session_not_found` response.
42 |
43 |
44 |
45 | ### get_pid/1 ###
46 |
47 |
48 | get_pid(SessionId::string()) -> {ok, pid()} | {error, user_session_not_found, Extra::string()}
49 |
50 |
51 |
52 | Get game session process ID
53 |
54 | Given the session identifier string, returns the game session `pid()` which
55 | then can be used to interface with [`nuk_user_server`](nuk_user_server.md) functions.
56 |
57 |
58 |
59 | ### list/0 ###
60 |
61 |
62 | list() -> [nuk_user_session:session()]
63 |
64 |
65 |
66 | List all sessions
67 |
68 | Returns a list of all user sessions. Used in tests.
69 |
70 |
71 |
72 | ### put/1 ###
73 |
74 |
75 | put(Pid::pid()) -> SessionId::string()
76 |
77 |
78 |
79 | Create a new user session identifier
80 |
81 | Given a user session process ID, creates a new session identifier string.
82 |
83 |
--------------------------------------------------------------------------------
/doc/nuk_user_sessions.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_sessions #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_sessions` module.
9 |
10 |
11 |
12 | ## Description ##
13 |
14 | This module should be used as an API for mapping user session identifiers
15 | to process IDs:
16 | - given a user process `pid()` create a new unique session identifier
17 | - translate a given unique session identifier to the user session `pid()`
18 |
19 | It also provides several convenience functions for getting and extracting
20 | data from user sessions.
21 |
22 | The backend implementation of this is swappable. See
23 | [`nuk_user_session_storage`](nuk_user_session_storage.md) behavior and
24 | [`nuk_user_session_store_server`](nuk_user_session_store_server.md) default implementation.
25 |
26 | ## Function Index ##
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## Function Details ##
35 |
36 |
37 |
38 | ### delete/1 ###
39 |
40 |
41 | delete(SessionId::string()) -> ok
42 |
43 |
44 |
45 | Delete a session
46 |
47 | Delete the session associated with the given session identifier.
48 |
49 |
50 |
51 | ### get/1 ###
52 |
53 |
54 | get(SessionId::string()) -> {ok, nuk_user_session:session()} | {error, user_session_not_found, Extra::string()}
55 |
56 |
57 |
58 | Get session
59 |
60 | Given a previously created session identifier, retrieve a process ID. Then
61 | call the process to retrive its session [`nuk_user_session:session()`](nuk_user_session.md#type-session)
62 | data type.
63 |
64 |
65 |
66 | ### get_pid/1 ###
67 |
68 |
69 | get_pid(SessionId::string()) -> {ok, pid()} | {error, user_session_not_found, Extra::string()}
70 |
71 |
72 |
73 | Get a process ID
74 |
75 | Given a previously created session identifier, retrieve a process ID.
76 |
77 |
78 |
79 | ### get_user/1 ###
80 |
81 |
82 | get_user(SessionId::string()) -> {ok, nuk_user:user()} | {error, user_session_not_found, Extra::string()}
83 |
84 |
85 |
86 | Get user
87 |
88 | Gets user that is associated with this session identifier.
89 |
90 |
91 |
92 | ### list/0 ###
93 |
94 |
95 | list() -> [nuk_user_session:session()]
96 |
97 |
98 |
99 | List all sessions
100 |
101 | Returns a list of all user sessions. Used in tests.
102 |
103 |
104 |
105 | ### logout/1 ###
106 |
107 |
108 | logout(SessionId::string()) -> ok
109 |
110 |
111 |
112 | Log out a user session
113 |
114 | Logs out the given user session. Note that the [`delete/1`](#delete-1) happens
115 | after the [`nuk_user_server`](nuk_user_server.md) terminates successfully.
116 |
117 |
118 |
119 | ### put/1 ###
120 |
121 |
122 | put(Pid::pid()) -> SessionId::string()
123 |
124 |
125 |
126 | Create a new session
127 |
128 | Given a process ID, create a new unique session identifier.
129 |
130 |
--------------------------------------------------------------------------------
/doc/nuk_user_storage.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_storage #
4 | * [Description](#description)
5 |
6 | `nuk_user_storage` module.
7 |
8 |
9 |
10 | ## Description ##
11 | This behavior allows to extend the storage service for registered users.
12 | When a new user is created engine with nuk via
13 | [`nuk_users:put/1`](nuk_users.md#put-1) it is stored internally by the system.
14 | Implementing this behavior allows a custom storage backend to be defined.
15 | The default simple implementation is provided with
16 | [`nuk_user_store_server`](nuk_user_store_server.md).
--------------------------------------------------------------------------------
/doc/nuk_user_store_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_store_server #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_store_server` module.
9 |
10 | __Behaviours:__ [`gen_server`](gen_server.md), [`nuk_user_storage`](nuk_user_storage.md).
11 |
12 |
13 |
14 | ## Description ##
15 |
16 | This is an implementation of [`nuk_user_storage`](nuk_user_storage.md) behavior. It is meant
17 | for testing and proof of concept purposes only.
18 |
19 | This is a `gen_server` that's started by the [`nuk_user_store_sup`](nuk_user_store_sup.md)
20 | supervisor. It provides storage interface to registered users. For public
21 | API the [`nuk_users`](nuk_users.md) module should be used which, in turn, will use the
22 | appropriate storage backend.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### code_change/3 ###
37 |
38 | `code_change(OldVersion, State, Extra) -> any()`
39 |
40 |
41 |
42 | ### delete/1 ###
43 |
44 |
45 | delete(Username::string()) -> ok
46 |
47 |
48 |
49 | Delete a user
50 |
51 | Deletes a user by username from the user data storage.
52 |
53 |
54 |
55 | ### get/1 ###
56 |
57 |
58 | get(Username::string()) -> {ok, nuk_user:user()} | {error, user_not_found, string()}
59 |
60 |
61 |
62 | Get a user
63 |
64 | Retrieves a user by username from the user data storage.
65 |
66 |
67 |
68 | ### handle_call/3 ###
69 |
70 | `handle_call(X1, From, State) -> any()`
71 |
72 |
73 |
74 | ### handle_cast/2 ###
75 |
76 | `handle_cast(Msg, State) -> any()`
77 |
78 |
79 |
80 | ### handle_info/2 ###
81 |
82 | `handle_info(Msg, State) -> any()`
83 |
84 |
85 |
86 | ### init/1 ###
87 |
88 | `init(X1) -> any()`
89 |
90 |
91 |
92 | ### list/0 ###
93 |
94 |
95 | list() -> [nuk_user:user()]
96 |
97 |
98 |
99 | List all users
100 |
101 | Lists all registered users in the user data storage.
102 |
103 |
104 |
105 | ### put/1 ###
106 |
107 |
108 | put(User::nuk_user:user()) -> ok
109 |
110 |
111 |
112 | Create or replace a user
113 |
114 | If a user by the username is already registered, replaces that registration;
115 | otherwise creates a new registered user.
116 |
117 |
118 |
119 | ### start_link/0 ###
120 |
121 | `start_link() -> any()`
122 |
123 |
124 |
125 | ### terminate/2 ###
126 |
127 | `terminate(Reason, State) -> any()`
128 |
129 |
130 |
131 | ### validate/2 ###
132 |
133 |
134 | validate(Username::string(), Password::string()) -> {ok, nuk_user:user()} | {error, wrong_password | user_not_found, string()}
135 |
136 |
137 |
138 | Validate user credentials
139 |
140 | Given a username and a password validate that the credentials are correct.
141 |
142 |
--------------------------------------------------------------------------------
/doc/nuk_user_store_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_store_sup #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_store_sup` module.
9 |
10 | __Behaviours:__ [`supervisor`](supervisor.md).
11 |
12 |
13 |
14 | ## Description ##
15 | This supervisor is started by [`nuk_sup`](nuk_sup.md) top level supervisor. It
16 | supervises [`nuk_user_store_server`](nuk_user_store_server.md).
17 |
18 | ## Function Index ##
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Function Details ##
27 |
28 |
29 |
30 | ### init/1 ###
31 |
32 | `init(X1) -> any()`
33 |
34 |
35 |
36 | ### start_link/0 ###
37 |
38 | `start_link() -> any()`
39 |
40 |
--------------------------------------------------------------------------------
/doc/nuk_user_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_user_sup #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_user_sup` module.
9 |
10 | __Behaviours:__ [`supervisor`](supervisor.md).
11 |
12 |
13 |
14 | ## Description ##
15 |
16 | This supervisor is started by [`nuk_sup`](nuk_sup.md) top level supervisor.
17 |
18 | Whenever a user logs in, nuk spawns a new [`nuk_user_server`](nuk_user_server.md). This
19 | module is a `simple_one_for_one` supervisor that supervises those servers.
20 |
21 | ## Function Index ##
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | ## Function Details ##
30 |
31 |
32 |
33 | ### init/1 ###
34 |
35 | `init(X1) -> any()`
36 |
37 |
38 |
39 | ### start_link/0 ###
40 |
41 | `start_link() -> any()`
42 |
43 |
--------------------------------------------------------------------------------
/doc/nuk_users.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module nuk_users #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | `nuk_users` module.
9 |
10 |
11 |
12 | ## Description ##
13 | This module should be used as an API to interacting with all user related
14 | actions.
15 |
16 | ## Function Index ##
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## Function Details ##
25 |
26 |
27 |
28 | ### delete/1 ###
29 |
30 |
31 | delete(Username::string()) -> ok
32 |
33 |
34 |
35 | Delete an existing user
36 |
37 | This deletes an existing user from user storage.
38 |
39 |
40 |
41 | ### get/1 ###
42 |
43 |
44 | get(Username::string()) -> {ok, nuk_user:user()} | {error, user_not_found, string()}
45 |
46 |
47 |
48 | Get a user
49 |
50 | Retrieves a user from user data storage given a username. The return is a
51 | [`nuk_user:user()`](nuk_user.md#type-user) data type. Use [`nuk_user`](nuk_user.md) module to extract
52 | needed information.
53 |
54 |
55 |
56 | ### list/0 ###
57 |
58 |
59 | list() -> [nuk_user:user()]
60 |
61 |
62 |
63 | List all users
64 |
65 | Get all users from user storage. The return is a list of
66 | [`nuk_user:user()`](nuk_user.md#type-user) data types. Use [`nuk_user`](nuk_user.md) modue to extract
67 | needed information.
68 |
69 |
70 |
71 | ### login/2 ###
72 |
73 |
74 | login(Username::string(), Password::string()) -> {ok, SessionId::string()} | {error, ErrorCode::atom(), Extra::string()}
75 |
76 |
77 |
78 | Login a user
79 |
80 | Given a username and a password attempt to login a user. If login is
81 | successful, a new user session identifier is returned; otherwise, an error
82 | is returned.
83 |
84 |
85 |
86 | ### logout/1 ###
87 |
88 |
89 | logout(SessionId::string()) -> ok
90 |
91 |
92 |
93 | Equivalent to [`nuk_user_sessions:logout(SessionId)`](nuk_user_sessions.md#logout-1).
94 |
95 | Log out a user session
96 |
97 |
98 |
99 | ### put/1 ###
100 |
101 |
102 | put(User::nuk_user:user()) -> ok
103 |
104 |
105 |
106 | Create new or update an existing user
107 |
108 | Performs a put operation. If a user with the given username exists it will
109 | overwrite with the new data. If a new username is given, a new user will be
110 | created. Create a [`nuk_user:user()`](nuk_user.md#type-user) data type and use it here.
111 |
112 |
--------------------------------------------------------------------------------
/doc/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #000000;
9 | background-color: #ffffff;
10 | }
11 | h1,h2 {
12 | margin-left: -0.2in;
13 | }
14 | div.navbar {
15 | background-color: #add8e6;
16 | padding: 0.2em;
17 | }
18 | h2.indextitle {
19 | padding: 0.4em;
20 | background-color: #add8e6;
21 | }
22 | h3.function,h3.typedecl {
23 | background-color: #add8e6;
24 | padding-left: 1em;
25 | }
26 | div.spec {
27 | margin-left: 2em;
28 | background-color: #eeeeee;
29 | }
30 | a.module {
31 | text-decoration:none
32 | }
33 | a.module:hover {
34 | background-color: #eeeeee;
35 | }
36 | ul.definitions {
37 | list-style-type: none;
38 | }
39 | ul.index {
40 | list-style-type: none;
41 | background-color: #eeeeee;
42 | }
43 |
44 | /*
45 | * Minor style tweaks
46 | */
47 | ul {
48 | list-style-type: square;
49 | }
50 | table {
51 | border-collapse: collapse;
52 | }
53 | td {
54 | padding: 3
55 | }
56 |
--------------------------------------------------------------------------------
/guide/developer-overview.md:
--------------------------------------------------------------------------------
1 | Developer Overview
2 | ==================
3 |
4 | Organization
5 | ------------
6 |
7 | nuk is an OTP application that is supervised by `nuk_sup` top level supervisor; which, in turn, starts the following supervisors:
8 |
9 | - `nuk_user_sup`: supervises `simple_one_for_one` workers `nuk_user_server` for each logged in user
10 | - `nuk_user_store_sup`: supervises `one_for_one` worker `nuk_user_store_server` (see below for more info)
11 | - `nuk_game_sup`: supervises `simple_one_for_one` workers `nuk_game_server` for each game session
12 | - `nuk_game_store_sup`: supervises `one_for_one` worker `nuk_game_store_server` which handles registration of game engines
13 |
14 | Users and Sessions
15 | ------------------
16 |
17 | Public interfaces:
18 |
19 | - `nuk_users` for all user related actions: `get`, `put`, `delete`, `list`, `login`
20 | - `nuk_user_sessions` for all user session related actions: `get`, `delete`, `list`
21 | - `nuk_games` for all game related actions: registering, starting, joining, turns, etc.
22 | - `nuk_game_sessions` for all game session related actions
23 |
24 | nuk provides behaviors for implementing callbacks for user, game and session storage:
25 |
26 | - `nuk_user_storage`
27 | - `nuk_user_session_storage`
28 | - `nuk_game_storage`
29 | - `nuk_game_session_storage`
30 |
31 | The provided implementations of these behaviors are:
32 |
33 | - `nuk_user_store_server`: stores users in local server state and provides behavior callbacks to operate on users
34 | - `nuk_user_session_store_server`: provides behavior callbacks to operate on user sessions; the default session "storage" is the `nuk_user_sup` and its children active sessions
35 | - `nuk_game_store_server`: stores registered games in local server state and provides behavior callbacks to operate on game registration
36 | - `nuk_game_session_store_server`: provides behavior callbacks to operate on game sessions; the main function of this behavior is to translate a session token to a `nuk_game_server` process ID
37 |
38 | These implementations are only there for proof on concept and testing purposes. i.e. you might want to hook up a more robust user and session storage than the default implementations provided with nuk. This is possible by implementing above behaviors and then setting them in nuk application environment values:
39 |
40 | ```erlang
41 | application:set_env(nuk, users, custom_user_store_service).
42 | application:set_env(nuk, user_sessions, custom_user_session_store_service).
43 | application:set_env(nuk, games, custom_game_store_service).
44 | application:set_env(nuk, game_sessions, custom_game_session_store_service).
45 | ```
46 |
47 | Replace the `custom_*` module names above with the actual module names.
48 |
49 | Note that you may pick which behaviors to implement. For example, you could implement a custom user, user session and game session storage, but leave the existing nuk game storage.
50 |
51 | Games
52 | -----
53 |
54 | nuk starts a new `nuk_game_server` worker whenever a game session is started. `nuk_game_server` does the following:
55 |
56 | - handles interaction with the players
57 | - keeps the state and general data about the specific game session and its players
58 | - invokes game engine callbacks for specific events during the game
59 | - stores game engine state and passes it with every callback
60 |
61 | nuk provides the `nuk_game_engine` behavior for a general hookup for any turn based game engine. `nuk_game_engine` defines callbacks that `nuk_game_server` invokes during specific events, passing along both the internal state and general data that it keeps about the game session.
62 |
63 | These callbacks are:
64 |
65 | - `initialize` - when a player first creates an instance of a new game
66 | - `player_join` - when a player joins a game
67 | - `player_leave` - when a player leaves a game
68 | - `start` - when a player attempts to start a game
69 | - `turn` - when a player makes a turn
70 | - `finish` - when a game is completed
71 |
72 | `nuk_game_server` already handles general validation and bookkeeping of these actions and game implementations (although they could) need not concern themselves with them. For example, nuk's game state will keep track of which users have joined a specific game, whose turn it is next, game's status, etc.
73 |
74 | For further information about how to implement a game engine, refer to [Implementing a game](implementing-a-game.md).
75 |
--------------------------------------------------------------------------------
/guide/guide.md:
--------------------------------------------------------------------------------
1 | Guide
2 | =====
3 |
4 | nuk is a bare bones turn based game server implementation in Erlang. It is meant to provide the following interfaces and functionality:
5 |
6 | - interface for clients/players to interact with the game server
7 | - interface for arbitrary turn-based game implementations
8 | - functionality for common turn-based game servers
9 |
10 | This is a simple guide to demonstrate how to register a game and use the player inteface with the game server.
11 |
12 | Registering a Game
13 | ------------------
14 |
15 | nuk provides a behavior for game writers to implement arbitrary turn based games. This is done by implementing the `nuk_game_engine` behavior. That topic is covered separately in [Implementing a game](implementing-a-game.md). Once you have a specific game implementation, you must first register that game with nuk before it can be used:
16 |
17 | For example, if we have implemented a Coin Flip game, we can register it like this:
18 |
19 | ```erlang
20 | % create a game data type passing name, module, min and max players
21 | Game = nuk_game:new("Coin Flip", nuk_game_coinflip, 1, 2),
22 |
23 | % register the game
24 | ok = nuk_games:register(Game).
25 | ```
26 |
27 | That's it! The game is registered and can be referenced with the unique name "Coin Flip".
28 |
29 | Users Interface
30 | ---------------
31 |
32 | Next we must have valid users who can play a game.
33 |
34 | To create users:
35 |
36 | ```erlang
37 | ok = nuk_users:put(nuk_user:new("User1", "Pass1")).
38 | ok = nuk_users:put(nuk_user:new("User2", "Pass2")).
39 | ```
40 |
41 | To log in:
42 |
43 | ```erlang
44 | {ok, UserSessionId1} = nuk_users:login("User1", "Pass1").
45 | {ok, UserSessionId2} = nuk_users:login("User2", "Pass2").
46 | ```
47 |
48 | Player Interface
49 | ----------------
50 |
51 | Having registered a game and created users, we can start a new game.
52 |
53 | First, we must create a new game session, allowing any valid number of users to join it:
54 |
55 | ```erlang
56 | {ok, GameSessionId} = nuk_games:create(UserSessionId1, "Coin Flip").
57 | ```
58 |
59 | Now we can have other logged in user sessions join the newly created game:
60 |
61 | ```erlang
62 | ok = nuk_games:join(GameSessionId, UserSessionId2).
63 | ```
64 |
65 | After we are satisifed with the players who have joined the game then it can be started:
66 |
67 | ```erlang
68 | ok = nuk_games:start(GameSessionId, UserSessionId1).
69 | ```
70 |
71 | After the game has started players can request to get the data in the game session. The game session data consists of few components:
72 |
73 | - game registration data
74 | - general nuk game state, containing information about
75 | - players in the game
76 | - turn information: turn number and whose turn it is next
77 | - game engine specific game state, consisting of
78 | - private game state which will always be empty for every player
79 | - public game state which is shared among all players
80 | - player specific state which is shared only with the requesting player
81 |
82 | Here's how to get these states and extract the data:
83 |
84 | ```erlang
85 | % get game session data
86 | {ok, GameSession} = nuk_games:get_game_session(GameSessionId, UserSessionId1).
87 |
88 | % extract general nuk info
89 |
90 | % players in the game
91 | Players = nuk_game_session:get_players(GameSession).
92 |
93 | % status, just started games will most likely be in await_turn
94 | await_turn = nuk_game_session:get_status(GameSession).
95 |
96 | % whose turn is it next?
97 | NextTurnPlayers = nuk_game_session:get_players_turn(GameSession).
98 |
99 | % game specific state provided by the game engine, e.g. coin flip game above
100 | GameState = nuk_game_session:get_game_state(GameSession).
101 |
102 | % game public state
103 | PublicState = nuk_game_engine_state:get_public(GameState).
104 |
105 | % game player specific state
106 | PlayerState = nuk_game_engine_state:get_player(GameState, "User1").
107 | ```
108 |
109 | Here's how to make a turn:
110 |
111 | ```erlang
112 | ok = nuk_games:turn(GameSessionId, UserSessionId1, heads).
113 | ```
114 |
115 | Note that in the above example, to make a turn we supply the game and user session IDs and the turn data. In our simple Coin Flip game example, the turn is simply atom `heads` but it is a term entirely dependent on the game being played. It must the the data structure that the game engine expects.
116 |
117 | After the turn is made, the players can proceed to get the game session and state data as shown above and keep making turns until the game ends. Once the game ends, the status will be `complete` and winners and losers can then be obtained:
118 |
119 | ```erlang
120 | complete = nuk_game_session:get_status(GameSession).
121 | {Winners, Losers} = nuk_game_session:get_winners_losers(GameSession).
122 | ```
123 |
124 | This is it for a simple guide of how users and games work in nuk!
125 |
126 | How to Play
127 | -----------
128 |
129 | ```erlang
130 |
131 | % register a game
132 | nuk_games:register(nuk_game:new("Coin Flip", nuk_game_coinflip, 1, 1)).
133 |
134 | % create and login players
135 | nuk_users:put(nuk_user:new("User1", "Pass1")).
136 | {ok, UserSessionId1} = nuk_users:login("User1", "Pass1").
137 |
138 | % create a new game session
139 | {ok, GameSessionId1} = nuk_games:create(UserSessionId1, "Coin Flip").
140 |
141 | ```
142 |
--------------------------------------------------------------------------------
/guide/implementing-a-game.md:
--------------------------------------------------------------------------------
1 | Implementing a Game
2 | ===================
3 |
4 | Before implementing a game please read the [guide](guide.md) and [developer overview](developer-overview.md) chapters. They set a useful context for this topic.
5 |
6 | The aim of nuk is to provide a framework for turn based games, so that impelmentation of each game does not have to deal with general functionality. Each implementation only need to concern itself with the game specific details. To that end, nuk:
7 | - tracks commonly used game session related data
8 | - provides handling, authentication and validation for common player actions
9 | - invokes game engine via behavior callbacks
10 |
11 | Assuming you have decided on the rules of your game, then implementing a game engine in nuk is a matter of implementing the `nuk_game_engine` behavior callbacks. Generally speaking, these callbacks:
12 | - represent actions or events occurring during the game
13 | - take current state(s) of the game session and the action being performed
14 | - are expected to return the new state(s) of the game session
15 |
16 | States
17 | ------
18 |
19 | There are 2 states to be aware of when implementing `nuk_game_engine` callbacks.
20 |
21 | `nuk_game_state:state()` is a general game data that is tracked by nuk automatically. The game engines need not track this data on their own. The `nuk_game_state` module can be used to operate on this data type.
22 |
23 | `nuk_game_engine_state` is a game specific state. This contains data that's relevant to a specific game engine and is not generally commonly shared across different games. This data type has 3 components:
24 | - private: game session data that stays private to the game engine; it is never shared with players
25 | - public: game session data that's shared with all players
26 | - players: a map of player username => data that's only shared with a specific player
27 |
28 | Since this data is specific to a game being implemented the game engines are entirely responsible for the contents of the `nuk_game_engine_state:state()` data type. `nuk_game_engine_state` module must be used to operate on this data type.
29 |
30 | Common Checks
31 | -------------
32 |
33 | Prior to invoking callbacks, nuk automatically performs the following checks, as applicable:
34 | - given user session is authenticated
35 | - given game session is valid
36 |
37 | In addition, nuk automatically performs few other checks specific to the action being performed. Below are details on `nuk_game_engine` callbacks.
38 |
39 | initialize/2
40 | ------------
41 |
42 | Invoked: when a user attempts to create a new game session via `nuk_games:create/2-3`.
43 |
44 | Arguments:
45 | - `nuk_user:user()` that's trying to create a game session and a list of options.
46 |
47 | This callback is useful for game engines to parse initial options and set up initial game session states. Valid returns are:
48 | - `{ok, nuk_game_engine_state:state()}` when successful
49 | - `{error, invalid_options, string()}` when invalid option was specified
50 |
51 | player_join/3
52 | -------------
53 |
54 | Invoked: when a user attempts to join an existing game session via `nuk_games:join/2`.
55 |
56 | Arguments:
57 | - `nuk_user:user()` that's trying to join the game session
58 | - `nuk_game_engine_state:state()` current game engine state
59 | - `nuk_game_state:state()` current nuk state for this game session
60 |
61 | In addition to common checks, prior to invoking callback, nuk verifies:
62 | - user is not currently part of this game session
63 | - maximum number of players allowed by the game has not been reached
64 |
65 | This callback is useful for game engines to set up initial player state. Valid return is:
66 | - `{ok, nuk_game_engine_state:state()`
67 |
68 | player_leave/3
69 | --------------
70 |
71 | Invoked: when a user attempts to leave an existing game session via `nuk_games:leave/2`.
72 |
73 | Arguments:
74 | - `nuk_user:user()` that's trying to leave the game session
75 | - `nuk_game_engine_state:state()` current game engine state
76 | - `nuk_game_state:state()` current nuk state for this game session
77 |
78 | In addition to common checks, prior to invoking callback, nuk verifies:
79 | - user is currently part of this game session
80 |
81 | This callback is useful for game engines to process player's request to leave the game. Depending on the game's rules and the session state, it might result in: (1) game staying in `initialized` state if it hadn't yet started; (2) game ending because it is impossible to continue the game without this player; (3) game being allowed to continue in `await_turn` state; or in rare cases (4) returning an error to the user. Valid returns are:
82 | - `{ok, initialized, nuk_game_engine_state:state()}` when the game has not yet been started and may remain in `initialized` state
83 | - `{ok, await_turn, [nuk_user:user()], nuk_game_engine_state:state()}` when game has already started but may continue without the leaving player; in this case the return contains the list of users whose turn it is next, and the new engine state
84 | - `{ok, complete, [nuk_user:user()], [nuk_user:user()], nuk_game_engine_state:state()}` when the game cannot continue, returning the list of winners and losers
85 | - `{error, game_already_started, string()}` when the game engine needs to return an error to the player
86 |
87 | start/2
88 | -------
89 |
90 | Invoked: when a user attempts to start an existing game session via `nuk_games:start/2`.
91 |
92 | Arguments:
93 | - `nuk_game_engine_state:state()` current game engine state
94 | - `nuk_game_state:state()` current nuk state for this game session
95 |
96 | In addition to common checks, prior to invoking callback, nuk verifies:
97 | - user is currently part of this game session
98 |
99 | This callback is useful for game engines to start the game play. After this callback the game session status changes from `initialized` to `await_turn` and game engine must return a list of players whose turn it is next. Valid return is:
100 | - `{ok, await_turn, [nuk_user:user()], nuk_game_engine_state:state()}`
101 |
102 | turn/3
103 | ------
104 |
105 | Invoked: when a player attempts to make a turn via `nuk_games:turn/2`.
106 |
107 | Arguments:
108 | - `nuk_user:user()` who is making a turn
109 | - `term()` the arbitrary game specific turn data
110 | - `nuk_game_engine_state:state()` current game engine state
111 | - `nuk_game_state:state()` current nuk state for this game session
112 |
113 | In addition to common checks, prior to invoking callback, nuk verifies:
114 | - user is currently part of this game session
115 | - this user is in the list of players whose turn it is next
116 |
117 | This callback allows game engines to process a turn. The turn data is completely arbitrary information that's dictated by the game engine. There are 3 possible outcomes as a result of this action: (1) the turn was successfully made which resulted in updated game session state and new turn order; (2) the turn resulted in game being completed and there are winners and/or losers; or (3) the turn data was invalid and the player should get an error. Valid returns are:
118 | - `{ok, await_turn, [nuk_user:user()]}` upon successful turn when game is still in progress
119 | - `{ok, complete, [nuk_user:user()], [nuk_user:user()]}` when the turn completes the game and winners and losers are returned
120 | - `{error, invalid_turn, string()}` when turn contains invalid data
121 |
122 | finish/2
123 | --------
124 |
125 | Invoked: when a game session is about to terminate.
126 |
127 | Arguments:
128 | - `nuk_game_engine_state:state()` current game engine state
129 | - `nuk_game_state:state()` current nuk state for this game session
130 |
131 | This is a callback invoked by the system prior to the termination of the `nuk_game_server` process. It is useful for game engines to perform any game session related cleanup (closing connections, removing temporary files, etc.). Valid return is
132 | - `ok`
133 |
--------------------------------------------------------------------------------
/relx.config:
--------------------------------------------------------------------------------
1 | {release, {nuk_release, "0.0.1"}, [nuk]}.
2 | {paths, ["./deps"]}.
3 | {extended_start_script, true}.
4 |
--------------------------------------------------------------------------------
/src/nuk.app.src:
--------------------------------------------------------------------------------
1 | {application, 'nuk',
2 | [{description, "An OTP application"},
3 | {vsn, "0.1.0"},
4 | {registered, []},
5 | {mod, {'nuk_app', []}},
6 | {applications,
7 | [kernel,
8 | stdlib
9 | ]},
10 | {env,[]},
11 | {modules, []}
12 | ]}.
13 |
--------------------------------------------------------------------------------
/src/nuk_app.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_app' module
3 | %%
4 | %% This starts the nuk application.
5 | %% @end
6 | %%%-------------------------------------------------------------------
7 |
8 | -module(nuk_app).
9 |
10 | -behaviour(application).
11 |
12 | %% API
13 | -export([get_storage_module/1]).
14 |
15 | %% Supervision
16 | -export([start/2, stop/1]).
17 |
18 | %%====================================================================
19 | %% Supervision
20 | %%====================================================================
21 |
22 | start(_StartType, _StartArgs) ->
23 | nuk_sup:start_link().
24 |
25 | stop(_State) ->
26 | ok.
27 |
28 | %%====================================================================
29 | %% API
30 | %%====================================================================
31 |
32 | %% @doc Get storage module
33 | %%
34 | %% Returns the storage module associated with the specified storage type. If
35 | %% none specified, nuk default is returned.
36 | %% @end
37 | -spec get_storage_module(Type :: atom()) -> atom().
38 | get_storage_module(Type) ->
39 | case application:get_env(Type) of
40 | undefined ->
41 | get_storage_module_default(Type);
42 | {ok, Module} when is_atom(Module) ->
43 | Module
44 | end.
45 |
46 | %%====================================================================
47 | %% Internal functions
48 | %%====================================================================
49 |
50 | %% @doc Get default storage module
51 | %% @private
52 | %%
53 | %% Returns the default storage module defined for specified type of storage.
54 | %% @end
55 | -spec get_storage_module_default(Type :: atom()) -> atom().
56 | get_storage_module_default(users) -> nuk_user_store_server;
57 | get_storage_module_default(user_sessions) -> nuk_user_session_store_server;
58 | get_storage_module_default(games) -> nuk_game_store_server;
59 | get_storage_module_default(game_sessions) -> nuk_game_session_store_server.
60 |
--------------------------------------------------------------------------------
/src/nuk_game.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game' module
3 | %%
4 | %% This module is used to operate on the {@link nuk_game:game()} data type.
5 | %% This data type is used during game registration in nuk - i.e. when calling
6 | %% {@link nuk_games:register/1}, {@link nuk_games:get/1},
7 | %% {@link nuk_games:list/0}.
8 | %% @end
9 | %%%-------------------------------------------------------------------
10 |
11 | -module(nuk_game).
12 |
13 | %% API
14 | -export([new/4]).
15 | -export([get_name/1]).
16 | -export([get_module/1]).
17 | -export([get_min_players/1]).
18 | -export([get_max_players/1]).
19 |
20 | %% Types
21 | -export_type([game/0]).
22 |
23 | -opaque game() :: #{name => string(),
24 | module => atom(),
25 | min_players => integer(),
26 | max_players => integer()}.
27 | %% Data type used to register games in nuk. Use functions in this module to
28 | %% operate on this data type.
29 |
30 | %%====================================================================
31 | %% API
32 | %%====================================================================
33 |
34 | %% @doc Create a new {@link nuk_game:game()} data type
35 | %%
36 | %% `GameName' is a string used to identify the game during registration.
37 | %% `Module' is the name of the module implementing the
38 | %% {@link nuk_game_engine} behavior. `MinPlayers' and `MaxPlayers' are integers
39 | %% used for game registration. They are used by nuk to validate correct
40 | %% number of players before starting a game.
41 | %% @end
42 | -spec new(GameName :: string(),
43 | Module :: atom(),
44 | MinPlayers :: integer(),
45 | MaxPlayers :: integer) -> game().
46 | new(GameName, Module, MinPlayers, MaxPlayers) ->
47 | #{name => GameName,
48 | module => Module,
49 | min_players => MinPlayers,
50 | max_players => MaxPlayers}.
51 |
52 | %% @doc Get game name
53 | %%
54 | %% Extract game name from {@link nuk_game:game()} data type.
55 | %% @end
56 | -spec get_name(Game :: game()) -> string().
57 | get_name(#{name := Name}) ->
58 | Name.
59 |
60 | %% @doc Get module name
61 | %%
62 | %% Extract module name from {@link nuk_game:game()} data type.
63 | -spec get_module(Game :: game()) -> atom().
64 | get_module(#{module := Module}) ->
65 | Module.
66 |
67 | %% @doc Get minimum number of players
68 | %%
69 | %% Extract minimum number of players from {@link nuk_game:game()} data type.
70 | %% @end
71 | -spec get_min_players(Game :: game()) -> integer().
72 | get_min_players(#{min_players := MinPlayers}) ->
73 | MinPlayers.
74 |
75 | %% @doc Get maximum number of players
76 | %%
77 | %% Extract maximum number of players from {@link nuk_game:game()} data type.
78 | %% @end
79 | -spec get_max_players(Game :: game()) -> integer().
80 | get_max_players(#{max_players := MaxPlayers}) ->
81 | MaxPlayers.
82 |
--------------------------------------------------------------------------------
/src/nuk_game_coinflip.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_coinflip_multi' module
3 | %% @end
4 | %%%-------------------------------------------------------------------
5 |
6 | -module(nuk_game_coinflip).
7 |
8 | -behaviour(nuk_game_engine).
9 |
10 | -export([initialize/2, player_join/3, player_leave/3, start/2, turn/4, finish/2]).
11 |
12 | -spec initialize(User :: nuk_user:user(), OptionsOverride :: list()) ->
13 | {error, invalid_options, string()} |
14 | {ok, nuk_game_engine_state:state()}.
15 | initialize(User, OptionsOverride) when is_list(OptionsOverride) ->
16 | % parse and override default options
17 | OptionsDefault = #{max_turns => 3},
18 | try lists:foldl(
19 | fun({Name, Value}, Acc) -> Acc#{Name := Value} end,
20 | OptionsDefault,
21 | OptionsOverride
22 | ) of
23 | Options ->
24 | Username = nuk_user:get_username(User),
25 | StatePrivate = #{coin_position => nil},
26 | StatePublic = Options#{turn_number => 0},
27 | StatePlayers = #{Username => #{wins => 0, losses => 0}},
28 | State = nuk_game_engine_state:new(StatePrivate,
29 | StatePublic,
30 | StatePlayers),
31 | {ok, State}
32 | catch
33 | error:{badkey, OptionName} ->
34 | {error, invalid_options, OptionName}
35 | end.
36 |
37 | player_join(User, State, _NukState) ->
38 | Username = nuk_user:get_username(User),
39 | StatePlayer = #{wins => 0, losses => 0},
40 | StateNew = nuk_game_engine_state:put_player(State, Username, StatePlayer),
41 | {ok, StateNew}.
42 |
43 | player_leave(User, State, NukState) ->
44 | case nuk_game_state:get_status(NukState) of
45 | initialized -> % game hasn't started
46 | case length(nuk_game_state:get_players(NukState)) of
47 | 1 -> % last player leaving, game is finished
48 | % NOTE we don't remove the last player so that player can
49 | % still get the game session for testing
50 | %StateNew = nuk_game_engine_state:set_players(State, #{}),
51 | {ok, complete, [], [], State};
52 | _ -> % there are more players, let this one go
53 | Username = nuk_user:get_username(User),
54 | StateNew = nuk_game_engine_state:remove_player(State,
55 | Username),
56 | {ok, initialized, StateNew}
57 | end;
58 | _ -> % game has started, not allowed to leave
59 | {error, game_already_started, "Game has already started."}
60 | end.
61 |
62 | start(State, NukState) ->
63 | % flip a coin, increment a public turn number
64 | #{max_turns := MaxTurns, turn_number := TurnNumber} =
65 | nuk_game_engine_state:get_public(State),
66 | StatePrivate = #{coin_position => flip_coin()},
67 | StatePublic = #{max_turns => MaxTurns, turn_number => TurnNumber + 1},
68 | StateNew1 = nuk_game_engine_state:set_private(State, StatePrivate),
69 | StateNew2 = nuk_game_engine_state:set_public(StateNew1, StatePublic),
70 | % it's a coin flip game, it's everybody's turn!
71 | {ok, await_turn, nuk_game_state:get_players(NukState), StateNew2}.
72 |
73 | turn(User, Turn, State, NukState) when Turn =:= heads; Turn =:= tails ->
74 | % add wins/losses to user state
75 | Username = nuk_user:get_username(User),
76 | StatePrivate = nuk_game_engine_state:get_private(State),
77 | StatePlayers = nuk_game_engine_state:get_players(State),
78 | StatePlayer = nuk_game_engine_state:get_player(State, Username),
79 | #{wins := Wins, losses := Losses} = StatePlayer,
80 | [Win, Loss] = get_turn_result(Turn, StatePrivate),
81 | StatePlayerNew = StatePlayer#{wins := Wins + Win, losses := Losses + Loss},
82 | StatePlayersNew = StatePlayers#{Username := StatePlayerNew},
83 | % remove user from list of turn users, update state
84 | NextTurnPlayers = nuk_game_state:get_players_turn(NukState),
85 | NextTurnPlayersNew = lists:delete(User, NextTurnPlayers),
86 | % return result
87 | case NextTurnPlayersNew of
88 | [] ->
89 | % next turn players list is empty
90 | StatePublic = nuk_game_engine_state:get_public(State),
91 | #{turn_number := TurnNumber, max_turns := MaxTurns} = StatePublic,
92 | NewTurnNumber = TurnNumber + 1,
93 | StatePublicNew = StatePublic#{turn_number := NewTurnNumber},
94 | if
95 | TurnNumber =:= MaxTurns ->
96 | % last turn, calc winners/losers and end
97 | Players = nuk_game_state:get_players(NukState),
98 | [Winners, Losers] = get_winners_losers(StatePlayersNew, Players),
99 | StateNew = nuk_game_engine_state:set_all(State,
100 | StatePrivate,
101 | StatePublicNew,
102 | StatePlayersNew),
103 | {ok, complete, Winners, Losers, StateNew};
104 | true ->
105 | % add all players back to turn users
106 | StatePrivateNew = StatePrivate#{coin_position := flip_coin()},
107 | StateNew = nuk_game_engine_state:set_all(State,
108 | StatePrivateNew,
109 | StatePublicNew,
110 | StatePlayersNew),
111 | {ok, await_turn, nuk_game_state:get_players(NukState), StateNew}
112 | end;
113 | _ ->
114 | % next turn players is not empty
115 | StateNew = nuk_game_engine_state:set_players(State, StatePlayersNew),
116 | {ok, await_turn, NextTurnPlayersNew, StateNew}
117 | end;
118 | turn(_User, _Turn, _State, _NukState) ->
119 | {error, invalid_turn, "Turn must be 'heads' or 'tails'"}.
120 |
121 | finish(_State, _NukState) ->
122 | ok.
123 |
124 | %%====================================================================
125 | %% Internal functions
126 | %%====================================================================
127 |
128 | %% @doc Flips a virtual coin
129 | %% @private
130 | %%
131 | %% Generates `heads' or `tails' randomly and returns result.
132 | %% @end
133 | flip_coin() ->
134 | case rand:uniform(2) of
135 | 1 -> heads;
136 | 2 -> tails
137 | end.
138 |
139 | %% @doc Return result of player turn
140 | %% @private
141 | %%
142 | %% Compares the player guess to the coin position and returns a list containing
143 | %% number of wins and losses as a result.
144 | %% @end
145 | -spec get_turn_result(Turn :: atom(), State :: map()) -> [integer()].
146 | get_turn_result(Turn, #{coin_position := Turn}) when is_atom(Turn) -> [1, 0];
147 | get_turn_result(Turn, _State) when is_atom(Turn) -> [0, 1].
148 |
149 | %% @doc Return a list of winners and losers
150 | %% @private
151 | %%
152 | %% Given player states returns a list of 2 lists, each containing
153 | %% `nuk_user:user()' data types; the first list is of winners and the second is
154 | %% of losers.
155 | %% @end
156 | -spec get_winners_losers(StatePlayers :: map(), Players :: [nuk_user:user()])
157 | -> [[]].
158 | get_winners_losers(StatePlayers, Players) ->
159 | #{winners := Winners, losers := Losers} = lists:foldl(
160 | fun(User,
161 | #{win_ratio := WinRatio, winners := Winners, losers := Losers} = Acc) ->
162 | Username = nuk_user:get_username(User),
163 | #{wins := Wins, losses := Losses} = maps:get(Username, StatePlayers),
164 | if
165 | Wins / Losses > WinRatio ->
166 | Acc#{win_ratio := WinRatio,
167 | winners := [User],
168 | losers := Losers ++ Winners};
169 | Wins / Losses == WinRatio ->
170 | Acc#{winners := [User | Winners]};
171 | true ->
172 | Acc#{losers := [User | Losers]}
173 | end
174 | end,
175 | #{win_ratio => 0, winners => [], losers => []},
176 | Players
177 | ),
178 | [Winners, Losers].
179 |
--------------------------------------------------------------------------------
/src/nuk_game_engine.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_engine' module
3 | %%
4 | %% This is a behavior that all game engines must implement. It is also the only
5 | %% logic that game engines need implement. All callback function returns allow
6 | %% engines to: (1) set the arbitrary state that's relevant to the game being
7 | %% implemented, (2) get callbacks for important game events with the arbitrary
8 | %% state previously set.
9 | %% @end
10 | %%%-------------------------------------------------------------------
11 |
12 | -module(nuk_game_engine).
13 |
14 | -callback initialize(Player :: nuk_user:user(), Options :: list()) ->
15 | {ok, EngineState :: nuk_game_engine_state:state()} |
16 | {error, invalid_options, Extra :: string()}.
17 |
18 | -callback player_join(Player :: nuk_user:user(),
19 | EngineState :: nuk_game_engine_state:state(),
20 | NukState :: nuk_game_state:state()) ->
21 | {ok, NewEngineState :: nuk_game_engine_state:state()}.
22 |
23 | -callback player_leave(Player :: nuk_user:user(),
24 | EngineState :: nuk_game_engine_state:state(),
25 | NukState :: nuk_game_state:state()) ->
26 | {ok, initialized, NewEngineState :: nuk_game_engine_state:state()} |
27 | {ok, await_turn, NextTurnPlayers :: [nuk_user:user()],
28 | NewEngineState :: nuk_game_engine_state:state()} |
29 | {ok, complete, Winners :: [nuk_user:user()], Losers :: [nuk_user:user()],
30 | NewEngineState :: nuk_game_engine_state:state()} |
31 | {error, game_already_started, Extra :: string()}.
32 |
33 | -callback start(EngineState :: nuk_game_engine_state:state(),
34 | NukState :: nuk_game_state:state()) ->
35 | {ok, await_turn, NextTurnPlayers :: [nuk_user:user()],
36 | NewEngineState :: nuk_game_engine_state:state()}.
37 |
38 | -callback turn(Player :: nuk_user:user(), Turn :: term(),
39 | EngineState :: nuk_game_engine_state:state(),
40 | NukState :: nuk_game_state:state()) ->
41 | {ok, await_turn, NextTurnPlayers :: [nuk_user:user()],
42 | NewEngineState :: nuk_game_engine_state:state()} |
43 | {ok, complete, Winners :: [nuk_user:user()], Losers :: [nuk_user:user()],
44 | NewEngineState :: nuk_game_engine_state:state()} |
45 | {error, invalid_turn, Extra :: string()}.
46 |
47 | -callback finish(EngineState :: nuk_game_engine_state:state(),
48 | NukState :: nuk_game_state:state()) ->
49 | ok.
50 |
--------------------------------------------------------------------------------
/src/nuk_game_engine_state.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_engine_state' module
3 | %%
4 | %% This module is used to operate on {@link nuk_game_engine_state:state()} data
5 | %% type. This data type is used when retrieving game engine's state from
6 | %% {@link nuk_game_session:get_nuk_state/1}.
7 | %% @end
8 | %%%-------------------------------------------------------------------
9 |
10 | -module(nuk_game_engine_state).
11 |
12 | %% API
13 | -export([new/3]).
14 | -export([get_all/1]).
15 | -export([get_private/1]).
16 | -export([get_public/1]).
17 | -export([get_player/2]).
18 | -export([get_players/1]).
19 | -export([set_all/4]).
20 | -export([set_private/2]).
21 | -export([set_public/2]).
22 | -export([put_player/3]).
23 | -export([remove_player/2]).
24 | -export([set_player/3]).
25 | -export([set_players/2]).
26 |
27 | -export_type([state/0]).
28 |
29 | -opaque state() :: #{private => term(),
30 | public => term(),
31 | players => map()}.
32 | %% Data type containing game engine's game state. This is part of
33 | %% {@link nuk_game_session:session()} data type. Functions in this module can
34 | %% be used to operate on the following data:
35 | %% - `private': part of the state that stays private to the game engine; it is
36 | %% never shared with any players
37 | %% - `public': part of the state that is public - it is shared with all players
38 | %% - `players': a map of players usernames to the data that will be shared to
39 | %% those specific players only
40 | %% All above data is of type `term()' - i.e. it's up to the game engine
41 |
42 | %% @doc Create new a new {@link state()} data type
43 | %%
44 | %% Creates a new state with specified values.
45 | %% @end
46 | -spec new(Private :: term(), Public :: term(), Players :: map()) ->
47 | state().
48 | new(Private, Public, Players) ->
49 | #{private => Private, public => Public, players => Players}.
50 |
51 | %% @doc Get all components of engine state
52 | %%
53 | %% Returns a list containing all - private, public and players - states.
54 | %% @end
55 | -spec get_all(State :: state()) -> list().
56 | get_all(#{private := Private, public := Public, players := Players}) ->
57 | [Private, Public, Players].
58 |
59 | %% @doc Get private state
60 | %%
61 | %% Gets game engine's private state.
62 | %% @end
63 | -spec get_private(State :: state()) -> term().
64 | get_private(#{private := Private}) ->
65 | Private.
66 |
67 | %% @doc Get public state
68 | %%
69 | %% Gets game engine's public state.
70 | %% @end
71 | -spec get_public(State :: state()) -> term().
72 | get_public(#{public := Public}) ->
73 | Public.
74 |
75 | %% @doc Get player specific state
76 | %%
77 | %% Gets state specific to the given player.
78 | %% @end
79 | -spec get_player(State :: state(), Username :: string()) -> term().
80 | get_player(State, Username) ->
81 | Players = get_players(State),
82 | maps:get(Username, Players).
83 |
84 | %% @doc Get a map of all player states
85 | %%
86 | %% Gets a map of player states with player usernames as keys.
87 | %% @end
88 | -spec get_players(State :: state()) -> map().
89 | get_players(#{players := Players}) ->
90 | Players.
91 |
92 | %% @doc Set all components of the engine state
93 | %%
94 | %% Sets all - private, public and players - states
95 | %% @end
96 | -spec set_all(State :: state(), Private :: term(), Public :: term(),
97 | Players :: map()) -> state().
98 | set_all(State, Private, Public, Players) ->
99 | State#{private := Private, public := Public, players := Players}.
100 |
101 | %% @doc Set private state
102 | %%
103 | %% Sets game engine private session state.
104 | %% @end
105 | -spec set_private(State :: state(), Private :: term()) -> state().
106 | set_private(State, Private) ->
107 | State#{private := Private}.
108 |
109 | %% @doc Set public state
110 | %%
111 | %% Sets game engine public session state.
112 | %% @end
113 | -spec set_public(State :: state(), Public :: term()) -> state().
114 | set_public(State, Public) ->
115 | State#{public := Public}.
116 |
117 | %% @doc Put a state for a new or existing player
118 | %%
119 | %% Sets a state for a specific player; if the Username doesn't exist, it is
120 | %% added; if it exists its data is overwritten.
121 | %% @end
122 | -spec put_player(State :: state(), Username :: string(), Player :: term()) ->
123 | state().
124 | put_player(#{players := Players} = State, Username, Player) ->
125 | State#{players := Players#{Username => Player}}.
126 |
127 | %% @doc Remove player from players state
128 | %%
129 | %% Completely removes a player from the map of player states
130 | %% @end
131 | -spec remove_player(State :: state(), Username :: string()) -> state().
132 | remove_player(#{players := Players}, Username) ->
133 | maps:remove(Username, Players).
134 |
135 | %% @doc Set a state for an existing player
136 | %%
137 | %% Sets a state for a specific existing player.
138 | %% @end
139 | -spec set_player(State :: state(), Username :: string(), Player :: term()) ->
140 | state().
141 | set_player(#{players := Players} = State, Username, Player) ->
142 | State#{players := Players#{Username := Player}}.
143 |
144 | %% @doc Set all players state
145 | %%
146 | %% Sets a map of states for all players.
147 | %% @end
148 | -spec set_players(State :: state(), Players :: map()) -> state().
149 | set_players(State, Players) ->
150 | State#{players := Players}.
151 |
--------------------------------------------------------------------------------
/src/nuk_game_session.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_session' module
3 | %%
4 | %% This module is used to operate on {@link nuk_game_session:session()} data
5 | %% type. This data type is used when retrieving the game session state from
6 | %% {@link nuk_games:get_game_session/2}. It tracks the following data:
7 | %% - Game {@link nuk_game:game()} which this session is for
8 | %% - nuk's general game session state
9 | %% - Game engine's arbitrary state
10 | %% @end
11 | %%%-------------------------------------------------------------------
12 |
13 | -module(nuk_game_session).
14 |
15 | %% API
16 | -export([new/1]).
17 | -export([get_game/1]).
18 | -export([get_game_state/1]).
19 | -export([get_nuk_state/1]).
20 | -export([get_players/1]).
21 | -export([get_players_count/1]).
22 | -export([get_players_turn/1]).
23 | -export([get_status/1]).
24 | -export([get_turn_number/1]).
25 | -export([get_winners_losers/1]).
26 | -export([has_player/2]).
27 | -export([is_players_turn/2]).
28 | -export([increment_turn_number/1]).
29 | -export([set_game_state/2]).
30 | -export([add_player/2]).
31 | -export([remove_player/2]).
32 | -export([set_players/2]).
33 | -export([set_players_turn/2]).
34 | -export([set_status/2]).
35 | -export([set_turn_number/2]).
36 | -export([set_winners_losers/3]).
37 |
38 | %% Types
39 | -export_type([session/0]).
40 |
41 | -opaque session() :: #{game => nuk_game:game(),
42 | nuk_state => nuk_game_state:state(),
43 | game_state => nuk_game_engine_state:state()}.
44 | %% Data type used to represent a game session state. Use functions in this
45 | %% module to operate on this data type. It contains the following:
46 | %% - `game': {@link nuk_game:game()} data type, use {@link get_game/1} to
47 | %% extract
48 | %% - `nuk_state': {@link nuk_game_state:state()} data type, use functions in
49 | %% this module to extract specific values from this state
50 | %% - `game_state': {@link nuk_game_engine_state:state()} data type containing
51 | %% game engine specific state; use {@link nuk_game_engine_state} functions
52 | %% to operate on this data
53 | %% information from this data type
54 |
55 | %%====================================================================
56 | %% API
57 | %%====================================================================
58 |
59 | %% @doc Create a new {@link session()} data type.
60 | %%
61 | %% `Game' is a {@link nuk_game:game()} data type which is stored inside the
62 | %% session. All other values are set to their defaults.
63 | %% @end
64 | -spec new(Game :: nuk_game:game()) -> session().
65 | new(Game) ->
66 | #{game => Game,
67 | nuk_state => #{status => nil,
68 | turn_number => 0,
69 | players => [],
70 | players_turn => [],
71 | players_winners => [],
72 | players_losers => []},
73 | game_state => nuk_game_engine_state:new([], [], #{})}.
74 |
75 | %% @doc Get game
76 | %%
77 | %% Returns the {@link nuk_game:game()} data type to which this session belongs.
78 | %% @end
79 | -spec get_game(Session :: session()) -> nuk_game:game().
80 | get_game(#{game := Game}) ->
81 | Game.
82 |
83 | %% @doc Get game engine arbitrary state
84 | %%
85 | %% Returns game engine specific state of the session. Refer to
86 | %% {@link nuk_game_engine_state} module to operate on this data type.
87 | %% @end
88 | -spec get_game_state(Session :: session()) -> nuk_game_engine_state:state().
89 | get_game_state(#{game_state := GameState}) ->
90 | GameState.
91 |
92 | %% @doc Get general nuk game state
93 | %%
94 | %% Returns the general nuk state in the form of {@link nuk_game_state:state()}
95 | %% data type. Use {@link nuk_game_state} module to operate on this data type.
96 | %% @end
97 | -spec get_nuk_state(Session :: session()) -> nuk_game_state:state().
98 | get_nuk_state(#{nuk_state := NukState}) ->
99 | NukState.
100 |
101 | %% @doc Get players currently in the game session
102 | %%
103 | %% Returns a list of {@link nuk_user:user()} data types that represent a list
104 | %% of players currently joined to this game session.
105 | %% @end
106 | -spec get_players(Session :: session()) -> [nuk_user:user()].
107 | get_players(#{nuk_state := NukState}) ->
108 | nuk_game_state:get_players(NukState).
109 |
110 | %% @doc Get number of players currently in the game session
111 | %%
112 | %% Returns number of players currently in this game session.
113 | %% @end
114 | -spec get_players_count(Session :: session()) -> non_neg_integer().
115 | get_players_count(Session) ->
116 | length(get_players(Session)).
117 |
118 | %% @doc Get players whose turn it is next
119 | %%
120 | %% Returns a list of {@link nuk_user:user()} data types that represent a list
121 | %% of players who the game engine is expecting to make the turn(s) next. i.e.
122 | %% the answer to "whose turn is it?" question.
123 | %% @end
124 | -spec get_players_turn(Session :: session()) -> [nuk_user:user()].
125 | get_players_turn(#{nuk_state := NukState}) ->
126 | nuk_game_state:get_players_turn(NukState).
127 |
128 | %% @doc Get game session status
129 | %%
130 | %% Returns an atom status of the game session
131 | %% @end
132 | -spec get_status(Session :: session()) -> nuk_game_state:status().
133 | get_status(#{nuk_state := NukState}) ->
134 | nuk_game_state:get_status(NukState).
135 |
136 | %% @doc Get turn number
137 | %%
138 | %% Every time any player makes a turn nuk increments an internal turn counter.
139 | %% This returns the current turn number from the game session.
140 | %% @end
141 | -spec get_turn_number(Session :: session()) -> non_neg_integer().
142 | get_turn_number(#{nuk_state := NukState}) ->
143 | nuk_game_state:get_turn_number(NukState).
144 |
145 | %% @doc Get winners and losers lists
146 | %%
147 | %% Returns two lists of {@link nuk_user:user()} data types that represent a
148 | %% list of players who have won and who have lost the game. This is only
149 | %% relevant once the game has completed. In all other cases these lists will
150 | %% be empty.
151 | %% @end
152 | -spec get_winners_losers(Session :: session()) ->
153 | {Winners :: [nuk_user:user()], Losers :: [nuk_user:user()]}.
154 | get_winners_losers(#{nuk_state := NukState}) ->
155 | nuk_game_state:get_winners_losers(NukState).
156 |
157 | %% @doc Is a player a member of this game session?
158 | %%
159 | %% This is useful for checking whether a given user is a player in this game
160 | %% session.
161 | %% @end
162 | -spec has_player(Session :: session(), Player :: nuk_user:user()) -> boolean().
163 | has_player(Session, Player) ->
164 | Players = get_players(Session),
165 | %% TODO should this check by username instead?
166 | lists:member(Player, Players).
167 |
168 | %% @doc Is it a given player's turn?
169 | %%
170 | %% This is useful to checking whether it is OK for a given player to make a
171 | %% turn.
172 | %% @end
173 | -spec is_players_turn(Session :: session(), Player :: nuk_user:user()) ->
174 | boolean().
175 | is_players_turn(Session, Player) ->
176 | Players = get_players_turn(Session),
177 | %% TODO should this check by username instead?
178 | lists:member(Player, Players).
179 |
180 | %% @doc Increments the internal turn number
181 | %%
182 | %% nuk uses an internal turn counter every time any player makes a turn. This
183 | %% is used to increment that turn counter.
184 | %% @end
185 | -spec increment_turn_number(Session :: session()) -> session().
186 | increment_turn_number(Session) ->
187 | TurnNumberNew = get_turn_number(Session) + 1,
188 | set_turn_number(Session, TurnNumberNew).
189 |
190 | %% @doc Sets game engine state in the session
191 | %%
192 | %% With every successful {@link nuk_game_engine} callback the game engine
193 | %% returns its new state. This function is used to then store that state in the
194 | %% game session.
195 | %% @end
196 | -spec set_game_state(Session :: session(), GameState :: term()) -> session().
197 | set_game_state(Session, GameState) ->
198 | Session#{game_state := GameState}.
199 |
200 | %% @doc Add a player to the game session
201 | %%
202 | %% Whenver a players joins a game, nuk uses this function to add that player to
203 | %% the game session.
204 | %% @end
205 | -spec add_player(Session :: session(), Player :: nuk_user:user()) -> session().
206 | add_player(#{nuk_state := NukState} = Session, Player) ->
207 | Players = nuk_game_state:get_players(NukState),
208 | NewPlayers = [Player|Players],
209 | Session#{nuk_state := nuk_game_state:set_players(NukState, NewPlayers)}.
210 |
211 | %% @doc Remove a player from the game session
212 | %%
213 | %% Whenver a player leaves a game, nuk uses this function to remove that player
214 | %% from the game session.
215 | %% @end
216 | -spec remove_player(Session :: session(), Player :: nuk_user:user()) ->
217 | session().
218 | remove_player(#{nuk_state := NukState} = Session, Player) ->
219 | Players = nuk_game_state:get_players(NukState),
220 | NewPlayers = lists:delete(Player, Players),
221 | Session#{nuk_state := nuk_game_state:set_players(NukState, NewPlayers)}.
222 |
223 | %% @doc Set a list of players to the current game session
224 | %%
225 | %% nuk uses this function to set the initial players to the game session.
226 | %% @end
227 | -spec set_players(Session :: session(), Players :: [nuk_user:user()]) ->
228 | session().
229 | set_players(#{nuk_state := NukState} = Session, Players)
230 | when is_list(Players) ->
231 | Session#{nuk_state := nuk_game_state:set_players(NukState, Players)}.
232 |
233 | %% @doc Set players whose turn it is next
234 | %%
235 | %% In cases where {@link nuk_game_engine} callback returns the players whose
236 | %% turn it is next, nuk uses this function to update them in its session state.
237 | %% @end
238 | -spec set_players_turn(Session :: session(), Players :: [nuk_user:user()]) ->
239 | session().
240 | set_players_turn(#{nuk_state := NukState} = Session, Players)
241 | when is_list(Players) ->
242 | Session#{nuk_state := nuk_game_state:set_players_turn(NukState, Players)}.
243 |
244 | %% @doc Set game session status
245 | %%
246 | %% nuk uses this function to update the general game status.
247 | %% @end
248 | -spec set_status(Session:: session(), Status :: nuk_game_state:status()) ->
249 | session().
250 | set_status(#{nuk_state := NukState} = Session, Status) when is_atom(Status) ->
251 | Session#{nuk_state := nuk_game_state:set_status(NukState, Status)}.
252 |
253 | %% @doc Set turn number
254 | %%
255 | %% Sets turn number to a specified integer.
256 | %% @end
257 | -spec set_turn_number(Session :: session(), TurnNumber :: non_neg_integer()) ->
258 | session().
259 | set_turn_number(#{nuk_state := NukState} = Session, TurnNumber)
260 | when is_integer(TurnNumber) ->
261 | Session#{nuk_state := nuk_game_state:set_turn_number(NukState, TurnNumber)}.
262 |
263 | %% @doc Set winners and losers
264 | %%
265 | %% nuk uses this function to set winners and losers in the game session. This
266 | %% is used once the game has completed.
267 | %% @end
268 | -spec set_winners_losers(Session :: session(),
269 | Winners :: [nuk_user:user()],
270 | Losers :: [nuk_user:user()]) ->
271 | session().
272 | set_winners_losers(#{nuk_state := NukState} = Session, Winners, Losers) ->
273 | Session#{nuk_state := nuk_game_state:set_winners_losers(NukState,
274 | Winners,
275 | Losers)}.
276 |
--------------------------------------------------------------------------------
/src/nuk_game_session_storage.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_session_storage' module
3 | %%
4 | %% This is a behavior that allows to extend the game session ID to game process
5 | %% ID map. The default simple proof of concept implementation is provided in
6 | %% {@link nuk_game_session_store_server}.
7 | %% @end
8 | %%%-------------------------------------------------------------------
9 |
10 | -module(nuk_game_session_storage).
11 |
12 | -callback get_pid(SessionId :: string()) ->
13 | {ok, pid()} |
14 | {error, ErrorCode :: game_session_not_found, ErrorText :: string()}.
15 |
16 | -callback put(Pid :: pid()) -> SessionId :: string().
17 |
18 | -callback delete(SessionId :: string()) ->
19 | ok.
20 |
--------------------------------------------------------------------------------
/src/nuk_game_session_store_server.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_session_store_server' module
3 | %%
4 | %% This is an implementation of {@link nuk_game_session_storage} behavior. It
5 | %% is meant for testing and proof of concept purposes only.
6 | %% @end
7 | %%%-------------------------------------------------------------------
8 |
9 | -module(nuk_game_session_store_server).
10 |
11 | -behaviour(nuk_game_session_storage).
12 |
13 | %% API
14 | -export([get_pid/1, put/1, delete/1]).
15 |
16 | %%====================================================================
17 | %% API
18 | %%====================================================================
19 |
20 | %% @doc Get game session process ID
21 | %%
22 | %% Given the session identifier string, returns the game session `pid()' which
23 | %% then can be used to interface with {@link nuk_game_server} functions.
24 | %% @end
25 | -spec get_pid(SessionId :: string()) ->
26 | {ok, pid()} |
27 | {error, game_session_not_found, Extra :: string()}.
28 | get_pid(SessionId) ->
29 | try list_to_pid(SessionId) of
30 | Pid -> {ok, Pid}
31 | catch
32 | error:badarg -> {error, game_session_not_found, SessionId}
33 | end.
34 |
35 | %% @doc Create a new game session identifier
36 | %%
37 | %% Given a game session process ID, creates a new session identifier string.
38 | %% @end
39 | -spec put(Pid :: pid()) -> SessionId :: string().
40 | put(Pid) when is_pid(Pid) ->
41 | pid_to_list(Pid).
42 |
43 | %% @doc Delete a session ID mapping
44 | %%
45 | %% Given a session identifier string, deletes its mapping to the game session
46 | %% process ID, so that next call to {@link nuk_game_session_store_server:get/1}
47 | %% results in `game_session_not_found' response.
48 | %% @end
49 | -spec delete(SessionId :: string()) -> ok.
50 | delete(_SessionId) ->
51 | ok.
52 |
--------------------------------------------------------------------------------
/src/nuk_game_sessions.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_sessions' module
3 | %%
4 | %% This module should be used as an API for mapping game session identifiers
5 | %% to process IDs:
6 | %% - given a game process `pid()' create a new unique session identifier
7 | %% - translate a given unique session identifier to the game session `pid()'
8 | %%
9 | %% The backend implementation of this is swappable. See
10 | %% {@link nuk_game_session_storage} behavior and
11 | %% {@link nuk_game_session_store_server} default implementation.
12 | %% @end
13 | %%%-------------------------------------------------------------------
14 |
15 | -module(nuk_game_sessions).
16 |
17 | %% API
18 | -export([get_pid/1, put/1, delete/1]).
19 |
20 | %% @doc Get a process ID
21 | %%
22 | %% Given a previously created session identifier, retrieve a process ID.
23 | %% @end
24 | -spec get_pid(SessionId :: string()) ->
25 | {ok, Pid :: pid()} |
26 | {error, game_session_not_found, Extra :: string()}.
27 | get_pid(SessionId) ->
28 | SessionStorageModule = nuk_app:get_storage_module(game_sessions),
29 | SessionStorageModule:get_pid(SessionId).
30 |
31 | %% @doc Create a new session
32 | %%
33 | %% Given a process ID, create a new unique session identifier.
34 | %% @end
35 | -spec put(Pid :: pid()) -> SessionId :: string().
36 | put(Pid) when is_pid(Pid) ->
37 | SessionStorageModule = nuk_app:get_storage_module(game_sessions),
38 | SessionStorageModule:put(Pid).
39 |
40 | %% @doc Delete a session
41 | %%
42 | %% Delete the session associated with the given session identifier.
43 | %% @end
44 | -spec delete(SessionId :: string()) -> ok.
45 | delete(SessionId) ->
46 | SessionStorageModule = nuk_app:get_storage_module(game_sessions),
47 | SessionStorageModule:delete(SessionId).
48 |
--------------------------------------------------------------------------------
/src/nuk_game_state.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_state' module
3 | %%
4 | %% This module is used to operate on {@link nuk_game_state:state()} data type.
5 | %% This data type is used when retrieving nuk's general game session state from
6 | %% {@link nuk_game_session:get_nuk_state/1}.
7 | %% @end
8 | %%%-------------------------------------------------------------------
9 |
10 | -module(nuk_game_state).
11 |
12 | %% API
13 | -export([new/0]).
14 | -export([get_players/1]).
15 | -export([get_players_turn/1]).
16 | -export([get_status/1]).
17 | -export([get_turn_number/1]).
18 | -export([get_winners_losers/1]).
19 | -export([set_players/2]).
20 | -export([set_players_turn/2]).
21 | -export([set_status/2]).
22 | -export([set_turn_number/2]).
23 | -export([set_winners_losers/3]).
24 |
25 | -export_type([state/0]).
26 | -export_type([status/0]).
27 |
28 | -type status() :: nil | initialized | await_turn | complete.
29 | %% General game session status tracked by nuk.
30 |
31 | -opaque state() :: #{status => status(),
32 | turn_number => integer(),
33 | players => [nuk_user:user()],
34 | players_turn => [nuk_user:user()],
35 | players_winners => [nuk_user:user()],
36 | players_losers => [nuk_user:user()]}.
37 | %% Data type containing nuk's general game state. This is part of
38 | %% {@link nuk_game_session:session()} data type. Functions in this module can
39 | %% be used to operate on the following data:
40 | %% - `status': game session status, default `nil', use {@link get_status/1} to
41 | %% extract
42 | %% - `turn_number': current turn number, default `0', use
43 | %% {@link get_turn_number/1} to extract
44 | %% - `players': list of players currently in the game session, default `[]',
45 | %% use {@link get_players/1} to extract
46 | %% - `players_turn': list of players who should make turn(s) next,
47 | %% default `[]', use {@link get_players_turn/1} to extract
48 | %% - `players_winners': list of players who won the game, only populated
49 | %% after the game completes, default `[]', use {@link get_winners_losers/1}
50 | %% to extract
51 | %% - `players_losers': list of players who lost the game, only populated
52 | %% after the game completes, default `[]', use {@link get_winners_losers/1}
53 | %% to extract
54 |
55 | %% @doc Create a new {@link state()} data type.
56 | %%
57 | %% Creates a new state with default values.
58 | %% @end
59 | -spec new() -> state().
60 | new() ->
61 | #{status => nil,
62 | turn_number => 0,
63 | players => [],
64 | players_turn => [],
65 | players_winners => [],
66 | players_losers => []}.
67 |
68 | %% @doc Get players currently in the game session
69 | %%
70 | %% Returns a list of {@link nuk_user:user()} data types that represent a list
71 | %% of players currently joined to this game session.
72 | %% @end
73 | -spec get_players(State :: state()) -> [nuk_user:user()].
74 | get_players(#{players := Players}) ->
75 | Players.
76 |
77 | %% @doc Get players whose turn it is next
78 | %%
79 | %% Returns a list of {@link nuk_user:user()} data types that represent a list
80 | %% of players who the game engine is expecting to make the turn(s) next. i.e.
81 | %% the answer to "whose turn is it?" question.
82 | %% @end
83 | -spec get_players_turn(State :: state()) -> [nuk_user:user()].
84 | get_players_turn(#{players_turn := PlayersTurn}) ->
85 | PlayersTurn.
86 |
87 | %% @doc Get game session status
88 | %%
89 | %% Returns an atom status of the game session.
90 | %% @end
91 | -spec get_status(State :: state()) -> status().
92 | get_status(#{status := Status}) ->
93 | Status.
94 |
95 | %% @doc Get turn number
96 | %%
97 | %% Every time any player makes a turn nuk increments an internal turn counter.
98 | %% This returns the current turn number from the game session.
99 | %% @end
100 | -spec get_turn_number(State :: state()) -> non_neg_integer().
101 | get_turn_number(#{turn_number := TurnNumber}) ->
102 | TurnNumber.
103 |
104 | %% @doc Get winners and losers lists
105 | %%
106 | %% Returns two lists of {@link nuk_user:user()} data types that represent a
107 | %% list of players who have won and who have lost the game. This is only
108 | %% relevant once the game has completed. In all other cases these lists are
109 | %% likely to be empty.
110 | %% @end
111 | -spec get_winners_losers(State :: state()) ->
112 | {Winners :: [nuk_user:user()], Losers :: [nuk_user:user()]}.
113 | get_winners_losers(#{players_winners := Winners, players_losers := Losers}) ->
114 | {Winners, Losers}.
115 |
116 | %% @doc Set a list of players to the current game session
117 | %%
118 | %% Useful for setting initial set of players for the game session.
119 | %% @end
120 | -spec set_players(State :: state(), Players :: [nuk_user:user()]) ->
121 | state().
122 | set_players(State, Players) when is_list(Players) ->
123 | State#{players := Players}.
124 |
125 | %% @doc Set players whose turn it is next
126 | %%
127 | %% Sets a new list of players, replacing an existing list.
128 | %% @end
129 | -spec set_players_turn(State :: state(), Players :: [nuk_user:user()]) ->
130 | state().
131 | set_players_turn(State, Players) when is_list(Players) ->
132 | State#{players_turn := Players}.
133 |
134 | %% @doc Set status
135 | %%
136 | %% Sets a new status in the game state.
137 | %% @end
138 | -spec set_status(State :: state(), Status :: status()) ->
139 | state().
140 | set_status(State, Status) when is_atom(Status) ->
141 | State#{status := Status}.
142 |
143 | %% @doc Set turn number
144 | %%
145 | %% Sets turn number to a specified integer.
146 | %% @end
147 | -spec set_turn_number(State:: state(), TurnNumber :: non_neg_integer()) ->
148 | state().
149 | set_turn_number(State, TurnNumber) when is_integer(TurnNumber) ->
150 | State#{turn_number := TurnNumber}.
151 |
152 | %% @doc Set winners and losers
153 | %%
154 | %% This is typically used once the game has completed.
155 | %% @end
156 | -spec set_winners_losers(State :: state(),
157 | Winners :: [nuk_user:user()],
158 | Losers :: [nuk_user:user()]) ->
159 | state().
160 | set_winners_losers(State, Winners, Losers) ->
161 | State#{players_winners := Winners, players_losers := Losers}.
162 |
--------------------------------------------------------------------------------
/src/nuk_game_storage.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_storage' module
3 | %%
4 | %% This behavior allows to extend the storage service for registered games.
5 | %% When a new game engine is registered with nuk via
6 | %% {@link nuk_games:register/1} it is stored internally by the system.
7 | %% Implementing this behavior allows a custom storage backend to be defined.
8 | %% The default simple implementation is provided with
9 | %% {@link nuk_game_store_server}.
10 | %% @end
11 | %%%-------------------------------------------------------------------
12 |
13 | -module(nuk_game_storage).
14 |
15 | -callback delete(GameName :: string()) ->
16 | 'ok'.
17 |
18 | -callback get(GameName :: string()) ->
19 | {ok, nuk_game:game()} |
20 | {error, ErrorCode :: game_not_found, ErrorText :: string()}.
21 |
22 | -callback put(Game :: nuk_game:game()) ->
23 | 'ok'.
24 |
25 | -callback list() ->
26 | [nuk_game:game()].
27 |
--------------------------------------------------------------------------------
/src/nuk_game_store_server.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_store_server' module
3 | %%
4 | %% This is an implementation of {@link nuk_game_storage} behavior. It is meant
5 | %% for testing and proof of concept purposes only.
6 | %%
7 | %% This is a `gen_server' that's started by the {@link nuk_game_store_sup}
8 | %% supervisor. It provides storage interface to registered games. For public
9 | %% API the {@link nuk_games} module should be used which, in turn, will use the
10 | %% appropriate storage backend.
11 | %% @end
12 | %%%-------------------------------------------------------------------
13 |
14 | -module(nuk_game_store_server).
15 |
16 | -behaviour(gen_server).
17 | -behaviour(nuk_game_storage).
18 |
19 | %% Supervision
20 | -export([start_link/0, init/1]).
21 |
22 | %% Behavior callbacks
23 | -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
24 |
25 | %% API
26 | -export([delete/1, get/1, list/0, put/1]).
27 |
28 | %%===================================================================
29 | %% Supervision
30 | %%====================================================================
31 |
32 | start_link() ->
33 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
34 |
35 | init([]) ->
36 | {ok, #{data => #{}}}.
37 |
38 | %%====================================================================
39 | %% API
40 | %%====================================================================
41 |
42 | %% @doc Delete a game from registry
43 | %%
44 | %% Deletes a game by its name from the game registration database.
45 | %% @end
46 | -spec delete(GameName :: string()) -> ok.
47 | delete(GameName) ->
48 | ok = gen_server:call(?MODULE, {delete, GameName}).
49 |
50 | %% @doc Get a game
51 | %%
52 | %% Retrieves a game registration by its name from the database.
53 | %% @end
54 | -spec get(GameName :: string()) ->
55 | {ok, nuk_game:game()} |
56 | {error, game_not_found, string()}.
57 | get(GameName) ->
58 | gen_server:call(?MODULE, {get, GameName}).
59 |
60 | %% @doc List all games
61 | %%
62 | %% Lists all registered games from the registration database.
63 | %% @end
64 | -spec list() -> [nuk_game:game()].
65 | list() ->
66 | gen_server:call(?MODULE, {list}).
67 |
68 | %% @doc Create or replace a game
69 | %%
70 | %% If a game by the name is already registered, replaces that registration;
71 | %% otherwise creates a new registration.
72 | %% @end
73 | -spec put(Game :: nuk_game:game()) -> ok.
74 | put(Game) ->
75 | ok = gen_server:call(?MODULE, {put, Game}).
76 |
77 | %%====================================================================
78 | %% Behavior callbacks
79 | %%====================================================================
80 |
81 | handle_call({delete, GameName}, _From, #{data := Data} = State) ->
82 | NewData = delete_game(GameName, Data),
83 | NewState = State#{data := NewData},
84 | {reply, ok, NewState};
85 |
86 | handle_call({get, GameName}, _From, #{data := Data} = State) ->
87 | {reply, lookup_game(GameName, Data), State};
88 |
89 | handle_call({list}, _From, #{data := Data} = State) ->
90 | {reply, list_games(Data), State};
91 |
92 | handle_call({put, Game}, _From, #{data := Data} = State) ->
93 | GameName = nuk_game:get_name(Game),
94 | NewState = State#{data := Data#{GameName => Game}},
95 | {reply, ok, NewState}.
96 |
97 | handle_cast(_Msg, State) -> {noreply, State}.
98 |
99 | handle_info(_Msg, State) -> {noreply, State}.
100 |
101 | terminate(_Reason, _State) -> ok.
102 |
103 | code_change(_OldVersion, State, _Extra) -> {ok, State}.
104 |
105 | %%====================================================================
106 | %% Internal functions
107 | %%====================================================================
108 |
109 | %% @doc Delete a game from data storage
110 | %% @private
111 | %%
112 | %% Deletes a game by its name from the internal map.
113 | %% @end
114 | delete_game(GameName, Data) ->
115 | maps:remove(GameName, Data).
116 |
117 | %% @doc List all games
118 | %% @private
119 | %%
120 | %% Returns all games stored inside the map.
121 | %% @end
122 | list_games(Data) ->
123 | maps:values(Data).
124 |
125 | %% @doc Look up a game
126 | %% @private
127 | %%
128 | %% Search for a game by its name in the map.
129 | %% @end
130 | lookup_game(GameName, Data) ->
131 | try maps:get(GameName, Data) of
132 | Game -> {ok, Game}
133 | catch
134 | error:{badkey, GameName} -> {error, game_not_found, GameName}
135 | end.
136 |
--------------------------------------------------------------------------------
/src/nuk_game_store_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_store_sup' module
3 | %%
4 | %% This supervisor is started by {@link nuk_sup} top level supervisor. It
5 | %% supervises {@link nuk_game_store_server}.
6 | %% @end
7 | %%%-------------------------------------------------------------------
8 |
9 | -module(nuk_game_store_sup).
10 |
11 | -behaviour(supervisor).
12 |
13 | %% Supervision
14 | -export([start_link/0, init/1]).
15 |
16 | -define(SERVER, ?MODULE).
17 |
18 | %% Helper macro for declaring children of supervisor
19 | -define(CHILD(Id, Module, Args, Type), {Id, {Module, start_link, Args},
20 | permanent, 5000, Type, [Module]}).
21 |
22 | %%====================================================================
23 | %% Supervision
24 | %%====================================================================
25 |
26 | start_link() ->
27 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
28 |
29 | init([]) ->
30 | {ok, { {one_for_one, 0, 1}, children()} }.
31 |
32 | %%====================================================================
33 | %% Internal functions
34 | %%====================================================================
35 |
36 | %% @doc Get children specs
37 | %% @private
38 | %%
39 | %% A convenience function to return all children specs.
40 | %% @end
41 | children() ->
42 | UserStore = ?CHILD(nuk_game_store_server, nuk_game_store_server, [], worker),
43 | [UserStore].
44 |
--------------------------------------------------------------------------------
/src/nuk_game_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_game_sup' module
3 | %%
4 | %% This supervisor is started by {@link nuk_sup} top level supervisor.
5 | %%
6 | %% Whenever a new game session is created, nuk spawns a new
7 | %% {@link nuk_game_server}. This module is a `simple_one_for_one' supervisor
8 | %% that supervises those servers.
9 | %% @end
10 | %%%-------------------------------------------------------------------
11 |
12 | -module(nuk_game_sup).
13 |
14 | -behaviour(supervisor).
15 |
16 | %% Supervision
17 | -export([start_link/0, init/1]).
18 |
19 | -define(SERVER, ?MODULE).
20 |
21 | %%====================================================================
22 | %% Supervision
23 | %%====================================================================
24 |
25 | start_link() ->
26 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
27 |
28 | init([]) ->
29 | MaxRestart = 1,
30 | MaxTime = 3600,
31 | ChildSpec = {nuk_game_server,
32 | {nuk_game_server, start_link, []},
33 | temporary,
34 | 5000, % shutdown time
35 | worker,
36 | [nuk_game_server]},
37 | {ok, {{simple_one_for_one, MaxRestart, MaxTime}, [ChildSpec]}}.
38 |
--------------------------------------------------------------------------------
/src/nuk_games.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_games' module
3 | %%
4 | %% This module should be used as an API to interacting with all game related
5 | %% actions. There are 2 types of actions: (1) game registration, and (2) game
6 | %% flow.
7 | %%
8 | %% The game registration functions are:{@link register/2},
9 | %% {@link unregister/1}, {@link get/1}, {@link list/0}.
10 | %%
11 | %% The game flow functions are: {@link create/2}, {@link create/3},
12 | %% {@link join/2}, {@link leave/2}, {@link start/2},
13 | %% {@link get_game_session/2}, {@link turn/3}.
14 | %% @end
15 | %%%-------------------------------------------------------------------
16 |
17 | -module(nuk_games).
18 |
19 | %% API
20 |
21 | % Game registration
22 | -export([register/1]).
23 | -export([unregister/1]).
24 | -export([get/1, list/0]).
25 |
26 | % Game flow
27 | -export([create/2]).
28 | -export([create/3]).
29 | -export([join/2]).
30 | -export([leave/2]).
31 | -export([start/2]).
32 | -export([get_game_session/2]).
33 | -export([turn/3]).
34 |
35 | %%====================================================================
36 | %% Game registration
37 | %%====================================================================
38 |
39 | %% @doc Register a game engine
40 | %%
41 | %% This registers a game engine with nuk. After creating a new game engine
42 | %% by implementing the {@link nuk_game_engine} behavior, create a
43 | %% {@link nuk_game:game()} data type and use it here to register that game.
44 | %% A game engine must be registered before it can be played.
45 | %% @end
46 | -spec register(Game :: nuk_game:game()) -> ok.
47 | register(Game) ->
48 | StorageModule = nuk_app:get_storage_module(games),
49 | ok = StorageModule:put(Game).
50 |
51 | %% @doc Unregister a game engine
52 | %%
53 | %% Unregistering has the opposite effect of registering via {@link register/1}.
54 | %% It makes nuk forget about the registered game.
55 | %% @end
56 | -spec unregister(GameName :: string()) -> ok.
57 | unregister(GameName) ->
58 | StorageModule = nuk_app:get_storage_module(games),
59 | ok = StorageModule:delete(GameName).
60 |
61 | %% @doc Get a game by its name
62 | %%
63 | %% This can be used to look up any registered game metadata for a specific game
64 | %% engine. Given a game name, get a {@link nuk_game:game()} data type. Then use
65 | %% {@link nuk_game} module functions to extract needed information.
66 | %% @end
67 | -spec get(GameName :: string()) ->
68 | {ok, nuk_game:game()} |
69 | {error, game_not_found, Extra :: string()}.
70 | get(GameName) ->
71 | StorageModule = nuk_app:get_storage_module(games),
72 | StorageModule:get(GameName).
73 |
74 | %% @doc List all registered games
75 | %%
76 | %% Produces a list of all registered games in the system. The return is a list
77 | %% of {@link nuk_game:game()} data types. Use {@link nuk_game} module
78 | %% functions to extract needed information from each game element.
79 | %% @end
80 | -spec list() -> [nuk_game:game()].
81 | list() ->
82 | StorageModule = nuk_app:get_storage_module(games),
83 | StorageModule:list().
84 |
85 | %%====================================================================
86 | %% Game flow
87 | %%====================================================================
88 |
89 | %% @doc Create a new game session with default options
90 | %% @equiv create(UserSessionId, GameName, [])
91 | %% @end
92 | -spec create(UserSessionId :: string(), GameName :: string()) ->
93 | {ok, GameSessionId :: string()} |
94 | {error, invalid_user_session, Extra :: string()} |
95 | {error, invalid_game_name, Extra :: string()}.
96 | create(UserSessionId, GameName) ->
97 | create(UserSessionId, GameName, []).
98 |
99 | %% @doc Create a new game with options
100 | %%
101 | %% Using a logged in user session, create a new game by supplying its
102 | %% registered name. This does not start a game, but merely creates a new
103 | %% session allowing other players to join. Game session must be created before
104 | %% it can be started.
105 | %%
106 | %% Calling this function triggers the {@link nuk_game_engine:initialize/2}
107 | %% callback.
108 | %% @end
109 | -spec create(UserSessionId :: string(),
110 | GameName :: string(),
111 | Options :: list(tuple())) ->
112 | {ok, GameSessionId :: string()} |
113 | {error, invalid_user_session, Extra :: string()} |
114 | {error, invalid_game_name, Extra :: string()}.
115 | create(UserSessionId, GameName, Options) ->
116 | case get_user(UserSessionId) of
117 | {error, user_session_not_found, Reason} ->
118 | {error, user_session_not_found, Reason};
119 | {ok, User} ->
120 | nuk_game_server:create(User, GameName, Options)
121 | end.
122 |
123 | %% @doc Join a player to a game session
124 | %%
125 | %% Joins a given logged in user session to an existing game session. Game
126 | %% session must be created first, see {@link create/2} and {@link create/3}.
127 | %%
128 | %% Calling this function triggers the {@link nuk_game_engine:player_join/3}
129 | %% callback.
130 | %% @end
131 | -spec join(GameSessionId :: string(), UserSessionId :: string()) ->
132 | ok |
133 | {error, game_session_not_found, Extra :: string()} |
134 | {error, user_session_not_found, Extra :: string()} |
135 | {error, user_already_joined, Extra :: string()} |
136 | {error, max_users_reached, Extra :: string()}.
137 | join(GameSessionId, UserSessionId) ->
138 | case get_user_game_pid(UserSessionId, GameSessionId) of
139 | {error, ErrorCode, Reason} ->
140 | {error, ErrorCode, Reason};
141 | {ok, User, GamePid} ->
142 | nuk_game_server:join(GamePid, User)
143 | end.
144 |
145 | %% @doc Remove a player from a game session
146 | %%
147 | %% This does the opposite of {@link join/2} - it allows a player to leave an
148 | %% existing game session that the player has already joined.
149 | %%
150 | %% Calling this function triggers the {@link nuk_game_engine:player_leave/3}
151 | %% callback.
152 | %% @end
153 | -spec leave(GameSessionId :: string(), UserSessionId :: string()) ->
154 | ok |
155 | {error, game_session_not_found, Extra :: string()} |
156 | {error, user_session_not_found, Extra :: string()} |
157 | {error, user_not_in_game, Extra :: string()} |
158 | {error, game_already_started, Extra :: string()}.
159 | leave(GameSessionId, UserSessionId) ->
160 | case get_user_game_pid(UserSessionId, GameSessionId) of
161 | {error, ErrorCode, Reason} ->
162 | {error, ErrorCode, Reason};
163 | {ok, User, GamePid} ->
164 | nuk_game_server:leave(GamePid, User)
165 | end.
166 |
167 | %% @doc Start a game
168 | %%
169 | %% This starts an existing game session. In general, at this point all players
170 | %% wishing to participate should have already joined the game via
171 | %% {@link join/2}.
172 | %%
173 | %% Calling this function triggers the {@link nuk_game_engine:start/2} callback.
174 | %% @end
175 | -spec start(GameSessionId :: string(), UserSessionId :: string()) ->
176 | ok |
177 | {error, game_session_not_found, Extra :: string()} |
178 | {error, user_session_not_found, Extra :: string()} |
179 | {error, min_users_not_met, Extra :: string()} |
180 | {error, user_not_in_game, Extra :: string()}.
181 | start(GameSessionId, UserSessionId) ->
182 | case get_user_game_pid(UserSessionId, GameSessionId) of
183 | {error, ErrorCode, Reason} ->
184 | {error, ErrorCode, Reason};
185 | {ok, User, GamePid} ->
186 | nuk_game_server:start(GamePid, User)
187 | end.
188 |
189 | %% @doc Get game session containing nuk and game engine states
190 | %%
191 | %% Returns the current snapshot of the game session state. The
192 | %% {@link nuk_game_session} module functions should be used to extract needed
193 | %% data from the returned {@link nuk_game_session:session()} data type. This is
194 | %% useful for players to get a new game session state during the game.
195 | %%
196 | %% Note that {@link nuk_game_session:get_game_state/1} can be used to extract
197 | %% {@link nuk_game_engine_state} set by the specific {@link nuk_game_engine}.
198 | %% @end
199 | -spec get_game_session(GameSessionId :: string(), UserSessionId :: string()) ->
200 | {ok, nuk_game_session:session()} |
201 | {error, game_session_not_found, Extra :: string()} |
202 | {error, user_session_not_found, Extra :: string()} |
203 | {error, user_not_in_game, Extra :: string()}.
204 | get_game_session(GameSessionId, UserSessionId) ->
205 | case get_user_game_pid(UserSessionId, GameSessionId) of
206 | {error, ErrorCode, Reason} ->
207 | {error, ErrorCode, Reason};
208 | {ok, User, GamePid} ->
209 | {ok, nuk_game_server:get_session(GamePid, User)}
210 | end.
211 |
212 | %% @doc Make a player turn
213 | %%
214 | %% This function should be used when it's time for a specific player to make a
215 | %% turn. The `Turn' argument is an arbitrary term that is expected by the game
216 | %% engine. It is not validated by nuk and is passed to the game engine
217 | %% directly.
218 | %%
219 | %% Calling this function triggers the {@link nuk_game_engine:turn/4} callback.
220 | %% @end
221 | -spec turn(GameSessionId :: string(), UserSessionId :: string(), Turn :: term()) ->
222 | ok |
223 | {error, game_session_not_found, Extra :: string()} |
224 | {error, user_session_not_found, Extra :: string()} |
225 | {error, user_not_in_game, Extra :: string()} |
226 | {error, bad_turn_order, Extra :: string()} |
227 | {error, invalid_turn, Extra :: string()}.
228 | turn(GameSessionId, UserSessionId, Turn) ->
229 | case get_user_game_pid(UserSessionId, GameSessionId) of
230 | {error, ErrorCode, Reason} ->
231 | {error, ErrorCode, Reason};
232 | {ok, User, GamePid} ->
233 | nuk_game_server:turn(GamePid, User, Turn)
234 | end.
235 |
236 | %%====================================================================
237 | %% Internal functions
238 | %%====================================================================
239 |
240 | %% @doc Get user from user session ID
241 | %% @private
242 | %%
243 | %% This is a convenience function to get a user {@link nuk_user:user()} data
244 | %% type given the user session ID.
245 | %% @end
246 | -spec get_user(UserSessionId :: string()) ->
247 | {ok, User :: nuk_user:user()} |
248 | {error, user_session_not_found, Extra :: string()}.
249 | get_user(UserSessionId) ->
250 | nuk_user_sessions:get_user(UserSessionId).
251 |
252 | %% @doc Get both user and game process ID based on respective session IDs
253 | %% @private
254 | %%
255 | %% This is a convenience function get both a user {@link nuk_user:user()} data
256 | %% type and the game session process `pid()' given the user session ID and the
257 | %% game session ID. It attempts to get the user session first. If that's
258 | %% successful then it attempts to get the game session process ID.
259 | %% end
260 | -spec get_user_game_pid(UserSessionId :: string(), GameSessionId :: string()) ->
261 | {ok, User :: nuk_user:user(), GamePid :: pid()} |
262 | {error, user_session_not_found, Extra :: string()} |
263 | {error, game_session_not_found, Extra :: string()}.
264 | get_user_game_pid(UserSessionId, GameSessionId) ->
265 | case nuk_user_sessions:get_user(UserSessionId) of
266 | {error, user_session_not_found, Reason} ->
267 | {error, user_session_not_found, Reason};
268 | {ok, User} ->
269 | case nuk_game_sessions:get_pid(GameSessionId) of
270 | {error, game_session_not_found, Reason} ->
271 | {error, game_session_not_found, Reason};
272 | {ok, GamePid} ->
273 | {ok, User, GamePid}
274 | end
275 | end.
276 |
--------------------------------------------------------------------------------
/src/nuk_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_sup' module
3 | %%
4 | %% This is a top level `one_for_one' nuk supervisor started by the nuk
5 | %% application. It, in turn, starts the following supervisors under it:
6 | %% - {@link nuk_user_sup}
7 | %% - {@link nuk_user_store_sup}
8 | %% - {@link nuk_game_sup}
9 | %% - {@link nuk_game_store_sup}
10 | %% @end
11 | %%%-------------------------------------------------------------------
12 |
13 | -module(nuk_sup).
14 |
15 | -behaviour(supervisor).
16 |
17 | %% Supervision
18 | -export([start_link/0, init/1]).
19 |
20 | -define(SERVER, ?MODULE).
21 |
22 | %% Helper macro for declaring children of supervisor
23 | -define(CHILD(Id, Module, Args, Type), {Id, {Module, start_link, Args},
24 | permanent, 5000, Type, [Module]}).
25 |
26 | %%====================================================================
27 | %% Supervision
28 | %%====================================================================
29 |
30 | start_link() ->
31 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
32 |
33 | init([]) ->
34 | {ok, { {one_for_one, 0, 1}, children()} }.
35 |
36 | %%====================================================================
37 | %% Internal functions
38 | %%====================================================================
39 |
40 | %% @doc Get children specs
41 | %% @private
42 | %%
43 | %% A convenience function to return all children specs.
44 | %% @end
45 | children() ->
46 | UserSup = ?CHILD(nuk_user_sup, nuk_user_sup, [], supervisor),
47 | UserStoreSup = ?CHILD(nuk_user_store_sup, nuk_user_store_sup, [], supervisor),
48 | GameSup = ?CHILD(nuk_game_sup, nuk_game_sup, [], supervisor),
49 | GameStoreSup = ?CHILD(nuk_game_store_sup, nuk_game_store_sup, [], supervisor),
50 | [UserSup, UserStoreSup, GameSup, GameStoreSup].
51 |
--------------------------------------------------------------------------------
/src/nuk_user.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user' module
3 | %%
4 | %% This module is used to operate on {@link nuk_user:user()} data type. This
5 | %% data type is used when dealing with users in nuk, for example, when calling
6 | %% {@link nuk_users:put/1} and {@link nuk_users:get/1}.
7 | %% @end
8 | %%%-------------------------------------------------------------------
9 |
10 | -module(nuk_user).
11 |
12 | %% API
13 | -export([new/2, check_password/2, get_username/1]).
14 | -export_type([user/0]).
15 | -export_type([username/0]).
16 |
17 | -opaque user() :: #{username => username(), password => string()}.
18 | %% Data type used to operate on users in nuk. Use functions in this module to
19 | %% operate on this data type.
20 |
21 | -type username() :: string() | anonymous.
22 | %% Data type for user name, can be any string or atom `anonymous'.
23 |
24 | %%====================================================================
25 | %% API
26 | %%====================================================================
27 |
28 | %% @doc Create a new {@link nuk_user:user()} data type
29 | %%
30 | %% `Username' is a string that uniquely identifies a user in the user storage.
31 | %% `Password' is a string used to validate user for login. Note that the
32 | %% password is here for proof of concept and testing purposes only. It does not
33 | %% provide hashing or secure password storage. When using
34 | %% {@link nuk_user_storage} implementations for the hashed password is likely
35 | %% stored there, so this password shouldn't be used in that case.
36 | %% @end
37 | -spec new(Username :: string(), Password :: string()) -> user().
38 | new(Username, Password) ->
39 | #{username => Username, password => Password}.
40 |
41 | %% @doc Verify user's password
42 | %%
43 | %% This function is only here for testing and proof of concept purposes. In
44 | %% production scenarios where users are stored in external
45 | %% {@link nuk_user_storage} implementations the authentication should be
46 | %% performed by that system, and this function should not be used.
47 | %% @end
48 | -spec check_password(user(), EnteredPassword :: string()) -> boolean().
49 | check_password(#{password := StoredPassword}, EnteredPassword) ->
50 | StoredPassword =:= EnteredPassword.
51 |
52 | %% @doc Get username
53 | %%
54 | %% Extract username from {@link nuk_user:user()} data type.
55 | %% @end
56 | -spec get_username(user()) -> username().
57 | get_username(#{username := Username}) ->
58 | Username.
59 |
--------------------------------------------------------------------------------
/src/nuk_user_server.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_server' module
3 | %%
4 | %% When a user logs in, a new process is spawned that keeps the session of the
5 | %% user. This is a `gen_server' that keeps the session state and provides
6 | %% interface to its manipulation.
7 | %%
8 | %% For public API to accessing this functionality use the {@link nuk_users} and
9 | %% {@link nuk_user_sessions} modules. Do not call the functions of this module
10 | %% directly.
11 | %% @end
12 | %%%-------------------------------------------------------------------
13 |
14 | -module(nuk_user_server).
15 |
16 | -behaviour(gen_server).
17 |
18 | %% Supervision
19 | -export([start_link/0, init/1]).
20 |
21 | %% Behavior callbacks
22 | -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
23 |
24 | %% API
25 | -export([login/3, logout/1, get_session/1]).
26 |
27 | %%====================================================================
28 | %% Supervision
29 | %%====================================================================
30 |
31 | start_link() ->
32 | gen_server:start_link(?MODULE, [], []).
33 |
34 | init([]) ->
35 | {ok, #{session_id => "", session => nuk_user_session:new()}}.
36 |
37 | %%====================================================================
38 | %% API
39 | %%====================================================================
40 |
41 | %% @doc Log in a user
42 | %%
43 | %% Attempts to log a user in given the username and password. Upon successful
44 | %% login a new process is spawned and the string session identifier returned.
45 | %% @end
46 | -spec login(Username :: string(), Password :: string(),
47 | StorageModule :: atom()) ->
48 | {ok, string()} |
49 | {error, wrong_password | user_not_found, string()}.
50 | login(Username, Password, StorageModule) when is_atom(StorageModule) ->
51 | {ok, Pid} = supervisor:start_child(nuk_user_sup, []),
52 | gen_server:call(Pid, {login, Username, Password, StorageModule}).
53 |
54 | %% @doc Log out a user
55 | %%
56 | %% Logs a user session out - i.e. stops the process that was keeping the logged
57 | %% in user state.
58 | %% @end
59 | -spec logout(Pid :: pid()) -> ok.
60 | logout(Pid) ->
61 | gen_server:call(Pid, {logout}).
62 |
63 | %% @doc Get logged in user session
64 | %%
65 | %% Gets the {@link nuk_user_session:session()} data type for the given logged
66 | %% in user session. Use {@link nuk_user_session} module to operate on the
67 | %% returned data type.
68 | %% @end
69 | -spec get_session(Pid :: pid()) -> nuk_user_session:session().
70 | get_session(Pid) ->
71 | gen_server:call(Pid, {get_session}).
72 |
73 | %%====================================================================
74 | %% Behavior callbacks
75 | %%====================================================================
76 |
77 | handle_call({login, Username, Password, StorageModule}, _From,
78 | #{session := Session} = State) ->
79 | case StorageModule:validate(Username, Password) of
80 | {ok, User} ->
81 | SessionId = nuk_user_sessions:put(self()),
82 | SessionNew = nuk_user_session:set_user(Session, User),
83 | StateNew = State#{session_id := SessionId, session := SessionNew},
84 | {reply, {ok, SessionId}, StateNew};
85 | {error, Reason, Extra} ->
86 | {stop, normal, {error, Reason, Extra}, State}
87 | end;
88 | handle_call({logout}, _From, State) ->
89 | {stop, normal, ok, State};
90 | handle_call({get_session}, _From, #{session := Session} = State) ->
91 | {reply, Session, State}.
92 |
93 | handle_cast(_Msg, State) -> {noreply, State}.
94 |
95 | handle_info(_Msg, State) -> {noreply, State}.
96 |
97 | terminate(_Reason, #{session_id := SessionId} = _State) ->
98 | nuk_user_sessions:delete(SessionId),
99 | ok.
100 |
101 | code_change(_OldVersion, State, _Extra) -> {ok, State}.
102 |
--------------------------------------------------------------------------------
/src/nuk_user_session.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_session' module
3 | %%
4 | %% This module is used to operate on {@link nuk_user_session:session()} data
5 | %% type. This data type is used when dealing with user sessions in nuk, for
6 | %% example, when calling {@link nuk_user_sessions:get/1}.
7 | %%
8 | %% For public API the {@link nuk_user_sessions} module should be used which
9 | %% already provides convenience functions for extracting information from
10 | %% user sessions.
11 | %% @end
12 | %%%-------------------------------------------------------------------
13 |
14 | -module(nuk_user_session).
15 |
16 | %% API
17 | -export([new/0, get_user/1, set_user/2]).
18 | -export_type([session/0]).
19 |
20 | -opaque session() :: #{username => nuk_user:username(), user => nuk_user:user()}.
21 |
22 | %%====================================================================
23 | %% API
24 | %%====================================================================
25 |
26 | %% @doc Create a new {@link nuk_user_session:session()} data type
27 | %%
28 | %% Creates a new data type with default values.
29 | %% @end
30 | -spec new() -> session().
31 | new() ->
32 | #{username => anonymous, user => #{}}.
33 |
34 | %% @doc Get user
35 | %%
36 | %% Extracts a {@link nuk_user:user()} stored in the session.
37 | %% @end
38 | -spec get_user(session()) -> nuk_user:user().
39 | get_user(#{user := User}) ->
40 | User.
41 |
42 | %% @doc Set user
43 | %%
44 | %% Set a specified {@link nuk_user:user()} in the session.
45 | %% @end
46 | -spec set_user(Session :: session(), User :: nuk_user:user()) -> session().
47 | set_user(Session, User) ->
48 | Username = nuk_user:get_username(User),
49 | Session#{username := Username, user := User}.
50 |
--------------------------------------------------------------------------------
/src/nuk_user_session_storage.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_session_storage' module
3 | %%
4 | %% This is a behavior that allows to extend the user session ID to user process
5 | %% ID map. The default simple proof of concept implementation is provided in
6 | %% {@link nuk_user_session_store_server}.
7 | %% @end
8 | %%%-------------------------------------------------------------------
9 |
10 | -module(nuk_user_session_storage).
11 |
12 | -callback get_pid(SessionId :: string()) ->
13 | {ok, pid()} |
14 | {error, ErrorCode :: user_session_not_found, ErrorText :: string()}.
15 |
16 | -callback put(Pid :: pid()) ->
17 | SessionId :: string().
18 |
19 | -callback delete(SessionId :: string()) ->
20 | ok.
21 |
22 | -callback list() ->
23 | [nuk_user_session:session()].
24 |
--------------------------------------------------------------------------------
/src/nuk_user_session_store_server.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_session_store_server' module
3 | %%
4 | %% This is an implementation of {@link nuk_user_session_storage} behavior. It
5 | %% is meant for testing and proof of concept purposes only.
6 | %% @end
7 | %%%-------------------------------------------------------------------
8 |
9 | -module(nuk_user_session_store_server).
10 |
11 | -behaviour(nuk_user_session_storage).
12 |
13 | %% API
14 | -export([get_pid/1, put/1, delete/1, list/0]).
15 |
16 | %%====================================================================
17 | %% API
18 | %%====================================================================
19 |
20 | %% @doc Get game session process ID
21 | %%
22 | %% Given the session identifier string, returns the game session `pid()' which
23 | %% then can be used to interface with {@link nuk_user_server} functions.
24 | %% @end
25 | -spec get_pid(SessionId :: string()) ->
26 | {ok, pid()} |
27 | {error, user_session_not_found, Extra :: string()}.
28 | get_pid(SessionId) ->
29 | try list_to_pid(SessionId) of
30 | Pid -> {ok, Pid}
31 | catch
32 | error:badarg -> {error, user_session_not_found, SessionId}
33 | end.
34 |
35 | %% @doc Create a new user session identifier
36 | %%
37 | %% Given a user session process ID, creates a new session identifier string.
38 | %% @end
39 | -spec put(Pid :: pid()) -> SessionId :: string().
40 | put(Pid) when is_pid(Pid) ->
41 | pid_to_list(Pid).
42 |
43 | %% @doc Delete a session ID mapping
44 | %%
45 | %% Given a session identifier string, deletes its mapping to the user session
46 | %% process ID, so that next call to {@link nuk_user_session_store_server:get/1}
47 | %% results in `game_session_not_found' response.
48 | %% @end
49 | -spec delete(SessionId :: string()) -> ok.
50 | delete(_SessionId) ->
51 | ok.
52 |
53 | %% @doc List all sessions
54 | %%
55 | %% Returns a list of all user sessions. Used in tests.
56 | %% @end
57 | -spec list() -> [nuk_user_session:session()].
58 | list() ->
59 | UserProcesses = supervisor:which_children(nuk_user_sup),
60 | lists:map(fun({_, Pid, worker, [nuk_user_server]}) ->
61 | nuk_user_server:get_session(Pid) end,
62 | UserProcesses).
63 |
--------------------------------------------------------------------------------
/src/nuk_user_sessions.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_sessions' module
3 | %%
4 | %% This module should be used as an API for mapping user session identifiers
5 | %% to process IDs:
6 | %% - given a user process `pid()' create a new unique session identifier
7 | %% - translate a given unique session identifier to the user session `pid()'
8 | %%
9 | %% It also provides several convenience functions for getting and extracting
10 | %% data from user sessions.
11 | %%
12 | %% The backend implementation of this is swappable. See
13 | %% {@link nuk_user_session_storage} behavior and
14 | %% {@link nuk_user_session_store_server} default implementation.
15 | %% @end
16 | %%%-------------------------------------------------------------------
17 |
18 | -module(nuk_user_sessions).
19 |
20 | %% API
21 | -export([get/1, get_pid/1, get_user/1, put/1, logout/1, delete/1, list/0]).
22 |
23 | %% @doc Get session
24 | %%
25 | %% Given a previously created session identifier, retrieve a process ID. Then
26 | %% call the process to retrive its session {@link nuk_user_session:session()}
27 | %% data type.
28 | %% @end
29 | -spec get(SessionId :: string()) ->
30 | {ok, nuk_user_session:session()} |
31 | {error, user_session_not_found, Extra :: string()}.
32 | get(SessionId) ->
33 | case get_pid(SessionId) of
34 | {ok, Pid} ->
35 | {ok, nuk_user_server:get_session(Pid)};
36 | {error, user_session_not_found, Reason} ->
37 | {error, user_session_not_found, Reason}
38 | end.
39 |
40 | %% @doc Get a process ID
41 | %%
42 | %% Given a previously created session identifier, retrieve a process ID.
43 | %% @end
44 | -spec get_pid(SessionId :: string()) ->
45 | {ok, pid()} |
46 | {error, user_session_not_found, Extra :: string()}.
47 | get_pid(SessionId) ->
48 | SessionStorageModule = nuk_app:get_storage_module(user_sessions),
49 | SessionStorageModule:get_pid(SessionId).
50 |
51 | %% @doc Get user
52 | %%
53 | %% Gets user that is associated with this session identifier.
54 | %% @end
55 | -spec get_user(SessionId :: string()) ->
56 | {ok, nuk_user:user()} |
57 | {error, user_session_not_found, Extra :: string()}.
58 | get_user(SessionId) ->
59 | case nuk_user_sessions:get(SessionId) of
60 | {ok, Session} ->
61 | {ok, nuk_user_session:get_user(Session)};
62 | {error, user_session_not_found, Reason} ->
63 | {error, user_session_not_found, Reason}
64 | end.
65 |
66 | %% @doc Create a new session
67 | %%
68 | %% Given a process ID, create a new unique session identifier.
69 | %% @end
70 | -spec put(Pid :: pid()) -> SessionId :: string().
71 | put(Pid) when is_pid(Pid) ->
72 | SessionStorageModule = nuk_app:get_storage_module(user_sessions),
73 | SessionStorageModule:put(Pid).
74 |
75 | %% @doc Log out a user session
76 | %%
77 | %% Logs out the given user session. Note that the {@link delete/1} happens
78 | %% after the {@link nuk_user_server} terminates successfully.
79 | %% @end
80 | -spec logout(SessionId :: string()) -> ok.
81 | logout(SessionId) ->
82 | {ok, Pid} = get_pid(SessionId),
83 | nuk_user_server:logout(Pid).
84 |
85 | %% @doc Delete a session
86 | %%
87 | %% Delete the session associated with the given session identifier.
88 | %% @end
89 | -spec delete(SessionId :: string()) -> ok.
90 | delete(SessionId) ->
91 | SessionStorageModule = nuk_app:get_storage_module(user_sessions),
92 | SessionStorageModule:delete(SessionId).
93 |
94 | %% @doc List all sessions
95 | %%
96 | %% Returns a list of all user sessions. Used in tests.
97 | %% @end
98 | -spec list() -> [nuk_user_session:session()].
99 | list() ->
100 | SessionStorageModule = nuk_app:get_storage_module(user_sessions),
101 | SessionStorageModule:list().
102 |
--------------------------------------------------------------------------------
/src/nuk_user_storage.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_storage' module
3 | %%
4 | %% This behavior allows to extend the storage service for registered users.
5 | %% When a new user is created engine with nuk via
6 | %% {@link nuk_users:put/1} it is stored internally by the system.
7 | %% Implementing this behavior allows a custom storage backend to be defined.
8 | %% The default simple implementation is provided with
9 | %% {@link nuk_user_store_server}.
10 | %% @end
11 | %%%-------------------------------------------------------------------
12 |
13 | -module(nuk_user_storage).
14 |
15 | -callback delete(Username :: string()) ->
16 | 'ok'.
17 |
18 | -callback get(Username :: string()) ->
19 | {ok, nuk_user:user()} |
20 | {error, ErrorCode :: user_not_found, ErrorText :: string()}.
21 |
22 | -callback put(User :: nuk_user:user()) ->
23 | 'ok'.
24 |
25 | -callback validate(Username :: string(), Password :: string()) ->
26 | {ok, User :: nuk_user:user()} |
27 | {error, ErrorCode :: wrong_password | user_not_found, ErrorText :: string()}.
28 |
29 | -callback list() ->
30 | [nuk_user:user()].
31 |
--------------------------------------------------------------------------------
/src/nuk_user_store_server.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_store_server' module
3 | %%
4 | %% This is an implementation of {@link nuk_user_storage} behavior. It is meant
5 | %% for testing and proof of concept purposes only.
6 | %%
7 | %% This is a `gen_server' that's started by the {@link nuk_user_store_sup}
8 | %% supervisor. It provides storage interface to registered users. For public
9 | %% API the {@link nuk_users} module should be used which, in turn, will use the
10 | %% appropriate storage backend.
11 | %% @end
12 | %%%-------------------------------------------------------------------
13 |
14 |
15 | -module(nuk_user_store_server).
16 |
17 | -behaviour(gen_server).
18 | -behaviour(nuk_user_storage).
19 |
20 | %% Supervision
21 | -export([start_link/0, init/1]).
22 |
23 | %% Behavior callbacks
24 | -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
25 |
26 | %% API
27 | -export([delete/1, get/1, list/0, put/1, validate/2]).
28 |
29 | %%===================================================================
30 | %% Supervision
31 | %%====================================================================
32 |
33 | start_link() ->
34 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
35 |
36 | init([]) ->
37 | {ok, #{data => #{}}}.
38 |
39 | %%====================================================================
40 | %% API
41 | %%====================================================================
42 |
43 | %% @doc Delete a user
44 | %%
45 | %% Deletes a user by username from the user data storage.
46 | %% @end
47 | -spec delete(Username :: string()) -> ok.
48 | delete(Username) ->
49 | ok = gen_server:call(?MODULE, {delete, Username}).
50 |
51 | %% @doc Get a user
52 | %%
53 | %% Retrieves a user by username from the user data storage.
54 | %% @end
55 | -spec get(Username :: string()) ->
56 | {ok, nuk_user:user()} |
57 | {error, user_not_found, string()}.
58 | get(Username) ->
59 | gen_server:call(?MODULE, {get, Username}).
60 |
61 | %% @doc List all users
62 | %%
63 | %% Lists all registered users in the user data storage.
64 | %% @end
65 | -spec list() -> [nuk_user:user()].
66 | list() ->
67 | gen_server:call(?MODULE, {list}).
68 |
69 | %% @doc Create or replace a user
70 | %%
71 | %% If a user by the username is already registered, replaces that registration;
72 | %% otherwise creates a new registered user.
73 | %% @end
74 | -spec put(User :: nuk_user:user()) -> ok.
75 | put(User) ->
76 | ok = gen_server:call(?MODULE, {put, User}).
77 |
78 | %% @doc Validate user credentials
79 | %%
80 | %% Given a username and a password validate that the credentials are correct.
81 | %% @end
82 | -spec validate(Username :: string(), Password :: string()) ->
83 | {ok, nuk_user:user()} |
84 | {error, wrong_password | user_not_found, string()}.
85 | validate(Username, Password) ->
86 | gen_server:call(?MODULE, {validate, Username, Password}).
87 |
88 | %%====================================================================
89 | %% Behavior callbacks
90 | %%====================================================================
91 |
92 | handle_call({delete, Username}, _From, #{data := Data} = State) ->
93 | NewData = delete_user(Username, Data),
94 | NewState = State#{data := NewData},
95 | {reply, ok, NewState};
96 |
97 | handle_call({get, Username}, _From, #{data := Data} = State) ->
98 | {reply, lookup_user(Username, Data), State};
99 |
100 | handle_call({list}, _From, #{data := Data} = State) ->
101 | {reply, list_users(Data), State};
102 |
103 | handle_call({put, #{username := Username} = User}, _From, #{data := Data} = State) ->
104 | NewState = State#{data := Data#{Username => User}},
105 | {reply, ok, NewState};
106 |
107 | handle_call({validate, Username, Password}, _From, #{data := Data} = State) ->
108 | {reply, validate_user_with_password(Username, Password, Data), State}.
109 |
110 | handle_cast(_Msg, State) -> {noreply, State}.
111 |
112 | handle_info(_Msg, State) -> {noreply, State}.
113 |
114 | terminate(_Reason, _State) -> ok.
115 |
116 | code_change(_OldVersion, State, _Extra) -> {ok, State}.
117 |
118 | %%====================================================================
119 | %% Internal functions
120 | %%====================================================================
121 |
122 | %% @doc Delete a user from user data storage
123 | %% @private
124 | %%
125 | %% Deletes a user by username from the internal map.
126 | %% @end
127 | delete_user(Username, Data) ->
128 | maps:remove(Username, Data).
129 |
130 | %% @doc List all users
131 | %% @private
132 | %%
133 | %% Returns all users stored inside the map.
134 | %% @end
135 | list_users(Data) ->
136 | maps:values(Data).
137 |
138 | %% @doc Look up a user
139 | %% @private
140 | %%
141 | %% Search for a user by username in the map.
142 | %% @end
143 | lookup_user(Username, Data) ->
144 | try maps:get(Username, Data) of
145 | User -> {ok, User}
146 | catch
147 | error:{badkey, Username} -> {error, user_not_found, Username}
148 | end.
149 |
150 | %% @doc Check password of the user
151 | %% @private
152 | %%
153 | %% Checks password of the user.
154 | %% @end
155 | check_password(User, EnteredPassword) ->
156 | nuk_user:check_password(User, EnteredPassword).
157 |
158 | %% @doc Check username and password validity
159 | %% @private
160 | %%
161 | %% Validate that the given username and password combination is correct.
162 | %% @end
163 | validate_user_with_password(Username, Password, Data) ->
164 | case lookup_user(Username, Data) of
165 | {ok, User} ->
166 | case check_password(User, Password) of
167 | true -> {ok, User};
168 | _ -> {error, wrong_password, "Wrong password"}
169 | end;
170 | {error, Reason, Extra} -> {error, Reason, Extra}
171 | end.
172 |
--------------------------------------------------------------------------------
/src/nuk_user_store_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_store_sup' module
3 | %%
4 | %% This supervisor is started by {@link nuk_sup} top level supervisor. It
5 | %% supervises {@link nuk_user_store_server}.
6 | %% @end
7 | %%%-------------------------------------------------------------------
8 |
9 | -module(nuk_user_store_sup).
10 |
11 | -behaviour(supervisor).
12 |
13 | %% Supervision
14 | -export([start_link/0, init/1]).
15 |
16 | -define(SERVER, ?MODULE).
17 |
18 | %% Helper macro for declaring children of supervisor
19 | -define(CHILD(Id, Module, Args, Type), {Id, {Module, start_link, Args},
20 | permanent, 5000, Type, [Module]}).
21 |
22 | %%====================================================================
23 | %% Supervision
24 | %%====================================================================
25 |
26 | start_link() ->
27 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
28 |
29 | init([]) ->
30 | {ok, { {one_for_one, 0, 1}, children()} }.
31 |
32 | %%====================================================================
33 | %% Internal functions
34 | %%====================================================================
35 |
36 | %% @doc Get children specs
37 | %% @private
38 | %%
39 | %% A convenience function to return all children specs.
40 | %% @end
41 | children() ->
42 | UserStore = ?CHILD(nuk_user_store_server, nuk_user_store_server, [], worker),
43 | [UserStore].
44 |
--------------------------------------------------------------------------------
/src/nuk_user_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_user_sup' module
3 | %%
4 | %% This supervisor is started by {@link nuk_sup} top level supervisor.
5 | %%
6 | %% Whenever a user logs in, nuk spawns a new {@link nuk_user_server}. This
7 | %% module is a `simple_one_for_one' supervisor that supervises those servers.
8 | %% @end
9 | %%%-------------------------------------------------------------------
10 |
11 | -module(nuk_user_sup).
12 |
13 | -behaviour(supervisor).
14 |
15 | %% Supervision
16 | -export([start_link/0, init/1]).
17 |
18 | -define(SERVER, ?MODULE).
19 |
20 | %%====================================================================
21 | %% Supervision
22 | %%====================================================================
23 |
24 | start_link() ->
25 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
26 |
27 | init([]) ->
28 | MaxRestart = 1,
29 | MaxTime = 3600,
30 | ChildSpec = {nuk_user_server,
31 | {nuk_user_server, start_link, []},
32 | temporary,
33 | 5000, % shutdown time
34 | worker,
35 | [nuk_user_server]},
36 | {ok, {{simple_one_for_one, MaxRestart, MaxTime}, [ChildSpec]}}.
37 |
--------------------------------------------------------------------------------
/src/nuk_users.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %% @doc `nuk_users' module
3 | %%
4 | %% This module should be used as an API to interacting with all user related
5 | %% actions.
6 | %% @end
7 | %%%-------------------------------------------------------------------
8 |
9 | -module(nuk_users).
10 |
11 | %% API
12 | -export([delete/1, get/1, put/1, login/2, logout/1, list/0]).
13 |
14 | %% @doc Delete an existing user
15 | %%
16 | %% This deletes an existing user from user storage.
17 | %% @end
18 | -spec delete(Username :: string()) -> ok.
19 | delete(Username) ->
20 | StorageModule = nuk_app:get_storage_module(users),
21 | ok = StorageModule:delete(Username).
22 |
23 | %% @doc Get a user
24 | %%
25 | %% Retrieves a user from user data storage given a username. The return is a
26 | %% {@link nuk_user:user()} data type. Use {@link nuk_user} module to extract
27 | %% needed information.
28 | %% @end
29 | -spec get(Username :: string()) ->
30 | {ok, nuk_user:user()} |
31 | {error, user_not_found, string()}.
32 | get(Username) ->
33 | StorageModule = nuk_app:get_storage_module(users),
34 | StorageModule:get(Username).
35 |
36 | %% @doc Create new or update an existing user
37 | %%
38 | %% Performs a put operation. If a user with the given username exists it will
39 | %% overwrite with the new data. If a new username is given, a new user will be
40 | %% created. Create a {@link nuk_user:user()} data type and use it here.
41 | %% @end
42 | -spec put(User :: nuk_user:user()) -> ok.
43 | put(User) ->
44 | StorageModule = nuk_app:get_storage_module(users),
45 | ok = StorageModule:put(User).
46 |
47 | %% @doc Login a user
48 | %%
49 | %% Given a username and a password attempt to login a user. If login is
50 | %% successful, a new user session identifier is returned; otherwise, an error
51 | %% is returned.
52 | %% @end
53 | -spec login(Username :: string(), Password :: string()) ->
54 | {ok, SessionId :: string()} |
55 | {error, ErrorCode :: atom(), Extra :: string()}.
56 | login(Username, Password) ->
57 | StorageModule = nuk_app:get_storage_module(users),
58 | nuk_user_server:login(Username, Password, StorageModule).
59 |
60 | %% @doc Log out a user session
61 | %% @equiv nuk_user_sessions:logout(SessionId)
62 | %% @end
63 | -spec logout(SessionId :: string()) -> ok.
64 | logout(SessionId) ->
65 | nuk_user_sessions:logout(SessionId).
66 |
67 | %% @doc List all users
68 | %%
69 | %% Get all users from user storage. The return is a list of
70 | %% {@link nuk_user:user()} data types. Use {@link nuk_user} modue to extract
71 | %% needed information.
72 | %% @end
73 | -spec list() -> [nuk_user:user()].
74 | list() ->
75 | StorageModule = nuk_app:get_storage_module(users),
76 | StorageModule:list().
77 |
--------------------------------------------------------------------------------
/test/ct.cover.spec:
--------------------------------------------------------------------------------
1 | {incl_app, nuk, details}.
2 | {excl_mods, nuk, [nuk_game_SUITE, nuk_user_SUITE]}.
3 | {export,"../ct.coverdata"}.
4 |
--------------------------------------------------------------------------------
/test/nuk_user_SUITE.erl:
--------------------------------------------------------------------------------
1 | -module(nuk_user_SUITE).
2 |
3 | -include_lib("common_test/include/ct.hrl").
4 |
5 | %% ct functions
6 | -export([all/0]).
7 | -export([init_per_suite/1]).
8 | -export([end_per_suite/1]).
9 | -export([init_per_testcase/2]).
10 | -export([end_per_testcase/2]).
11 |
12 | %% Tests
13 | -export([
14 | nuk_users_get/1,
15 | nuk_users_delete/1,
16 | nuk_user_store_server_store_validate/1,
17 | nuk_users_login_bad/1,
18 | nuk_users_login_good/1,
19 | nuk_users_list/1,
20 | nuk_user_session_set_user/1,
21 | nuk_user_sessions_get/1,
22 | nuk_user_sessions_list/1,
23 | nuk_users_logout/1
24 | ]).
25 |
26 | %%====================================================================
27 | %% ct functions
28 | %%====================================================================
29 |
30 | all() ->
31 | [
32 | nuk_users_get,
33 | nuk_users_delete,
34 | nuk_user_store_server_store_validate,
35 | nuk_users_login_bad,
36 | nuk_users_login_good,
37 | nuk_users_list,
38 | nuk_user_session_set_user,
39 | nuk_user_sessions_get,
40 | nuk_user_sessions_list,
41 | nuk_users_logout
42 | ].
43 |
44 | init_per_suite(Config) ->
45 | ok = application:start(nuk),
46 | Config.
47 |
48 | end_per_suite(_) ->
49 | application:stop(nuk),
50 | ok.
51 |
52 | init_per_testcase(_, Config) ->
53 | Config.
54 |
55 | end_per_testcase(_, _Config) ->
56 | clear_all_sessions(),
57 | ok.
58 |
59 | %%====================================================================
60 | %% Helpers
61 | %%====================================================================
62 |
63 | clear_all_sessions() ->
64 | UserProcesses = supervisor:which_children(nuk_user_sup),
65 | lists:foreach(fun({_, Pid, worker, [nuk_user_server]}) ->
66 | ok = nuk_user_server:logout(Pid)
67 | end,
68 | UserProcesses).
69 |
70 | %%====================================================================
71 | %% Tests
72 | %%====================================================================
73 |
74 | nuk_users_get(_) ->
75 | ok = nuk_users:put(nuk_user:new("GoodUser1", "GoodPass1")),
76 | {error, user_not_found, _} = nuk_users:get("BadUser"),
77 | {ok, _User} = nuk_users:get("GoodUser1").
78 |
79 | nuk_users_delete(_) ->
80 | ok = nuk_users:put(nuk_user:new("GoodUser1", "GoodPass1")),
81 | ok = nuk_users:delete("GoodUser1"),
82 | {error, user_not_found, _} = nuk_users:get("GoodUser1").
83 |
84 | nuk_user_store_server_store_validate(_) ->
85 | ok = nuk_users:put(nuk_user:new("GoodUser1", "GoodPass1")),
86 | ok = nuk_users:put(nuk_user:new("GoodUser2", "GoodPass2")),
87 | {ok, _User1} = nuk_user_store_server:validate("GoodUser1", "GoodPass1"),
88 | {ok, _User2} = nuk_user_store_server:validate("GoodUser2", "GoodPass2"),
89 | {error, wrong_password, _} = nuk_user_store_server:validate("GoodUser1", "BadPass"),
90 | {error, user_not_found, _} = nuk_user_store_server:validate("BadUser1", "GoodPass1").
91 |
92 | nuk_users_login_bad(_) ->
93 | {error, user_not_found, "BadUser"} = nuk_users:login("BadUser", "BadPass").
94 |
95 | nuk_users_login_good(_) ->
96 | ok = nuk_users:put(nuk_user:new("GoodUser1", "GoodPass1")),
97 | {ok, _SessionId} = nuk_users:login("GoodUser1", "GoodPass1").
98 |
99 | nuk_users_list(_) ->
100 | User1 = nuk_user:new("GoodUser1", "GoodPass1"),
101 | User2 = nuk_user:new("GoodUser2", "GoodPass2"),
102 | ok = nuk_users:put(User1),
103 | ok = nuk_users:put(User2),
104 | Expected = lists:sort([User1, User2]),
105 | Expected = lists:sort(nuk_users:list()).
106 |
107 | nuk_user_session_set_user(_) ->
108 | User1 = nuk_user:new("GoodUser1", "GoodPass1"),
109 | Session1 = nuk_user_session:new(),
110 | Session1_New = nuk_user_session:set_user(Session1, User1),
111 | User1 = nuk_user_session:get_user(Session1_New).
112 |
113 | nuk_user_sessions_get(_) ->
114 | User1 = nuk_user:new("GoodUser1", "GoodPass1"),
115 | ok = nuk_users:put(User1),
116 | {ok, SessionId1} = nuk_users:login("GoodUser1", "GoodPass1"),
117 | {ok, Session1} = nuk_user_sessions:get(SessionId1),
118 | User1 = nuk_user_session:get_user(Session1).
119 |
120 | nuk_user_sessions_list(_) ->
121 | nuk_users:put(nuk_user:new("GoodUser1", "GoodPass1")),
122 | nuk_users:put(nuk_user:new("GoodUser2", "GoodPass2")),
123 | {ok, SessionId1} = nuk_users:login("GoodUser1", "GoodPass1"),
124 | {ok, SessionId2} = nuk_users:login("GoodUser2", "GoodPass2"),
125 | {ok, Session1} = nuk_user_sessions:get(SessionId1),
126 | {ok, Session2} = nuk_user_sessions:get(SessionId2),
127 | Expected = lists:sort([Session1, Session2]),
128 | Expected = lists:sort(nuk_user_sessions:list()).
129 |
130 | nuk_users_logout(_) ->
131 | User1 = nuk_user:new("GoodUser1", "GoodPass1"),
132 | User2 = nuk_user:new("GoodUser2", "GoodPass2"),
133 | ok = nuk_users:put(User1),
134 | ok = nuk_users:put(User2),
135 | {ok, SessionId1} = nuk_users:login("GoodUser1", "GoodPass1"),
136 | {ok, SessionId2} = nuk_users:login("GoodUser2", "GoodPass2"),
137 | ok = nuk_users:logout(SessionId1),
138 | {ok, Session2} = nuk_user_sessions:get(SessionId2),
139 | [Session2] = nuk_user_sessions:list().
140 |
--------------------------------------------------------------------------------