diff --git a/client.go b/client.go index 426ddf4..6d04b3a 100644 --- a/client.go +++ b/client.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "net/url" "strings" @@ -13,11 +14,16 @@ import ( type client struct { *blammo.Log - host string - port int + host string + port int + lastSeq int + conn *websocket.Conn + outbox chan request } func (c *client) run(ctx context.Context) { + c.outbox = make(chan request) + dialer := websocket.Dialer{ HandshakeTimeout: 3 * time.Second, ReadBufferSize: 32 * 1024, @@ -36,6 +42,7 @@ func (c *client) run(ctx context.Context) { c.Error("dial error: %v", err) return } + c.conn = conn c.Debug("dial response status: %d", res.StatusCode) for k, vals := range res.Header { @@ -47,6 +54,29 @@ func (c *client) run(ctx context.Context) { for { select { + case req := <-c.outbox: + payload, err := json.Marshal(req) + if err != nil { + c.Error("unable to marshal a request: %v", err) + break + } + + w, err := conn.NextWriter(websocket.TextMessage) + if err != nil { + c.Error("unable to get a websocket frame writer: %v", err) + break + } + + n, err := w.Write(payload) + if err != nil { + c.Error("failed to write payload of length %d: %v", len(payload), err) + break + } + c.Info("wrote %d bytes for payload of length %d", n, len(payload)) + + if err := w.Close(); err != nil { + c.Error("failed to close websocket write frame: %v", err) + } case <-tick.C: w, err := conn.NextWriter(websocket.TextMessage) if err != nil { @@ -67,7 +97,27 @@ func (c *client) run(ctx context.Context) { c.Error("failed to close connection: %v", err) } c.Info("connection closed") + c.conn = nil return } } } + +func (c *client) send(cmd string, args map[string]interface{}) { + eargs := make(map[string]json.RawMessage, len(args)) + for k, v := range args { + b, err := json.Marshal(v) + if err != nil { + c.Error("failed to marshal an argument: %v", err) + return + } + eargs[k] = json.RawMessage(b) + } + c.lastSeq++ + req := request{ + Seq: c.lastSeq, + Command: cmd, + Args: eargs, + } + c.outbox <- req +} diff --git a/request.go b/request.go new file mode 100644 index 0000000..b20ec58 --- /dev/null +++ b/request.go @@ -0,0 +1,17 @@ +package main + +import "encoding/json" + +type request struct { + // client-side sequence number. Clients are expected to send requests with + // a monotonically-incrementing sequence number. + Seq int `json:"seq"` + + // command represents the command to be executed on the server. Command + // names are globally unique. + Command string `json:"cmd"` + + // args are the arguments passed to the command for parameterized commands + // executed on the server. + Args map[string]json.RawMessage `json:"args"` +} diff --git a/room.go b/room.go new file mode 100644 index 0000000..9ef509a --- /dev/null +++ b/room.go @@ -0,0 +1,8 @@ +package main + +// room represents a rectangular space in the world state +type room struct { + name string + width int + height int +} diff --git a/server.go b/server.go index b218640..1e51da9 100644 --- a/server.go +++ b/server.go @@ -11,8 +11,9 @@ import ( type server struct { *blammo.Log - host string - port int + host string + port int + world *room } func (s *server) listen() error { diff --git a/ui.go b/ui.go index d54f947..cc72035 100644 --- a/ui.go +++ b/ui.go @@ -102,7 +102,7 @@ func (ui *ui) menu() { } } - ui.mode.HandleEvent(e) + ui.mode.handleEvent(ui, e) ui.screen.Clear() ui.mode.draw(ui) ui.screen.Show() diff --git a/ui_mode.go b/ui_mode.go index 8425e6f..2cda515 100644 --- a/ui_mode.go +++ b/ui_mode.go @@ -5,7 +5,7 @@ import ( ) type uiMode interface { - HandleEvent(tcell.Event) bool + handleEvent(*ui, tcell.Event) bool draw(*ui) } @@ -15,19 +15,23 @@ type boxWalker struct { position point } -func (m *boxWalker) HandleEvent(e tcell.Event) bool { +func (m *boxWalker) handleEvent(ui *ui, e tcell.Event) bool { switch v := e.(type) { case *tcell.EventKey: key := v.Key() if key == tcell.KeyRune { switch v.Rune() { case 'w': + ui.client.send("self/move", map[string]interface{}{"delta": []int{0, -1}}) m.move(0, -1) case 'a': + ui.client.send("self/move", map[string]interface{}{"delta": []int{-1, 0}}) m.move(-1, 0) case 's': + ui.client.send("self/move", map[string]interface{}{"delta": []int{0, 1}}) m.move(0, 1) case 'd': + ui.client.send("self/move", map[string]interface{}{"delta": []int{1, 0}}) m.move(1, 0) } }