login promptingggggggg

master
Jordan Orelli 4 years ago
parent f7b044d428
commit 30e47e6f7f

@ -13,8 +13,11 @@ type buffer struct {
} }
func newBuffer(width, height int) *buffer { func newBuffer(width, height int) *buffer {
b := &buffer{width: width, height: height} b := &buffer{
b.clear() width: width,
height: height,
tiles: make([]tile, width*height),
}
return b return b
} }
@ -43,7 +46,11 @@ func (b *buffer) writeString(s string, start math.Vec, style tcell.Style) {
} }
} }
func (b *buffer) clear() { b.tiles = make([]tile, b.width*b.height) } func (b *buffer) clear() {
for i, _ := range b.tiles {
b.tiles[i] = tile{}
}
}
func (b *buffer) blit(s tcell.Screen, offset math.Vec) { func (b *buffer) blit(s tcell.Screen, offset math.Vec) {
for x := 0; x < b.width; x++ { for x := 0; x < b.width; x++ {

@ -0,0 +1,35 @@
package app
import (
"github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/math"
)
type form struct {
fields []textField
active int
}
func (f *form) handleEvent(e tcell.Event) change {
switch t := e.(type) {
case *tcell.EventKey:
key := t.Key()
switch key {
case tcell.KeyEnter:
return login{name: f.fields[0].textInput.entered}
}
}
return f.fields[0].handleEvent(e)
}
func (f *form) draw(b *buffer, _ *state) {
for i, field := range f.fields {
b.writeString(field.label, math.Vec{0, i * 2}, tcell.StyleDefault)
b.writeString(field.prompt+field.entered, math.Vec{0, i*2 + 1}, tcell.StyleDefault)
}
}
type textField struct {
label string
textInput
}

@ -3,79 +3,71 @@ package app
import ( import (
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/sim" "github.com/jordanorelli/astro-domu/internal/sim"
"github.com/jordanorelli/astro-domu/internal/wire"
"github.com/jordanorelli/blammo" "github.com/jordanorelli/blammo"
) )
type gameView struct { type gameView struct {
*blammo.Log *blammo.Log
room *wire.Room
me *wire.Entity
inFocus bool inFocus bool
} }
func (v *gameView) handleEvent(ui *UI, e tcell.Event) bool { func (v *gameView) handleEvent(e tcell.Event) change {
switch t := e.(type) { switch t := e.(type) {
case *tcell.EventKey: case *tcell.EventKey:
key := t.Key() key := t.Key()
if key == tcell.KeyRune { if key == tcell.KeyRune {
switch t.Rune() { switch t.Rune() {
case 'w': case 'w':
v.move(ui, 0, -1) return move{0, -1}
case 'a': case 'a':
v.move(ui, -1, 0) return move{-1, 0}
case 's': case 's':
v.move(ui, 0, 1) return move{0, 1}
case 'd': case 'd':
v.move(ui, 1, 0) return move{1, 0}
} }
} }
default: default:
// ui.Debug("screen saw unhandled event of type %T", e) // ui.Debug("screen saw unhandled event of type %T", e)
} }
return true return nil
} }
func (v *gameView) move(ui *UI, dx, dy int) { func (v *gameView) draw(b *buffer, st *state) {
// fuck lol v.drawHeader(b, st)
go ui.client.Send(sim.Move{dx, dy})
}
func (v *gameView) draw(b *buffer) {
v.drawHeader(b)
// fill in background dots first // fill in background dots first
for x := 0; x < v.room.Width; x++ { for x := 0; x < st.room.Width; x++ {
for y := 0; y < v.room.Height; y++ { for y := 0; y < st.room.Height; y++ {
b.set(x+1, y+2, tile{r: '·', style: tcell.StyleDefault}) b.set(x+1, y+2, tile{r: '·', style: tcell.StyleDefault})
} }
} }
b.set(0, 1, tile{r: '┌'}) b.set(0, 1, tile{r: '┌'})
b.set(v.room.Width+1, 1, tile{r: '┐'}) b.set(st.room.Width+1, 1, tile{r: '┐'})
b.set(0, v.room.Height+2, tile{r: '└'}) b.set(0, st.room.Height+2, tile{r: '└'})
b.set(v.room.Width+1, v.room.Height+2, tile{r: '┘'}) b.set(st.room.Width+1, st.room.Height+2, tile{r: '┘'})
for x := 0; x < v.room.Width; x++ { for x := 0; x < st.room.Width; x++ {
b.set(x+1, 1, tile{r: '─'}) b.set(x+1, 1, tile{r: '─'})
b.set(x+1, v.room.Height+2, tile{r: '─'}) b.set(x+1, st.room.Height+2, tile{r: '─'})
} }
for y := 0; y < v.room.Height; y++ { for y := 0; y < st.room.Height; y++ {
b.set(0, y+2, tile{r: '│'}) b.set(0, y+2, tile{r: '│'})
b.set(v.room.Width+1, y+2, tile{r: '│'}) b.set(st.room.Width+1, y+2, tile{r: '│'})
} }
for _, e := range v.room.Entities { for _, e := range st.room.Entities {
pos := e.Position pos := e.Position
b.set(pos.X+1, pos.Y+2, tile{r: e.Glyph, style: tcell.StyleDefault}) b.set(pos.X+1, pos.Y+2, tile{r: e.Glyph, style: tcell.StyleDefault})
} }
} }
func (v *gameView) drawHeader(b *buffer) { func (v *gameView) drawHeader(b *buffer, st *state) {
// the first row is the name of the room that we're currently in // the first row is the name of the room that we're currently in
var style tcell.Style var style tcell.Style
style = style.Background(tcell.NewRGBColor(64, 64, 128)) style = style.Background(tcell.NewRGBColor(64, 64, 128))
style = style.Foreground(tcell.NewRGBColor(0, 0, 0)) style = style.Foreground(tcell.NewRGBColor(0, 0, 0))
runes := []rune(v.room.Name) runes := []rune(st.room.Name)
for i := 0; i < b.width; i++ { for i := 0; i < b.width; i++ {
if i < len(runes) { if i < len(runes) {
@ -87,3 +79,12 @@ func (v *gameView) drawHeader(b *buffer) {
} }
func (v *gameView) setFocus(yes bool) { v.inFocus = yes } func (v *gameView) setFocus(yes bool) { v.inFocus = yes }
type move struct {
x int
y int
}
func (m move) exec(ui *UI) {
ui.client.Send(sim.Move{m.x, m.y})
}

@ -0,0 +1,46 @@
package app
import (
"github.com/jordanorelli/astro-domu/internal/wire"
)
type login struct {
name string
}
func (l login) exec(ui *UI) {
ui.client = &wire.Client{
Log: ui.Child("client"),
Host: "cdm.jordanorelli.com",
Port: 12805,
}
n, err := ui.client.Dial()
if err != nil {
panic("unable to dial server: " + err.Error())
}
ui.notifications = n
res, err := ui.client.Send(wire.Login{Name: l.name})
if err != nil {
panic("unable to login: " + err.Error())
}
welcome := res.Body.(*wire.Welcome)
ui.Info("cool beans! a login response: %#v", welcome)
ui.state.playerName = l.name
if ui.state.room == nil {
ui.state.room = new(wire.Room)
}
p := welcome.Players[l.name]
// avi := p.Avatar
room := welcome.Rooms[p.Room]
// e := room.Entities[p.Avatar]
ui.state.room = &room
ui.root = &node{
view: &gameView{
Log: ui.Child("game-view"),
},
}
ui.Info("done logging in, we replaced the root view whaduheck")
}

@ -3,43 +3,48 @@ package app
import ( import (
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/math" "github.com/jordanorelli/astro-domu/internal/math"
"github.com/jordanorelli/blammo"
) )
type menuList struct { type menuList struct {
*blammo.Log choices []menuItem
choices []string highlight int
selected int
} }
func (m *menuList) handleEvent(ui *UI, e tcell.Event) bool { func (m *menuList) handleEvent(e tcell.Event) change {
switch t := e.(type) { switch t := e.(type) {
case *tcell.EventKey: case *tcell.EventKey:
key := t.Key() key := t.Key()
switch key { switch key {
case tcell.KeyEnter:
return m.choices[m.highlight].onSelect
case tcell.KeyDown: case tcell.KeyDown:
m.selected = (m.selected + 1) % len(m.choices) m.highlight = (m.highlight + 1) % len(m.choices)
return nil
case tcell.KeyUp: case tcell.KeyUp:
if m.selected == 0 { if m.highlight == 0 {
m.selected = len(m.choices) - 1 m.highlight = len(m.choices) - 1
} else { } else {
m.selected-- m.highlight--
} }
return nil
} }
default:
// ui.Debug("screen saw unhandled event of type %T", e)
} }
return true return nil
} }
func (m *menuList) draw(b *buffer) { func (m *menuList) draw(b *buffer, _ *state) {
for i, choice := range m.choices { for i, choice := range m.choices {
if i == m.selected { if i == m.highlight {
b.writeString("▷ "+choice, math.Vec{0, i}, tcell.StyleDefault) b.writeString("▷ "+choice.name, math.Vec{0, i}, tcell.StyleDefault)
} else { } else {
b.writeString(" "+choice, math.Vec{0, i}, tcell.StyleDefault) b.writeString(" "+choice.name, math.Vec{0, i}, tcell.StyleDefault)
} }
} }
} }
func (m *menuList) setFocus(bool) {} func (m *menuList) setFocus(bool) {}
type menuItem struct {
name string
onSelect change
}

@ -0,0 +1,6 @@
package app
type node struct {
view
children []*node
}

@ -0,0 +1,8 @@
package app
import "github.com/jordanorelli/astro-domu/internal/wire"
type state struct {
playerName string
room *wire.Room
}

@ -0,0 +1,41 @@
package app
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/math"
)
type textInput struct {
prompt string
entered string
}
func (t *textInput) handleEvent(e tcell.Event) change {
switch v := e.(type) {
case *tcell.EventKey:
key := v.Key()
if key == tcell.KeyBackspace || key == tcell.KeyBackspace2 {
line := []rune(t.entered)
if len(line) > 0 {
line = line[:len(line)-1]
}
t.entered = string(line)
break
}
if key == tcell.KeyRune {
t.entered = fmt.Sprintf("%s%c", t.entered, v.Rune())
}
default:
// ui.Debug("screen saw unhandled event of type %T", e)
}
return nil
}
func (t *textInput) draw(b *buffer, _ *state) {
b.writeString(t.prompt+t.entered, math.Vec{0, 0}, tcell.StyleDefault)
}

@ -1,7 +1,7 @@
package app package app
import ( import (
"fmt" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/exit" "github.com/jordanorelli/astro-domu/internal/exit"
@ -13,87 +13,58 @@ import (
type UI struct { type UI struct {
*blammo.Log *blammo.Log
PlayerName string
screen tcell.Screen screen tcell.Screen
room *wire.Room
client *wire.Client client *wire.Client
notifications <-chan wire.Response
statusBar *statusBar state state
gameView *gameView root *node
testList *menuList
chatView *chatView
focussed view
} }
func (ui *UI) Run() { func (ui *UI) Run() {
ui.setupTerminal() ui.setupTerminal()
defer ui.clearTerminal() defer ui.clearTerminal()
ui.room = new(wire.Room) ui.root = mainMenu
if err := ui.connect(); err != nil { input := make(chan tcell.Event)
return go ui.pollInput(input)
}
res, err := ui.client.Send(wire.Login{Name: ui.PlayerName}) tick := time.Tick(time.Second / time.Duration(30))
if err != nil {
ui.Error("login error: %v", err)
return
}
welcome, ok := res.Body.(*wire.Welcome) width, height := ui.screen.Size()
if !ok { b := newBuffer(width, height)
ui.Error("unexpected initial message of type %t", res.Body)
// b, prev := newBuffer(width, height), newBuffer(width, height)
for {
notify := ui.notifications
select {
case e := <-input:
switch v := e.(type) {
case *tcell.EventKey:
key := v.Key()
if key == tcell.KeyCtrlC {
ui.Info("saw ctrl+c keyboard input, shutting down")
return return
} }
ui.Info("welcome: %v", welcome)
meta := welcome.Players[ui.PlayerName]
room := welcome.Rooms[meta.Room]
ui.room = &room
ui.gameView = &gameView{
Log: ui.Child("game-view"),
room: &room,
me: &wire.Entity{
ID: meta.Avatar,
Glyph: room.Entities[meta.Avatar].Glyph,
Position: room.Entities[meta.Avatar].Position,
},
}
ui.chatView = &chatView{
Log: ui.Child("chat-view"),
history: make([]sim.ChatMessage, 0, 32),
} }
ui.statusBar = &statusBar{} ui.Info("input event: %v", e)
ui.testList = &menuList{ wants := ui.root.handleEvent(e)
Log: ui.Child("menu-list"), if wants != nil {
choices: []string{"apple", "banana", "orange"}, wants.exec(ui)
} }
ui.focussed = ui.gameView case <-tick:
b.clear()
ui.Info("running ui") ui.root.draw(b, &ui.state)
if ui.handleUserInput() { b.blit(ui.screen, math.Vec{0, 0})
ui.Info("user requested close") ui.screen.Show()
ui.Info("closing client") case n := <-notify:
ui.client.Close() ui.Info("notification: %v", n)
ui.Info("client closed") ui.handleNotification(n.Body)
ui.Info("finalizing screen")
}
ui.Info("run loop done, shutting down")
}
func (ui *UI) connect() error {
ui.client = &wire.Client{
Log: ui.Child("client"),
Host: "cdm.jordanorelli.com",
Port: 12805,
} }
c, err := ui.client.Dial()
if err != nil {
return fmt.Errorf("unable to dial server: %v", err)
} }
go ui.handleNotifications(c)
return nil
} }
func (ui *UI) setupTerminal() { func (ui *UI) setupTerminal() {
@ -116,63 +87,45 @@ func (ui *UI) setupTerminal() {
} }
func (ui *UI) clearTerminal() { func (ui *UI) clearTerminal() {
ui.statusBar.clearCount++
ui.screen.Clear() ui.screen.Clear()
ui.screen.Fini() ui.screen.Fini()
} }
func (ui *UI) handleNotifications(c <-chan wire.Response) {
for n := range c {
ui.statusBar.msgCount++
if ui.handleNotification(n.Body) {
if ui.gameView != nil {
ui.render()
}
}
}
ui.Info("notifications channel is closed so we must be done")
ui.Info("clearing and finalizing screen from notifications goroutine")
ui.statusBar.clearCount++
ui.screen.Clear()
ui.screen.Fini()
ui.Info("screen finalized")
}
func (ui *UI) handleNotification(v wire.Value) bool { func (ui *UI) handleNotification(v wire.Value) bool {
switch n := v.(type) { switch n := v.(type) {
case *wire.Entity: case *wire.Entity:
ui.room.Entities[n.ID] = *n ui.state.room.Entities[n.ID] = *n
return true return true
case *wire.Frame: case *wire.Frame:
if ui.room == nil { if ui.state.room == nil {
ui.room = new(wire.Room) ui.state.room = new(wire.Room)
} }
ui.room.Name = n.RoomName ui.state.room.Name = n.RoomName
ui.room.Rect = n.RoomSize ui.state.room.Rect = n.RoomSize
ui.room.Entities = n.Entities ui.state.room.Entities = n.Entities
return true return true
case *wire.Delta: case *wire.Delta:
if n.RoomSize != nil { if n.RoomSize != nil {
ui.room.Rect = *n.RoomSize ui.state.room.Rect = *n.RoomSize
} }
if len(n.Entities) > 0 { if len(n.Entities) > 0 {
for id, e := range n.Entities { for id, e := range n.Entities {
if e != nil { if e != nil {
ui.room.Entities[id] = *e ui.state.room.Entities[id] = *e
} else { } else {
delete(ui.room.Entities, id) delete(ui.state.room.Entities, id)
} }
} }
} }
return true return true
case *sim.ChatMessage: case *sim.ChatMessage:
ui.chatView.history = append(ui.chatView.history, *n) // ui.chatView.history = append(ui.chatView.history, *n)
return true return true
default: default:
@ -198,79 +151,47 @@ func (ui *UI) writeString(x, y int, s string, style tcell.Style) {
} }
} }
func (ui *UI) handleUserInput() bool { func (ui *UI) pollInput(c chan tcell.Event) {
ui.statusBar.clearCount++ defer close(c)
ui.screen.Clear()
ui.render()
for { for {
e := ui.screen.PollEvent() e := ui.screen.PollEvent()
if e == nil { if e == nil {
ui.Info("run loop sees nil event, breaking out") ui.Info("run loop sees nil event, breaking out")
// someone else shut us down, so return false return
return false
}
switch v := e.(type) {
case *tcell.EventKey:
key := v.Key()
if key == tcell.KeyCtrlC {
ui.Info("saw ctrl+c keyboard input, shutting down")
// we want to shut things down
return true
}
if key == tcell.KeyTab {
ui.Info("saw tab from keyboard input, switching focussed view")
ui.focussed.setFocus(false)
switch ui.focussed {
case ui.gameView:
ui.focussed = ui.chatView
case ui.chatView:
ui.focussed = ui.testList
case ui.testList:
ui.focussed = ui.gameView
}
ui.focussed.setFocus(true)
goto HANDLED
}
} }
c <- e
ui.focussed.handleEvent(ui, e)
ui.statusBar.clearCount++
ui.screen.Clear()
ui.render()
ui.statusBar.showCount++
ui.screen.Show()
HANDLED:
} }
} }
func (ui *UI) render() { var mainMenu = &node{
width, height := ui.screen.Size() view: &menuList{
choices: []menuItem{
{ menuItem{
b := newBuffer(width, 1) name: "join",
ui.statusBar.draw(b) onSelect: changeFn(func(ui *UI) {
b.blit(ui.screen, math.Vec{0, 0}) ui.root = joinForm
} }),
},
gameViewHeight := math.Max((height-1)/2, 8) menuItem{
{ name: "exit",
b := newBuffer(width/2, gameViewHeight) onSelect: changeFn(func(ui *UI) {
ui.gameView.draw(b) panic("this is bad programming")
b.blit(ui.screen, math.Vec{0, 1}) }),
} },
{ },
b := newBuffer(width/2, gameViewHeight) },
ui.testList.draw(b) }
b.blit(ui.screen, math.Vec{width / 2, 1})
}
{
b := newBuffer(width, height-gameViewHeight-1)
ui.chatView.draw(b)
b.blit(ui.screen, math.Vec{0, gameViewHeight + 1})
}
ui.statusBar.showCount++ var joinForm = &node{
ui.screen.Show() view: &form{
fields: []textField{
textField{
label: "What is your name?",
textInput: textInput{
prompt: "> ",
},
},
},
},
} }

@ -5,7 +5,18 @@ import (
) )
type view interface { type view interface {
handleEvent(*UI, tcell.Event) bool handleEvent(tcell.Event) change
draw(*buffer) draw(*buffer, *state)
}
type focusable interface {
setFocus(bool) setFocus(bool)
} }
type change interface {
exec(*UI)
}
type changeFn func(*UI)
func (f changeFn) exec(ui *UI) { f(ui) }

@ -224,7 +224,7 @@ func (w *world) register(c connect) {
outbox: make(chan wire.Response, 8), outbox: make(chan wire.Response, 8),
pending: &Request{ pending: &Request{
From: c.login.Name, From: c.login.Name,
Seq: 1, Seq: 90,
Wants: &spawnPlayer{}, Wants: &spawnPlayer{},
}, },
} }

@ -164,7 +164,7 @@ func (c *Client) writeLoop() {
case res := <-c.resolved: case res := <-c.resolved:
p, ok := sent[res.Re] p, ok := sent[res.Re]
if !ok { if !ok {
c.Error("saw response for unknown seq %d") c.Error("saw response for unknown seq %d", res.Re)
break break
} }
delete(sent, res.Re) delete(sent, res.Re)

@ -35,7 +35,7 @@ func main() {
switch os.Args[1] { switch os.Args[1] {
case "client": case "client":
runClient(os.Args[2]) runClient()
case "server": case "server":
s := sim.Server{} s := sim.Server{}
if err := s.Start("cdm.jordanorelli.com", 12805); err != nil { if err := s.Start("cdm.jordanorelli.com", 12805); err != nil {
@ -51,7 +51,7 @@ func main() {
} }
} }
func runClient(name string) { func runClient() {
log := newLog("./astro.log").Child("client") log := newLog("./astro.log").Child("client")
start := time.Now() start := time.Now()
@ -64,7 +64,6 @@ func runClient(name string) {
app := app.UI{ app := app.UI{
Log: log.Child("ui"), Log: log.Child("ui"),
PlayerName: name,
} }
app.Run() app.Run()
} }

Loading…
Cancel
Save