summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cfg/flags.go8
-rw-r--r--cfg/paths.go8
-rw-r--r--client/client.go1
-rw-r--r--data/characters.go4
-rw-r--r--data/cirno/cirno.go6
-rw-r--r--data/dummy/cards.go17
-rw-r--r--data/dummy/dummy.go6
-rw-r--r--data/marisa/marisa.go6
-rw-r--r--data/reimu/reimu.go6
-rw-r--r--engine/action.go62
-rw-r--r--engine/card.go (renamed from engine/card/card.go)2
-rw-r--r--engine/card/attack.go26
-rw-r--r--engine/card/damage.go4
-rw-r--r--engine/card/predicate/predicate.go18
-rw-r--r--engine/card/reaction.go7
-rw-r--r--engine/character.go11
-rw-r--r--engine/character/character.go15
-rw-r--r--engine/game.go47
-rw-r--r--engine/game/game.go33
-rw-r--r--engine/interaction.go3
-rw-r--r--engine/play/play.go52
-rw-r--r--engine/player.go42
-rw-r--r--engine/player/action.go12
-rw-r--r--engine/player/player.go34
-rw-r--r--engine/stack.go22
-rw-r--r--engine/vigour.go (renamed from engine/player/vigour.go)2
-rw-r--r--go.mod5
-rw-r--r--go.sum10
-rw-r--r--server/api/game.go79
-rw-r--r--server/server.go34
-rw-r--r--server/web/index.gohtml24
31 files changed, 488 insertions, 118 deletions
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/card.go b/engine/card.go
index f2dce83..52d5a6e 100644
--- a/engine/card/card.go
+++ b/engine/card.go
@@ -1,4 +1,4 @@
-package card
+package engine
type Card struct {
Name string
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/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/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/player/vigour.go b/engine/vigour.go
index 1d4022e..62ececd 100644
--- a/engine/player/vigour.go
+++ b/engine/vigour.go
@@ -1,4 +1,4 @@
-package player
+package engine
import (
"sakura/engine/pool"
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 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>Document</title>
+</head>
+<body>
+
+<script>
+ const start = async () => {
+ const game = await fetch("/api/game/create")
+ const gameId = await game.text()
+ const connection = new WebSocket(`/api/game/${gameId}/connect`)
+ connection.addEventListener("message", (ev) => {
+ console.debug(JSON.parse(ev.data))
+ })
+ }
+ start()
+</script>
+</body>
+</html> \ No newline at end of file