diff --git a/internal/server/server.go b/internal/server/server.go index ffcec2f..9b05777 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -88,14 +88,15 @@ func (s *Server) createSession(conn *websocket.Conn) *session { conn: conn, outbox: make(chan wire.Response), done: make(chan bool, 1), + world: s.world, } if s.sessions == nil { s.sessions = make(map[int]*session) } s.waitOnSessions.Add(1) s.sessions[sn.id] = sn - sn.entityID = s.world.SpawnPlayer(sn.id) - s.Info("created session %d, %d sessions active", sn.id, len(s.sessions)) + // sn.entityID = s.world.SpawnPlayer(sn.id) + // s.Info("created session %d, %d sessions active", sn.id, len(s.sessions)) return sn } diff --git a/internal/server/session.go b/internal/server/session.go index f7b807f..a3cfb91 100644 --- a/internal/server/session.go +++ b/internal/server/session.go @@ -6,13 +6,16 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/jordanorelli/astro-domu/internal/sim" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) type session struct { *blammo.Log + Name string id int + world *sim.World entityID int start time.Time conn *websocket.Conn @@ -79,13 +82,34 @@ func (sn *session) read() { switch t { case websocket.TextMessage: sn.Log.Child("received-frame").Info(string(b)) + var req wire.Request if err := json.Unmarshal(b, &req); err != nil { sn.Error("unable to parse request: %v", err) sn.outbox <- wire.ErrorResponse(0, "unable to parse request: %v", err) break } - sn.outbox <- wire.Response{req.Seq, wire.OK{}} + sn.Info("received message of type %T", req.Body) + + switch v := req.Body.(type) { + case *Login: + sn.Name = v.Name + sn.world.Inbox <- sim.Request{ + From: sn.Name, + Wants: sim.SpawnPlayer{ + Outbox: sn.outbox, + }, + } + sn.outbox <- wire.Response{req.Seq, wire.OK{}} + case sim.Effect: + sn.world.Inbox <- sim.Request{ + From: sn.Name, + Wants: v, + } + sn.outbox <- wire.Response{req.Seq, wire.OK{}} + default: + sn.outbox <- wire.ErrorResponse(req.Seq, "not sure how to handle that") + } case websocket.BinaryMessage: sn.outbox <- wire.ErrorResponse(0, "unable to parse binary frames") } @@ -116,3 +140,13 @@ func (sn *session) sendResponse(res wire.Response) error { sn.Child("sent-frame").Info(string(payload)) return nil } + +type Login struct { + Name string `json:"name"` +} + +func (Login) NetTag() string { return "login" } + +func init() { + wire.Register(func() wire.Value { return new(Login) }) +} diff --git a/internal/sim/effect.go b/internal/sim/effect.go new file mode 100644 index 0000000..15f9d8a --- /dev/null +++ b/internal/sim/effect.go @@ -0,0 +1,8 @@ +package sim + +import "github.com/jordanorelli/astro-domu/internal/wire" + +type Effect interface { + wire.Value + exec(*World, string) +} diff --git a/internal/sim/player.go b/internal/sim/player.go index f31fb7d..aaa0ffb 100644 --- a/internal/sim/player.go +++ b/internal/sim/player.go @@ -3,16 +3,43 @@ package sim import ( "time" + "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) // player represents a player character in the simulation type player struct { *blammo.Log - entityID int + sessionID int + outbox chan wire.Response + entityID int + pending []Request } -func (p *player) update(dt time.Duration) { -} +func (p *player) update(dt time.Duration) {} func (p *player) id() int { return p.entityID } + +type Move [2]int + +func (Move) NetTag() string { return "move" } + +// SpawnPlayer is a request to spawn a player +type SpawnPlayer struct { + Outbox chan wire.Response +} + +func (s SpawnPlayer) exec(w *World, from string) { + w.Info("spawn player requested for: %s", from) +} + +func (SpawnPlayer) NetTag() string { return "player/spawn" } + +// PlayerSpawned is an announcement that a player has spawned +type PlayerSpawned struct { + Name string +} + +func init() { + wire.Register(func() wire.Value { return new(Move) }) +} diff --git a/internal/sim/request.go b/internal/sim/request.go new file mode 100644 index 0000000..2853dd6 --- /dev/null +++ b/internal/sim/request.go @@ -0,0 +1,6 @@ +package sim + +type Request struct { + From string + Wants Effect +} diff --git a/internal/sim/world.go b/internal/sim/world.go index 0a28d6b..358d118 100644 --- a/internal/sim/world.go +++ b/internal/sim/world.go @@ -1,7 +1,6 @@ package sim import ( - "strconv" "time" "github.com/jordanorelli/blammo" @@ -10,9 +9,12 @@ import ( // World is the entire simulated world. A world consists of many rooms. type World struct { *blammo.Log + Inbox chan Request + rooms []room done chan bool lastEntityID int + players map[string]*player } func NewWorld(log *blammo.Log) *World { @@ -25,9 +27,11 @@ func NewWorld(log *blammo.Log) *World { tiles: make([]tile, 100), } return &World{ - Log: log, - rooms: []room{foyer}, - done: make(chan bool), + Log: log, + rooms: []room{foyer}, + done: make(chan bool), + Inbox: make(chan Request), + players: make(map[string]*player), } } @@ -38,11 +42,25 @@ func (w *World) Run(hz int) { w.Info("starting world with a tick rate of %dhz, frame duration of %v", hz, period) ticker := time.NewTicker(period) lastTick := time.Now() + for { select { + case req := <-w.Inbox: + w.Info("read from inbox: %v", req) + if req.From == "" { + req.Wants.exec(w, "") + break + } + p, ok := w.players[req.From] + if !ok { + break + } + p.pending = append(p.pending, req) + case <-ticker.C: w.tick(time.Since(lastTick)) lastTick = time.Now() + case <-w.done: return } @@ -55,18 +73,18 @@ func (w *World) Stop() error { return nil } -func (w *World) SpawnPlayer(id int) int { - w.lastEntityID++ - r := w.rooms[0] - w.Info("spawning player with id: %d into room %q", id, r.name) - t := &r.tiles[0] - p := player{ - Log: w.Child("players").Child(strconv.Itoa(id)), - entityID: w.lastEntityID, - } - t.addEntity(&p) - return p.entityID -} +// func (w *World) SpawnPlayer(id int) int { +// w.lastEntityID++ +// r := w.rooms[0] +// w.Info("spawning player with id: %d into room %q", id, r.name) +// t := &r.tiles[0] +// p := player{ +// Log: w.Child("players").Child(strconv.Itoa(id)), +// entityID: w.lastEntityID, +// } +// t.addEntity(&p) +// return p.entityID +// } func (w *World) DespawnPlayer(id int) { w.Info("despawning player with id: %d", id) diff --git a/internal/ui/mode.go b/internal/ui/mode.go index 4f8d3b5..dad9df8 100644 --- a/internal/ui/mode.go +++ b/internal/ui/mode.go @@ -2,7 +2,7 @@ package ui import ( "github.com/gdamore/tcell/v2" - "github.com/jordanorelli/astro-domu/internal/wire" + "github.com/jordanorelli/astro-domu/internal/sim" ) type Mode interface { @@ -23,16 +23,16 @@ func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool { if key == tcell.KeyRune { switch v.Rune() { case 'w': - ui.client.Send(wire.Self_Move{Delta: true, X: 0, Y: -1}) + ui.client.Send(sim.Move{0, -1}) m.move(0, -1) case 'a': - ui.client.Send(wire.Self_Move{Delta: true, X: -1, Y: 0}) + ui.client.Send(sim.Move{-1, 0}) m.move(-1, 0) case 's': - ui.client.Send(wire.Self_Move{Delta: true, X: 0, Y: 1}) + ui.client.Send(sim.Move{0, 1}) m.move(0, 1) case 'd': - ui.client.Send(wire.Self_Move{Delta: true, X: 1, Y: 0}) + ui.client.Send(sim.Move{1, 0}) m.move(1, 0) } } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 7bed2d0..8505e9a 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -5,15 +5,17 @@ import ( "github.com/gdamore/tcell/v2" "github.com/jordanorelli/astro-domu/internal/exit" + "github.com/jordanorelli/astro-domu/internal/server" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) type UI struct { *blammo.Log - screen tcell.Screen - mode Mode - client *wire.Client + PlayerName string + screen tcell.Screen + mode Mode + client *wire.Client } func (ui *UI) Run() { @@ -24,6 +26,8 @@ func (ui *UI) Run() { return } + ui.client.Send(server.Login{Name: ui.PlayerName}) + ui.mode = &boxWalker{width: 10, height: 6} ui.Info("running ui") if ui.handleUserInput() { diff --git a/internal/wire/error.go b/internal/wire/error.go index 272e670..cd632cc 100644 --- a/internal/wire/error.go +++ b/internal/wire/error.go @@ -17,6 +17,15 @@ func (e Error) MarshalJSON() ([]byte, error) { return json.Marshal(e.Error()) } +func (e *Error) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + *e = Errorf(s) + return nil +} + func Errorf(t string, args ...interface{}) Error { return Error{val: fmt.Errorf(t, args...)} } diff --git a/internal/wire/self.go b/internal/wire/self.go deleted file mode 100644 index e9583aa..0000000 --- a/internal/wire/self.go +++ /dev/null @@ -1,13 +0,0 @@ -package wire - -type Self_Move struct { - Delta bool `json:"delta"` - X int `json:"x"` - Y int `json:"y"` -} - -func (Self_Move) NetTag() string { return "self/move" } - -func init() { - Register(func() Value { return new(Self_Move) }) -} diff --git a/main.go b/main.go index dad41a0..6788bad 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,7 @@ func main() { switch os.Args[1] { case "client": - runClient() + runClient(os.Args[2]) case "server": s := server.Server{} if err := s.Start(); err != nil { @@ -51,7 +51,7 @@ func main() { } } -func runClient() { +func runClient(name string) { log := newLog("./astro.log").Child("client") start := time.Now() @@ -63,7 +63,8 @@ func runClient() { }() ui := ui.UI{ - Log: log.Child("ui"), + Log: log.Child("ui"), + PlayerName: name, } ui.Run() }