master
Jordan Orelli 4 years ago
parent 55d638c7f0
commit b1cc63e471

@ -98,6 +98,7 @@ func (sn *session) read() {
From: sn.Name, From: sn.Name,
Seq: req.Seq, Seq: req.Seq,
Wants: &sim.SpawnPlayer{ Wants: &sim.SpawnPlayer{
Name: sn.Name,
Outbox: sn.outbox, Outbox: sn.outbox,
}, },
} }

@ -4,7 +4,7 @@ import "github.com/jordanorelli/astro-domu/internal/wire"
type Effect interface { type Effect interface {
wire.Value wire.Value
exec(*room, string, int) result exec(*room, *player, int) result
} }
type result struct { type result struct {

@ -1,11 +1,17 @@
package sim package sim
import "time" import (
"time"
)
// entity is any entity that can be simulated. type Entity struct {
type entity interface { ID int `json:"id"`
id() int Position [2]int `json:"pos"`
Glyph rune `json:"glyph"`
behavior
}
type behavior interface {
// update is the standard tick function // update is the standard tick function
update(time.Duration) update(time.Duration)
} }

@ -1,65 +1,85 @@
package sim package sim
import ( import (
"time"
"github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/astro-domu/internal/wire"
"github.com/jordanorelli/blammo" "github.com/jordanorelli/blammo"
) )
// player represents a player character in the simulation // player is a player session in the simulation, eek
type player struct { type player struct {
*blammo.Log *blammo.Log
*room *room
name string name string
outbox chan wire.Response outbox chan wire.Response
sessionID int pending []Request
entityID int entity *Entity
pending []Request
} }
func (p *player) update(dt time.Duration) {}
func (p *player) id() int { return p.entityID }
type Move [2]int type Move [2]int
func (Move) NetTag() string { return "move" } 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 // SpawnPlayer is a request to spawn a player
type SpawnPlayer struct { type SpawnPlayer struct {
Outbox chan wire.Response Outbox chan wire.Response
Name string
queued bool 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 { 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 { if _, ok := r.players[s.Name]; ok {
s.Outbox <- wire.ErrorResponse(seq, "a player is already logged in as %q", from) s.Outbox <- wire.ErrorResponse(seq, "a player is already logged in as %q", s.Name)
return result{} return result{}
} }
p := &player{ p := &player{
Log: r.Log.Child("players").Child(from), Log: r.Log.Child("players").Child(s.Name),
room: r, room: r,
name: from, name: s.Name,
outbox: s.Outbox, outbox: s.Outbox,
pending: make([]Request, 0, 32), 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}) p.pending = append(p.pending, Request{Seq: seq, From: s.Name, Wants: s})
r.players[from] = p r.players[s.Name] = p
s.queued = true s.queued = true
return result{} return result{}
} }
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 // PlayerSpawned is an announcement that a player has spawned
type PlayerSpawned struct { type PlayerSpawned struct {
Name string Name string `json:"name"`
Position [2]int `json:"position"`
} }
func (PlayerSpawned) NetTag() string { return "player/spawned" }
type Welcome struct { 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 (Welcome) NetTag() string { return "player/welcome" }
func init() { func init() {
wire.Register(func() wire.Value { return new(Move) }) wire.Register(func() wire.Value { return new(Move) })
wire.Register(func() wire.Value { return new(Welcome) }) wire.Register(func() wire.Value { return new(Welcome) })
// wire.Register(func() wire.Value { return new(pawn) })
} }

@ -18,21 +18,29 @@ type room struct {
} }
func (r *room) update(dt time.Duration) { func (r *room) update(dt time.Duration) {
// announcements := make([]result, 0, 8)
for _, p := range r.players { for _, p := range r.players {
for _, req := range p.pending { 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.outbox <- wire.Response{Re: req.Seq, Body: res.reply}
} }
p.pending = p.pending[0:0] p.pending = p.pending[0:0]
} }
for _, t := range r.tiles { for _, t := range r.tiles {
for _, e := range t.contents { if t.here != nil {
e.update(dt) 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) { func (r *room) addPlayer(p *player) {

@ -1,30 +1,6 @@
package sim package sim
// tile is an individual cell within the world simulation. Everything happens
// on a tile.
type tile struct { 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 floor floor
here *Entity
// 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
} }

@ -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) spawn.Outbox <- wire.ErrorResponse(req.Seq, "a player is already logged in as %q", req.From)
break 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 break
} }
@ -68,7 +70,6 @@ func (w *World) Run(hz int) {
if !ok { if !ok {
w.Error("received non login request of type %T from unknown player %q", req.Wants, req.From) w.Error("received non login request of type %T from unknown player %q", req.Wants, req.From)
} }
break
p.pending = append(p.pending, req) p.pending = append(p.pending, req)

@ -10,13 +10,13 @@ type Mode interface {
draw(*UI) draw(*UI)
} }
type boxWalker struct { type roomDisplay struct {
width int width int
height int height int
position point 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) { switch v := e.(type) {
case *tcell.EventKey: case *tcell.EventKey:
key := v.Key() key := v.Key()
@ -24,16 +24,16 @@ func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool {
switch v.Rune() { switch v.Rune() {
case 'w': case 'w':
ui.client.Send(sim.Move{0, -1}) ui.client.Send(sim.Move{0, -1})
m.move(0, -1) // m.move(0, -1)
case 'a': case 'a':
ui.client.Send(sim.Move{-1, 0}) ui.client.Send(sim.Move{-1, 0})
m.move(-1, 0) // m.move(-1, 0)
case 's': case 's':
ui.client.Send(sim.Move{0, 1}) ui.client.Send(sim.Move{0, 1})
m.move(0, 1) // m.move(0, 1)
case 'd': case 'd':
ui.client.Send(sim.Move{1, 0}) ui.client.Send(sim.Move{1, 0})
m.move(1, 0) // m.move(1, 0)
} }
} }
default: default:
@ -42,12 +42,12 @@ func (m *boxWalker) handleEvent(ui *UI, e tcell.Event) bool {
return true 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.x = clamp(m.position.x+dx, 0, m.width-1)
m.position.y = clamp(m.position.y+dy, 0, m.height-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} offset := point{1, 1}
// fill in background dots first // fill in background dots first
@ -72,5 +72,5 @@ func (m *boxWalker) draw(ui *UI) {
} }
// add all characters // 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)
} }

@ -6,6 +6,7 @@ import (
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/exit" "github.com/jordanorelli/astro-domu/internal/exit"
"github.com/jordanorelli/astro-domu/internal/server" "github.com/jordanorelli/astro-domu/internal/server"
"github.com/jordanorelli/astro-domu/internal/sim"
"github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/astro-domu/internal/wire"
"github.com/jordanorelli/blammo" "github.com/jordanorelli/blammo"
) )
@ -26,9 +27,23 @@ func (ui *UI) Run() {
return 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") ui.Info("running ui")
if ui.handleUserInput() { if ui.handleUserInput() {
ui.Info("user requested close") ui.Info("user requested close")

@ -0,0 +1 @@
package wire
Loading…
Cancel
Save