├── .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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
nuk_app
nuk_game
nuk_game_coinflip
nuk_game_engine
nuk_game_engine_state
nuk_game_server
nuk_game_session
nuk_game_session_storage
nuk_game_session_store_server
nuk_game_sessions
nuk_game_state
nuk_game_storage
nuk_game_store_server
nuk_game_store_sup
nuk_game_sup
nuk_games
nuk_sup
nuk_user
nuk_user_server
nuk_user_session
nuk_user_session_storage
nuk_user_session_store_server
nuk_user_sessions
nuk_user_storage
nuk_user_store_server
nuk_user_store_sup
nuk_user_sup
nuk_users
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 |
get_storage_module/1Get storage module.
start/2
stop/1
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 |
get_max_players/1Get maximum number of players.
get_min_players/1Get minimum number of players.
get_module/1Get module name.
get_name/1Get game name.
new/4Create a new nuk_game:game() data type.
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 |
finish/2
initialize/2
player_join/3
player_leave/3
start/2
turn/4
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 |
get_all/1Get all components of engine state.
get_player/2Get player specific state.
get_players/1Get a map of all player states.
get_private/1Get private state.
get_public/1Get public state.
new/3Create new a new state() data type.
put_player/3Put a state for a new or existing player.
remove_player/2Remove player from players state.
set_all/4Set all components of the engine state.
set_player/3Set a state for an existing player.
set_players/2Set all players state.
set_private/2Set private state.
set_public/2Set public state.
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 |
code_change/3
create/3Create a new game session.
get_session/2Get a snapshot of game session.
handle_call/3
handle_cast/2
handle_info/2
init/1
join/2Join a user to the game session.
leave/2Remove a user from the game session.
start/2Start a game.
start_link/1
terminate/2
turn/3Process player's turn.
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 |
add_player/2Add a player to the game session.
get_game/1Get game.
get_game_state/1Get game engine arbitrary state.
get_nuk_state/1Get general nuk game state.
get_players/1Get players currently in the game session.
get_players_count/1Get number of players currently in the game session.
get_players_turn/1Get players whose turn it is next.
get_status/1Get game session status.
get_turn_number/1Get turn number.
get_winners_losers/1Get winners and losers lists.
has_player/2Is a player a member of this game session?.
increment_turn_number/1Increments the internal turn number.
is_players_turn/2Is it a given player's turn?.
new/1Create a new session() data type.
remove_player/2Remove a player from the game session.
set_game_state/2Sets game engine state in the session.
set_players/2Set a list of players to the current game session.
set_players_turn/2Set players whose turn it is next.
set_status/2Set game session status.
set_turn_number/2Set turn number.
set_winners_losers/3Set winners and losers.
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/1Delete a session ID mapping.
get_pid/1Get game session process ID.
put/1Create 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 |
delete/1Delete a session.
get_pid/1Get a process ID.
put/1Create a new session.
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 |
get_players/1Get players currently in the game session.
get_players_turn/1Get players whose turn it is next.
get_status/1Get game session status.
get_turn_number/1Get turn number.
get_winners_losers/1Get winners and losers lists.
new/0Create a new state() data type.
set_players/2Set a list of players to the current game session.
set_players_turn/2Set players whose turn it is next.
set_status/2Set status.
set_turn_number/2Set turn number.
set_winners_losers/3Set winners and losers.
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 |
code_change/3
delete/1Delete a game from registry.
get/1Get a game.
handle_call/3
handle_cast/2
handle_info/2
init/1
list/0List all games.
put/1Create or replace a game.
start_link/0
terminate/2
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 |
init/1
start_link/0
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 |
init/1
start_link/0
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 |
create/2Create a new game session with default options.
create/3Create a new game with options.
get/1Get a game by its name.
get_game_session/2Get game session containing nuk and game engine states.
join/2Join a player to a game session.
leave/2Remove a player from a game session.
list/0List all registered games.
register/1Register a game engine.
start/2Start a game.
turn/3Make a player turn.
unregister/1Unregister a game engine.
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 |
init/1
start_link/0
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 |
check_password/2Verify user's password.
get_username/1Get username.
new/2Create a new nuk_user:user() data type.
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 |
code_change/3
get_session/1Get logged in user session.
handle_call/3
handle_cast/2
handle_info/2
init/1
login/3Log in a user.
logout/1Log out a user.
start_link/0
terminate/2
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 |
get_user/1Get user.
new/0Create a new nuk_user_session:session() data type.
set_user/2Set user.
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 |
delete/1Delete a session ID mapping.
get_pid/1Get game session process ID.
list/0List all sessions.
put/1Create a new user 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 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 |
delete/1Delete a session.
get/1Get session.
get_pid/1Get a process ID.
get_user/1Get user.
list/0List all sessions.
logout/1Log out a user session.
put/1Create a new session.
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 |
code_change/3
delete/1Delete a user.
get/1Get a user.
handle_call/3
handle_cast/2
handle_info/2
init/1
list/0List all users.
put/1Create or replace a user.
start_link/0
terminate/2
validate/2Validate user credentials.
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 |
init/1
start_link/0
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 |
init/1
start_link/0
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 |
delete/1Delete an existing user.
get/1Get a user.
list/0List all users.
login/2Login a user.
logout/1Log out a user session.
put/1Create new or update an existing user.
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 | --------------------------------------------------------------------------------