diff --git a/internal/app/game_view.go b/internal/app/game_view.go index 709814b..a390096 100644 --- a/internal/app/game_view.go +++ b/internal/app/game_view.go @@ -2,6 +2,7 @@ package app import ( "github.com/gdamore/tcell/v2" + "github.com/jordanorelli/astro-domu/internal/math" "github.com/jordanorelli/astro-domu/internal/server/sim" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" @@ -9,9 +10,12 @@ import ( type gameView struct { *blammo.Log - width int - height int - // entities map[int]wire.Entity + roomName string + width int + height int + me wire.Entity + allRooms map[string]wire.Room + allEntities map[int]wire.Entity } func (v *gameView) handleEvent(ui *UI, e tcell.Event) bool { @@ -38,18 +42,30 @@ func (v *gameView) handleEvent(ui *UI, e tcell.Event) bool { func (v *gameView) notify(wv wire.Value) { v.Error("ignoring notifications at the moment: %v", wv) - // if e, ok := v.(*wire.Entity); ok { - // m.entities[e.ID] = *e - // } + switch z := wv.(type) { + case *wire.UpdateEntity: + if z.Room == v.roomName { + v.Info("we want to read this one: %v", z) + } + } } func (v *gameView) move(ui *UI, dx, dy int) { - _, err := ui.client.Send(sim.Move{dx, dy}) + reply, err := ui.client.Send(sim.Move{dx, dy}) if err != nil { return } - // e := reply.Body.(*wire.Entity) - // v.entities[e.ID] = *e + + e := reply.Body.(*wire.UpdateEntity) + // ughhhhhh + v.me = wire.Entity{ + ID: e.ID, + Position: e.Position, + Glyph: e.Glyph, + } + v.allEntities[e.ID] = v.me + // jfc this sucks + v.allRooms[v.roomName].Entities[e.ID] = v.me } func (v *gameView) draw(ui *UI) { @@ -76,7 +92,8 @@ func (v *gameView) draw(ui *UI) { ui.screen.SetContent(offset.x+v.width, y+offset.y, '│', nil, tcell.StyleDefault) } - // for _, e := range v.entities { - // ui.screen.SetContent(e.Position[0]+offset.x, e.Position[1]+offset.y, e.Glyph, nil, tcell.StyleDefault) - // } + for _, e := range v.allRooms[v.roomName].Entities { + pos := e.Position.Add(math.Vec{1, 1}) + ui.screen.SetContent(pos.X, pos.Y, e.Glyph, nil, tcell.StyleDefault) + } } diff --git a/internal/app/ui.go b/internal/app/ui.go index a355468..ac02718 100644 --- a/internal/app/ui.go +++ b/internal/app/ui.go @@ -36,18 +36,25 @@ func (ui *UI) Run() { ui.Error("unexpected initial message of type %t", res.Body) return } - ui.Info("spawned into room %s", welcome.Room) - - // entities := make(map[int]sim.Entity, len(welcome.Contents)) - // for _, e := range welcome.Contents { - // entities[e.ID] = e - // } + ui.Info("welcome: %v", welcome) + meta := welcome.Players[ui.PlayerName] + room := welcome.Rooms[meta.Room] + allEntities := make(map[int]wire.Entity) + for _, r := range welcome.Rooms { + for id, e := range r.Entities { + allEntities[id] = e + } + } ui.view = &gameView{ - Log: ui.Child("game-view"), - width: welcome.Room.Width, - height: welcome.Room.Height, - // entities: entities, + Log: ui.Child("game-view"), + roomName: room.Name, + width: room.Width(), + height: room.Height(), + me: room.Entities[meta.Avatar], + allRooms: welcome.Rooms, + allEntities: allEntities, } + ui.Info("running ui") if ui.handleUserInput() { ui.Info("user requested close") diff --git a/internal/math/abs.go b/internal/math/abs.go new file mode 100644 index 0000000..dcb3aa9 --- /dev/null +++ b/internal/math/abs.go @@ -0,0 +1,8 @@ +package math + +func Abs(n int) int { + if n >= 0 { + return n + } + return -n +} diff --git a/internal/math/bounds.go b/internal/math/bounds.go new file mode 100644 index 0000000..f8db2af --- /dev/null +++ b/internal/math/bounds.go @@ -0,0 +1,35 @@ +package math + +import "encoding/json" + +type Bounds struct { + Min Vec `json:"min"` + Max Vec `json:"max"` +} + +func CreateBounds(width, height int) Bounds { + return Bounds{ + Min: Vec{0, 0}, + Max: Vec{width - 1, height - 1}, + } +} + +func (b Bounds) Width() int { return Abs(b.Max.X - b.Min.X) } +func (b Bounds) Height() int { return Abs(b.Max.Y - b.Min.Y) } +func (b Bounds) Area() int { return b.Width() * b.Height() } + +func (b Bounds) Contains(v Vec) bool { + return v.X >= b.Min.X && v.X <= b.Max.X && v.Y >= b.Min.Y && v.Y <= b.Max.Y +} + +func (b Bounds) MarshalJSON() ([]byte, error) { return json.Marshal([2]Vec{b.Min, b.Max}) } + +func (b *Bounds) UnmarshalJSON(buf []byte) error { + var raw [2]Vec + if err := json.Unmarshal(buf, &raw); err != nil { + return err + } + b.Min = raw[0] + b.Max = raw[1] + return nil +} diff --git a/internal/server/sim/player.go b/internal/server/sim/player.go index c90f800..b26af61 100644 --- a/internal/server/sim/player.go +++ b/internal/server/sim/player.go @@ -13,7 +13,7 @@ type player struct { name string outbox chan wire.Response pending []Request - entity *entity + avatar *entity } type Move math.Vec @@ -21,24 +21,25 @@ type Move math.Vec func (Move) NetTag() string { return "move" } func (m *Move) exec(r *room, p *player, seq int) result { - pos := p.entity.Position + pos := p.avatar.Position target := pos.Add(math.Vec(*m)) p.Info("running move for player %s from %v to %v", p.name, *m, target) - if target.X >= r.width || target.X < 0 { + if !p.room.bounds.Contains(target) { return result{reply: wire.Errorf("target cell (%d, %d) is out of bounds", target.X, target.Y)} } - if target.Y >= r.height || target.Y < 0 { - return result{reply: wire.Errorf("target cell (%d, %d) is out of bounds", target.X, target.Y)} - } - n := target.X*r.width + target.Y - if r.tiles[n].here != nil { + + currentTile := r.getTile(pos) + nextTile := r.getTile(target) + if nextTile.here != nil { return result{reply: wire.Errorf("target cell (%d, %d) is occupied", target.X, target.Y)} } - r.tiles[p.entity.Position.X*r.width+p.entity.Position.Y].here = nil - p.entity.Position = target - r.tiles[n].here = p.entity - e := wire.Entity{ - Position: p.entity.Position, + + currentTile.here, nextTile.here = nil, p.avatar + p.avatar.Position = target + e := wire.UpdateEntity{ + Room: r.name, + ID: p.avatar.ID, + Position: p.avatar.Position, Glyph: '@', } return result{reply: e, announce: e} @@ -63,30 +64,51 @@ func (s *SpawnPlayer) exec(r *room, _ *player, seq int) result { } lastEntityID++ + avatar := &entity{ + ID: lastEntityID, + Position: math.Vec{0, 0}, + Glyph: '@', + behavior: doNothing{}, + } p := &player{ Log: r.Log.Child("players").Child(s.Name), room: r, name: s.Name, outbox: s.Outbox, pending: make([]Request, 0, 32), - entity: &entity{ - ID: lastEntityID, - Position: math.Vec{0, 0}, - Glyph: '@', - behavior: doNothing{}, - }, + avatar: avatar, } p.pending = append(p.pending, Request{Seq: seq, From: s.Name, Wants: s}) r.players[s.Name] = p - r.tiles[0].here = p.entity + r.tiles[0].here = p.avatar s.queued = true return result{} } - var welcome wire.Welcome - welcome.Room.Width = r.width - welcome.Room.Height = r.height - welcome.Room.Origin = math.Vec{0, 0} + welcome := wire.Welcome{ + Rooms: make(map[string]wire.Room), + Players: make(map[string]wire.Player), + } + ents := make(map[int]wire.Entity) + for id, e := range r.allEntities() { + ents[id] = wire.Entity{ + ID: id, + Position: e.Position, + Glyph: e.Glyph, + } + } + welcome.Rooms[r.name] = wire.Room{ + Name: r.name, + Bounds: r.bounds, + Entities: ents, + } + for _, p := range r.players { + welcome.Players[p.name] = wire.Player{ + Name: p.name, + Avatar: p.avatar.ID, + Room: r.name, + } + } return result{reply: welcome} } @@ -102,5 +124,4 @@ func (PlayerSpawned) NetTag() string { return "player/spawned" } func init() { wire.Register(func() wire.Value { return new(Move) }) - // wire.Register(func() wire.Value { return new(pawn) }) } diff --git a/internal/server/sim/room.go b/internal/server/sim/room.go index bf403e4..1f39882 100644 --- a/internal/server/sim/room.go +++ b/internal/server/sim/room.go @@ -3,6 +3,7 @@ package sim import ( "time" + "github.com/jordanorelli/astro-domu/internal/math" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) @@ -11,8 +12,7 @@ type room struct { *blammo.Log name string origin point - width int - height int + bounds math.Bounds tiles []tile players map[string]*player } @@ -39,11 +39,12 @@ func (r *room) update(dt time.Duration) { } } -func (r *room) allEntities() []entity { - all := make([]entity, 0, 4) +func (r *room) allEntities() map[int]*entity { + all := make(map[int]*entity, 4) for _, t := range r.tiles { if t.here != nil { - all = append(all, *t.here) + e := t.here + all[e.ID] = e } } return all @@ -60,3 +61,11 @@ func (r *room) removePlayer(name string) bool { } return false } + +func (r *room) getTile(pos math.Vec) *tile { + if !r.bounds.Contains(pos) { + return nil + } + n := pos.X*r.bounds.Width() + pos.Y + return &r.tiles[n] +} diff --git a/internal/server/sim/world.go b/internal/server/sim/world.go index 99dfef3..3bddc5c 100644 --- a/internal/server/sim/world.go +++ b/internal/server/sim/world.go @@ -20,13 +20,13 @@ type World struct { } func NewWorld(log *blammo.Log) *World { + bounds := math.CreateBounds(10, 10) foyer := room{ Log: log.Child("foyer"), name: "foyer", origin: point{0, 0}, - width: 10, - height: 10, - tiles: make([]tile, 100), + bounds: bounds, + tiles: make([]tile, bounds.Area()), players: make(map[string]*player), } foyer.tiles[55].here = &entity{ diff --git a/internal/wire/entity.go b/internal/wire/entity.go index bded82b..17cb33b 100644 --- a/internal/wire/entity.go +++ b/internal/wire/entity.go @@ -5,12 +5,23 @@ import ( ) type Entity struct { + ID int `json:"id"` Position math.Vec `json:"position"` Glyph rune `json:"glyph"` } func (Entity) NetTag() string { return "entity" } +type UpdateEntity struct { + Room string `json:"room"` + ID int `json:"id"` + Position math.Vec `json:"position"` + Glyph rune `json:"glyph"` +} + +func (UpdateEntity) NetTag() string { return "entity/updated" } + func init() { Register(func() Value { return new(Entity) }) + Register(func() Value { return new(UpdateEntity) }) } diff --git a/internal/wire/player.go b/internal/wire/player.go new file mode 100644 index 0000000..857789d --- /dev/null +++ b/internal/wire/player.go @@ -0,0 +1,7 @@ +package wire + +type Player struct { + Name string `json:"name"` + Room string `json:"room"` + Avatar int `json:"avatar"` +} diff --git a/internal/wire/room.go b/internal/wire/room.go new file mode 100644 index 0000000..01706ee --- /dev/null +++ b/internal/wire/room.go @@ -0,0 +1,15 @@ +package wire + +import ( + "github.com/jordanorelli/astro-domu/internal/math" +) + +// Room represents a 2-dimensional coordinate space. +type Room struct { + Name string `json:"name"` + Bounds math.Bounds `json:"bounds"` + Entities map[int]Entity `json:"entities"` +} + +func (r Room) Width() int { return r.Bounds.Width() } +func (r Room) Height() int { return r.Bounds.Height() } diff --git a/internal/wire/welcome.go b/internal/wire/welcome.go index c936b49..02ee6e9 100644 --- a/internal/wire/welcome.go +++ b/internal/wire/welcome.go @@ -1,16 +1,8 @@ package wire -import ( - "github.com/jordanorelli/astro-domu/internal/math" -) - type Welcome struct { - Room struct { - Origin math.Vec `json:"origin"` - Width int `json:"width"` - Height int `json:"height"` - } `json:"room"` - Entities map[int]Entity `json:"entities"` + Rooms map[string]Room `json:"rooms"` + Players map[string]Player `json:"players"` } func (Welcome) NetTag() string { return "welcome" }