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