Draw screen only when required

This commit is contained in:
Trevor Slocum 2021-08-30 21:26:49 -07:00
parent 23fba33928
commit 0c1c6ea6c3
7 changed files with 265 additions and 99 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
.idea
*.sh
*.wasm
boxcars
dist

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -51,6 +51,8 @@ type board struct {
moveQueue chan *stateUpdate
drawFrame chan bool
debug int // Print and draw debug information
}
@ -69,6 +71,7 @@ func NewBoard() *board {
spaceRects: make([][4]int, 26),
V: make([]int, 42),
moveQueue: make(chan *stateUpdate, 10),
drawFrame: make(chan bool, 10),
}
for i := range b.Sprites.sprites {
@ -88,6 +91,8 @@ func NewBoard() *board {
b.spaces[space] = append(b.spaces[space], s)
}
go b.handleDraw()
go b.handlePieceMoves()
b.op = &ebiten.DrawImageOptions{}
@ -97,6 +102,31 @@ func NewBoard() *board {
return b
}
func (b *board) handleDraw() {
drawFreq := time.Second / 144 // TODO
lastDraw := time.Now()
for v := range b.drawFrame {
if !v {
return
}
since := time.Since(lastDraw)
if since < drawFreq {
t := time.NewTimer(drawFreq - since)
DELAYDRAW:
for {
select {
case <-b.drawFrame:
continue DELAYDRAW
case <-t.C:
break DELAYDRAW
}
}
}
ebiten.ScheduleFrame()
lastDraw = time.Now()
}
}
func (b *board) newSprite(white bool) *Sprite {
s := &Sprite{}
s.colorWhite = white
@ -106,6 +136,18 @@ func (b *board) newSprite(white bool) *Sprite {
func (b *board) updateBackgroundImage() {
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}
borderSize := b.horizontalBorderSize
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)
// Table
box := image.NewRGBA(image.Rect(0, 0, b.w, b.h))
@ -113,39 +155,33 @@ func (b *board) updateBackgroundImage() {
img.Fill(tableColor)
b.backgroundImage = ebiten.NewImageFromImage(img)
// Border
borderColor := color.RGBA{65, 40, 14, 255}
borderSize := b.horizontalBorderSize
if borderSize > b.barWidth/2 {
borderSize = b.barWidth / 2
}
box = image.NewRGBA(image.Rect(0, 0, b.w-((b.horizontalBorderSize-borderSize)*2), b.h))
// Frame
box = image.NewRGBA(image.Rect(0, 0, frameW, b.h))
img = ebiten.NewImageFromImage(box)
img.Fill(borderColor)
img.Fill(frameColor)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64(b.horizontalBorderSize-borderSize), 0)
b.backgroundImage.DrawImage(img, b.op)
// Face
box = image.NewRGBA(image.Rect(0, 0, b.w-(b.horizontalBorderSize*2), b.h-(b.verticalBorderSize*2)))
box = image.NewRGBA(image.Rect(0, 0, innerW, b.h-(b.verticalBorderSize*2)))
img = ebiten.NewImageFromImage(box)
img.Fill(color.RGBA{120, 63, 25, 255})
img.Fill(faceColor)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64(b.horizontalBorderSize), float64(b.verticalBorderSize))
b.backgroundImage.DrawImage(img, b.op)
baseImg := image.NewRGBA(image.Rect(0, 0, b.w-(b.horizontalBorderSize*2), b.h-(b.verticalBorderSize*2)))
gc := draw2dimg.NewGraphicContext(baseImg)
// Bar
box = image.NewRGBA(image.Rect(0, 0, b.barWidth, b.h))
img = ebiten.NewImageFromImage(box)
img.Fill(borderColor)
img.Fill(frameColor)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64((b.w/2)-(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)))
gc := draw2dimg.NewGraphicContext(baseImg)
for i := 0; i < 2; i++ {
triangleTip := float64((b.h - (b.verticalBorderSize * 2)) / 2)
if i == 0 {
@ -160,9 +196,9 @@ func (b *board) updateBackgroundImage() {
}
if colorA {
gc.SetFillColor(color.RGBA{219.0, 185, 113, 255})
gc.SetFillColor(triangleA)
} else {
gc.SetFillColor(color.RGBA{120.0, 17.0, 0, 255})
gc.SetFillColor(triangleB)
}
tx := b.spaceWidth * j
@ -177,12 +213,56 @@ func (b *board) updateBackgroundImage() {
gc.Fill()
}
}
img = ebiten.NewImageFromImage(baseImg)
b.op.GeoM.Reset()
b.op.GeoM.Translate(float64(b.horizontalBorderSize), float64(b.verticalBorderSize))
b.backgroundImage.DrawImage(img, b.op)
// Border
borderImage := image.NewRGBA(image.Rect(0, 0, b.w, b.h))
gc = draw2dimg.NewGraphicContext(borderImage)
gc.SetStrokeColor(borderColor)
// - Outside left
gc.SetLineWidth(2)
gc.MoveTo(float64(1), float64(0))
gc.LineTo(float64(1), float64(b.h))
// - Center
gc.SetLineWidth(2)
gc.MoveTo(float64(frameW/2), float64(0))
gc.LineTo(float64(frameW/2), float64(b.h))
// - Outside right
gc.MoveTo(float64(frameW), float64(0))
gc.LineTo(float64(frameW), float64(b.h))
gc.Close()
gc.Stroke()
// - Inside left
gc.SetLineWidth(1)
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(float64(borderSize), float64(b.verticalBorderSize))
gc.Close()
gc.Stroke()
// - Inside right
edgeStart := float64((innerW / 2) + (b.barWidth / 2) + borderSize)
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(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.backgroundImage.DrawImage(img, b.op)
}
func (b *board) ScheduleFrame() {
b.drawFrame <- true
}
func (b *board) draw(screen *ebiten.Image) {
@ -280,7 +360,7 @@ func (b *board) setRect(x, y, w, h int) {
b.horizontalBorderSize = 0
b.triangleOffset = float64(b.h-(b.verticalBorderSize*2)) / 33
b.triangleOffset = float64(b.h-(b.verticalBorderSize*2)) / 15
for {
b.verticalBorderSize = 7 // TODO configurable
@ -304,7 +384,6 @@ func (b *board) setRect(x, y, w, h int) {
if extraSpace >= largeBarWidth {
b.barWidth = largeBarWidth
}
// TODO barwidth in calcs is wrong
b.horizontalBorderSize = ((b.w - (b.spaceWidth * 12)) - b.barWidth) / 2
if b.horizontalBorderSize < 0 {
@ -312,6 +391,7 @@ func (b *board) setRect(x, y, w, h int) {
}
loadAssets(b.spaceWidth)
for i := 0; i < b.Sprites.num; i++ {
s := b.Sprites.sprites[i]
s.w, s.h = imgCheckerWhite.Size()
@ -524,37 +604,77 @@ func (b *board) ProcessState() {
func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int) {
moveSize := 1
moveDelay := time.Duration(2/speed) * time.Millisecond
moveDelay := time.Duration(1/speed) * time.Millisecond
stackTo := len(b.spaces[to])
if stackTo == 1 && sprite.colorWhite != b.spaces[to][0].colorWhite {
stackTo = 0 // Hit
}
x, y, _, _ := b.stackSpaceRect(to, stackTo)
x, y = b.offsetPosition(x, y)
space := from
for {
if space == to {
break
} else if to > space {
space++
} else {
space--
}
if sprite.x != x {
// Center
cy := (b.h / 2) - (b.spaceWidth / 2)
for {
if sprite.y == cy {
break
}
if sprite.y < cy {
sprite.y += moveSize
if sprite.y > cy {
sprite.y = cy
// Go to bar or home immediately
if from == 0 || from == 25 || to == 0 || to == 25 {
space = to
}
stack := len(b.spaces[space])
if stack == 1 && sprite.colorWhite != b.spaces[space][0].colorWhite {
stack = 0 // Hit
}
x, y, _, _ := b.stackSpaceRect(space, stack)
x, y = b.offsetPosition(x, y)
cy := y
if cy > sprite.y == b.bottomRow(space) {
cy = sprite.y
}
if sprite.x != x {
// Center
for {
if sprite.y == cy {
break
}
} else if sprite.y > cy {
sprite.y -= moveSize
if sprite.y < cy {
sprite.y = cy
sprite.y += moveSize
if sprite.y > cy {
sprite.y = cy
}
} else if sprite.y > cy {
sprite.y -= moveSize
if sprite.y < cy {
sprite.y = cy
}
}
b.ScheduleFrame()
time.Sleep(moveDelay)
}
for {
if sprite.x == x {
break
}
if sprite.x < x {
sprite.x += moveSize
if sprite.x > x {
sprite.x = x
}
} else if sprite.x > x {
sprite.x -= moveSize
if sprite.x < x {
sprite.x = x
}
}
b.ScheduleFrame()
time.Sleep(moveDelay / 2)
}
time.Sleep(moveDelay)
}
for {
if sprite.x == x {
if sprite.x == x && sprite.y == y {
break
}
if sprite.x < x {
@ -568,36 +688,20 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int) {
sprite.x = x
}
}
time.Sleep(moveDelay / 2)
}
}
for {
if sprite.x == x && sprite.y == y {
break
}
if sprite.x < x {
sprite.x += moveSize
if sprite.x > x {
sprite.x = x
}
} else if sprite.x > x {
sprite.x -= moveSize
if sprite.x < x {
sprite.x = x
}
}
if sprite.y < y {
sprite.y += moveSize
if sprite.y > y {
sprite.y = y
}
} else if sprite.y > y {
sprite.y -= moveSize
if sprite.y < y {
sprite.y = y
sprite.y += moveSize
if sprite.y > y {
sprite.y = y
}
} else if sprite.y > y {
sprite.y -= moveSize
if sprite.y < y {
sprite.y = y
}
}
b.ScheduleFrame()
time.Sleep(moveDelay)
}
time.Sleep(moveDelay)
}
// TODO do not add bear off pieces
@ -609,6 +713,10 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int) {
}
}
b.moving = nil
b.ScheduleFrame()
time.Sleep(time.Second)
}
func (b *board) handlePieceMoves() {
@ -638,8 +746,11 @@ func (b *board) handlePieceMoves() {
b._movePiece(sprite, from, to, 1)
if moveAfter != nil {
toBar := 0
if b.V[fibs.StateDirection] == 1 {
toBar = 25
if b.V[fibs.StateTurn] == b.V[fibs.StatePlayerColor] {
toBar = 25 // TODO how is this determined?
}
if b.V[fibs.StateDirection] == -1 {
toBar = 25 - toBar
}
b._movePiece(moveAfter, to, toBar, 2)
}
@ -658,9 +769,8 @@ func (b *board) update() {
// TODO allow grabbing multiple pieces by grabbing further down the stack
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
x, y := ebiten.CursorPosition()
if b.dragging == nil {
x, y := ebiten.CursorPosition()
s := b.spriteAt(x, y)
if s != nil {
b.dragging = s

View file

@ -8,10 +8,12 @@ import (
"image/color"
_ "image/png"
"log"
"os"
"strings"
"time"
"code.rocketnine.space/tslocum/fibs"
"code.rocketnine.space/tslocum/kibodo"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
@ -162,6 +164,9 @@ type Game struct {
inputBuffer string
Debug int
keyboard *kibodo.Keyboard
shownKeyboard bool
}
func NewGame() *Game {
@ -176,8 +181,12 @@ func NewGame() *Game {
Board: NewBoard(),
runeBuffer: make([]rune, 24),
}
keyboard: kibodo.NewKeyboard(),
}
g.keyboard.SetKeys(kibodo.KeysQWERTY)
// TODO
go func() {
t := time.NewTicker(time.Second / 4)
for range t.C {
@ -270,10 +279,30 @@ func (g *Game) Update() error { // Called by ebiten only when input occurs
return err
}
if ebiten.IsWindowBeingClosed() {
g.Exit()
return nil
}
err = g.keyboard.Update()
if err != nil {
return fmt.Errorf("failed to update virtual keyboard: %s", err)
}
if !g.loggedIn {
f := func() {
var clearBuffer bool
defer func() {
if strings.ContainsRune(g.inputBuffer, '\n') {
g.inputBuffer = strings.Split(g.inputBuffer, "\n")[0]
clearBuffer = true
}
if !g.usernameConfirmed {
g.Username = g.inputBuffer
} else {
g.Password = g.inputBuffer
}
if clearBuffer {
g.inputBuffer = ""
@ -285,6 +314,23 @@ func (g *Game) Update() error { // Called by ebiten only when input occurs
}
}()
if !g.shownKeyboard {
ch := make(chan *kibodo.Input, 10)
go func() {
for input := range ch {
if input.Rune > 0 {
g.inputBuffer += string(input.Rune)
continue
}
if input.Key == ebiten.KeyEnter {
g.inputBuffer += "\n"
}
}
}()
g.keyboard.Show(ch)
g.shownKeyboard = true
}
if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) && len(g.inputBuffer) > 0 {
g.inputBuffer = g.inputBuffer[:len(g.inputBuffer)-1]
}
@ -296,17 +342,6 @@ func (g *Game) Update() error { // Called by ebiten only when input occurs
g.runeBuffer = ebiten.AppendInputChars(g.runeBuffer[:0])
if len(g.runeBuffer) > 0 {
g.inputBuffer += string(g.runeBuffer)
if strings.ContainsRune(g.inputBuffer, '\n') {
g.inputBuffer = strings.Split(g.inputBuffer, "\n")[0]
clearBuffer = true
}
if !g.usernameConfirmed {
g.Username = g.inputBuffer
} else {
g.Password = g.inputBuffer
}
log.Println("INPUT BUFFER IS:" + g.inputBuffer)
}
}
@ -350,6 +385,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Log in screen
if !g.loggedIn {
g.keyboard.Draw(screen)
const welcomeText = `Please enter your FIBS username and password.
If you do not have a FIBS account yet, visit
http://www.fibs.com/help.html#register`
@ -372,19 +409,18 @@ http://www.fibs.com/help.html#register`
g.Board.draw(screen)
if g.Debug == 1 {
if g.Debug > 0 {
debugBox := image.NewRGBA(image.Rect(10, 20, 200, 200))
debugImg := ebiten.NewImageFromImage(debugBox)
g.drawBuffer.Reset()
g.drawBuffer.Write([]byte(fmt.Sprintf("FPS %0.0f\nTPS %0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS())))
g.drawBuffer.Write([]byte(fmt.Sprintf("FPS %0.0f %c\nTPS %0.0f", ebiten.CurrentFPS(), spinner[g.spinnerIndex], ebiten.CurrentTPS())))
/* TODO enable when vsync is able to be turned off
g.spinnerIndex++
if g.spinnerIndex == 4 {
g.spinnerIndex = 0
}*/
}
scaleFactor := ebiten.DeviceScaleFactor()
if scaleFactor != 1.0 {
@ -415,9 +451,17 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
g.screenW, g.screenH = outsideWidth, outsideHeight
g.Board.setRect(0, 0, g.screenW, g.screenH)
displayArea := 200
g.keyboard.SetRect(0, displayArea, g.screenW, g.screenH-displayArea)
return outsideWidth, outsideHeight
}
func (g *Game) resetImageOptions() {
g.op.GeoM.Reset()
}
func (g *Game) Exit() {
g.Board.drawFrame <- false
os.Exit(0)
}

3
go.mod
View file

@ -4,7 +4,8 @@ go 1.17
require (
code.rocketnine.space/tslocum/fibs v0.0.0-00010101000000-000000000000
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210825183521-91a72880271d
code.rocketnine.space/tslocum/kibodo v0.0.0-20210830194839-05789279ce56
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210828073710-0e5dca9453a5
github.com/llgcode/draw2d v0.0.0-20210313082411-577c1ead272a
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d

6
go.sum
View file

@ -1,3 +1,5 @@
code.rocketnine.space/tslocum/kibodo v0.0.0-20210830194839-05789279ce56 h1:+KVT4Zw9CEQkxdNP81wuTvX3EHRPpPUQFwn6UVLoMFY=
code.rocketnine.space/tslocum/kibodo v0.0.0-20210830194839-05789279ce56/go.mod h1:nWGK8LvmYgMZQcwGMYOOuZ19VsVYL5E1hREEJ2gV46M=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326 h1:QqWaXlVeUGwSH7hO8giZP2Y06Qjl1LWR+FWC22YQsU8=
@ -8,8 +10,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hajimehoshi/bitmapfont/v2 v2.1.3 h1:JefUkL0M4nrdVwVq7MMZxSTh6mSxOylm+C4Anoucbb0=
github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210825183521-91a72880271d h1:EMEmUvZhS8hZ4eq1732CWs7aKcSWXhVvcxllfhVZc9Y=
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210825183521-91a72880271d/go.mod h1:B4Cje+Kb1ZjztrKFPaiMWOGXO3Vp8u+zIBdxkZqkyD4=
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210828073710-0e5dca9453a5 h1:fUSKz2wvklV02UTmBXXDlNKc6molRGUu5O8b80AvEa4=
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.12.0.20210828073710-0e5dca9453a5/go.mod h1:B4Cje+Kb1ZjztrKFPaiMWOGXO3Vp8u+zIBdxkZqkyD4=
github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=

20
main.go
View file

@ -5,6 +5,8 @@ import (
"flag"
"log"
"os"
"os/signal"
"syscall"
"time"
"code.rocketnine.space/tslocum/boxcars/game"
@ -20,14 +22,10 @@ func main() {
ebiten.SetWindowTitle("Boxcars")
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowResizable(true)
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)
ebiten.SetMaxTPS(60) // TODO allow users to set custom value
ebiten.SetRunnableOnUnfocused(true) // Note - this currently does nothing in ebiten
// TODO set up system to call scheduleframe automatically
//ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)
// TODO breaks justpressedkey
//ebiten.SetWindowClosingHandled(true) TODO implement
ebiten.SetWindowClosingHandled(true)
fullscreenWidth, fullscreenHeight := ebiten.ScreenSizeInFullscreen()
if fullscreenWidth <= screenWidth || fullscreenHeight <= screenHeight {
@ -68,6 +66,16 @@ func main() {
}
}()
sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
syscall.SIGINT,
syscall.SIGTERM)
go func() {
<-sigc
g.Exit()
}()
if err := ebiten.RunGame(g); err != nil {
log.Fatal(err)
}