Compare commits

..

5 Commits

Author SHA1 Message Date
bel
0bade0bd4f todo last event is big one 2024-12-14 23:25:08 -07:00
bel
85fdfac888 games inits itself 2024-12-14 21:37:57 -07:00
bel
d3689e36a7 games struct stubbed 2024-12-14 21:36:08 -07:00
bel
91357afb1f locking 2024-12-14 21:32:39 -07:00
bel
73e79212a9 db.Query and db.Exec 2024-12-14 21:19:04 -07:00
6 changed files with 239 additions and 18 deletions

BIN
cmd/server/.games.go.swp Normal file

Binary file not shown.

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"io"
"sync"
"time"
_ "github.com/glebarez/sqlite"
@@ -12,6 +13,7 @@ import (
type DB struct {
scheme string
conn string
rw *sync.RWMutex
}
func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
@@ -21,6 +23,7 @@ func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
db := DB{
scheme: scheme,
conn: conn,
rw: &sync.RWMutex{},
}
sql, err := db.dial(ctx)
@@ -29,26 +32,58 @@ func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
}
defer sql.Close()
if _, err := sql.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS users (
uuid TEXT,
name TEXT
);
CREATE TABLE IF NOT EXISTS games (
uuid TEXT
);
CREATE TABLE IF NOT EXISTS players (
user_uuid TEXT,
game_uuid TEXT
);
CREATE TABLE IF NOT EXISTS events (
game_uuid TEXT
);
`); err != nil {
return DB{}, err
return db, err
}
return db, err
func (db DB) WithLock(cb func() error) error {
db.rw.Lock()
defer db.rw.Unlock()
return cb()
}
func (db DB) Exec(ctx context.Context, q string, args ...any) error {
db.rw.RLock()
defer db.rw.RUnlock()
return db.exec(ctx, q, args...)
}
func (db DB) exec(ctx context.Context, q string, args ...any) error {
c, err := db.dial(ctx)
if err != nil {
return err
}
defer c.Close()
_, err = c.ExecContext(ctx, q, args...)
return err
}
func (db DB) Query(ctx context.Context, cb func(*sql.Rows) error, q string, args ...any) error {
db.rw.RLock()
defer db.rw.RUnlock()
return db.query(ctx, cb, q, args...)
}
func (db DB) query(ctx context.Context, cb func(*sql.Rows) error, q string, args ...any) error {
c, err := db.dial(ctx)
if err != nil {
return err
}
defer c.Close()
rows, err := c.QueryContext(ctx, q, args...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
if err := cb(rows); err != nil {
return err
}
}
return rows.Err()
}
func (db DB) dial(ctx context.Context) (*sql.DB, error) {

175
cmd/server/games.go Normal file
View File

@@ -0,0 +1,175 @@
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(&timestamp, &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
}

View File

@@ -27,10 +27,20 @@ func run(ctx context.Context) error {
return err
}
db, err := NewDB(ctx, config.DB.Scheme, config.DB.Conn)
if err != nil {
return err
}
games, err := NewGames(ctx, db)
if err != nil {
return err
}
S := &S{
ctx: ctx,
limiter: rate.NewLimiter(10, 10),
config: config,
games: games,
}
s := &http.Server{

View File

@@ -13,6 +13,7 @@ type S struct {
ctx context.Context
limiter *rate.Limiter
config Config
games Games
}
func (s *S) ServeHTTP(w http.ResponseWriter, r *http.Request) {

BIN
cmd/testapi/exec-testapi Executable file

Binary file not shown.