package main import ( "context" "database/sql" "encoding/json" "fmt" "io" "time" ) type Games struct { db DB } 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, updated DATETIME, name TEXT, completed DATETIME ); CREATE TABLE IF NOT EXISTS players ( user_uuid TEXT, game_uuid TEXT, updated DATETIME ); CREATE TABLE IF NOT EXISTS events ( game_uuid TEXT, 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 }