Compare commits
2 Commits
ef4602a062
...
d129911b8e
Author | SHA1 | Date | |
---|---|---|---|
d129911b8e | |||
b4ca889999 |
BIN
backend/backend
Executable file
BIN
backend/backend
Executable file
Binary file not shown.
@ -14,7 +14,6 @@ version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
storail = ">= 3.0.0 and < 4.0.0"
|
||||
tom = ">= 1.1.1 and < 2.0.0"
|
||||
simplifile = ">= 2.2.1 and < 3.0.0"
|
||||
glint = ">= 1.2.1 and < 2.0.0"
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
packages = [
|
||||
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
|
||||
{ name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" },
|
||||
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
|
||||
{ name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
|
||||
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
|
||||
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
|
||||
@ -24,11 +22,9 @@ packages = [
|
||||
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
|
||||
{ name = "mist", version = "4.0.7", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "F7D15A1E3232E124C7CE31900253633434E59B34ED0E99F273DEE61CDB573CDD" },
|
||||
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
|
||||
{ name = "shared", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_json", "gleam_stdlib"], source = "local", path = "../shared" },
|
||||
{ name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" },
|
||||
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
|
||||
{ name = "storail", version = "3.0.0", build_tools = ["gleam"], requirements = ["directories", "filepath", "gleam_crypto", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "storail", source = "hex", outer_checksum = "D032EE5C89AA4B6306FF81929BF9B5DD2583E9F743F0047788AE5F1F52AFE3B4" },
|
||||
{ name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" },
|
||||
{ name = "tom", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0910EE688A713994515ACAF1F486A4F05752E585B9E3209D8F35A85B234C2719" },
|
||||
]
|
||||
@ -47,5 +43,4 @@ glint = { version = ">= 1.2.1 and < 2.0.0" }
|
||||
mist = { version = ">= 4.0.7 and < 5.0.0" }
|
||||
shared = { path = "../shared" }
|
||||
simplifile = { version = ">= 2.2.1 and < 3.0.0" }
|
||||
storail = { version = ">= 3.0.0 and < 4.0.0" }
|
||||
tom = { version = ">= 1.1.1 and < 2.0.0" }
|
||||
|
@ -1,9 +1,7 @@
|
||||
import app/clients/host_client
|
||||
import app/clients/new_user_client
|
||||
import app/web
|
||||
import gleam/bit_array
|
||||
import gleam/bytes_tree
|
||||
import gleam/dict
|
||||
import gleam/erlang/process
|
||||
import gleam/http/cookie
|
||||
import gleam/http/request.{type Request}
|
||||
@ -18,9 +16,9 @@ import gleam/string
|
||||
import login
|
||||
import mist.{type Connection, type ResponseData}
|
||||
import player
|
||||
import session
|
||||
import player_manager
|
||||
import session_manager
|
||||
import socket_manager
|
||||
import storail
|
||||
|
||||
type JoinAs {
|
||||
JoinAsHost
|
||||
@ -54,41 +52,36 @@ fn server_login(
|
||||
let assert Ok(body) = bit_array.to_string(req.body)
|
||||
|
||||
let assert Ok(login_req) = json.parse(body, login.login_request_decoder())
|
||||
let #(token, user_id) =
|
||||
web.register_user(ctx, login_req.username, login_req.game_code)
|
||||
let assert Ok(#(user_id, token)) =
|
||||
player_manager.add_player(
|
||||
ctx.players,
|
||||
login_req.username,
|
||||
login_req.game_code,
|
||||
)
|
||||
|
||||
let _ = case join_type {
|
||||
JoinAsHost -> {
|
||||
let key = storail.key(ctx.sessions, login_req.game_code)
|
||||
let session = session.Session(login_req.game_code, user_id, dict.new())
|
||||
let assert Ok(Nil) = storail.write(key, session)
|
||||
let assert Ok(_) =
|
||||
session_manager.create_new_game(
|
||||
ctx.sessions,
|
||||
login_req.game_code,
|
||||
user_id,
|
||||
)
|
||||
|
||||
Nil
|
||||
}
|
||||
JoinAsPlayer -> {
|
||||
let key = storail.key(ctx.sessions, login_req.game_code)
|
||||
let assert Ok(session) = storail.read(key)
|
||||
|
||||
let player =
|
||||
let assert Ok(_) =
|
||||
session_manager.add_player_to_game(
|
||||
ctx.sessions,
|
||||
login_req.game_code,
|
||||
player.Player(
|
||||
name: login_req.username,
|
||||
id: user_id,
|
||||
score: 0,
|
||||
buzz_in_time: None,
|
||||
),
|
||||
)
|
||||
let session =
|
||||
session.Session(
|
||||
..session,
|
||||
players: dict.insert(session.players, player.id, player),
|
||||
)
|
||||
|
||||
let assert Ok(Nil) = storail.write(key, session)
|
||||
web.send_message_to_host(
|
||||
ctx,
|
||||
session.host_user_id,
|
||||
host_client.UpdatePlayerStates(session.players),
|
||||
)
|
||||
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,14 @@ import app/clients/host_client
|
||||
import app/clients/new_user_client
|
||||
import app/clients/player_client
|
||||
import config
|
||||
import gleam/bit_array
|
||||
import gleam/crypto
|
||||
import gleam/dict
|
||||
import gleam/erlang/process
|
||||
import gleam/float
|
||||
import gleam/int
|
||||
import gleam/io
|
||||
import gleam/json
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/otp/actor
|
||||
import player
|
||||
import player_session
|
||||
import session
|
||||
import player_manager
|
||||
import session_manager
|
||||
import socket_manager
|
||||
import storail
|
||||
|
||||
pub type Error {
|
||||
InvalidToken
|
||||
@ -26,8 +18,8 @@ pub type Error {
|
||||
pub type Context {
|
||||
Context(
|
||||
config: config.Config,
|
||||
sessions: storail.Collection(session.Session),
|
||||
player_sessions: storail.Collection(player_session.PlayerSession),
|
||||
sessions: session_manager.SessionManager,
|
||||
players: player_manager.PlayerManager,
|
||||
socket_manager: socket_manager.SocketManager(String),
|
||||
)
|
||||
}
|
||||
@ -58,32 +50,6 @@ pub fn handle_client_msg(
|
||||
}
|
||||
}
|
||||
|
||||
fn set_buzz_in(
|
||||
socket_state: SocketState,
|
||||
player_state: player_client.PlayerState,
|
||||
time: Float,
|
||||
) {
|
||||
let key = storail.key(socket_state.ctx.sessions, player_state.game_id)
|
||||
let assert Ok(session) = storail.read(key)
|
||||
|
||||
let players =
|
||||
dict.upsert(session.players, player_state.user_id, fn(player) {
|
||||
let assert Some(player) = player
|
||||
|
||||
player.Player(..player, buzz_in_time: Some(time))
|
||||
})
|
||||
|
||||
let session = session.Session(..session, players:)
|
||||
|
||||
let assert Ok(_) = storail.write(key, session)
|
||||
|
||||
send_message_to_host(
|
||||
socket_state.ctx,
|
||||
session.host_user_id,
|
||||
host_client.UpdatePlayerStates(session.players),
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_player(
|
||||
socket_state: SocketState,
|
||||
player_state: player_client.PlayerState,
|
||||
@ -101,7 +67,13 @@ fn handle_player(
|
||||
<> float.to_string(time),
|
||||
)
|
||||
|
||||
set_buzz_in(socket_state, player_state, time)
|
||||
let assert Ok(_) =
|
||||
session_manager.buzz_in_player(
|
||||
socket_state.ctx.sessions,
|
||||
player_state.game_id,
|
||||
player_state.user_id,
|
||||
time,
|
||||
)
|
||||
|
||||
#(
|
||||
socket_state,
|
||||
@ -113,67 +85,6 @@ fn handle_player(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_user(
|
||||
ctx: Context,
|
||||
username: String,
|
||||
game_code: String,
|
||||
) -> #(String, Int) {
|
||||
let secret = crypto.strong_random_bytes(16)
|
||||
let token =
|
||||
crypto.new_hasher(crypto.Sha512)
|
||||
|> crypto.hash_chunk(secret)
|
||||
|> crypto.hash_chunk(bit_array.from_string(username))
|
||||
|> crypto.hash_chunk(bit_array.from_string(game_code))
|
||||
|> crypto.digest
|
||||
|> bit_array.base64_encode(True)
|
||||
|
||||
let token_hash = player_session.hash_token(token)
|
||||
|
||||
let assert <<user_id:int-size(64)>> = crypto.strong_random_bytes(8)
|
||||
|
||||
let key = storail.key(ctx.player_sessions, int.to_base16(user_id))
|
||||
|
||||
let assert Ok(Nil) =
|
||||
storail.write(
|
||||
key,
|
||||
player_session.PlayerSession(
|
||||
id: user_id,
|
||||
token_hash:,
|
||||
username:,
|
||||
socket_id: None,
|
||||
),
|
||||
)
|
||||
|
||||
#(token, user_id)
|
||||
}
|
||||
|
||||
pub fn attach_socket_to_user(
|
||||
state: SocketState,
|
||||
user_id: Int,
|
||||
token: String,
|
||||
) -> Result(Nil, Error) {
|
||||
let key = storail.key(state.ctx.player_sessions, int.to_base16(user_id))
|
||||
let assert Ok(player_state) = storail.read(key)
|
||||
|
||||
let hashed_token = player_session.hash_token(token)
|
||||
|
||||
case hashed_token == player_state.token_hash {
|
||||
False -> Error(InvalidToken)
|
||||
True -> {
|
||||
let assert Ok(Nil) =
|
||||
storail.write(
|
||||
key,
|
||||
player_session.PlayerSession(
|
||||
..player_state,
|
||||
socket_id: Some(state.id),
|
||||
),
|
||||
)
|
||||
|
||||
Ok(Nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_new_user(
|
||||
socket_state: SocketState,
|
||||
_new_user_state: new_user_client.NewUserState,
|
||||
@ -184,7 +95,13 @@ fn handle_new_user(
|
||||
|
||||
let #(socket_state, new_user_msg) = case msg {
|
||||
new_user_client.JoinAsHost(token, user_id, game_id) -> {
|
||||
let assert Ok(_) = attach_socket_to_user(socket_state, user_id, token)
|
||||
let assert Ok(_) =
|
||||
player_manager.attack_socket_to_player(
|
||||
socket_state.ctx.players,
|
||||
user_id,
|
||||
socket_state.id,
|
||||
token,
|
||||
)
|
||||
|
||||
#(
|
||||
SocketState(
|
||||
@ -195,7 +112,13 @@ fn handle_new_user(
|
||||
)
|
||||
}
|
||||
new_user_client.JoinAsPlayer(token, user_id, game_id) -> {
|
||||
let assert Ok(_) = attach_socket_to_user(socket_state, user_id, token)
|
||||
let assert Ok(_) =
|
||||
player_manager.attack_socket_to_player(
|
||||
socket_state.ctx.players,
|
||||
user_id,
|
||||
socket_state.id,
|
||||
token,
|
||||
)
|
||||
|
||||
#(
|
||||
SocketState(
|
||||
@ -213,106 +136,6 @@ fn handle_new_user(
|
||||
)
|
||||
}
|
||||
|
||||
fn get_player_session_from_id(
|
||||
ctx: Context,
|
||||
id: Int,
|
||||
) -> player_session.PlayerSession {
|
||||
let player_key = storail.key(ctx.player_sessions, int.to_base16(id))
|
||||
let assert Ok(player) = storail.read(player_key)
|
||||
player
|
||||
}
|
||||
|
||||
fn get_player_session_subject(
|
||||
ctx: Context,
|
||||
player: player_session.PlayerSession,
|
||||
) -> Option(process.Subject(String)) {
|
||||
case player.socket_id {
|
||||
None -> None
|
||||
Some(id) -> {
|
||||
let assert Ok(subject) =
|
||||
socket_manager.get_socket_subj_by_id(ctx.socket_manager, id)
|
||||
|
||||
Some(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn broadcast_message_to_players(
|
||||
ctx: Context,
|
||||
session: session.Session,
|
||||
msg: player_client.PlayerServerMessages,
|
||||
) {
|
||||
dict.keys(session.players)
|
||||
|> list.map(get_player_session_from_id(ctx, _))
|
||||
|> list.map(get_player_session_subject(ctx, _))
|
||||
|> list.filter_map(fn(subject) { option.to_result(subject, "Missing") })
|
||||
|> list.each(fn(subject) {
|
||||
let client_msg = player_client.encode_player_server_messages(msg)
|
||||
actor.send(subject, json.to_string(client_msg))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_message_to_player(
|
||||
ctx: Context,
|
||||
id: Int,
|
||||
msg: player_client.PlayerServerMessages,
|
||||
) {
|
||||
let client_msg = player_client.encode_player_server_messages(msg)
|
||||
|
||||
let subject =
|
||||
get_player_session_from_id(ctx, id)
|
||||
|> get_player_session_subject(ctx, _)
|
||||
|
||||
case subject {
|
||||
None -> Nil
|
||||
Some(subject) -> {
|
||||
actor.send(subject, json.to_string(client_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message_to_host(
|
||||
ctx: Context,
|
||||
id: Int,
|
||||
msg: host_client.HostServerMessages,
|
||||
) {
|
||||
let client_msg = host_client.encode_host_server_messages(msg)
|
||||
|
||||
let subject =
|
||||
get_player_session_from_id(ctx, id)
|
||||
|> get_player_session_subject(ctx, _)
|
||||
|
||||
case subject {
|
||||
None -> Nil
|
||||
Some(subject) -> {
|
||||
actor.send(subject, json.to_string(client_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_player_buzzer(
|
||||
socket_state: SocketState,
|
||||
session_key: storail.Key(session.Session),
|
||||
id: Int,
|
||||
) {
|
||||
let assert Ok(session) = storail.read(session_key)
|
||||
|
||||
let assert Ok(player) = dict.get(session.players, id)
|
||||
let player = player.Player(..player, buzz_in_time: None)
|
||||
|
||||
let players = dict.insert(session.players, id, player)
|
||||
|
||||
let session = session.Session(..session, players: players)
|
||||
|
||||
let assert Ok(_) = storail.write(session_key, session)
|
||||
|
||||
send_message_to_host(
|
||||
socket_state.ctx,
|
||||
session.host_user_id,
|
||||
host_client.UpdatePlayerStates(session.players),
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_host(
|
||||
socket_state: SocketState,
|
||||
host_state: host_client.HostState,
|
||||
@ -321,48 +144,32 @@ fn handle_host(
|
||||
let assert Ok(msg) =
|
||||
json.parse(msg, host_client.host_client_messages_decoder())
|
||||
|
||||
let session_key = storail.key(socket_state.ctx.sessions, host_state.game_id)
|
||||
let assert Ok(session) = storail.read(session_key)
|
||||
|
||||
let #(socket_state, resp) = case msg {
|
||||
host_client.ResetAllBuzers -> {
|
||||
dict.to_list(session.players)
|
||||
|> list.each(fn(player_entry) {
|
||||
reset_player_buzzer(socket_state, session_key, player_entry.0)
|
||||
})
|
||||
|
||||
broadcast_message_to_players(
|
||||
socket_state.ctx,
|
||||
session,
|
||||
player_client.ResetBuzzer,
|
||||
let assert Ok(_) =
|
||||
session_manager.reset_all_buzzers(
|
||||
socket_state.ctx.sessions,
|
||||
host_state.game_id,
|
||||
)
|
||||
#(socket_state, host_client.Ack)
|
||||
}
|
||||
host_client.ResetPlayerBuzzer(id) -> {
|
||||
reset_player_buzzer(socket_state, session_key, id)
|
||||
send_message_to_player(socket_state.ctx, id, player_client.ResetBuzzer)
|
||||
let assert Ok(_) =
|
||||
session_manager.reset_player_buzzer(
|
||||
socket_state.ctx.sessions,
|
||||
host_state.game_id,
|
||||
id,
|
||||
)
|
||||
|
||||
#(socket_state, host_client.Ack)
|
||||
}
|
||||
host_client.UpdateScore(id, score) -> {
|
||||
let assert Ok(player) = dict.get(session.players, id)
|
||||
let player = player.Player(..player, score: player.score + score)
|
||||
let session =
|
||||
session.Session(
|
||||
..session,
|
||||
players: dict.insert(session.players, player.id, player),
|
||||
)
|
||||
let assert Ok(_) = storail.write(session_key, session)
|
||||
|
||||
send_message_to_player(
|
||||
socket_state.ctx,
|
||||
let assert Ok(_) =
|
||||
session_manager.updater_player_score(
|
||||
socket_state.ctx.sessions,
|
||||
host_state.game_id,
|
||||
id,
|
||||
player_client.UpdatePointTotal(player.score),
|
||||
)
|
||||
|
||||
send_message_to_host(
|
||||
socket_state.ctx,
|
||||
session.host_user_id,
|
||||
host_client.UpdatePlayerStates(session.players),
|
||||
score,
|
||||
)
|
||||
|
||||
#(socket_state, host_client.Ack)
|
||||
|
@ -6,10 +6,9 @@ import gleam/erlang/process
|
||||
import gleam/result
|
||||
import glint
|
||||
import mist
|
||||
import player_session
|
||||
import session
|
||||
import player_manager
|
||||
import session_manager
|
||||
import socket_manager
|
||||
import storail
|
||||
|
||||
pub type Error {
|
||||
ConfigError(config.ConfigError)
|
||||
@ -21,28 +20,6 @@ pub fn error_string(err: Error) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_session_collection(
|
||||
config: storail.Config,
|
||||
) -> storail.Collection(session.Session) {
|
||||
storail.Collection(
|
||||
name: "Sessions",
|
||||
to_json: session.serialize,
|
||||
decoder: session.decoder(),
|
||||
config:,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn setup_player_session_collection(
|
||||
config: storail.Config,
|
||||
) -> storail.Collection(player_session.PlayerSession) {
|
||||
storail.Collection(
|
||||
name: "PlayerSessions",
|
||||
to_json: player_session.encode_player_session,
|
||||
decoder: player_session.player_session_decoder(),
|
||||
config:,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
||||
use <- glint.command_help("Runs the backend of Play of the Game")
|
||||
use _, args, _ <- glint.command()
|
||||
@ -59,14 +36,16 @@ pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
||||
}),
|
||||
)
|
||||
|
||||
let storail_cfg = storail.Config(cfg.storage_path)
|
||||
let player_manager = player_manager.new()
|
||||
let socket_manager = socket_manager.new()
|
||||
let session_manager = session_manager.new(player_manager, socket_manager)
|
||||
|
||||
let ctx =
|
||||
web.Context(
|
||||
config: cfg,
|
||||
sessions: setup_session_collection(storail_cfg),
|
||||
player_sessions: setup_player_session_collection(storail_cfg),
|
||||
socket_manager: socket_manager.new(),
|
||||
players: player_manager,
|
||||
socket_manager:,
|
||||
sessions: session_manager,
|
||||
)
|
||||
|
||||
let handler = router.handle_request(_, ctx)
|
||||
|
30
backend/src/manager.gleam
Normal file
30
backend/src/manager.gleam
Normal file
@ -0,0 +1,30 @@
|
||||
import gleam/erlang/process
|
||||
import gleam/otp/actor
|
||||
|
||||
pub const call_timeout = 1000
|
||||
|
||||
pub type Manager(resp_type, msg_type) {
|
||||
Manager(subject: process.Subject(Request(resp_type, msg_type)))
|
||||
}
|
||||
|
||||
pub type State(state_type) {
|
||||
State(internal: state_type)
|
||||
}
|
||||
|
||||
pub type Request(resp_type, msg_type) {
|
||||
Request(reply_to: process.Subject(resp_type), msg: msg_type)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
init_state: fn() -> state_type,
|
||||
handle: fn(Request(resp_type, msg_type), State(state_type)) ->
|
||||
actor.Next(Request(resp_type, msg_type), State(state_type)),
|
||||
) -> Manager(resp_type, msg_type) {
|
||||
let assert Ok(subject) = actor.start(State(init_state()), handle)
|
||||
|
||||
Manager(subject)
|
||||
}
|
||||
|
||||
pub fn call(manager: Manager(resp_type, msg_type), msg: msg_type) -> resp_type {
|
||||
actor.call(manager.subject, Request(_, msg), call_timeout)
|
||||
}
|
263
backend/src/player_manager.gleam
Normal file
263
backend/src/player_manager.gleam
Normal file
@ -0,0 +1,263 @@
|
||||
import gleam/bit_array
|
||||
import gleam/crypto
|
||||
import gleam/dict
|
||||
import gleam/erlang/process
|
||||
import gleam/int
|
||||
import gleam/io
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/otp/actor
|
||||
import gleam/result
|
||||
import manager
|
||||
import player_session
|
||||
|
||||
pub type PlayerManager =
|
||||
manager.Manager(Response, Message)
|
||||
|
||||
pub type Message {
|
||||
AddPlayer(username: String, game_code: String)
|
||||
AttachSocketToPlayer(user_id: Int, socket_id: Int, token_hash: String)
|
||||
RemoveSocket(user_id: Int)
|
||||
RemovePlayer(user_id: Int)
|
||||
GetPlayerSocket(user_id: Int)
|
||||
}
|
||||
|
||||
pub type PlayerError {
|
||||
PlayerNotFound(Int)
|
||||
UsernameAlreadyExists(String)
|
||||
TokenHashMistmatch
|
||||
}
|
||||
|
||||
pub fn error_to_string(error: PlayerError) -> String {
|
||||
case error {
|
||||
PlayerNotFound(id) -> "Player of id=" <> int.to_string(id) <> " not found"
|
||||
UsernameAlreadyExists(username) ->
|
||||
"Username '" <> username <> "' already exists!"
|
||||
TokenHashMistmatch -> "Error authenticating user"
|
||||
}
|
||||
}
|
||||
|
||||
pub type Return {
|
||||
Ack
|
||||
AddPlayerResp(user_id: Int, token: String)
|
||||
GetPlayerSocketResp(socket_id: Option(Int))
|
||||
}
|
||||
|
||||
pub type Response =
|
||||
Result(Return, PlayerError)
|
||||
|
||||
pub type Request =
|
||||
manager.Request(Response, Message)
|
||||
|
||||
pub type InternalState {
|
||||
InternalState(
|
||||
next_id: Int,
|
||||
players: dict.Dict(Int, player_session.PlayerSession),
|
||||
)
|
||||
}
|
||||
|
||||
pub type State =
|
||||
manager.State(InternalState)
|
||||
|
||||
fn call(
|
||||
player_manager: PlayerManager,
|
||||
msg: Message,
|
||||
) -> Result(Return, PlayerError) {
|
||||
manager.call(player_manager, msg)
|
||||
}
|
||||
|
||||
pub fn new() -> PlayerManager {
|
||||
manager.new(fn() { InternalState(0, dict.new()) }, handle)
|
||||
}
|
||||
|
||||
pub fn add_player(
|
||||
player_manager: PlayerManager,
|
||||
username: String,
|
||||
gamecode: String,
|
||||
) -> Result(#(Int, String), PlayerError) {
|
||||
use ret <- result.try(call(player_manager, AddPlayer(username, gamecode)))
|
||||
|
||||
let assert AddPlayerResp(user_id, token) = ret
|
||||
Ok(#(user_id, token))
|
||||
}
|
||||
|
||||
pub fn attack_socket_to_player(
|
||||
player_manager: PlayerManager,
|
||||
user_id: Int,
|
||||
socket_id: Int,
|
||||
token_hash: String,
|
||||
) -> Result(Nil, PlayerError) {
|
||||
use _ret <- result.try(call(
|
||||
player_manager,
|
||||
AttachSocketToPlayer(user_id, socket_id, token_hash),
|
||||
))
|
||||
|
||||
Ok(Nil)
|
||||
}
|
||||
|
||||
pub fn remove_socket(
|
||||
player_manager: PlayerManager,
|
||||
user_id: Int,
|
||||
) -> Result(Nil, PlayerError) {
|
||||
use _ret <- result.try(call(player_manager, RemoveSocket(user_id)))
|
||||
|
||||
Ok(Nil)
|
||||
}
|
||||
|
||||
pub fn remove_player(
|
||||
player_manager: PlayerManager,
|
||||
user_id: Int,
|
||||
) -> Result(Nil, PlayerError) {
|
||||
use _ret <- result.try(call(player_manager, RemovePlayer(user_id)))
|
||||
|
||||
Ok(Nil)
|
||||
}
|
||||
|
||||
pub fn get_player_socket(
|
||||
player_manager: PlayerManager,
|
||||
user_id: Int,
|
||||
) -> Result(Option(Int), PlayerError) {
|
||||
use ret <- result.try(call(player_manager, GetPlayerSocket(user_id)))
|
||||
|
||||
let assert GetPlayerSocketResp(socket_id) = ret
|
||||
|
||||
Ok(socket_id)
|
||||
}
|
||||
|
||||
fn handle(request: Request, state: State) -> actor.Next(Request, State) {
|
||||
let ret = case request.msg {
|
||||
AddPlayer(username, gamecode) ->
|
||||
add_player_internal(state, username, gamecode)
|
||||
AttachSocketToPlayer(user_id, socket_id, token_hash) ->
|
||||
attach_socket_to_player_internal(state, user_id, socket_id, token_hash)
|
||||
RemovePlayer(user_id) -> remove_player_internal(state, user_id)
|
||||
RemoveSocket(user_id) -> remove_socket_internal(state, user_id)
|
||||
GetPlayerSocket(user_id) -> get_player_socket_internal(state, user_id)
|
||||
}
|
||||
|
||||
case ret {
|
||||
Error(err) -> {
|
||||
io.println_error("Player Manager error: " <> error_to_string(err))
|
||||
process.send(request.reply_to, Error(err))
|
||||
|
||||
actor.continue(state)
|
||||
}
|
||||
Ok(#(resp, actor_state)) -> {
|
||||
process.send(request.reply_to, Ok(resp))
|
||||
|
||||
actor_state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_player(
|
||||
state: State,
|
||||
player_id: Int,
|
||||
) -> Result(player_session.PlayerSession, PlayerError) {
|
||||
result.map_error(dict.get(state.internal.players, player_id), fn(_) {
|
||||
PlayerNotFound(player_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_player_to_state(
|
||||
state: State,
|
||||
player: player_session.PlayerSession,
|
||||
) -> State {
|
||||
let players = dict.insert(state.internal.players, player.id, player)
|
||||
|
||||
manager.State(internal: InternalState(..state.internal, players:))
|
||||
}
|
||||
|
||||
fn add_player_internal(
|
||||
state: State,
|
||||
username: String,
|
||||
game_code: String,
|
||||
) -> Result(#(Return, actor.Next(Request, State)), PlayerError) {
|
||||
let secret = crypto.strong_random_bytes(16)
|
||||
let token =
|
||||
crypto.new_hasher(crypto.Sha512)
|
||||
|> crypto.hash_chunk(secret)
|
||||
|> crypto.hash_chunk(bit_array.from_string(username))
|
||||
|> crypto.hash_chunk(bit_array.from_string(game_code))
|
||||
|> crypto.digest
|
||||
|> bit_array.base64_encode(True)
|
||||
|
||||
let token_hash = player_session.hash_token(token)
|
||||
|
||||
let user_id = state.internal.next_id
|
||||
|
||||
let sessions =
|
||||
player_session.PlayerSession(user_id, token_hash, username, None)
|
||||
|> dict.insert(state.internal.players, user_id, _)
|
||||
|
||||
let continue =
|
||||
manager.State(internal: InternalState(state.internal.next_id + 1, sessions))
|
||||
|> actor.continue
|
||||
|
||||
io.print("Adding" <> username <> "id=" <> int.to_string(user_id) <> "to game")
|
||||
#(AddPlayerResp(user_id, token), continue)
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn attach_socket_to_player_internal(
|
||||
state: State,
|
||||
user_id: Int,
|
||||
socket_id: Int,
|
||||
token_hash: String,
|
||||
) -> Result(#(Return, actor.Next(Request, State)), PlayerError) {
|
||||
use player <- result.try(get_player(state, user_id))
|
||||
use _ <- result.try(
|
||||
case player.token_hash == player_session.hash_token(token_hash) {
|
||||
False -> Error(TokenHashMistmatch)
|
||||
True -> Ok(Nil)
|
||||
},
|
||||
)
|
||||
|
||||
let state =
|
||||
player_session.PlayerSession(..player, socket_id: Some(socket_id))
|
||||
|> add_player_to_state(state, _)
|
||||
|> actor.continue
|
||||
|
||||
#(Ack, state)
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn remove_socket_internal(
|
||||
state: State,
|
||||
user_id: Int,
|
||||
) -> Result(#(Return, actor.Next(Request, State)), PlayerError) {
|
||||
use player <- result.try(get_player(state, user_id))
|
||||
|
||||
let state =
|
||||
player_session.PlayerSession(..player, socket_id: None)
|
||||
|> add_player_to_state(state, _)
|
||||
|> actor.continue
|
||||
|
||||
#(Ack, state)
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn remove_player_internal(
|
||||
state: State,
|
||||
user_id: Int,
|
||||
) -> Result(#(Return, actor.Next(Request, State)), PlayerError) {
|
||||
use player <- result.try(get_player(state, user_id))
|
||||
|
||||
let players = dict.delete(state.internal.players, player.id)
|
||||
|
||||
let state =
|
||||
manager.State(internal: InternalState(..state.internal, players:))
|
||||
|> actor.continue
|
||||
|
||||
#(Ack, state)
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn get_player_socket_internal(
|
||||
state: State,
|
||||
user_id: Int,
|
||||
) -> Result(#(Return, actor.Next(Request, State)), PlayerError) {
|
||||
use player <- result.try(get_player(state, user_id))
|
||||
|
||||
#(GetPlayerSocketResp(player.socket_id), actor.continue(state))
|
||||
|> Ok
|
||||
}
|
415
backend/src/session_manager.gleam
Normal file
415
backend/src/session_manager.gleam
Normal file
@ -0,0 +1,415 @@
|
||||
import app/clients/host_client
|
||||
import app/clients/player_client
|
||||
import gleam/dict
|
||||
import gleam/erlang/process
|
||||
import gleam/int
|
||||
import gleam/io
|
||||
import gleam/json
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/otp/actor
|
||||
import gleam/result
|
||||
import manager
|
||||
import player
|
||||
import player_manager
|
||||
import session
|
||||
import socket_manager
|
||||
|
||||
pub type SessionManager =
|
||||
manager.Manager(Response, Message)
|
||||
|
||||
pub type Request =
|
||||
manager.Request(Response, Message)
|
||||
|
||||
pub type State =
|
||||
manager.State(InternalState)
|
||||
|
||||
pub type InternalState {
|
||||
InternalState(
|
||||
games: dict.Dict(String, session.Session),
|
||||
player_manager: player_manager.PlayerManager,
|
||||
socket_manager: socket_manager.SocketManager(String),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new(player_manager, socket_manager) -> SessionManager {
|
||||
manager.new(
|
||||
fn() { InternalState(dict.new(), player_manager, socket_manager) },
|
||||
handle,
|
||||
)
|
||||
}
|
||||
|
||||
pub type Message {
|
||||
CreateNewGame(game_id: String, host_id: Int)
|
||||
EndGame(game_id: String)
|
||||
AddPlayerToGame(game_id: String, player: player.Player)
|
||||
RemovePlayerFromGame(game_id: String, player_id: Int)
|
||||
BuzzInPlayer(game_id: String, player_id: Int, time: Float)
|
||||
ResetAllBuzers(game_id: String)
|
||||
ResetPlayerBuzzer(game_id: String, player_id: Int)
|
||||
UpdatePlayerScore(game_id: String, player_id: Int, score_diff: Int)
|
||||
|
||||
Shutdown
|
||||
}
|
||||
|
||||
pub type Response =
|
||||
Result(Nil, SessionError)
|
||||
|
||||
pub type SessionError {
|
||||
GameAlreadyExists(game_id: String)
|
||||
GameNotFound(game_id: String)
|
||||
PlayerNotFound(player_id: Int)
|
||||
}
|
||||
|
||||
pub fn error_to_string(error: SessionError) -> String {
|
||||
case error {
|
||||
GameAlreadyExists(game_id) ->
|
||||
"Game with id=" <> game_id <> " already exists."
|
||||
GameNotFound(game_id) -> "Game with id=" <> game_id <> " not found."
|
||||
PlayerNotFound(player_id) ->
|
||||
"Player with id=" <> int.to_string(player_id) <> " not found."
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
session_manager: SessionManager,
|
||||
msg: Message,
|
||||
) -> Result(Nil, SessionError) {
|
||||
manager.call(session_manager, msg)
|
||||
}
|
||||
|
||||
pub fn create_new_game(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
host_id: Int,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, CreateNewGame(game_id, host_id))
|
||||
}
|
||||
|
||||
pub fn end_game(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, EndGame(game_id))
|
||||
}
|
||||
|
||||
pub fn add_player_to_game(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
player: player.Player,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, AddPlayerToGame(game_id, player))
|
||||
}
|
||||
|
||||
pub fn remove_player_from_game(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, RemovePlayerFromGame(game_id, player_id))
|
||||
}
|
||||
|
||||
pub fn buzz_in_player(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
time: Float,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, BuzzInPlayer(game_id, player_id, time))
|
||||
}
|
||||
|
||||
pub fn reset_all_buzzers(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, ResetAllBuzers(game_id))
|
||||
}
|
||||
|
||||
pub fn reset_player_buzzer(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
user_id: Int,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, ResetPlayerBuzzer(game_id, user_id))
|
||||
}
|
||||
|
||||
pub fn updater_player_score(
|
||||
session_manager: SessionManager,
|
||||
game_id: String,
|
||||
user_id: Int,
|
||||
score_diff: Int,
|
||||
) -> Result(Nil, SessionError) {
|
||||
call(session_manager, UpdatePlayerScore(game_id, user_id, score_diff))
|
||||
}
|
||||
|
||||
fn get_game(state: State, game_id) -> Result(session.Session, SessionError) {
|
||||
result.map_error(dict.get(state.internal.games, game_id), fn(_) {
|
||||
GameNotFound(game_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_player_in_game(
|
||||
game: session.Session,
|
||||
player_id: Int,
|
||||
) -> Result(player.Player, SessionError) {
|
||||
result.map_error(dict.get(game.players, player_id), fn(_) {
|
||||
PlayerNotFound(player_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_game_to_manager(
|
||||
state: State,
|
||||
game_id: String,
|
||||
game: session.Session,
|
||||
) -> State {
|
||||
send_message_to_host(
|
||||
state,
|
||||
game.host_user_id,
|
||||
host_client.UpdatePlayerStates(game.players),
|
||||
)
|
||||
|
||||
let games = dict.insert(state.internal.games, game_id, game)
|
||||
|
||||
manager.State(internal: InternalState(..state.internal, games:))
|
||||
}
|
||||
|
||||
fn add_player_to_game_internal(
|
||||
game: session.Session,
|
||||
player: player.Player,
|
||||
) -> session.Session {
|
||||
session.Session(..game, players: dict.insert(game.players, player.id, player))
|
||||
}
|
||||
|
||||
// Actor Logic
|
||||
|
||||
fn get_player_session_subject(
|
||||
state: State,
|
||||
player_id: Int,
|
||||
) -> Option(process.Subject(String)) {
|
||||
let assert Ok(socket_id) =
|
||||
player_manager.get_player_socket(state.internal.player_manager, player_id)
|
||||
case socket_id {
|
||||
None -> None
|
||||
Some(id) -> {
|
||||
let assert Ok(subject) =
|
||||
socket_manager.get_socket_subj_by_id(state.internal.socket_manager, id)
|
||||
|
||||
Some(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_message_to_socket(state: State, player_id: Int, msg: String) {
|
||||
let subject = get_player_session_subject(state, player_id)
|
||||
|
||||
case subject {
|
||||
None -> Nil
|
||||
Some(subject) -> {
|
||||
actor.send(subject, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_message_to_player(
|
||||
state: State,
|
||||
player_id: Int,
|
||||
msg: player_client.PlayerServerMessages,
|
||||
) {
|
||||
player_client.encode_player_server_messages(msg)
|
||||
|> json.to_string
|
||||
|> send_message_to_socket(state, player_id, _)
|
||||
}
|
||||
|
||||
fn broadcast_msg_to_players(
|
||||
state: State,
|
||||
game: session.Session,
|
||||
msg: player_client.PlayerServerMessages,
|
||||
) {
|
||||
game.players
|
||||
|> dict.keys
|
||||
|> list.each(fn(player_id) { send_message_to_player(state, player_id, msg) })
|
||||
}
|
||||
|
||||
fn send_message_to_host(
|
||||
state: State,
|
||||
player_id: Int,
|
||||
msg: host_client.HostServerMessages,
|
||||
) {
|
||||
host_client.encode_host_server_messages(msg)
|
||||
|> json.to_string
|
||||
|> send_message_to_socket(state, player_id, _)
|
||||
}
|
||||
|
||||
fn add_player_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
player: player.Player,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
|
||||
add_player_to_game_internal(game, player)
|
||||
|> add_game_to_manager(state, game_id, _)
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn buzz_in_player_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
time: Float,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
use player <- result.try(get_player_in_game(game, player_id))
|
||||
|
||||
player.Player(..player, buzz_in_time: Some(time))
|
||||
|> add_player_to_game_internal(game, _)
|
||||
|> add_game_to_manager(state, game_id, _)
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn create_new_game_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
host_id: Int,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
case dict.has_key(state.internal.games, game_id) {
|
||||
True -> Error(GameAlreadyExists(game_id))
|
||||
False -> {
|
||||
session.Session(game_id, host_id, dict.new())
|
||||
|> add_game_to_manager(state, game_id, _)
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn end_game_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use _ <- result.try(get_game(state, game_id))
|
||||
|
||||
let games = dict.delete(state.internal.games, game_id)
|
||||
|
||||
manager.State(internal: InternalState(..state.internal, games:))
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn remove_player_from_game_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
use _ <- result.try(get_player_in_game(game, player_id))
|
||||
|
||||
let players = dict.delete(game.players, player_id)
|
||||
|
||||
session.Session(..game, players: players)
|
||||
|> add_game_to_manager(state, game_id, _)
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn reset_all_buzzers_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
|
||||
let players =
|
||||
dict.map_values(game.players, fn(_, player) {
|
||||
player.Player(..player, buzz_in_time: None)
|
||||
})
|
||||
|
||||
broadcast_msg_to_players(state, game, player_client.ResetBuzzer)
|
||||
|
||||
session.Session(..game, players: players)
|
||||
|> add_game_to_manager(state, game_id, _)
|
||||
|> actor.continue
|
||||
|> Ok
|
||||
}
|
||||
|
||||
fn reset_player_buzzer_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
use player <- result.try(get_player_in_game(game, player_id))
|
||||
|
||||
send_message_to_player(state, player.id, player_client.ResetBuzzer)
|
||||
|
||||
player.Player(..player, buzz_in_time: None)
|
||||
|> add_player_internal(state, game_id, _)
|
||||
}
|
||||
|
||||
fn update_player_score_internal(
|
||||
state: State,
|
||||
game_id: String,
|
||||
player_id: Int,
|
||||
score_diff: Int,
|
||||
) -> Result(actor.Next(Request, State), SessionError) {
|
||||
use game <- result.try(get_game(state, game_id))
|
||||
use player <- result.try(get_player_in_game(game, player_id))
|
||||
|
||||
let player = player.Player(..player, score: player.score + score_diff)
|
||||
|
||||
send_message_to_player(
|
||||
state,
|
||||
player_id,
|
||||
player_client.UpdatePointTotal(player.score),
|
||||
)
|
||||
|
||||
player
|
||||
|> add_player_internal(state, game_id, _)
|
||||
}
|
||||
|
||||
fn handle(request: Request, state: State) -> actor.Next(Request, State) {
|
||||
let ret = case request.msg {
|
||||
AddPlayerToGame(game_id, player) -> {
|
||||
add_player_internal(state, game_id, player)
|
||||
}
|
||||
BuzzInPlayer(game_id, player_id, time) -> {
|
||||
buzz_in_player_internal(state, game_id, player_id, time)
|
||||
}
|
||||
CreateNewGame(game_id, host_id) -> {
|
||||
create_new_game_internal(state, game_id, host_id)
|
||||
}
|
||||
EndGame(game_id) -> {
|
||||
end_game_internal(state, game_id)
|
||||
}
|
||||
RemovePlayerFromGame(game_id, player_id) -> {
|
||||
remove_player_from_game_internal(state, game_id, player_id)
|
||||
}
|
||||
ResetAllBuzers(game_id) -> {
|
||||
reset_all_buzzers_internal(state, game_id)
|
||||
}
|
||||
ResetPlayerBuzzer(game_id, player_id) -> {
|
||||
reset_player_buzzer_internal(state, game_id, player_id)
|
||||
}
|
||||
UpdatePlayerScore(game_id, player_id, score_diff) -> {
|
||||
update_player_score_internal(state, game_id, player_id, score_diff)
|
||||
}
|
||||
Shutdown -> {
|
||||
Ok(actor.Stop(process.Normal))
|
||||
}
|
||||
}
|
||||
|
||||
case ret {
|
||||
Error(err) -> {
|
||||
io.println_error("Session Manager error: " <> error_to_string(err))
|
||||
process.send(request.reply_to, Error(err))
|
||||
|
||||
actor.continue(state)
|
||||
}
|
||||
Ok(actor_state) -> {
|
||||
process.send(request.reply_to, Ok(Nil))
|
||||
|
||||
actor_state
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,5 @@
|
||||
import gleeunit
|
||||
import gleeunit/should
|
||||
|
||||
pub fn main() {
|
||||
gleeunit.main()
|
||||
}
|
||||
|
||||
// gleeunit test functions end in `_test`
|
||||
pub fn hello_world_test() {
|
||||
1
|
||||
|> should.equal(1)
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ fn get_ws_url() -> String {
|
||||
"ws"
|
||||
};
|
||||
|
||||
format!("wss://{}/ws", location.host().unwrap())
|
||||
format!("{}://{}/ws", protocol, location.host().unwrap())
|
||||
}
|
||||
|
||||
mod modname {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user