diff --git a/internal/server/session.go b/internal/server/session.go index 9234529..79603e3 100644 --- a/internal/server/session.go +++ b/internal/server/session.go @@ -98,6 +98,7 @@ func (sn *session) read() { From: sn.Name, Seq: req.Seq, Wants: &sim.SpawnPlayer{ + Name: sn.Name, Outbox: sn.outbox, }, } diff --git a/internal/sim/effect.go b/internal/sim/effect.go index fd4e5f8..7600a44 100644 --- a/internal/sim/effect.go +++ b/internal/sim/effect.go @@ -4,7 +4,7 @@ import "github.com/jordanorelli/astro-domu/internal/wire" type Effect interface { wire.Value - exec(*room, string, int) result + exec(*room, *player, int) result } type result struct { diff --git a/internal/sim/entity.go b/internal/sim/entity.go index 4aaa1dc..d5c0138 100644 --- a/internal/sim/entity.go +++ b/internal/sim/entity.go @@ -1,11 +1,17 @@ package sim -import "time" +import ( + "time" +) -// entity is any entity that can be simulated. -type entity interface { - id() int +type Entity struct { + ID int `json:"id"` + Position [2]int `json:"pos"` + Glyph rune `json:"glyph"` + behavior +} +type behavior interface { // update is the standard tick function update(time.Duration) } diff --git a/internal/sim/player.go b/internal/sim/player.go index dace622..19bc859 100644 --- a/internal/sim/player.go +++ b/internal/sim/player.go @@ -1,65 +1,85 @@ package sim import ( - "time" - "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) -// player represents a player character in the simulation +// player is a player session in the simulation, eek type player struct { *blammo.Log *room - name string - outbox chan wire.Response - sessionID int - entityID int - pending []Request + name string + outbox chan wire.Response + pending []Request + entity *Entity } -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" } -func (m *Move) exec(r *room, from *player, seq int) { - +func (m *Move) exec(r *room, p *player, seq int) result { + pos := p.entity.Position + target := [2]int{pos[0] + m[0], pos[1] + m[1]} + p.Info("running move for player %s from %v to %v", p.name, *m, target) + if target[0] >= r.width || target[0] < 0 { + return result{reply: wire.Errorf("target cell (%d, %d) is out of bounds", target[0], target[1])} + } + if target[1] >= r.height || target[1] < 0 { + return result{reply: wire.Errorf("target cell (%d, %d) is out of bounds", target[0], target[1])} + } + n := target[1]*r.width + target[0] + if r.tiles[n].here != nil { + return result{reply: wire.Errorf("target cell (%d, %d) is occupied", target[0], target[1])} + } + p.entity.Position = target + return result{reply: wire.OK{}} } +// type pawn struct { +// } + // SpawnPlayer is a request to spawn a player type SpawnPlayer struct { Outbox chan wire.Response + Name string queued bool } -func (s *SpawnPlayer) exec(r *room, from string, seq int) result { +func (s *SpawnPlayer) exec(r *room, _ *player, seq int) result { if !s.queued { - r.Info("spawn player requested for: %s", from) + r.Info("spawn player requested for: %s", s.Name) - if _, ok := r.players[from]; ok { - s.Outbox <- wire.ErrorResponse(seq, "a player is already logged in as %q", from) + if _, ok := r.players[s.Name]; ok { + s.Outbox <- wire.ErrorResponse(seq, "a player is already logged in as %q", s.Name) return result{} } p := &player{ - Log: r.Log.Child("players").Child(from), + Log: r.Log.Child("players").Child(s.Name), room: r, - name: from, + name: s.Name, outbox: s.Outbox, pending: make([]Request, 0, 32), + entity: &Entity{ + ID: 999, + Position: [2]int{0, 0}, + Glyph: '@', + }, } - p.pending = append(p.pending, Request{Seq: seq, From: from, Wants: s}) - r.players[from] = p + p.pending = append(p.pending, Request{Seq: seq, From: s.Name, Wants: s}) + r.players[s.Name] = p s.queued = true return result{} } return result{ - reply: Welcome{Room: r.name}, + reply: Welcome{ + Room: r.name, + Size: [2]int{r.width, r.height}, + Contents: r.allEntities(), + }, } } @@ -67,16 +87,39 @@ func (SpawnPlayer) NetTag() string { return "player/spawn" } // PlayerSpawned is an announcement that a player has spawned type PlayerSpawned struct { - Name string + Name string `json:"name"` + Position [2]int `json:"position"` } +func (PlayerSpawned) NetTag() string { return "player/spawned" } + type Welcome struct { - Room string `json:"room"` + Room string `json:"room"` + Size [2]int `json:"size"` + Contents []Entity `json:"contents"` } +/* + +{ + "name": "foyer", + "width": 10, + "height": 10, + "contents": [ + [5, 3, 10], + ], + "entities": [ + [3, "pawn", {"name": "bones"}], + [10, "pawn", {"name": "steve"}] + ] +} + +*/ + func (Welcome) NetTag() string { return "player/welcome" } func init() { wire.Register(func() wire.Value { return new(Move) }) wire.Register(func() wire.Value { return new(Welcome) }) + // wire.Register(func() wire.Value { return new(pawn) }) } diff --git a/internal/sim/room.go b/internal/sim/room.go index 9f89a55..55582a5 100644 --- a/internal/sim/room.go +++ b/internal/sim/room.go @@ -18,21 +18,29 @@ type room struct { } func (r *room) update(dt time.Duration) { - // announcements := make([]result, 0, 8) - for _, p := range r.players { for _, req := range p.pending { - res := req.Wants.exec(r, p.name, req.Seq) + res := req.Wants.exec(r, p, req.Seq) p.outbox <- wire.Response{Re: req.Seq, Body: res.reply} } p.pending = p.pending[0:0] } for _, t := range r.tiles { - for _, e := range t.contents { - e.update(dt) + if t.here != nil { + t.here.update(dt) + } + } +} + +func (r *room) allEntities() []Entity { + all := make([]Entity, 0, 4) + for _, t := range r.tiles { + if t.here != nil { + all = append(all, *t.here) } } + return all } func (r *room) addPlayer(p *player) { diff --git a/internal/sim/tile.go b/internal/sim/tile.go index 0a9bc37..6337cbe 100644 --- a/internal/sim/tile.go +++ b/internal/sim/tile.go @@ -1,30 +1,6 @@ package sim -// tile is an individual cell within the world simulation. Everything happens -// on a tile. type tile struct { - // floor is the surface for the tile. All things sit atop the floor. The - // floor informs clients how to draw the tile in the event that the tile's - // contents are empty. The floor also determins whether or not the tile is - // traversable. floor floor - - // contents is all of the entities on this tile. A given tile may have many - // entities. - contents map[int]entity -} - -func (t *tile) addEntity(e entity) { - if t.contents == nil { - t.contents = make(map[int]entity) - } - t.contents[e.id()] = e -} - -func (t *tile) removeEntity(id int) entity { - if e, here := t.contents[id]; here { - delete(t.contents, id) - return e - } - return nil + here *Entity } diff --git a/internal/sim/world.go b/internal/sim/world.go index 6657456..b7a1de2 100644 --- a/internal/sim/world.go +++ b/internal/sim/world.go @@ -60,7 +60,9 @@ func (w *World) Run(hz int) { spawn.Outbox <- wire.ErrorResponse(req.Seq, "a player is already logged in as %q", req.From) break } - spawn.exec(&w.rooms[0], req.From, req.Seq) + spawn.exec(&w.rooms[0], nil, req.Seq) + p := w.rooms[0].players[req.From] + w.players[req.From] = p break } @@ -68,7 +70,6 @@ func (w *World) Run(hz int) { if !ok { w.Error("received non login request of type %T from unknown player %q", req.Wants, req.From) } - break p.pending = append(p.pending, req) diff --git a/internal/ui/mode.go b/internal/ui/mode.go index dad9df8..34b4f84 100644 --- a/internal/ui/mode.go +++ b/internal/ui/mode.go @@ -10,13 +10,13 @@ type Mode interface { draw(*UI) } -type boxWalker struct { +type roomDisplay struct { width int height int position point } -func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool { +func (m *roomDisplay) handleEvent(ui *UI, e tcell.Event) bool { switch v := e.(type) { case *tcell.EventKey: key := v.Key() @@ -24,16 +24,16 @@ func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool { switch v.Rune() { case 'w': ui.client.Send(sim.Move{0, -1}) - m.move(0, -1) + // m.move(0, -1) case 'a': ui.client.Send(sim.Move{-1, 0}) - m.move(-1, 0) + // m.move(-1, 0) case 's': ui.client.Send(sim.Move{0, 1}) - m.move(0, 1) + // m.move(0, 1) case 'd': ui.client.Send(sim.Move{1, 0}) - m.move(1, 0) + // m.move(1, 0) } } default: @@ -42,12 +42,12 @@ func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool { return true } -func (m *boxWalker) move(dx, dy int) { +func (m *roomDisplay) move(dx, dy int) { m.position.x = clamp(m.position.x+dx, 0, m.width-1) m.position.y = clamp(m.position.y+dy, 0, m.height-1) } -func (m *boxWalker) draw(ui *UI) { +func (m *roomDisplay) draw(ui *UI) { offset := point{1, 1} // fill in background dots first @@ -72,5 +72,5 @@ func (m *boxWalker) draw(ui *UI) { } // add all characters - ui.screen.SetContent(m.position.x+offset.x, m.position.y+offset.y, '@', nil, tcell.StyleDefault) + // ui.screen.SetContent(m.position.x+offset.x, m.position.y+offset.y, '@', nil, tcell.StyleDefault) } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index d2895b1..d5b3714 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -6,6 +6,7 @@ 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/sim" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) @@ -26,9 +27,23 @@ func (ui *UI) Run() { return } - ui.client.Send(server.Login{Name: ui.PlayerName}) + res, err := ui.client.Send(server.Login{Name: ui.PlayerName}) + if err != nil { + ui.Error("login error: %v", err) + return + } - ui.mode = &boxWalker{width: 10, height: 6} + welcome, ok := res.Body.(*sim.Welcome) + if !ok { + ui.Error("unexpected initial message of type %t", res.Body) + return + } + ui.Info("spawned into room %s", welcome.Room) + + ui.mode = &roomDisplay{ + width: welcome.Size[0], + height: welcome.Size[1], + } ui.Info("running ui") if ui.handleUserInput() { ui.Info("user requested close") diff --git a/internal/wire/entity.go b/internal/wire/entity.go new file mode 100644 index 0000000..4048932 --- /dev/null +++ b/internal/wire/entity.go @@ -0,0 +1 @@ +package wire