mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: add initial connect no-show policy
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
InitialConnectWindow = 30 * time.Second
|
||||
CasualBotStartAfter = 60 * time.Second
|
||||
CasualNoShowCooldown = 60 * time.Second
|
||||
)
|
||||
|
||||
type ConnectParticipant struct {
|
||||
PlayerID string
|
||||
Team int
|
||||
Connected bool
|
||||
}
|
||||
|
||||
type InitialConnectAction string
|
||||
|
||||
const (
|
||||
InitialConnectWait InitialConnectAction = "WAIT"
|
||||
InitialConnectCancel InitialConnectAction = "CANCEL"
|
||||
InitialConnectStartWithBot InitialConnectAction = "START_WITH_BOTS"
|
||||
)
|
||||
|
||||
type InitialConnectDecision struct {
|
||||
Action InitialConnectAction
|
||||
NoShows []Abandonment
|
||||
Innocent []string
|
||||
}
|
||||
|
||||
// EvaluateInitialConnect only decides pre-live admission. It never computes a
|
||||
// game result or rating update; those remain unavailable until a match is
|
||||
// genuinely live and produces an authoritative result.
|
||||
func EvaluateInitialConnect(playlist Playlist, readyAt, now time.Time, participants []ConnectParticipant, priorAbandons map[string][]time.Time) (InitialConnectDecision, error) {
|
||||
if playlist != Ranked && playlist != Casual || readyAt.IsZero() || len(participants) == 0 {
|
||||
return InitialConnectDecision{}, fmt.Errorf("invalid initial-connect policy input")
|
||||
}
|
||||
if now.Before(readyAt.Add(InitialConnectWindow)) {
|
||||
return InitialConnectDecision{Action: InitialConnectWait}, nil
|
||||
}
|
||||
missing := make([]ConnectParticipant, 0)
|
||||
connected := make([]string, 0)
|
||||
teamConnected := map[int]bool{}
|
||||
for _, participant := range participants {
|
||||
if participant.PlayerID == "" || participant.Team < 0 {
|
||||
return InitialConnectDecision{}, fmt.Errorf("invalid participant")
|
||||
}
|
||||
if participant.Connected {
|
||||
connected = append(connected, participant.PlayerID)
|
||||
teamConnected[participant.Team] = true
|
||||
} else {
|
||||
missing = append(missing, participant)
|
||||
}
|
||||
}
|
||||
if playlist == Ranked {
|
||||
if len(participants) != 6 {
|
||||
return InitialConnectDecision{}, fmt.Errorf("ranked requires six participants")
|
||||
}
|
||||
return InitialConnectDecision{Action: InitialConnectCancel, NoShows: rankedNoShows(missing, now, priorAbandons), Innocent: sortedIDs(connected)}, nil
|
||||
}
|
||||
if now.Before(readyAt.Add(CasualBotStartAfter)) {
|
||||
return InitialConnectDecision{Action: InitialConnectWait}, nil
|
||||
}
|
||||
if teamConnected[0] && teamConnected[1] {
|
||||
noShows := make([]Abandonment, 0, len(missing))
|
||||
for _, participant := range missing {
|
||||
noShows = append(noShows, Abandonment{PlayerID: participant.PlayerID, Cooldown: CasualNoShowCooldown, AbandonedAt: now})
|
||||
}
|
||||
sort.Slice(noShows, func(i, j int) bool { return noShows[i].PlayerID < noShows[j].PlayerID })
|
||||
return InitialConnectDecision{Action: InitialConnectStartWithBot, NoShows: noShows, Innocent: sortedIDs(connected)}, nil
|
||||
}
|
||||
return InitialConnectDecision{Action: InitialConnectCancel, NoShows: casualNoShows(missing, now), Innocent: sortedIDs(connected)}, nil
|
||||
}
|
||||
|
||||
func rankedNoShows(missing []ConnectParticipant, now time.Time, history map[string][]time.Time) []Abandonment {
|
||||
result := make([]Abandonment, 0, len(missing))
|
||||
for _, participant := range missing {
|
||||
result = append(result, Abandonment{PlayerID: participant.PlayerID, Cooldown: abandonCooldown(history[participant.PlayerID], now), AbandonedAt: now})
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].PlayerID < result[j].PlayerID })
|
||||
return result
|
||||
}
|
||||
|
||||
func casualNoShows(missing []ConnectParticipant, now time.Time) []Abandonment {
|
||||
result := make([]Abandonment, 0, len(missing))
|
||||
for _, participant := range missing {
|
||||
result = append(result, Abandonment{PlayerID: participant.PlayerID, Cooldown: CasualNoShowCooldown, AbandonedAt: now})
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].PlayerID < result[j].PlayerID })
|
||||
return result
|
||||
}
|
||||
|
||||
func sortedIDs(participants []string) []string {
|
||||
result := append([]string(nil), participants...)
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user