Add game status buffer

This commit is contained in:
Trevor Slocum 2021-11-09 18:19:20 -08:00
parent a061c032c6
commit 57f265acf3
9 changed files with 231 additions and 153 deletions

View File

@ -37,12 +37,12 @@ type board struct {
dragTouchId ebiten.TouchID
touchIDs []ebiten.TouchID
spaceWidth int
barWidth int
spaceWidth float64
barWidth float64
triangleOffset float64
horizontalBorderSize int
verticalBorderSize int
overlapSize int
horizontalBorderSize float64
verticalBorderSize float64
overlapSize float64
lastDirection int
@ -62,8 +62,8 @@ func NewBoard() *board {
b := &board{
barWidth: 100,
triangleOffset: float64(50),
horizontalBorderSize: 50,
verticalBorderSize: 25,
horizontalBorderSize: 20,
verticalBorderSize: 10,
overlapSize: 97,
Sprites: &Sprites{
sprites: make([]*Sprite, 30),
@ -139,8 +139,8 @@ func (b *board) updateBackgroundImage() {
if borderSize > b.barWidth/2 {
borderSize = b.barWidth / 2
}
frameW := b.w - ((b.horizontalBorderSize - borderSize) * 2)
innerW := b.w - (b.horizontalBorderSize * 2) // Outer board width (including frame)
frameW := b.w - int((b.horizontalBorderSize-borderSize)*2)
innerW := float64(b.w) - b.horizontalBorderSize*2 // Outer board width (including frame)
// Table
box := image.NewRGBA(image.Rect(0, 0, b.w, b.h))
@ -157,7 +157,7 @@ func (b *board) updateBackgroundImage() {
b.backgroundImage.DrawImage(img, b.op)
// Face
box = image.NewRGBA(image.Rect(0, 0, innerW, b.h-(b.verticalBorderSize*2)))
box = image.NewRGBA(image.Rect(0, 0, int(innerW), b.h-int(b.verticalBorderSize*2)))
img = ebiten.NewImageFromImage(box)
img.Fill(faceColor)
b.op.GeoM.Reset()
@ -165,18 +165,18 @@ func (b *board) updateBackgroundImage() {
b.backgroundImage.DrawImage(img, b.op)
// Bar
box = image.NewRGBA(image.Rect(0, 0, b.barWidth, b.h))
box = image.NewRGBA(image.Rect(0, 0, int(b.barWidth), b.h))
img = ebiten.NewImageFromImage(box)
img.Fill(frameColor)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64((b.w/2)-(b.barWidth/2)), 0)
b.op.GeoM.Translate(float64((b.w/2)-int(b.barWidth/2)), 0)
b.backgroundImage.DrawImage(img, b.op)
// Draw triangles
baseImg := image.NewRGBA(image.Rect(0, 0, b.w-(b.horizontalBorderSize*2), b.h-(b.verticalBorderSize*2)))
baseImg := image.NewRGBA(image.Rect(0, 0, b.w-int(b.horizontalBorderSize*2), b.h-int(b.verticalBorderSize*2)))
gc := draw2dimg.NewGraphicContext(baseImg)
for i := 0; i < 2; i++ {
triangleTip := float64((b.h - (b.verticalBorderSize * 2)) / 2)
triangleTip := (float64(b.h) - (b.verticalBorderSize * 2)) / 2
if i == 0 {
triangleTip -= b.triangleOffset
} else {
@ -194,7 +194,7 @@ func (b *board) updateBackgroundImage() {
gc.SetFillColor(triangleB)
}
tx := b.spaceWidth * j
tx := b.spaceWidth * float64(j)
ty := b.h * i
if j >= 6 {
tx += b.barWidth
@ -233,8 +233,8 @@ func (b *board) updateBackgroundImage() {
edge := float64((((innerW) - b.barWidth) / 2) + borderSize)
gc.MoveTo(float64(borderSize), float64(b.verticalBorderSize))
gc.LineTo(edge, float64(b.verticalBorderSize))
gc.LineTo(edge, float64(b.h-b.verticalBorderSize))
gc.LineTo(float64(borderSize), float64(b.h-b.verticalBorderSize))
gc.LineTo(edge, float64(b.h-int(b.verticalBorderSize)))
gc.LineTo(float64(borderSize), float64(b.h-int(b.verticalBorderSize)))
gc.LineTo(float64(borderSize), float64(b.verticalBorderSize))
gc.Close()
gc.Stroke()
@ -243,14 +243,14 @@ func (b *board) updateBackgroundImage() {
edgeEnd := float64(innerW + borderSize)
gc.MoveTo(float64(edgeStart), float64(b.verticalBorderSize))
gc.LineTo(edgeEnd, float64(b.verticalBorderSize))
gc.LineTo(edgeEnd, float64(b.h-b.verticalBorderSize))
gc.LineTo(float64(edgeStart), float64(b.h-b.verticalBorderSize))
gc.LineTo(edgeEnd, float64(b.h-int(b.verticalBorderSize)))
gc.LineTo(float64(edgeStart), float64(b.h-int(b.verticalBorderSize)))
gc.LineTo(float64(edgeStart), float64(b.verticalBorderSize))
gc.Close()
gc.Stroke()
img = ebiten.NewImageFromImage(borderImage)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64(b.horizontalBorderSize-borderSize), 0)
b.op.GeoM.Translate(b.horizontalBorderSize-borderSize, 0)
b.backgroundImage.DrawImage(img, b.op)
}
@ -360,9 +360,9 @@ func (b *board) draw(screen *ebiten.Image) {
labelColor = color.RGBA{0, 0, 0, 255}
}
bounds := text.BoundString(mplusNormalFont, overlayText)
bounds := text.BoundString(normalFont, overlayText)
overlayImage := ebiten.NewImage(bounds.Dx()*2, bounds.Dy()*2)
text.Draw(overlayImage, overlayText, mplusNormalFont, 0, bounds.Dy(), labelColor)
text.Draw(overlayImage, overlayText, normalFont, 0, bounds.Dy(), labelColor)
x, y, w, h := b.stackSpaceRect(space, numPieces-1)
x += (w / 2) - (bounds.Dx() / 2)
@ -412,7 +412,7 @@ func (b *board) draw(screen *ebiten.Image) {
}
drawLabel := func(label string, labelColor color.Color, border bool, borderColor color.Color) *ebiten.Image {
bounds := text.BoundString(mplusNormalFont, label)
bounds := text.BoundString(normalFont, label)
w := int(float64(bounds.Dx()) * 1.5)
h := int(float64(bounds.Dy()) * 2)
@ -432,7 +432,7 @@ func (b *board) draw(screen *ebiten.Image) {
}
img := ebiten.NewImageFromImage(baseImg)
text.Draw(img, label, mplusNormalFont, (w-bounds.Dx())/2, int(float64(h-(bounds.Max.Y/2))*0.75), labelColor)
text.Draw(img, label, normalFont, (w-bounds.Dx())/2, int(float64(h-(bounds.Max.Y/2))*0.75), labelColor)
return img
}
@ -443,7 +443,7 @@ func (b *board) draw(screen *ebiten.Image) {
img := drawLabel(label, opponentColor, b.v[fibs.StateTurn] != b.v[fibs.StatePlayerColor], opponentColor)
bounds := img.Bounds()
x := ((b.innerW - borderSize) / 4) - (bounds.Dx() / 2)
x := int(((float64(b.innerW) - borderSize) / 4) - (float64(bounds.Dx()) / 2))
y := (b.innerH / 2) - (bounds.Dy() / 2)
x, y = b.offsetPosition(x, y)
b.op.GeoM.Reset()
@ -486,8 +486,8 @@ func (b *board) draw(screen *ebiten.Image) {
img.DrawImage(ebiten.NewImageFromImage(baseImg), nil)
label := "Reset"
bounds := text.BoundString(mplusNormalFont, label)
text.Draw(img, label, mplusNormalFont, (w-bounds.Dx())/2, (h+(bounds.Dy()/2))/2, color.Black)
bounds := text.BoundString(normalFont, label)
text.Draw(img, label, normalFont, (w-bounds.Dx())/2, (h+(bounds.Dy()/2))/2, color.Black)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64(x), float64(y))
@ -532,50 +532,34 @@ func (b *board) setRect(x, y, w, h int) {
if b.x == x && b.y == y && b.w == w && b.h == h {
return
}
const stackAllowance = 0.97 // TODO configurable
b.x, b.y, b.w, b.h = x, y, w, h
b.horizontalBorderSize = 0
b.triangleOffset = (float64(b.h) - (b.verticalBorderSize * 2)) / 15
b.triangleOffset = float64(b.h-(b.verticalBorderSize*2)) / 15
b.spaceWidth = (float64(b.w) - (b.horizontalBorderSize * 2)) / 13
b.barWidth = b.spaceWidth
for {
b.verticalBorderSize = 7 // TODO configurable
b.spaceWidth = (b.w - (b.horizontalBorderSize * 2)) / 13
b.barWidth = b.spaceWidth
b.overlapSize = (((b.h - (b.verticalBorderSize * 2)) - (int(b.triangleOffset) * 2)) / 2) / 5
o := int(float64(b.spaceWidth) * stackAllowance)
if b.overlapSize >= o {
b.overlapSize = o
break
}
b.horizontalBorderSize++
b.overlapSize = (((float64(b.h) - (b.verticalBorderSize * 2)) - (b.triangleOffset * 2)) / 2) / 5
if b.overlapSize > b.spaceWidth*0.94 {
b.overlapSize = b.spaceWidth * 0.94
}
extraSpace := b.w - (b.spaceWidth * 12)
largeBarWidth := int(float64(b.spaceWidth) * 1.25)
extraSpace := float64(b.w) - (b.spaceWidth * 12)
largeBarWidth := float64(b.spaceWidth) * 1.25
if extraSpace >= largeBarWidth {
b.barWidth = largeBarWidth
}
b.horizontalBorderSize = ((b.w - (b.spaceWidth * 12)) - b.barWidth) / 2
if b.horizontalBorderSize < 0 {
b.horizontalBorderSize = 0
b.spaceWidth = ((float64(b.w) - (b.horizontalBorderSize * 2)) - b.barWidth) / 12
}
borderSize := b.horizontalBorderSize
if borderSize > b.barWidth/2 {
borderSize = b.barWidth / 2
}
b.innerW = b.w - (b.horizontalBorderSize * 2)
b.innerH = b.h - (b.verticalBorderSize * 2)
b.innerW = int(float64(b.w) - (b.horizontalBorderSize * 2))
b.innerH = int(float64(b.h) - (b.verticalBorderSize * 2))
loadAssets(b.spaceWidth)
loadAssets(int(b.spaceWidth))
for i := 0; i < b.Sprites.num; i++ {
s := b.Sprites.sprites[i]
@ -588,7 +572,7 @@ func (b *board) setRect(x, y, w, h int) {
}
func (b *board) offsetPosition(x, y int) (int, int) {
return b.x + x + b.horizontalBorderSize, b.y + y + b.verticalBorderSize
return b.x + x + int(b.horizontalBorderSize), b.y + y + int(b.verticalBorderSize)
}
func (b *board) positionCheckers() {
@ -663,34 +647,34 @@ func (b *board) setSpaceRects() {
if !b.bottomRow(trueSpace) {
y = 0
} else {
y = (b.h / 2) - b.verticalBorderSize
y = int((float64(b.h) / 2) - b.verticalBorderSize)
}
w = b.spaceWidth
w = int(b.spaceWidth)
var hspace int // horizontal space
var add int
if space == 0 {
hspace = 6
w = b.barWidth
w = int(b.barWidth)
} else if space == 25 {
hspace = 6
w = b.barWidth
w = int(b.barWidth)
} else if space <= 6 {
hspace = space - 1
} else if space <= 12 {
hspace = space - 1
add = b.barWidth
add = int(b.barWidth)
} else if space <= 18 {
hspace = 24 - space
add = b.barWidth
add = int(b.barWidth)
} else {
hspace = 24 - space
}
x = (b.spaceWidth * hspace) + add
x = int((b.spaceWidth * float64(hspace)) + float64(add))
h = (b.h - (b.verticalBorderSize * 2)) / 2
h = int((float64(b.h) - (b.verticalBorderSize * 2)) / 2)
b.spaceRects[trueSpace] = [4]int{x, y, w, h}
}
@ -719,8 +703,8 @@ func (b *board) stackSpaceRect(space int, stack int) (x, y, w, h int) {
x, y, w, h = b.spaceRect(space)
// Stack pieces
osize := float64(stack)
var o int
osize := float64(stack)
if stack > 4 {
osize = 3.5
}
@ -728,15 +712,21 @@ func (b *board) stackSpaceRect(space int, stack int) (x, y, w, h int) {
osize += 1.0
}
o = int(osize * float64(b.overlapSize))
padding := int(b.spaceWidth - b.overlapSize)
if b.bottomRow(space) {
o += padding
} else {
o -= padding - 3
}
if !b.bottomRow(space) {
y += o
} else {
y = y + (h - o)
}
w, h = b.spaceWidth, b.spaceWidth
w, h = int(b.spaceWidth), int(b.spaceWidth)
if space == 0 || space == 25 {
w = b.barWidth
w = int(b.barWidth)
}
return x, y, w, h
@ -820,8 +810,10 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int, pause bo
stack++
}
x, y, _, _ := b.stackSpaceRect(space, stack)
x, y, w, _ := b.stackSpaceRect(space, stack)
x, y = b.offsetPosition(x, y)
// Center piece in space
x += (w - int(b.spaceWidth)) / 2
sprite.toX = x
sprite.toY = y

View File

@ -3,10 +3,12 @@ package game
import "image/color"
var (
tableColor = color.RGBA{0, 102, 51, 255}
frameColor = color.RGBA{65, 40, 14, 255}
borderColor = color.RGBA{0, 0, 0, 255}
faceColor = color.RGBA{120, 63, 25, 255}
triangleA = color.RGBA{225.0, 188, 125, 255}
triangleB = color.RGBA{120.0, 17.0, 0, 255}
tableColor = color.RGBA{0, 0, 0, 255}
//tableColor = color.RGBA{0, 102, 51, 255}
frameColor = color.RGBA{65, 40, 14, 255}
borderColor = color.RGBA{0, 0, 0, 255}
faceColor = color.RGBA{120, 63, 25, 255}
triangleA = color.RGBA{225, 188, 125, 255}
triangleALight = color.RGBA{255, 218, 155, 255}
triangleB = color.RGBA{120.0, 17.0, 0, 255}
)

View File

@ -5,7 +5,6 @@ import (
"embed"
"fmt"
"image"
"image/color"
_ "image/png"
"log"
"os"
@ -36,13 +35,16 @@ var (
imgCheckerWhite *ebiten.Image
imgCheckerBlack *ebiten.Image
mplusNormalFont font.Face
monoFont font.Face
mplusBigFont font.Face
smallFont font.Face
normalFont font.Face
monoFont font.Face
largeFont font.Face
)
const defaultServerAddress = "fibs.com:4321"
const maxStatusWidthRatio = 0.5
func init() {
loadAssets(0)
@ -79,7 +81,15 @@ func initializeFonts() {
}
const dpi = 72
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
smallFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: smallFontSize,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
normalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 24,
DPI: dpi,
Hinting: font.HintingFull,
@ -87,7 +97,7 @@ func initializeFonts() {
if err != nil {
log.Fatal(err)
}
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
largeFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 32,
DPI: dpi,
Hinting: font.HintingFull,
@ -101,7 +111,7 @@ func initializeFonts() {
log.Fatal(err)
}
monoFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 12,
Size: monoFontSize,
DPI: dpi,
Hinting: font.HintingNone,
})
@ -168,7 +178,8 @@ type Game struct {
keyboardInput []*kibodo.Input
shownKeyboard bool
buffers *tabbedBuffers
statusBuffer *tabbedBuffers
gameBuffer *tabbedBuffers
cpuProfile *os.File
@ -188,15 +199,17 @@ func NewGame() *Game {
keyboard: kibodo.NewKeyboard(),
buffers: newTabbedBuffers(),
statusBuffer: newTabbedBuffers(),
gameBuffer: newTabbedBuffers(),
debugImg: ebiten.NewImage(200, 200),
}
g.keyboard.SetKeys(kibodo.KeysQWERTY)
msgHandler := NewMessageHandler(g)
fibs.StatusWriter = msgHandler
fibs.GameWriter = msgHandler
g.statusBuffer.acceptInput = true
fibs.StatusWriter = NewMessageHandler(g.statusBuffer.buffers[0])
fibs.GameWriter = NewMessageHandler(g.gameBuffer.buffers[0])
// TODO
go func() {
@ -259,7 +272,7 @@ func (g *Game) Connect() {
g.Client = fibs.NewClient(address, g.Username, g.Password)
g.lobby.c = g.Client
g.Board.Client = g.Client
g.buffers.client = g.Client
g.statusBuffer.client = g.Client
go g.handleEvents()
@ -280,7 +293,7 @@ func (g *Game) Connect() {
go func() {
err := c.Connect()
if err != nil {
log.Fatal(err)
fibs.StatusWriter.Write([]byte(err.Error()))
}
}()
}
@ -398,7 +411,7 @@ func (g *Game) Update() error { // Called by ebiten only when input occurs
} else {
g.Board.update()
g.buffers.update()
g.statusBuffer.update()
}
return nil
@ -413,7 +426,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
}
g.lastDraw = time.Now()
screen.Fill(color.RGBA{0, 102, 51, 255})
screen.Fill(tableColor)
// Log in screen
if !g.loggedIn {
@ -442,9 +455,9 @@ http://www.fibs.com/help.html#register`
g.lobby.draw(screen)
} else {
// Game board screen
g.gameBuffer.draw(screen)
g.statusBuffer.draw(screen)
g.Board.draw(screen)
g.buffers.draw(screen)
}
if g.Debug > 0 {
@ -488,19 +501,37 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
g.screenW, g.screenH = outsideWidth, outsideHeight
g.Board.setRect(0, 0, g.screenW, g.screenH)
statusBufferWidth := g.statusBuffer.chatFontSize * 77
if statusBufferWidth > int(float64(g.screenW)*maxStatusWidthRatio) {
statusBufferWidth = int(float64(g.screenW) * maxStatusWidthRatio)
}
gameBufferheight := 100
g.Board.setRect(0, 0, g.screenW-statusBufferWidth, g.screenH-gameBufferheight)
g.lobby.setRect(0, 0, g.screenW, g.screenH)
availableWidth := g.Board.w - (g.Board.spaceWidth * 14)
if availableWidth >= 150 {
g.Board.setRect(0, 0, g.screenW-availableWidth, g.screenH)
g.buffers.setRect(g.screenW-availableWidth, 0, availableWidth, g.screenH)
// TODO set flag to restore user position (could use extra state for this? docked state?)
availableWidth := g.screenW - (g.Board.innerW + int(g.Board.barWidth))
if availableWidth > statusBufferWidth {
statusBufferWidth = availableWidth
g.Board.setRect(0, 0, g.screenW-statusBufferWidth, g.screenH-gameBufferheight)
}
if g.Board.h > g.Board.w {
g.Board.setRect(0, 0, g.Board.w, g.Board.w)
}
if true || availableWidth >= 150 { // TODO allow chat window to be repositioned
g.statusBuffer.docked = true
g.statusBuffer.setRect(g.screenW-statusBufferWidth, 0, statusBufferWidth, g.screenH)
g.gameBuffer.docked = true
g.gameBuffer.setRect(0, g.Board.h, g.Board.w, g.screenH-(g.Board.h))
} else {
// Clamp buffer position.
bx, by := g.buffers.x, g.buffers.y
bx, by := g.statusBuffer.x, g.statusBuffer.y
var bw, bh int
if g.buffers.w == 0 && g.buffers.h == 0 {
if g.statusBuffer.w == 0 && g.statusBuffer.h == 0 {
// Set initial buffer position.
bx = 0
by = g.screenH / 2
@ -510,7 +541,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
} else {
// Scale existing buffer size
bx, by = bx*(outsideWidth/g.screenW), by*(outsideHeight/g.screenH)
bw, bh = g.buffers.w*(outsideWidth/g.screenW), g.buffers.h*(outsideHeight/g.screenH)
bw, bh = g.statusBuffer.w*(outsideWidth/g.screenW), g.statusBuffer.h*(outsideHeight/g.screenH)
if bw < 200 {
bw = 200
}
@ -526,7 +557,8 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
by = g.screenH - padding
}
g.buffers.setRect(bx, by, bw, bh)
g.statusBuffer.docked = false
g.statusBuffer.setRect(bx, by, bw, bh)
}
displayArea := 200
@ -572,18 +604,18 @@ func (g *Game) Exit() {
}
type messageHandler struct {
g *Game
t *textBuffer
}
func NewMessageHandler(g *Game) *messageHandler {
func NewMessageHandler(t *textBuffer) *messageHandler {
return &messageHandler{
g: g,
t: t,
}
}
func (m messageHandler) Write(p []byte) (n int, err error) {
log.Print(string(p))
func (m *messageHandler) Write(p []byte) (n int, err error) {
fmt.Print(string(p))
m.g.buffers.buffers[0].Write(p)
m.t.Write(p)
return len(p), nil
}

View File

@ -120,12 +120,12 @@ func (l *lobby) _drawBufferButtons() {
}
}
}
bounds := text.BoundString(mplusNormalFont, button.label)
bounds := text.BoundString(normalFont, button.label)
labelColor := triangleA
img := ebiten.NewImage(bounds.Dx()*2, bounds.Dy()*2)
text.Draw(img, button.label, mplusNormalFont, 0, bounds.Dy(), labelColor)
text.Draw(img, button.label, normalFont, 0, bounds.Dy(), labelColor)
l.op.GeoM.Reset()
l.op.GeoM.Translate(float64(buttonWidth*i)+float64((buttonWidth-bounds.Dx())/2), float64(l.buttonBarHeight-bounds.Dy())/2-float64(bounds.Dy()/2))
@ -149,10 +149,10 @@ func (l *lobby) drawBuffer() {
lineHeight := 30
padding := 24.0
for i, label := range labels {
bounds := text.BoundString(mplusNormalFont, label)
bounds := text.BoundString(normalFont, label)
labelColor := triangleA
img := ebiten.NewImage(l.w-int(l.padding*2), int(l.entryH))
text.Draw(img, label, mplusNormalFont, 4, bounds.Dy(), labelColor)
text.Draw(img, label, normalFont, 4, bounds.Dy(), labelColor)
l.op.GeoM.Reset()
l.op.GeoM.Translate(padding, padding+float64(i*lineHeight))
l.buffer.DrawImage(img, l.op)
@ -160,9 +160,9 @@ func (l *lobby) drawBuffer() {
} else {
var img *ebiten.Image
drawEntry := func(cx float64, cy float64, colA string, colB string, colC string, highlight bool) {
boundsA := text.BoundString(mplusNormalFont, colA)
boundsB := text.BoundString(mplusNormalFont, colB)
boundsC := text.BoundString(mplusNormalFont, colC)
boundsA := text.BoundString(normalFont, colA)
boundsB := text.BoundString(normalFont, colB)
boundsC := text.BoundString(normalFont, colC)
y := (boundsA.Dy() + boundsB.Dy() + boundsC.Dy()) / 3 // TODO this isn't correct
labelColor := triangleA
@ -184,9 +184,9 @@ func (l *lobby) drawBuffer() {
}
}
text.Draw(img, colA, mplusNormalFont, 4, y+2, labelColor)
text.Draw(img, colB, mplusNormalFont, int(250*ebiten.DeviceScaleFactor()), y+2, labelColor)
text.Draw(img, colC, mplusNormalFont, int(500*ebiten.DeviceScaleFactor()), y+2, labelColor)
text.Draw(img, colA, normalFont, 4, y+2, labelColor)
text.Draw(img, colB, normalFont, int(250*ebiten.DeviceScaleFactor()), y+2, labelColor)
text.Draw(img, colC, normalFont, int(500*ebiten.DeviceScaleFactor()), y+2, labelColor)
l.op.GeoM.Reset()
l.op.GeoM.Translate(cx, cy)

View File

@ -1,13 +1,13 @@
package game
import (
"image"
"image/color"
"code.rocketnine.space/tslocum/fibs"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
"golang.org/x/exp/shiny/materialdesign/colornames"
"golang.org/x/image/font"
)
const (
@ -18,7 +18,8 @@ const (
const windowStartingAlpha = 0.9
const bufferCharacterWidth = 12
const smallFontSize = 14
const monoFontSize = 10
type tabbedBuffers struct {
buffers []*textBuffer
@ -27,6 +28,8 @@ type tabbedBuffers struct {
x, y int
w, h int
padding int
unfocusedAlpha float64
buffer *ebiten.Image
@ -38,6 +41,8 @@ type tabbedBuffers struct {
state int
docked bool
focused bool
touchIDs []ebiten.TouchID
@ -47,6 +52,11 @@ type tabbedBuffers struct {
inputBuffer []byte
client *fibs.Client
chatFont font.Face
chatFontSize int
acceptInput bool
}
func newTabbedBuffers() *tabbedBuffers {
@ -55,8 +65,14 @@ func newTabbedBuffers() *tabbedBuffers {
unfocusedAlpha: windowStartingAlpha,
buffer: ebiten.NewImage(1, 1),
op: &ebiten.DrawImageOptions{},
chatFont: monoFont,
chatFontSize: monoFontSize,
}
// TODO
//tab.chatFont = smallFont
//tab.chatFontSize = smallFontSize
b := &textBuffer{
tab: tab,
}
@ -70,15 +86,21 @@ func (t *tabbedBuffers) setRect(x, y, w, h int) {
return
}
// TODO dynamic padding
if t.w != w || t.h != h {
t.buffer = ebiten.NewImage(w, h)
t.bufferDirty = true
}
if t.w != w {
t.wrapWidth = w / bufferCharacterWidth
if w > 200 {
t.padding = 2
} else if w > 100 {
t.padding = 1
} else {
t.padding = 0
}
t.wrapWidth = (w - (t.padding * 4)) / t.chatFontSize
for _, b := range t.buffers {
b.wrapDirty = true
}
@ -88,20 +110,27 @@ func (t *tabbedBuffers) setRect(x, y, w, h int) {
}
func (t *tabbedBuffers) drawBuffer() {
t.buffer.Fill(borderColor)
t.buffer.Fill(color.Black)
sub := t.buffer.SubImage(image.Rect(1, 1, t.w-1, t.h-1)).(*ebiten.Image)
sub.Fill(frameColor)
textColor := triangleALight
/*sub := t.buffer.SubImage(image.Rect(1, 1, t.w-1, t.h-1)).(*ebiten.Image)
sub.Fill(frameColor)*/
b := t.buffers[0]
l := len(b.contentWrapped)
lineHeight := 16
lineHeight := 14
showLines := t.h / lineHeight
// Leave space for the input buffer.
if showLines > 1 {
showLines--
if t.acceptInput {
// Leave space for the input buffer.
if showLines > 1 {
showLines--
}
if showLines > 1 {
showLines--
}
}
if l < showLines {
@ -110,12 +139,14 @@ func (t *tabbedBuffers) drawBuffer() {
for i := 0; i < showLines; i++ {
line := b.contentWrapped[l-showLines+i]
bounds := text.BoundString(monoFont, line)
bounds := text.BoundString(t.chatFont, line)
_ = bounds
text.Draw(t.buffer, line, monoFont, 0, (lineHeight * (i + 1)), colornames.White)
text.Draw(t.buffer, line, t.chatFont, t.padding*2, t.padding+(lineHeight*(i+1)), textColor)
}
text.Draw(t.buffer, "> "+string(t.inputBuffer), monoFont, 0, t.h, colornames.White)
if t.acceptInput {
text.Draw(t.buffer, "> "+string(t.inputBuffer), t.chatFont, t.padding*2, t.h-(t.padding*2), textColor)
}
}
func (t *tabbedBuffers) draw(target *ebiten.Image) {
@ -123,7 +154,7 @@ func (t *tabbedBuffers) draw(target *ebiten.Image) {
return
}
if t.state == windowMinimized {
if !t.docked && t.state == windowMinimized {
return
}
@ -192,20 +223,31 @@ func (t *tabbedBuffers) update() {
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
if len(t.inputBuffer) == 0 {
if t.state == windowMinimized {
t.state = windowNormal
} else {
t.state = windowMinimized
if !t.docked {
if t.state == windowMinimized {
t.state = windowNormal
} else {
t.state = windowMinimized
}
t.bufferDirty = true
}
} else {
if t.client != nil {
t.client.Out <- t.inputBuffer
if len(t.inputBuffer) > 0 {
if t.inputBuffer[0] == '/' {
// TODO add chat modes and show (kibitz/yell)
t.inputBuffer = t.inputBuffer[1:]
}
t.client.Out <- t.inputBuffer
}
} else {
fibs.StatusWriter.Write([]byte("* You have not connected to a server yet"))
}
t.inputBuffer = nil
t.bufferDirty = true
}
t.bufferDirty = true
}
// TODO add show virtual keyboard button

View File

@ -1,6 +1,9 @@
package game
import (
"bytes"
"unicode"
"github.com/hajimehoshi/ebiten/v2"
)
@ -19,7 +22,7 @@ type textBuffer struct {
}
func (b *textBuffer) Write(p []byte) {
b.content = append(b.content, p)
b.content = append(b.content, bytes.TrimRightFunc(p, unicode.IsSpace))
b.wrapDirty = true
b.tab.bufferDirty = true

6
go.mod
View File

@ -8,7 +8,6 @@ require (
github.com/hajimehoshi/ebiten/v2 v2.2.2
github.com/llgcode/draw2d v0.0.0-20210904075650-80aa0a2a901d
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
golang.org/x/exp v0.0.0-20211105205138-14c72366447f
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
)
@ -21,9 +20,10 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/reiver/go-oi v1.0.0 // indirect
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e // indirect
golang.org/x/mobile v0.0.0-20211103151657-e68c98865fb2 // indirect
golang.org/x/exp v0.0.0-20211109222223-9df80dc805b5 // indirect
golang.org/x/mobile v0.0.0-20211109191125-d61a72f26a1a // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e // indirect
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 // indirect
golang.org/x/text v0.3.7 // indirect
nhooyr.io/websocket v1.8.7 // indirect
)

12
go.sum
View File

@ -351,8 +351,8 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20211012155715-ffe10e552389/go.mod h1:a3o/VtDNHN+dCVLEpzjjUHOzR+Ln3DHX056ZPzoZGGA=
golang.org/x/exp v0.0.0-20211105205138-14c72366447f h1:LOrKZCSwatuEIzc+tzZRm7m5pmv+xxDXjGqElpd2LGA=
golang.org/x/exp v0.0.0-20211105205138-14c72366447f/go.mod h1:OyI624f2tQ/aU3IMa7GB16Hk54CHURAfHfj6tMqtyhA=
golang.org/x/exp v0.0.0-20211109222223-9df80dc805b5 h1:z5bapnFL1WqWwTUpFQTSwLVQEgLuY39KXDYgBTM+DrI=
golang.org/x/exp v0.0.0-20211109222223-9df80dc805b5/go.mod h1:OyI624f2tQ/aU3IMa7GB16Hk54CHURAfHfj6tMqtyhA=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -370,8 +370,8 @@ golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mobile v0.0.0-20211103151657-e68c98865fb2 h1:SFpMH29/c2+VPCOcOlLPQiAVqU2sXiFvwfdtQfVEEJ8=
golang.org/x/mobile v0.0.0-20211103151657-e68c98865fb2/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20211109191125-d61a72f26a1a h1:VGK2tvC63sblAsLlPgn0AXI/qQJfdEODdMGCJdgmCBw=
golang.org/x/mobile v0.0.0-20211109191125-d61a72f26a1a/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -442,8 +442,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e h1:9nbuBbpiqktwdlzHKUohsD5+y2a0QvX98gIWK2ARkqc=
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

11
main.go
View File

@ -3,13 +3,14 @@ package main
import (
"bufio"
"log"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"code.rocketnine.space/tslocum/fibs"
"code.rocketnine.space/tslocum/boxcars/game"
"code.rocketnine.space/tslocum/fibs"
"github.com/hajimehoshi/ebiten/v2"
)
@ -40,6 +41,12 @@ func main() {
fibs.Debug = g.Debug
if g.Debug > 0 {
go func() {
log.Fatal(http.ListenAndServe("localhost:8880", nil))
}()
}
if AutoWatch {
g.Watch = true
}