saving work, working on managers

This commit is contained in:
Joey Hines 2025-05-30 22:57:44 -06:00
parent ef4602a062
commit b4ca889999
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
4 changed files with 406 additions and 0 deletions

BIN
backend/backend Executable file

Binary file not shown.

41
backend/src/manager.gleam Normal file
View 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)
}

View 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))
}

View 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
}
}
}