diff --git a/cmd/server/games.go b/cmd/server/games.go index 9d8be41..3e0b66b 100644 --- a/cmd/server/games.go +++ b/cmd/server/games.go @@ -1,6 +1,13 @@ package main -import "context" +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "io" + "time" +) type Games struct { db DB @@ -10,20 +17,159 @@ func NewGames(ctx context.Context, db DB) (Games, error) { err := db.Exec(ctx, ` CREATE TABLE IF NOT EXISTS users ( uuid TEXT, + updated DATETIME, name TEXT ); CREATE TABLE IF NOT EXISTS games ( - uuid TEXT + uuid TEXT, + updated DATETIME, + name TEXT, + completed DATETIME ); CREATE TABLE IF NOT EXISTS players ( user_uuid TEXT, - game_uuid TEXT + game_uuid TEXT, + updated DATETIME ); CREATE TABLE IF NOT EXISTS events ( game_uuid TEXT, - timestamp DATETIME, + updated DATETIME, payload TEXT ); `) return Games{db: db}, err } + +func (games Games) GamesForUser(ctx context.Context, id string) ([]string, error) { + result := []string{} + err := games.db.Query(ctx, func(rows *sql.Rows) error { + var game string + err := rows.Scan(&game) + result = append(result, game) + return err + }, ` + SELECT players.game_uuid + FROM players + WHERE players.user_uuid=? + `, id) + return result, err +} + +func (games Games) GameByName(ctx context.Context, name string) (string, error) { + return "", io.EOF +} + +type ( + GameState struct { + Completed time.Time + Players map[string]PlayerState + } + + PlayerState struct { + Kills []Kill + KillWords KillWords + } + + Kill struct { + Timestamp time.Time + Victim string + Public bool + } + + KillWords struct { + Global string + Assigned time.Time + Assignee string + Assignment Assignment + } + + Assignment struct { + Public []string + Private []string + } + + EventType int + + EventPlayerJoin struct { + ID string + } + EventPlayerLeave struct { + ID string + } + EventGameComplete struct{} + EventAssignmentRotation struct { + Killer string + Killed string + Assignments map[string]Assignment + } +) + +const ( + PlayerJoin EventType = iota + 1 + PlayerLeave + GameComplete + AssignmentRotation +) + +func (games Games) GameState(ctx context.Context, id string) (GameState, error) { + result := GameState{Players: map[string]PlayerState{}} + + err := games.db.Query(ctx, func(rows *sql.Rows) error { + var timestamp time.Time + var payload []byte + if err := rows.Scan(×tamp, &payload); err != nil { + return err + } + + var peek struct { + Type EventType + } + if err := json.Unmarshal(payload, &peek); err != nil { + return err + } + + switch peek.Type { + case PlayerJoin: + var playerJoin EventPlayerJoin + if err := json.Unmarshal(payload, &playerJoin); err != nil { + return err + } + result.Players[playerJoin.ID] = PlayerState{} + return nil + case PlayerLeave: + var playerLeave EventPlayerLeave + if err := json.Unmarshal(payload, &playerLeave); err != nil { + return err + } + delete(result.Players, playerLeave.ID) + return nil + case GameComplete: + var gameComplete EventGameComplete + if err := json.Unmarshal(payload, &gameComplete); err != nil { + return err + } + result.Completed = timestamp + return nil + case AssignmentRotation: + var assignmentRotation EventAssignmentRotation + if err := json.Unmarshal(payload, &assignmentRotation); err != nil { + return err + } + // TODO gather current assignees + // TODO get victim's target + // TODO assign victim's target to killer + // TODO randomize everyone else so not the same as before + return fmt.Errorf("not impl: assignment rotation: %+v", assignmentRotation) + default: + return fmt.Errorf("unknown event type %d: %s", peek.Type, payload) + } + + return nil + }, ` + SELECT timestamp, payload + FROM events + WHERE game_uuid=? + `, id) + + return result, err +}