diff --git a/internal/app/buffer.go b/internal/app/buffer.go index 7d61f6b..38bf1ed 100644 --- a/internal/app/buffer.go +++ b/internal/app/buffer.go @@ -1,5 +1,10 @@ package app +import ( + "github.com/gdamore/tcell/v2" + "github.com/jordanorelli/astro-domu/internal/math" +) + // buffer is a rect of tiles type buffer struct { width int @@ -8,12 +13,45 @@ type buffer struct { } func newBuffer(width, height int) *buffer { - return &buffer{ - width: width, - height: height, - tiles: make([]tile, width*height), + b := &buffer{width: width, height: height} + b.clear() + return b +} + +func (b *buffer) set(x, y int, t tile) bool { + n := y*b.width + x + if n >= len(b.tiles) { + return false + } + b.tiles[n] = t + return true +} + +func (b *buffer) get(x, y int) (tile, bool) { + n := y*b.width + x + if n >= len(b.tiles) { + return tile{}, false } + return b.tiles[n], true } -func (b *buffer) set(x, y int, t tile) { b.tiles[y*b.width+x] = t } -func (b *buffer) get(x, y int) tile { return b.tiles[y*b.width+x] } +func (b *buffer) writeString(s string, start math.Vec, style tcell.Style) { + for i, r := range []rune(s) { + if !b.set(start.X+i, start.Y, tile{r: r, style: style}) { + break + } + } +} + +func (b *buffer) clear() { b.tiles = make([]tile, b.width*b.height) } + +func (b *buffer) blit(s tcell.Screen, offset math.Vec) { + for x := 0; x < b.width; x++ { + for y := 0; y < b.height; y++ { + t, ok := b.get(x, y) + if ok { + s.SetContent(x+offset.X, y+offset.Y, t.r, nil, t.style) + } + } + } +} diff --git a/internal/app/game_view.go b/internal/app/game_view.go index 0203a5d..726e983 100644 --- a/internal/app/game_view.go +++ b/internal/app/game_view.go @@ -2,7 +2,6 @@ package app import ( "github.com/gdamore/tcell/v2" - "github.com/jordanorelli/astro-domu/internal/math" "github.com/jordanorelli/astro-domu/internal/sim" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" @@ -41,32 +40,47 @@ func (v *gameView) move(ui *UI, dx, dy int) { go ui.client.Send(sim.Move{dx, dy}) } -func (v *gameView) draw(ui *UI) { - offset := point{1, 1} +func (v *gameView) draw(b *buffer) { + v.drawHeader(b) // fill in background dots first for x := 0; x < v.room.Width; x++ { for y := 0; y < v.room.Height; y++ { - ui.screen.SetContent(x+offset.x, y+offset.y, '·', nil, tcell.StyleDefault) + b.set(x+1, y+2, tile{r: '·', style: tcell.StyleDefault}) } } - - // frame it - ui.screen.SetContent(offset.x-1, offset.y-1, '┌', nil, tcell.StyleDefault) - ui.screen.SetContent(offset.x+v.room.Width, offset.y-1, '┐', nil, tcell.StyleDefault) - ui.screen.SetContent(offset.x-1, offset.y+v.room.Height, '└', nil, tcell.StyleDefault) - ui.screen.SetContent(offset.x+v.room.Width, offset.y+v.room.Height, '┘', nil, tcell.StyleDefault) + b.set(0, 1, tile{r: '┌'}) + b.set(v.room.Width+1, 1, tile{r: '┐'}) + b.set(0, v.room.Height+2, tile{r: '└'}) + b.set(v.room.Width+1, v.room.Height+2, tile{r: '┘'}) for x := 0; x < v.room.Width; x++ { - ui.screen.SetContent(x+offset.x, offset.y-1, '─', nil, tcell.StyleDefault) - ui.screen.SetContent(x+offset.x, offset.y+v.room.Height, '─', nil, tcell.StyleDefault) + b.set(x+1, 1, tile{r: '─'}) + b.set(x+1, v.room.Height+2, tile{r: '─'}) } for y := 0; y < v.room.Height; y++ { - ui.screen.SetContent(offset.x-1, y+offset.y, '│', nil, tcell.StyleDefault) - ui.screen.SetContent(offset.x+v.room.Width, y+offset.y, '│', nil, tcell.StyleDefault) + b.set(0, y+2, tile{r: '│'}) + b.set(v.room.Width+1, y+2, tile{r: '│'}) } - for _, e := range v.room.Entities { - pos := e.Position.Add(math.Vec{1, 1}) - ui.screen.SetContent(pos.X, pos.Y, e.Glyph, nil, tcell.StyleDefault) + pos := e.Position + b.set(pos.X+1, pos.Y+2, tile{r: e.Glyph, style: tcell.StyleDefault}) + } + +} + +func (v *gameView) drawHeader(b *buffer) { + // the first row is the name of the room that we're currently in + var style tcell.Style + style = style.Background(tcell.NewRGBColor(64, 64, 128)) + style = style.Foreground(tcell.NewRGBColor(0, 0, 0)) + + runes := []rune(v.room.Name) + + for i := 0; i < b.width; i++ { + if i < len(runes) { + b.set(i, 0, tile{r: runes[i], style: style}) + } else { + b.set(i, 0, tile{r: ' ', style: style}) + } } } diff --git a/internal/app/ui.go b/internal/app/ui.go index 28e20eb..f145f51 100644 --- a/internal/app/ui.go +++ b/internal/app/ui.go @@ -5,6 +5,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/jordanorelli/astro-domu/internal/exit" + "github.com/jordanorelli/astro-domu/internal/math" "github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/blammo" ) @@ -107,8 +108,7 @@ func (ui *UI) handleNotifications(c <-chan wire.Response) { for n := range c { if ui.handleNotification(n.Body) { if ui.view != nil { - ui.view.draw(ui) - ui.screen.Show() + ui.render() } } } @@ -158,7 +158,7 @@ func (ui *UI) writeString(x, y int, s string, style tcell.Style) { func (ui *UI) handleUserInput() bool { ui.screen.Clear() - ui.view.draw(ui) + ui.render() for { e := ui.screen.PollEvent() @@ -180,7 +180,15 @@ func (ui *UI) handleUserInput() bool { ui.view.handleEvent(ui, e) ui.screen.Clear() - ui.view.draw(ui) + ui.render() ui.screen.Show() } } + +func (ui *UI) render() { + width, height := ui.screen.Size() + b := newBuffer(width, height) + ui.view.draw(b) + b.blit(ui.screen, math.Vec{1, 1}) + ui.screen.Show() +} diff --git a/internal/app/view.go b/internal/app/view.go index dec353e..c5fb45e 100644 --- a/internal/app/view.go +++ b/internal/app/view.go @@ -6,5 +6,5 @@ import ( type view interface { handleEvent(*UI, tcell.Event) bool - draw(*UI) + draw(*buffer) } diff --git a/internal/sim/server.go b/internal/sim/server.go index 089feb9..5b992c2 100644 --- a/internal/sim/server.go +++ b/internal/sim/server.go @@ -27,7 +27,7 @@ func (s *Server) Start(host string, port int) error { } s.world = newWorld(s.Log.Child("world")) - go s.world.run(2) + go s.world.run(30) addr := fmt.Sprintf("%s:%d", host, port) lis, err := net.Listen("tcp", addr)