Rewrite as client for bgammon.org

This commit is contained in:
Trevor Slocum 2023-09-13 22:56:04 -07:00
parent 67cb241e1e
commit fe9268f21e
16 changed files with 417 additions and 766 deletions

141
LICENSE
View File

@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@ -7,17 +7,15 @@
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -1,43 +1,16 @@
# boxcars
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
Online backgammon client ([FIBS](http://fibs.com))
# boxcars - Graphical backgammon client for bgammon.org
[![Donate](https://img.shields.io/liberapay/receives/rocket9labs.com.svg?logo=liberapay)](https://liberapay.com/rocket9labs.com)
**Note:** This application is in pre-alpha state. Here be dragons.
## Play
There are several ways to play Boxcars.
### Browser (recommended)
Visit https://boxcars.rocketnine.space
### Desktop
**Note:** You will need to install the dependencies listed for [your platform](https://github.com/hajimehoshi/ebiten/blob/main/README.md#platforms).
Run the following command to build a `boxcars` executable:
`go install code.rocketnine.space/tslocum/boxcars@latest`
Run `~/go/bin/boxcars` to play.
### Android
*Coming soon*
## Gameplay notes
- To bear off a piece, drag it from your home on to the bar.
## Support
Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/boxcars/issues).
Please share issues and suggestions [here](https://code.rocket9labs.com/tslocum/boxcars/issues).
## Dependencies
- [ebiten](https://github.com/hajimehoshi/ebiten) - 2D game engine
- [draw2d](https://github.com/llgcode/draw2d) - 2D shape drawing
- [kibodo](https://code.rocketnine.space/tslocum/kibodo) - On-screen keyboard
- [ebitengine](https://github.com/hajimehoshi/ebiten) - Game engine
- [draw2d](https://github.com/llgcode/draw2d) - Shape drawing
- [messeji](https://code.rocket9labs.com/tslocum/messeji) - Text display and input widgets
- [kibodo](https://code.rocket9labs.com/tslocum/kibodo) - On-screen keyboard
- [resize](https://github.com/nfnt/resize) - Image resizing

View File

@ -3,12 +3,6 @@
package main
import (
"code.rocketnine.space/tslocum/fibs"
)
func init() {
fibs.DefaultProxyAddress = "wss://fibsproxy.rocketnine.space"
AutoWatch = true
}

View File

@ -6,13 +6,13 @@ package main
import (
"flag"
"code.rocketnine.space/tslocum/boxcars/game"
"code.rocket9labs.com/tslocum/boxcars/game"
)
func parseFlags(g *game.Game) {
flag.StringVar(&g.Username, "username", "", "Username")
flag.StringVar(&g.Password, "password", "", "Password")
flag.StringVar(&g.ServerAddress, "address", "fibs.com:4321", "Server address")
flag.StringVar(&g.ServerAddress, "address", game.DefaultServerAddress, "Server address")
flag.BoolVar(&g.Watch, "watch", false, "Watch random game")
flag.BoolVar(&g.TV, "tv", false, "Watch random games continuously")
flag.IntVar(&g.Debug, "debug", 0, "Print debug information")

View File

@ -7,7 +7,7 @@ import (
"net/http"
"syscall/js"
"code.rocketnine.space/tslocum/boxcars/game"
"code.rocket9labs.com/tslocum/boxcars/game"
)
func parseFlags(g *game.Game) {

View File

@ -8,8 +8,9 @@ import (
"os"
"time"
"code.rocketnine.space/tslocum/fibs"
"github.com/hajimehoshi/ebiten/v2"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
@ -30,8 +31,8 @@ type board struct {
Sprites *Sprites
spaces [][]*Sprite // Space contents
spaceRects [][4]int
spaceSprites [][]*Sprite // Space contents
spaceSpriteRects [][4]int
dragging *Sprite
moving *Sprite // Moving automatically
@ -46,16 +47,15 @@ type board struct {
verticalBorderSize float64
overlapSize float64
lastDirection int
lastPlayerNumber int
s []string
v []int
gameState *bgammon.GameState
drawFrame chan bool
debug int // Print and draw debug information
Client *fibs.Client
Client *Client
dragX, dragY int
}
@ -71,11 +71,12 @@ func NewBoard() *board {
sprites: make([]*Sprite, 30),
num: 30,
},
spaces: make([][]*Sprite, 26),
spaceRects: make([][4]int, 26),
s: make([]string, 2),
v: make([]int, 42),
drawFrame: make(chan bool, 10),
spaceSprites: make([][]*Sprite, 26),
spaceSpriteRects: make([][4]int, 26),
drawFrame: make(chan bool, 10),
gameState: &bgammon.GameState{
Game: bgammon.NewGame(),
},
}
for i := range b.Sprites.sprites {
@ -92,7 +93,7 @@ func NewBoard() *board {
}
}
b.spaces[space] = append(b.spaces[space], s)
b.spaceSprites[space] = append(b.spaceSprites[space], s)
}
go b.handleDraw()
@ -373,7 +374,7 @@ func (b *board) draw(screen *ebiten.Image) {
b.iterateSpaces(func(space int) {
var numPieces int
for i, sprite := range b.spaces[space] {
for i, sprite := range b.spaceSprites[space] {
if sprite == b.dragging || sprite == b.moving {
continue
}
@ -448,7 +449,7 @@ func (b *board) draw(screen *ebiten.Image) {
opponentColor := color.Black
playerBorderColor := lightCheckerColor
opponentBorderColor := darkCheckerColor
if b.v[fibs.StatePlayerColor] == -1 {
if b.gameState.PlayerNumber == 1 {
playerColor = color.Black
opponentColor = color.White
playerBorderColor = darkCheckerColor
@ -481,10 +482,11 @@ func (b *board) draw(screen *ebiten.Image) {
return img
}
if b.s[fibs.StateOpponentName] != "" {
label := fmt.Sprintf("%s %d %d", b.s[fibs.StateOpponentName], b.v[fibs.StateOpponentDice1], b.v[fibs.StateOpponentDice2])
opponent := b.gameState.OpponentPlayer()
if opponent.Name != "" {
label := fmt.Sprintf("%s %d %d", opponent.Name, b.gameState.Roll1, b.gameState.Roll2)
img := drawLabel(label, opponentColor, b.v[fibs.StateTurn] != b.v[fibs.StatePlayerColor], opponentBorderColor)
img := drawLabel(label, opponentColor, b.gameState.Turn != b.gameState.PlayerNumber, opponentBorderColor)
bounds := img.Bounds()
x := int(((float64(b.innerW) - borderSize) / 4) - (float64(bounds.Dx()) / 2))
@ -497,10 +499,11 @@ func (b *board) draw(screen *ebiten.Image) {
// Draw player name and dice
if b.s[fibs.StatePlayerName] != "" {
label := fmt.Sprintf("%s %d %d", b.s[fibs.StatePlayerName], b.v[fibs.StatePlayerDice1], b.v[fibs.StatePlayerDice2])
player := b.gameState.LocalPlayer()
if player.Name != "" {
label := fmt.Sprintf("%s %d %d", player.Name, b.gameState.Roll1, b.gameState.Roll2)
img := drawLabel(label, playerColor, b.v[fibs.StateTurn] == b.v[fibs.StatePlayerColor], playerBorderColor)
img := drawLabel(label, playerColor, b.gameState.Turn == b.gameState.PlayerNumber, playerBorderColor)
bounds := img.Bounds()
x := ((b.innerW / 4) * 3) - (bounds.Dx() / 2)
@ -512,7 +515,7 @@ func (b *board) draw(screen *ebiten.Image) {
}
if len(b.Client.Board.GetPreMoves()) > 0 {
if len(b.gameState.Moves) > 0 {
x, y, w, h := b.resetButtonRect()
baseImg := image.NewRGBA(image.Rect(0, 0, w, h))
@ -549,7 +552,7 @@ func (b *board) draw(screen *ebiten.Image) {
}
if b.debug == 2 {
homeStart, homeEnd := b.Client.Board.PlayerHomeSpaces()
homeStart, homeEnd := bgammon.HomeRange(b.gameState.PlayerNumber)
b.iterateSpaces(func(space int) {
x, y, w, h := b.spaceRect(space)
spaceImage := ebiten.NewImage(w, h)
@ -557,9 +560,9 @@ func (b *board) draw(screen *ebiten.Image) {
if space >= homeStart && space <= homeEnd {
br += "H"
}
if space == b.Client.Board.PlayerBarSpace() {
if space == bgammon.SpaceBarPlayer {
br += "(PB)"
} else if space == 25-b.Client.Board.PlayerBarSpace() {
} else if space == bgammon.SpaceBarOpponent {
br += "(OB)"
}
ebitenutil.DebugPrint(spaceImage, fmt.Sprintf(" %d %s", space, br))
@ -629,7 +632,7 @@ func (b *board) offsetPosition(x, y int) (int, int) {
// Do not call _positionCheckers directly. Call ProcessState instead.
func (b *board) _positionCheckers() {
for space := 0; space < 26; space++ {
sprites := b.spaces[space]
sprites := b.spaceSprites[space]
for i := range sprites {
s := sprites[i]
@ -652,7 +655,7 @@ func (b *board) spriteAt(x, y int) *Sprite {
if space == -1 {
return nil
}
pieces := b.spaces[space]
pieces := b.spaceSprites[space]
if len(pieces) == 0 {
return nil
}
@ -677,7 +680,7 @@ func (b *board) iterateSpaces(f func(space int)) {
}
func (b *board) translateSpace(space int) int {
if b.v[fibs.StateDirection] == -1 {
/*if b.gameState.PlayerNumber == 2 {
// Spaces range from 24 - 1.
if space == 0 || space == 25 {
space = 25 - space
@ -686,7 +689,8 @@ func (b *board) translateSpace(space int) int {
} else {
space = space - 12
}
}
}*/
// TODO
return space
}
@ -728,13 +732,13 @@ func (b *board) setSpaceRects() {
h = int((float64(b.h) - (b.verticalBorderSize * 2)) / 2)
b.spaceRects[trueSpace] = [4]int{x, y, w, h}
b.spaceSpriteRects[trueSpace] = [4]int{x, y, w, h}
}
}
// relX, relY
func (b *board) spaceRect(space int) (x, y, w, h int) {
rect := b.spaceRects[space]
rect := b.spaceSpriteRects[space]
return rect[0], rect[1], rect[2], rect[3]
}
@ -742,7 +746,7 @@ func (b *board) bottomRow(space int) bool {
bottomStart := 1
bottomEnd := 12
bottomBar := 25
if b.v[fibs.StateDirection] == 1 {
if b.gameState.PlayerNumber == 2 {
bottomStart = 13
bottomEnd = 24
bottomBar = 0
@ -784,38 +788,20 @@ func (b *board) stackSpaceRect(space int, stack int) (x, y, w, h int) {
return x, y, w, h
}
func (b *board) SetState(s []string, v []int) {
log.Printf("OLD %+v", b.v)
log.Printf("NEW %+v", v)
for i := 0; i < 26; i++ {
var str string
if b.v[fibs.StateBoardSpace0+i] != v[fibs.StateBoardSpace0+i] {
str = "^^^"
}
log.Printf("SPACE %d WAS %d, NOW %d %s", i, b.v[fibs.StateBoardSpace0+i], v[fibs.StateBoardSpace0+i], str)
}
copy(b.s, s)
copy(b.v, v)
b.ProcessState()
}
func (b *board) ProcessState() {
v := b.v
if b.lastDirection != v[fibs.StateDirection] {
if b.lastPlayerNumber != b.gameState.PlayerNumber {
b.setSpaceRects()
}
b.lastDirection = v[fibs.StateDirection]
b.lastPlayerNumber = b.gameState.PlayerNumber
b.Sprites = &Sprites{}
b.spaces = make([][]*Sprite, 26)
for space := 0; space < 26; space++ {
spaceValue := v[fibs.StateBoardSpace0+space]
b.spaceSprites = make([][]*Sprite, 26)
for space := 0; space < bgammon.BoardSpaces; space++ {
spaceValue := b.gameState.Board[space]
white := spaceValue > 0
if spaceValue == 0 {
white = v[fibs.StatePlayerColor] == 1
white = b.gameState.PlayerNumber == 2
}
abs := spaceValue
@ -825,18 +811,19 @@ func (b *board) ProcessState() {
var preMovesTo int
var preMovesFrom int
if b.Client != nil {
/*if b.Client != nil {
preMovesTo = b.Client.Board.Premoveto[space]
preMovesFrom = b.Client.Board.Premovefrom[space]
}
}*/
// TODO
for i := 0; i < abs+(preMovesTo-preMovesFrom); i++ {
s := b.newSprite(white)
if i >= abs {
s.colorWhite = v[fibs.StatePlayerColor] == 1
s.colorWhite = b.gameState.PlayerNumber == 2
s.premove = true
}
b.spaces[space] = append(b.spaces[space], s)
b.spaceSprites[space] = append(b.spaceSprites[space], s)
b.Sprites.sprites = append(b.Sprites.sprites, s)
}
}
@ -847,15 +834,15 @@ func (b *board) ProcessState() {
// _movePiece returns after moving the piece.
func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int, pause bool) {
moveTime := (750 * time.Millisecond) / time.Duration(speed)
pauseTime := 500 * time.Millisecond
moveTime := (650 * time.Millisecond) / time.Duration(speed)
pauseTime := 250 * time.Millisecond
b.moving = sprite
space := to // Immediately go to target space
stack := len(b.spaces[space])
if stack == 1 && sprite.colorWhite != b.spaces[space][0].colorWhite {
stack := len(b.spaceSprites[space])
if stack == 1 && sprite.colorWhite != b.spaceSprites[space][0].colorWhite {
stack = 0 // Hit
} else if space != to {
stack++
@ -879,16 +866,16 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int, pause bo
ebiten.ScheduleFrame()
/*homeSpace := b.Client.Board.PlayerHomeSpace()
if b.v[fibs.StateTurn] != b.v[fibs.StatePlayerColor] {
if b.gameState.Turn != b.gameState.Player {
homeSpace = 25 - homeSpace
}
if to != homeSpace {*/
b.spaces[to] = append(b.spaces[to], sprite)
b.spaceSprites[to] = append(b.spaceSprites[to], sprite)
/*}*/
for i, s := range b.spaces[from] {
for i, s := range b.spaceSprites[from] {
if s == sprite {
b.spaces[from] = append(b.spaces[from][:i], b.spaces[from][i+1:]...)
b.spaceSprites[from] = append(b.spaceSprites[from][:i], b.spaceSprites[from][i+1:]...)
break
}
}
@ -903,7 +890,7 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int, pause bo
// movePiece returns when finished moving the piece.
func (b *board) movePiece(from int, to int) {
pieces := b.spaces[from]
pieces := b.spaceSprites[from]
if len(pieces) == 0 {
log.Printf("%d-%d: NO PIECES AT SPACE %d", from, to, from)
os.Exit(1)
@ -913,17 +900,17 @@ func (b *board) movePiece(from int, to int) {
sprite := pieces[len(pieces)-1]
var moveAfter *Sprite
if len(b.spaces[to]) == 1 {
if sprite.colorWhite != b.spaces[to][0].colorWhite {
moveAfter = b.spaces[to][0]
if len(b.spaceSprites[to]) == 1 {
if sprite.colorWhite != b.spaceSprites[to][0].colorWhite {
moveAfter = b.spaceSprites[to][0]
}
}
b._movePiece(sprite, from, to, 1, moveAfter == nil)
if moveAfter != nil {
bar := b.Client.Board.PlayerBarSpace()
if b.v[fibs.StateTurn] == b.v[fibs.StatePlayerColor] {
bar = 25 - bar
bar := bgammon.SpaceBarOpponent
if b.gameState.Turn == b.gameState.PlayerNumber {
bar = bgammon.SpaceBarPlayer
}
b._movePiece(moveAfter, to, bar, 1, true)
}
@ -931,16 +918,16 @@ func (b *board) movePiece(from int, to int) {
// WatchingGame returns whether the active game is being watched.
func (b *board) watchingGame() bool {
return !b.playingGame() && b.s[fibs.StatePlayerName] != "" && b.s[fibs.StateOpponentName] != ""
return !b.playingGame() && false // TODO
}
// PlayingGame returns whether the active game is being played.
func (b *board) playingGame() bool {
return b.s[fibs.StatePlayerName] == "You" || b.s[fibs.StateOpponentName] == "You"
return b.gameState.Player1.Name != "" || b.gameState.Player2.Name != ""
}
func (b *board) playerTurn() bool {
return b.playingGame() && b.v[fibs.StateTurn] == b.v[fibs.StatePlayerColor]
return b.playingGame() && b.gameState.Turn == b.gameState.PlayerNumber
}
func (b *board) update() {
@ -952,14 +939,15 @@ func (b *board) update() {
// TODO allow grabbing multiple pieces by grabbing further down the stack
handleReset := func(x, y int) bool {
if len(b.Client.Board.GetPreMoves()) > 0 {
/*if len(b.gameState.Moves) > 0 {
rx, ry, rw, rh := b.resetButtonRect()
if x >= rx && x <= rx+rw && y >= ry && y <= ry+rh {
b.Client.Board.ResetPreMoves()
b.ProcessState()
return true
}
}
}*/
panic("RESET") // TODO
return false
}
@ -1017,17 +1005,18 @@ func (b *board) update() {
if dropped != nil {
index := b.spaceAt(x, y)
// Bear off by dragging outside the board.
if index == fibs.SpaceUnknown && b.Client.Board.PlayerPieceAreHome() {
index = b.Client.Board.PlayerBearOffSpace()
if index == -1 {
// TODO check if all pieces are home
index = bgammon.SpaceHomePlayer
}
if index >= 0 && b.Client != nil {
ADDPREMOVE:
for space, pieces := range b.spaces {
for space, pieces := range b.spaceSprites {
for _, piece := range pieces {
if piece == dropped {
if space != index {
b.Client.Board.SetSelection(1, space)
b.Client.Board.AddPreMove(space, index)
//b.Client.Board.SetSelection(1, space)
b.Client.Out <- []byte(fmt.Sprintf("move %d/%d", space, index))
}
break ADDPREMOVE
}

1
game/buffer.go Normal file
View File

@ -0,0 +1 @@
package game

117
game/client.go Normal file
View File

@ -0,0 +1,117 @@
package game
import (
"bytes"
"context"
"fmt"
"log"
"net"
"github.com/gobwas/ws/wsutil"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/gobwas/ws"
)
type Client struct {
Address string
Username string
Password string
Events chan interface{}
Out chan []byte
conn *net.TCPConn
}
func newClient(address string, username string, password string) *Client {
const bufferSize = 10
return &Client{
Address: address,
Username: username,
Password: password,
Events: make(chan interface{}, bufferSize),
Out: make(chan []byte, bufferSize),
}
}
func (c *Client) Connect() {
if c.conn != nil {
return // TODO reconnect
}
conn, br, _, err := ws.Dial(context.Background(), c.Address)
if err != nil {
panic(err)
}
c.conn = conn.(*net.TCPConn)
if br != nil {
ws.PutReader(br)
}
// Log in.
loginInfo := c.Username
if c.Username != "" && c.Password != "" {
loginInfo = fmt.Sprintf("%s %s", c.Username, c.Password)
}
c.Out <- []byte(fmt.Sprintf("lj %s\nlist\n", loginInfo))
go c.handleWrite()
c.handleRead()
}
func (c *Client) handleWrite() {
for buf := range c.Out {
split := bytes.Split(buf, []byte("\n"))
for i := range split {
if len(split[i]) == 0 {
continue
}
err := wsutil.WriteClientMessage(c.conn, ws.OpText, split[i])
if err != nil {
panic(err)
}
//if debug > 0 {
log.Println(fmt.Sprintf("-> %s", split[i]))
//}
}
}
}
func (c *Client) handleRead() {
if c.conn == nil {
panic("nil con")
}
var messages []wsutil.Message
var err error
var i int
for {
log.Printf("READ FRAME %d", i)
i++
messages, err = wsutil.ReadServerMessage(c.conn, messages[:0])
if err != nil {
panic(err)
}
for _, msg := range messages {
ev, err := bgammon.DecodeEvent(msg.Payload)
if err != nil {
log.Printf("message: %s", msg.Payload)
panic(err)
}
c.Events <- ev
//if debug > 0 {
log.Println(fmt.Sprintf("<- %s", msg.Payload))
//}
}
}
}
func (c *Client) LoggedIn() bool {
return c.conn != nil
}

View File

@ -14,7 +14,8 @@ import (
"strings"
"time"
"code.rocketnine.space/tslocum/fibs"
"code.rocketnine.space/tslocum/messeji"
"code.rocketnine.space/tslocum/kibodo"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
@ -33,6 +34,28 @@ var debugExtra []byte
var debugGame *Game
var mplusNormalFont font.Face
func init() {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 28,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
StatusWriter = messeji.NewTextField(mplusNormalFont)
GameWriter = messeji.NewTextField(mplusNormalFont)
}
var (
imgCheckerLight *ebiten.Image
imgCheckerDark *ebiten.Image
@ -41,6 +64,9 @@ var (
mediumFont font.Face
monoFont font.Face
largeFont font.Face
StatusWriter *messeji.TextField
GameWriter *messeji.TextField
)
var (
@ -48,7 +74,7 @@ var (
darkCheckerColor = color.RGBA{0, 0, 0, 255}
)
const defaultServerAddress = "fibs.com:4321"
const DefaultServerAddress = "ws://localhost:1338" // TODO
const maxStatusWidthRatio = 0.5
@ -179,12 +205,12 @@ type Game struct {
Watch bool
TV bool
Client *fibs.Client
Client *Client
Board *board
lobby *lobby
pendingWho []*fibs.WhoInfo
pendingWho []*WhoInfo
runeBuffer []rune
inputBuffer string
@ -227,9 +253,6 @@ func NewGame() *Game {
g.statusBuffer.acceptInput = true
fibs.StatusWriter = NewMessageHandler(g.statusBuffer.buffers[0])
fibs.GameWriter = NewMessageHandler(g.gameBuffer.buffers[0])
// TODO
go func() {
/*
@ -253,9 +276,9 @@ func NewGame() *Game {
}
func (g *Game) handleEvents() {
for e := range g.Client.Event {
switch event := e.(type) {
case *fibs.EventWho:
for ev := range g.Client.Events {
/*switch event := e.(type) {
case *EventWho:
if viewBoard || g.lobby.refresh {
g.lobby.setWhoInfo(event.Who)
@ -266,19 +289,22 @@ func (g *Game) handleEvents() {
} else {
g.pendingWho = event.Who
}
case *fibs.EventBoardState:
case *EventBoardState:
log.Println("EVENTBOARDSTATE START")
g.Board.SetState(event.S, event.V)
// set gamestate var
// TODO
b.ProcessState()
log.Println("EVENTBOARDSTATE FINISH")
case *fibs.EventMove:
case *EventMove:
log.Printf("EVENTMOVE START %d %d", event.From, event.To)
g.Board.movePiece(event.From, event.To)
log.Println("EVENTMOVE FINISH")
case *fibs.EventDraw:
case *EventDraw:
log.Println("EVENTDRAW START")
g.Board.ProcessState()
log.Println("EVENTDRAW FINISH")
}
}*/
log.Printf("EVENT %+v", ev)
}
}
@ -287,9 +313,9 @@ func (g *Game) Connect() {
address := g.ServerAddress
if address == "" {
address = defaultServerAddress
address = DefaultServerAddress
}
g.Client = fibs.NewClient(address, g.Username, g.Password)
g.Client = newClient(address, g.Username, g.Password)
g.lobby.c = g.Client
g.Board.Client = g.Client
g.statusBuffer.client = g.Client
@ -310,12 +336,7 @@ func (g *Game) Connect() {
}()
}
go func() {
err := c.Connect()
if err != nil {
fibs.StatusWriter.Write([]byte(err.Error()))
}
}()
go c.Connect()
}
// Separate update function for all normal update logic, as Update may only be
@ -454,7 +475,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
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`
http://www.com/help.html#register`
debugBox := image.NewRGBA(image.Rect(0, 0, g.screenW, g.screenH))
debugImg := ebiten.NewImageFromImage(debugBox)
@ -671,3 +692,30 @@ func (m *messageHandler) Write(p []byte) (n int, err error) {
m.t.Write(p)
return len(p), nil
}
// TODO
type WhoInfo struct {
Username string
Opponent string
Watching string
Ready bool
Away bool
Rating int
Experience int
Idle int
LoginTime int
ClientName string
}
func (w *WhoInfo) String() string {
opponent := "In the lobby"
if w.Opponent != "" && w.Opponent != "-" {
opponent = "playing against " + w.Opponent
}
clientName := ""
if w.ClientName != "" && w.ClientName != "-" {
clientName = " using " + w.ClientName
}
return fmt.Sprintf("%s (rated %d with %d exp) is %s%s", w.Username, w.Rating, w.Experience, opponent, clientName)
}

View File

@ -8,7 +8,6 @@ import (
"sort"
"strings"
"code.rocketnine.space/tslocum/fibs"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
@ -43,7 +42,7 @@ type lobby struct {
entryH float64
buttonBarHeight int
who []*fibs.WhoInfo
who []*WhoInfo
touchIDs []ebiten.TouchID
@ -59,9 +58,9 @@ type lobby struct {
op *ebiten.DrawImageOptions
c *fibs.Client
c *Client
inviteUser *fibs.WhoInfo
inviteUser *WhoInfo
invitePoints int
refresh bool
@ -75,7 +74,7 @@ func NewLobby() *lobby {
return l
}
func (l *lobby) setWhoInfo(who []*fibs.WhoInfo) {
func (l *lobby) setWhoInfo(who []*WhoInfo) {
l.who = who
sort.Slice(l.who, func(i, j int) bool {

View File

@ -1,7 +1,7 @@
package mobile
import (
"code.rocketnine.space/tslocum/boxcars/game"
"code.rocket9labs.com/tslocum/boxcars/game"
"github.com/hajimehoshi/ebiten/v2/mobile"
)

View File

@ -4,7 +4,6 @@ import (
"fmt"
"image/color"
"code.rocketnine.space/tslocum/fibs"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
@ -61,7 +60,7 @@ type tabbedBuffers struct {
inputBuffer []byte
client *fibs.Client
client *Client
chatFont font.Face
chatFontSize int
@ -247,7 +246,7 @@ func (t *tabbedBuffers) update() {
}
}
} else {
fibs.StatusWriter.Write([]byte("* You have not connected to a server yet"))
StatusWriter.Write([]byte("* You have not connected to a server yet"))
}
t.inputBuffer = nil

37
go.mod
View File

@ -1,29 +1,28 @@
module code.rocketnine.space/tslocum/boxcars
module code.rocket9labs.com/tslocum/boxcars
go 1.17
require (
code.rocketnine.space/tslocum/fibs v0.0.0-20211112042838-16c24d47934a
code.rocketnine.space/tslocum/kibodo v0.0.0-20211027223129-7b870790d865
github.com/hajimehoshi/ebiten/v2 v2.2.2
github.com/llgcode/draw2d v0.0.0-20210904075650-80aa0a2a901d
code.rocket9labs.com/tslocum/bgammon v0.0.0-20230913074653-cf99329267a8
code.rocketnine.space/tslocum/kibodo v1.0.0
code.rocketnine.space/tslocum/messeji v1.0.3
github.com/gobwas/ws v1.3.0
github.com/hajimehoshi/ebiten/v2 v2.5.9
github.com/llgcode/draw2d v0.0.0-20230723155556-e595d7c7e75e
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/image v0.12.0
)
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/ebitengine/purego v0.4.0 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
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/exp v0.0.0-20211111183329-cb5df436b1a8 // 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-20211112193437-faf0a1b62c6b // indirect
golang.org/x/text v0.3.7 // indirect
nhooyr.io/websocket v1.8.7 // indirect
github.com/jezek/xgb v1.1.0 // indirect
golang.org/x/exp/shiny v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mobile v0.0.0-20230906132913-2077a3224571 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
)

554
go.sum
View File

@ -1,523 +1,71 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.rocketnine.space/tslocum/fibs v0.0.0-20211112042838-16c24d47934a h1:iKYMwcn8EPPHRr0GyuCdTSpte0eeZSUPU+MC+fy09hg=
code.rocketnine.space/tslocum/fibs v0.0.0-20211112042838-16c24d47934a/go.mod h1:zxixHM4hy1D4t6peJ79AcYa0lCl5+PaeAybgaRD2qp0=
code.rocketnine.space/tslocum/kibodo v0.0.0-20211027223129-7b870790d865 h1:Sm6hHfKceNAPvGw+zOmQm5u+TePLJgZzz8zyk2Q/HC0=
code.rocketnine.space/tslocum/kibodo v0.0.0-20211027223129-7b870790d865/go.mod h1:pQfyfr10kXO/Cqw/T+bTDEg3Xbhjxb4vn/vaPzPW/Vk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
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=
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20230913074653-cf99329267a8 h1:wdSojaTDI9adSY/4exRCTwaon8BOLecQBSmvb5S4r7Q=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20230913074653-cf99329267a8/go.mod h1:LS/m5Zq7/93dP8XJrLkL1T5ZTwtddkN8X9TyRrrdCkQ=
code.rocketnine.space/tslocum/kibodo v1.0.0 h1:/xs59UCuo+NGs89ABilARlowQnmIsjbNVjss57W5O7k=
code.rocketnine.space/tslocum/kibodo v1.0.0/go.mod h1:xYmBfho98sIbB+Gtf8SU5GDQD9HOSqOtZ64eZnlHmRI=
code.rocketnine.space/tslocum/messeji v1.0.3 h1:o0HqUckStFUFE2SkYrkzRHoAY7QT7cTsuRQ7JEmfw6w=
code.rocketnine.space/tslocum/messeji v1.0.3/go.mod h1:bSXsyjvKhFXQ7GsUxWZdO2JX83xOT/VTqFCR04thk+c=
github.com/ebitengine/purego v0.4.0 h1:RQVuMIxQPQ5iCGEJvjQ17YOK+1tMKjVau2FUMvXH4HE=
github.com/ebitengine/purego v0.4.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
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.1/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0=
github.com/hajimehoshi/ebiten/v2 v2.2.2 h1:92E+ogdNyH1P/LlvMQ7vonbFDh6bl+O7Ak+H1HX0RX8=
github.com/hajimehoshi/ebiten/v2 v2.2.2/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0=
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/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=
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/llgcode/draw2d v0.0.0-20210904075650-80aa0a2a901d h1:4/ycg+VrwjGurTqiHv2xM/h6Qm81qSra+KbfT4FH2FA=
github.com/llgcode/draw2d v0.0.0-20210904075650-80aa0a2a901d/go.mod h1:mVa0dA29Db2S4LVqDYLlsePDzRJLDfdhVZiI15uY0FA=
github.com/hajimehoshi/bitmapfont/v2 v2.2.3 h1:jmq/TMNj352V062Tr5e3hAoipkoxCbY1JWTzor0zNps=
github.com/hajimehoshi/ebiten/v2 v2.5.9 h1:xwPrSr4rgB7LgdAKBH9bW7YT8EBBpiruAzykf6QFCv8=
github.com/hajimehoshi/ebiten/v2 v2.5.9/go.mod h1:PrOaLXiRkqAtImDIx2x/7jQdZHHuTcrcQZx5WFQtnK0=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/llgcode/draw2d v0.0.0-20230723155556-e595d7c7e75e h1:hqFckor7F0B63l6cV/PoAsuQUOmDji/1oVF0+24EMUI=
github.com/llgcode/draw2d v0.0.0-20230723155556-e595d7c7e75e/go.mod h1:zNlGqkQNLxAN7D2uihSJsrEzrkWrSIK5kmSZU/dN5NY=
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb h1:61ndUreYSlWFeCY44JxDDkngVoI7/1MVhEl98Nm0KOk=
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=