saving work, working on managers
This commit is contained in:
parent
ef4602a062
commit
b4ca889999
BIN
backend/backend
Executable file
BIN
backend/backend
Executable file
Binary file not shown.
41
backend/src/manager.gleam
Normal file
41
backend/src/manager.gleam
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import app/web
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/otp/actor
|
||||||
|
|
||||||
|
pub const call_timeout = 1000
|
||||||
|
|
||||||
|
pub type Manager(resp_type, resp_error, msg_type) {
|
||||||
|
Manager(subject: process.Subject(Request(resp_type, resp_error, msg_type)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type State(state_type) {
|
||||||
|
State(ctx: web.Context, state: state_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Response(resp_type, resp_error) =
|
||||||
|
Result(resp_type, resp_error)
|
||||||
|
|
||||||
|
pub type Request(resp_type, resp_error, msg_type) {
|
||||||
|
Request(
|
||||||
|
reply_to: process.Subject(Response(resp_type, resp_error)),
|
||||||
|
msg: msg_type,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
ctx: web.Context,
|
||||||
|
init_state: fn() -> state_type,
|
||||||
|
handle: fn(Request(resp_type, resp_error, msg_type), State(state_type)) ->
|
||||||
|
actor.Next(Request(resp_type, resp_error, msg_type), State(state_type)),
|
||||||
|
) -> Manager(resp_type, resp_error, msg_type) {
|
||||||
|
let assert Ok(subject) = actor.start(State(ctx, init_state()), handle)
|
||||||
|
|
||||||
|
Manager(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(
|
||||||
|
manager: Manager(resp_type, resp_error, msg_type),
|
||||||
|
msg: msg_type,
|
||||||
|
) -> Result(resp_type, resp_error) {
|
||||||
|
actor.call(manager.subject, Request(_, msg), call_timeout)
|
||||||
|
}
|
38
backend/src/player_manager.gleam
Normal file
38
backend/src/player_manager.gleam
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import app/web
|
||||||
|
import gleam/dict
|
||||||
|
import gleam/erlang/process
|
||||||
|
import player_session
|
||||||
|
|
||||||
|
pub type PlayerManager {
|
||||||
|
PlayerManager(subject: process.Subject(Request))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Message {
|
||||||
|
AddPlayer(username: String, game_code: String)
|
||||||
|
AttachSocketToPlayer(user_id: Int, socket_id: Int)
|
||||||
|
RemoveSocket(user_id: Int)
|
||||||
|
RemovePlayer(user_id: Int)
|
||||||
|
BroadcastMessageToPlayer(user_id: Int, msg: String)
|
||||||
|
BroadcastMessageToPlayers(user_id: List(Int), msg: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PlayerError {
|
||||||
|
PlayerNotFound(Int)
|
||||||
|
UsernameAlreadyExists(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Return {
|
||||||
|
Ack
|
||||||
|
AddPlayerResp(user_id: Int, token: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Response =
|
||||||
|
Result(Return, PlayerError)
|
||||||
|
|
||||||
|
pub type Request {
|
||||||
|
Request(reply_wth: process.Subject(Response), message: Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type State {
|
||||||
|
State(ctx: web.Context, players: dict.Dict(Int, player_session.PlayerSession))
|
||||||
|
}
|
327
backend/src/session_manager.gleam
Normal file
327
backend/src/session_manager.gleam
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import app/web
|
||||||
|
import gleam/dict
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/int
|
||||||
|
import gleam/io
|
||||||
|
import gleam/option.{None, Some}
|
||||||
|
import gleam/otp/actor
|
||||||
|
import gleam/result
|
||||||
|
import player
|
||||||
|
import session
|
||||||
|
|
||||||
|
pub type SessionManager {
|
||||||
|
SessionManager(subject: process.Subject(Request))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(ctx: web.Context) -> SessionManager {
|
||||||
|
let assert Ok(subject) = actor.start(State(ctx, dict.new()), handle)
|
||||||
|
|
||||||
|
SessionManager(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Request {
|
||||||
|
Request(reply_with: process.Subject(Response), msg: Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
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_sring(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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type State {
|
||||||
|
State(ctx: web.Context, games: dict.Dict(String, session.Session))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
session_manager: SessionManager,
|
||||||
|
msg: Message,
|
||||||
|
) -> Result(Nil, SessionError) {
|
||||||
|
actor.call(session_manager.subject, Request(_, msg), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
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_buzzers(
|
||||||
|
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.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 {
|
||||||
|
let games = dict.insert(state.games, game_id, game)
|
||||||
|
|
||||||
|
State(..state, games: 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 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.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.games, game_id)
|
||||||
|
|
||||||
|
State(..state, games: 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
session.Session(..game, players: players)
|
||||||
|
|> add_game_to_manager(state, game_id, _)
|
||||||
|
|> actor.continue
|
||||||
|
|> Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_player_buzzer(
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
player.Player(..player, score: player.score + score_diff)
|
||||||
|
|> 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(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_sring(err))
|
||||||
|
process.send(request.reply_with, Error(err))
|
||||||
|
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
Ok(actor_state) -> {
|
||||||
|
process.send(request.reply_with, Ok(Nil))
|
||||||
|
|
||||||
|
actor_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user