From 899ef7448222f0d087c194b3379b702f8dd2ff8c Mon Sep 17 00:00:00 2001 From: ewy Date: Mon, 14 Sep 2026 19:48:36 +0200 Subject: add server and websocket comms --- cfg/flags.go | 8 ++++ cfg/paths.go | 8 ++++ client/client.go | 1 + data/characters.go | 4 +- data/cirno/cirno.go | 6 ++- data/dummy/cards.go | 17 ++++---- data/dummy/dummy.go | 6 ++- data/marisa/marisa.go | 6 ++- data/reimu/reimu.go | 6 ++- engine/action.go | 62 ++++++++++++++++++++++++++++++ engine/card.go | 16 ++++++++ engine/card/attack.go | 26 +++++++++++-- engine/card/card.go | 16 -------- engine/card/damage.go | 4 ++ engine/card/predicate/predicate.go | 18 +++++++++ engine/card/reaction.go | 7 ++++ engine/character.go | 11 ++++++ engine/character/character.go | 15 -------- engine/game.go | 47 +++++++++++++++++++++++ engine/game/game.go | 33 ---------------- engine/interaction.go | 3 ++ engine/play/play.go | 52 +++++++++++++++++++++++++ engine/player.go | 42 ++++++++++++++++++++ engine/player/action.go | 12 ------ engine/player/player.go | 34 ---------------- engine/player/vigour.go | 52 ------------------------- engine/stack.go | 22 +++++++++++ engine/vigour.go | 52 +++++++++++++++++++++++++ go.mod | 5 +++ go.sum | 10 +++++ server/api/game.go | 79 ++++++++++++++++++++++++++++++++++++++ server/server.go | 34 +++++++++++++++- server/web/index.gohtml | 24 ++++++++++++ 33 files changed, 554 insertions(+), 184 deletions(-) create mode 100644 cfg/flags.go create mode 100644 cfg/paths.go create mode 100644 client/client.go create mode 100644 engine/action.go create mode 100644 engine/card.go delete mode 100644 engine/card/card.go create mode 100644 engine/card/predicate/predicate.go create mode 100644 engine/card/reaction.go create mode 100644 engine/character.go delete mode 100644 engine/character/character.go create mode 100644 engine/game.go delete mode 100644 engine/game/game.go create mode 100644 engine/interaction.go create mode 100644 engine/play/play.go create mode 100644 engine/player.go delete mode 100644 engine/player/action.go delete mode 100644 engine/player/player.go delete mode 100644 engine/player/vigour.go create mode 100644 engine/stack.go create mode 100644 engine/vigour.go create mode 100644 server/api/game.go create mode 100644 server/web/index.gohtml diff --git a/cfg/flags.go b/cfg/flags.go new file mode 100644 index 0000000..8d708bb --- /dev/null +++ b/cfg/flags.go @@ -0,0 +1,8 @@ +package cfg + +import "github.com/spf13/pflag" + +var ( + Port = pflag.IntP("port", "p", 23882, "port to serve on") + Host = pflag.StringP("host", "h", "0.0.0.0", "host to serve on") +) diff --git a/cfg/paths.go b/cfg/paths.go new file mode 100644 index 0000000..0833e42 --- /dev/null +++ b/cfg/paths.go @@ -0,0 +1,8 @@ +package cfg + +var ( + CreateMethod = "POST" + CreatePath = "/api/game/create" + ConnectMethod = "GET" + ConnectPath = "/api/game/{id}/connect" +) diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/client/client.go @@ -0,0 +1 @@ +package client diff --git a/data/characters.go b/data/characters.go index 6ad9e62..9b6bc8a 100644 --- a/data/characters.go +++ b/data/characters.go @@ -5,10 +5,10 @@ import ( "sakura/data/dummy" "sakura/data/marisa" "sakura/data/reimu" - "sakura/engine/character" + "sakura/engine" ) -var Characters = []*character.Character{ +var Characters = []*engine.Character{ marisa.Marisa, reimu.Reimu, cirno.Cirno, diff --git a/data/cirno/cirno.go b/data/cirno/cirno.go index 3d9bf4b..d0d89c3 100644 --- a/data/cirno/cirno.go +++ b/data/cirno/cirno.go @@ -1,8 +1,10 @@ package cirno -import "sakura/engine/character" +import ( + "sakura/engine" +) -var Cirno = &character.Character{ +var Cirno = &engine.Character{ Name: "Cirno", Cards: nil, Ultimates: nil, diff --git a/data/dummy/cards.go b/data/dummy/cards.go index 04342b2..7fde0c8 100644 --- a/data/dummy/cards.go +++ b/data/dummy/cards.go @@ -1,18 +1,21 @@ package dummy -import "sakura/engine/card" +import ( + "sakura/engine" + "sakura/engine/card" +) -var Cards = []card.IsCard{ +var Cards = []engine.IsCard{ &card.Attack{ - Card: &card.Card{ + Card: &engine.Card{ Name: "Tap", AllOut: true, Text: "Attack a little", Flavor: "", }, - MinDistance: card.NoMinimumRange, - MaxDistance: card.NoMaximumRange, - AuraDamage: 1, - HealthDamage: 1, + MinDistance: card.NoMinimumRange, + MaxDistance: card.NoMaximumRange, + AuraDamage: 1, + LifeDamage: 1, }, } diff --git a/data/dummy/dummy.go b/data/dummy/dummy.go index aaf98e4..3b56303 100644 --- a/data/dummy/dummy.go +++ b/data/dummy/dummy.go @@ -1,8 +1,10 @@ package dummy -import "sakura/engine/character" +import ( + "sakura/engine" +) -var Dummy = &character.Character{ +var Dummy = &engine.Character{ Name: "Dummy", Cards: Cards, Ultimates: nil, diff --git a/data/marisa/marisa.go b/data/marisa/marisa.go index 45f6f00..5e4f290 100644 --- a/data/marisa/marisa.go +++ b/data/marisa/marisa.go @@ -1,8 +1,10 @@ package marisa -import "sakura/engine/character" +import ( + "sakura/engine" +) -var Marisa = &character.Character{ +var Marisa = &engine.Character{ Name: "Marisa", Cards: nil, Ultimates: nil, diff --git a/data/reimu/reimu.go b/data/reimu/reimu.go index ead2e5e..a94749a 100644 --- a/data/reimu/reimu.go +++ b/data/reimu/reimu.go @@ -1,8 +1,10 @@ package reimu -import "sakura/engine/character" +import ( + "sakura/engine" +) -var Reimu = &character.Character{ +var Reimu = &engine.Character{ Name: "Reimu", Cards: nil, Ultimates: nil, diff --git a/engine/action.go b/engine/action.go new file mode 100644 index 0000000..2a73982 --- /dev/null +++ b/engine/action.go @@ -0,0 +1,62 @@ +package engine + +type BaseAction struct { + User *Player + BaseSource IsCard +} + +func (b *BaseAction) Actor() *Player { + return b.User +} + +func (b *BaseAction) Source() IsCard { + return b.BaseSource +} + +func (b *BaseAction) Then(g *Game) { +} + +type Action interface { + Actor() *Player + Source() IsCard + Then(g *Game) +} + +func Interactions(a ...Action) map[InteractionID]Action { + m := make(map[InteractionID]Action, len(a)) + for i, ac := range a { + m[InteractionID(i)] = ac + } + return m +} + +type DamageDecision bool + +var ( + Aura DamageDecision = true + Life DamageDecision = false +) + +type DesignateDamage struct { + Attacker *Player + Defender *Player + SourceCard *Card +} + +func (d *DesignateDamage) Actor() *Player { + return d.Defender +} +func (d *DesignateDamage) Instigator() *Player { + return d.Attacker +} + +func (d *DesignateDamage) Then(g *Game) { +} + +func (d *DesignateDamage) Source() IsCard { + return d.SourceCard +} + +type PlayCard struct { + *BaseAction +} diff --git a/engine/card.go b/engine/card.go new file mode 100644 index 0000000..52d5a6e --- /dev/null +++ b/engine/card.go @@ -0,0 +1,16 @@ +package engine + +type Card struct { + Name string + AllOut bool + Text string + Flavor string +} + +func (c *Card) Base() *Card { + return c +} + +type IsCard interface { + Base() *Card +} diff --git a/engine/card/attack.go b/engine/card/attack.go index 19ad196..d05d44a 100644 --- a/engine/card/attack.go +++ b/engine/card/attack.go @@ -1,11 +1,31 @@ package card +import ( + "sakura/engine" + "sakura/engine/pool" +) + type Attack struct { - *Card + *engine.Card MinDistance RangeValue MaxDistance RangeValue - AuraDamage DamageValue - HealthDamage DamageValue + AuraDamage DamageValue + LifeDamage DamageValue + + Unreactable bool +} + +func (a *Attack) Attack() *Attack { + return a +} + +func (a *Attack) Do(g *engine.Game, to *pool.Pool) { +} + +type IsAttack interface { + Attack() *Attack + Prepare(g *engine.Game) + Do(g *engine.Game, to *pool.Pool) } diff --git a/engine/card/card.go b/engine/card/card.go deleted file mode 100644 index f2dce83..0000000 --- a/engine/card/card.go +++ /dev/null @@ -1,16 +0,0 @@ -package card - -type Card struct { - Name string - AllOut bool - Text string - Flavor string -} - -func (c *Card) Base() *Card { - return c -} - -type IsCard interface { - Base() *Card -} diff --git a/engine/card/damage.go b/engine/card/damage.go index fccb073..6d7dafd 100644 --- a/engine/card/damage.go +++ b/engine/card/damage.go @@ -3,3 +3,7 @@ package card type DamageValue int const Unselectable = DamageValue(-1) + +func (d DamageValue) Value() int { + return int(d) +} diff --git a/engine/card/predicate/predicate.go b/engine/card/predicate/predicate.go new file mode 100644 index 0000000..5770c3d --- /dev/null +++ b/engine/card/predicate/predicate.go @@ -0,0 +1,18 @@ +package predicate + +import ( + "sakura/engine" + "sakura/engine/card" +) + +func Reaction(c engine.IsCard) bool { + _, ok := c.(card.IsReaction) + return ok +} + +func ReactionApplicable(c engine.IsCard) bool { + r, isReaction := c.(card.IsReaction) + if isReaction { + return r.Applicable(c) + } +} diff --git a/engine/card/reaction.go b/engine/card/reaction.go new file mode 100644 index 0000000..3eed8c4 --- /dev/null +++ b/engine/card/reaction.go @@ -0,0 +1,7 @@ +package card + +import "sakura/engine" + +type IsReaction interface { + Applicable(c engine.IsCard) bool +} diff --git a/engine/character.go b/engine/character.go new file mode 100644 index 0000000..a526501 --- /dev/null +++ b/engine/character.go @@ -0,0 +1,11 @@ +package engine + +type Character struct { + Name string + Cards []IsCard + Ultimates []IsCard +} + +func (c *Character) String() string { + return c.Name +} diff --git a/engine/character/character.go b/engine/character/character.go deleted file mode 100644 index 66671ae..0000000 --- a/engine/character/character.go +++ /dev/null @@ -1,15 +0,0 @@ -package character - -import ( - "sakura/engine/card" -) - -type Character struct { - Name string - Cards []card.IsCard - Ultimates []card.IsCard -} - -func (c *Character) String() string { - return c.Name -} diff --git a/engine/game.go b/engine/game.go new file mode 100644 index 0000000..0ed93e2 --- /dev/null +++ b/engine/game.go @@ -0,0 +1,47 @@ +package engine + +import ( + "sakura/engine/pool" +) + +type Game struct { + Shadow *pool.Pool + Distance *pool.Pool + Yin *Player + Yang *Player + + Stack []IsCard + + Actions map[InteractionID]Action +} + +func (g *Game) propagate() { + g.Distance.OverflowsTo = g.Shadow +} + +func NewGame() *Game { + g := &Game{ + Shadow: pool.New(0, pool.Infinite), + Distance: pool.New(0, 10), + Actions: nil, + } + g.Yin = NewPlayer(g) + g.Yang = NewPlayer(g) + g.propagate() + return g +} + +var Tie = &Player{} + +func (g *Game) Winner() *Player { + if g.Yin.Life.Value <= 0 && g.Yang.Life.Value <= 0 { + return Tie + } + if g.Yin.Life.Value <= 0 { + return g.Yang + } + if g.Yang.Life.Value <= 0 { + return g.Yin + } + return nil +} diff --git a/engine/game/game.go b/engine/game/game.go deleted file mode 100644 index 7f3fa76..0000000 --- a/engine/game/game.go +++ /dev/null @@ -1,33 +0,0 @@ -package game - -import ( - "sakura/engine/player" - "sakura/engine/pool" -) - -type Game struct { - Shadow *pool.Pool - Distance *pool.Pool - Yin *player.Player - Yang *player.Player - - Must []player.PlayerAction - May []player.PlayerAction -} - -func (g *Game) propagate() { - g.Distance.OverflowsTo = g.Shadow -} - -func New() *Game { - g := &Game{ - Shadow: pool.New(0, pool.Infinite), - Distance: pool.New(0, 10), - Must: nil, - May: nil, - } - g.Yin = player.New(g) - g.Yang = player.New(g) - g.propagate() - return g -} diff --git a/engine/interaction.go b/engine/interaction.go new file mode 100644 index 0000000..0c01401 --- /dev/null +++ b/engine/interaction.go @@ -0,0 +1,3 @@ +package engine + +type InteractionID int diff --git a/engine/play/play.go b/engine/play/play.go new file mode 100644 index 0000000..42d9fa3 --- /dev/null +++ b/engine/play/play.go @@ -0,0 +1,52 @@ +package play + +import ( + "sakura/engine" + "sakura/engine/card" + "sakura/engine/card/predicate" +) + +func Card(g *engine.Game, p *engine.Player, anyCard engine.IsCard) { + switch c := anyCard.(type) { + case card.IsAttack: + Attack(g, p, c) + } + g.Stack = append(g.Stack, anyCard) +} + +func Attack(g *engine.Game, p *engine.Player, a card.IsAttack) { + att := a.Attack() + opp := p.Opponent(g) + if !att.Unreactable && opp.Hand.Contains(predicate.ReactionApplicable) { + reactions := opp.Hand.Filter(predicate.ReactionApplicable) + acs := make([]engine.Action, len(reactions), len(reactions)) + for i, r := range reactions { + acs[i] = &engine.PlayCard{ + BaseAction: &engine.BaseAction{ + User: p, + BaseSource: r, + }, + } + } + g.May = engine.Interactions(acs...) + } + switch { + case att.AuraDamage == card.Unselectable: + opp.Life.Subtract(att.LifeDamage.Value()) + case att.LifeDamage == card.Unselectable: + opp.Aura.Subtract(att.AuraDamage.Value()) + case att.AuraDamage.Value() > opp.Aura.Value: + opp.Life.Subtract(att.LifeDamage.Value()) + default: + g.Actions = engine.Interactions(&engine.DesignateDamage{ + Attacker: p, + Defender: opp, + SourceCard: a.Attack().Card, + }) + } +} + +func Vigor(g *engine.Game, p *engine.Player, a engine.VigorAction) { + a.From(p).Subtract(1) + a.To(p).Add(1) +} diff --git a/engine/player.go b/engine/player.go new file mode 100644 index 0000000..5f1d7ca --- /dev/null +++ b/engine/player.go @@ -0,0 +1,42 @@ +package engine + +import ( + "sakura/engine/pool" +) + +type Player struct { + Game *Game `json:"-"` + Vigour Vigour + Life *pool.Pool + Aura *pool.Pool + Flare *pool.Pool + Hand Stack + MasterDeck Stack + Ultimates []*Card +} + +func (p *Player) Opponent(g *Game) *Player { + if p == g.Yin { + return g.Yang + } else { + return g.Yin + } +} + +func (p *Player) propagate(g *Game) { + p.Game = g + p.Aura.SubtractsTo = g.Shadow + p.Flare.SubtractsTo = g.Shadow + p.Life.SubtractsTo = p.Flare +} + +func NewPlayer(g *Game) *Player { + p := &Player{ + Life: pool.New(pool.Empty, 10), + Aura: pool.New(3, 5), + Flare: pool.New(pool.Empty, pool.Infinite), + Vigour: Vigour(0), + } + p.propagate(g) + return p +} diff --git a/engine/player/action.go b/engine/player/action.go deleted file mode 100644 index 0d8aa58..0000000 --- a/engine/player/action.go +++ /dev/null @@ -1,12 +0,0 @@ -package player - -type Action int - -const ( - _ = iota -) - -type PlayerAction struct { - Player *Player - Action Action -} diff --git a/engine/player/player.go b/engine/player/player.go deleted file mode 100644 index 9dece55..0000000 --- a/engine/player/player.go +++ /dev/null @@ -1,34 +0,0 @@ -package player - -import ( - "sakura/engine/card" - "sakura/engine/game" - "sakura/engine/pool" -) - -type Player struct { - Game *game.Game - Vigour *Vigour - Life *pool.Pool - Aura *pool.Pool - Flare *pool.Pool - MasterDeck []*card.Card - Ultimates []*card.Card -} - -func (p *Player) propagate(g *game.Game) { - p.Game = g - p.Aura.SubtractsTo = g.Shadow - p.Flare.SubtractsTo = g.Shadow - p.Life.SubtractsTo = p.Flare -} - -func New(g *game.Game) *Player { - p := &Player{ - Life: pool.New(pool.Empty, 10), - Aura: pool.New(3, 5), - Flare: pool.New(pool.Empty, pool.Infinite), - } - p.propagate(g) - return p -} diff --git a/engine/player/vigour.go b/engine/player/vigour.go deleted file mode 100644 index 1d4022e..0000000 --- a/engine/player/vigour.go +++ /dev/null @@ -1,52 +0,0 @@ -package player - -import ( - "sakura/engine/pool" -) - -type Vigour int - -type VigorAction struct { - Name string - From func(p *Player) *pool.Pool - To func(p *Player) *pool.Pool -} - -var Actions = []*VigorAction{ - { - Name: "Approach", - From: func(p *Player) *pool.Pool { - return p.Game.Distance - }, - To: func(p *Player) *pool.Pool { - return p.Aura - }, - }, - { - Name: "Retreat", - From: func(p *Player) *pool.Pool { - return p.Aura - }, - To: func(p *Player) *pool.Pool { - return p.Game.Distance - }, - }, - { - Name: "Charge", - From: func(p *Player) *pool.Pool { - return p.Aura - }, - To: func(p *Player) *pool.Pool { - return p.Flare - }, - }, - { - Name: "Recover", - From: func(p *Player) *pool.Pool { - return p.Game.Shadow - }, - To: func(p *Player) *pool.Pool { - return p.Aura - }, - }, -} diff --git a/engine/stack.go b/engine/stack.go new file mode 100644 index 0000000..d7fc7be --- /dev/null +++ b/engine/stack.go @@ -0,0 +1,22 @@ +package engine + +type Stack []*Card + +func (s Stack) Contains(predicate func(c IsCard) bool) bool { + for _, c := range s { + if predicate(c) { + return true + } + } + return false +} + +func (s Stack) Filter(predicate func(c IsCard) bool) Stack { + res := make(Stack, 0, len(s)) + for _, c := range s { + if predicate(c) { + res = append(res, c) + } + } + return res +} diff --git a/engine/vigour.go b/engine/vigour.go new file mode 100644 index 0000000..62ececd --- /dev/null +++ b/engine/vigour.go @@ -0,0 +1,52 @@ +package engine + +import ( + "sakura/engine/pool" +) + +type Vigour int + +type VigorAction struct { + Name string + From func(p *Player) *pool.Pool + To func(p *Player) *pool.Pool +} + +var Actions = []*VigorAction{ + { + Name: "Approach", + From: func(p *Player) *pool.Pool { + return p.Game.Distance + }, + To: func(p *Player) *pool.Pool { + return p.Aura + }, + }, + { + Name: "Retreat", + From: func(p *Player) *pool.Pool { + return p.Aura + }, + To: func(p *Player) *pool.Pool { + return p.Game.Distance + }, + }, + { + Name: "Charge", + From: func(p *Player) *pool.Pool { + return p.Aura + }, + To: func(p *Player) *pool.Pool { + return p.Flare + }, + }, + { + Name: "Recover", + From: func(p *Player) *pool.Pool { + return p.Game.Shadow + }, + To: func(p *Player) *pool.Pool { + return p.Aura + }, + }, +} diff --git a/go.mod b/go.mod index 92ef75b..65f1164 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,11 @@ module sakura go 1.27 require ( + github.com/gorilla/websocket v1.5.3 // indirect + github.com/r3labs/diff/v3 v3.0.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/testify v1.12.1 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect ) diff --git a/go.sum b/go.sum index c233683..ac2ae0b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,14 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/r3labs/diff/v3 v3.0.2 h1:yVuxAY1V6MeM4+HNur92xkS39kB/N+cFi2hMkY06BbA= +github.com/r3labs/diff/v3 v3.0.2/go.mod h1:Cy542hv0BAEmhDYWtGxXRQ4kqRsVIcEjG9gChUlTmkw= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= diff --git a/server/api/game.go b/server/api/game.go new file mode 100644 index 0000000..bccf12a --- /dev/null +++ b/server/api/game.go @@ -0,0 +1,79 @@ +package api + +import ( + "github.com/gorilla/websocket" + "net/http" + "sakura/engine" + "slices" + "uuid" +) + +type Conn struct { + Socket *websocket.Conn +} + +var upgrader = websocket.Upgrader{} + +var games = make(map[uuid.UUID]*engine.Game) +var connections = make(map[uuid.UUID][]*Conn) + +func GameCreate(w http.ResponseWriter, r *http.Request) { + id := uuid.NewV7() + games[id] = engine.NewGame() + w.Write([]byte(id.String())) +} + +func GameConnect(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + uid, err := uuid.Parse(id) + if err != nil { + http.Error(w, "unable to parse game uuid "+err.Error(), http.StatusBadRequest) + return + } + game := games[uid] + if game == nil { + http.Error(w, "game not found", http.StatusNotFound) + return + } + wsConn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if wsConn == nil { + http.Error(w, "unable to initialize websocket", http.StatusInternalServerError) + return + } + conn := &Conn{wsConn} + connections[uid] = append(connections[uid], conn) + wsConn.SetCloseHandler(func(code int, text string) error { + connections[uid] = slices.DeleteFunc(connections[uid], func(potential *Conn) bool { + return potential == conn + }) + return nil + }) + err = wsConn.WriteJSON(game) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + go PumpRead(uid, game, wsConn) +} + +func Update(id uuid.UUID, g *engine.Game) { + for _, c := range connections[id] { + _ = c.Socket.WriteJSON(g) + } +} + +func PumpRead(id uuid.UUID, g *engine.Game, conn *websocket.Conn) { + var interaction engine.InteractionID + for { + err := conn.ReadJSON(&interaction) + if err != nil { + break + } + g.Actions[interaction].Then(g) + Update(id, g) + } +} diff --git a/server/server.go b/server/server.go index 8230f87..798bab6 100644 --- a/server/server.go +++ b/server/server.go @@ -1,11 +1,41 @@ package server import ( + "embed" "fmt" - "sakura/data" + "github.com/spf13/pflag" + "html/template" + "net" + "net/http" + "sakura/cfg" + "sakura/server/api" + "strconv" ) +//go:embed web +var web embed.FS + +var tmpl *template.Template + func Start() { + pflag.Parse() fmt.Println("Server started") - fmt.Printf("Characters: %v\n", data.Characters) + addr := net.JoinHostPort(*cfg.Host, strconv.Itoa(*cfg.Port)) + fmt.Printf("Listening on %s\n", addr) + var err error + tmpl, err = template.ParseFS(web, "web/**.gohtml") + http.HandleFunc("/{$}", serveHome) + http.HandleFunc(cfg.CreateMethod+" "+cfg.CreatePath, api.GameCreate) + http.HandleFunc(cfg.ConnectMethod+" "+cfg.ConnectPath, api.GameConnect) + err = http.ListenAndServe(addr, nil) + if err != nil { + panic(err) + } +} + +func serveHome(w http.ResponseWriter, r *http.Request) { + err := tmpl.ExecuteTemplate(w, "index.gohtml", nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } diff --git a/server/web/index.gohtml b/server/web/index.gohtml new file mode 100644 index 0000000..bf60372 --- /dev/null +++ b/server/web/index.gohtml @@ -0,0 +1,24 @@ + + + + + + + Document + + + + + + \ No newline at end of file -- cgit v1.3.1