Position checkers around board
This commit is contained in:
parent
50646f451a
commit
6966d690d2
|
@ -1 +1,3 @@
|
|||
.idea
|
||||
*.sh
|
||||
*.wasm
|
||||
|
|
26
README.md
26
README.md
|
@ -5,10 +5,34 @@ Online backgammon client ([FIBS](http://fibs.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*
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/boxcars/issues).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [ebiten](github.com/hajimehoshi/ebiten) - 2D game engine
|
||||
- [ebiten](https://github.com/hajimehoshi/ebiten) - 2D game engine
|
||||
- [draw2d](https://github.com/llgcode/draw2d) - 2D shape drawing
|
||||
- [resize](https://github.com/nfnt/resize) - Image resizing
|
||||
|
|
198
game/board.go
198
game/board.go
|
@ -3,6 +3,7 @@ package game
|
|||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
|
@ -13,31 +14,46 @@ import (
|
|||
)
|
||||
|
||||
type board struct {
|
||||
Sprites Sprites
|
||||
Sprites *Sprites
|
||||
op *ebiten.DrawImageOptions
|
||||
|
||||
backgroundImage *ebiten.Image
|
||||
|
||||
dragging *Sprite
|
||||
|
||||
dragTouchId ebiten.TouchID
|
||||
|
||||
touchIDs []ebiten.TouchID
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
spaceWidth int // spaceWidth is also the width and height of checkers
|
||||
barWidth int
|
||||
triangleOffset float64
|
||||
horizontalBorderSize int
|
||||
verticalBorderSize int
|
||||
overlapSize int
|
||||
}
|
||||
|
||||
func NewBoard() *board {
|
||||
b := &board{}
|
||||
b := &board{
|
||||
barWidth: 100,
|
||||
triangleOffset: float64(50),
|
||||
horizontalBorderSize: 50,
|
||||
verticalBorderSize: 25,
|
||||
overlapSize: 97,
|
||||
Sprites: &Sprites{
|
||||
sprites: make([]*Sprite, 24),
|
||||
num: 24,
|
||||
},
|
||||
}
|
||||
|
||||
b.Sprites.sprites = make([]*Sprite, 30)
|
||||
b.Sprites.num = 30
|
||||
for i := range b.Sprites.sprites {
|
||||
s := &Sprite{}
|
||||
|
||||
r := rand.Intn(2)
|
||||
if r != 1 {
|
||||
s.image = imgCheckerWhite
|
||||
} else {
|
||||
s.image = imgCheckerBlack
|
||||
}
|
||||
s.colorWhite = r != 1
|
||||
|
||||
s.w, s.h = imgCheckerWhite.Size()
|
||||
|
||||
|
@ -46,44 +62,79 @@ func NewBoard() *board {
|
|||
|
||||
b.op = &ebiten.DrawImageOptions{}
|
||||
|
||||
b.dragTouchId = -1
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// relX, relY
|
||||
func (b *board) spacePosition(index int) (int, int) {
|
||||
log.Printf("%d", index)
|
||||
if index <= 12 {
|
||||
return b.spaceWidth * (index - 1), 0
|
||||
}
|
||||
// TODO add innerW innerH
|
||||
return b.spaceWidth * (index - 13), (b.h - (b.verticalBorderSize)*2) - b.spaceWidth
|
||||
}
|
||||
|
||||
func (b *board) updateBackgroundImage() {
|
||||
// TODO percentage of screen instead
|
||||
|
||||
borderColor := color.RGBA{65, 40, 14, 255}
|
||||
|
||||
// Border
|
||||
box := image.NewRGBA(image.Rect(0, 0, b.w, b.h))
|
||||
|
||||
img := ebiten.NewImageFromImage(box)
|
||||
img.Fill(color.RGBA{0, 0, 0, 255})
|
||||
|
||||
img.Fill(borderColor)
|
||||
b.backgroundImage = ebiten.NewImageFromImage(img)
|
||||
|
||||
box = image.NewRGBA(image.Rect(0, 0, b.w-10, b.h-10))
|
||||
|
||||
// Face
|
||||
box = image.NewRGBA(image.Rect(0, 0, b.w-(b.horizontalBorderSize*2), b.h-(b.verticalBorderSize*2)))
|
||||
img = ebiten.NewImageFromImage(box)
|
||||
img.Fill(color.RGBA{101, 56, 24, 255})
|
||||
|
||||
img.Fill(color.RGBA{120, 63, 25, 255})
|
||||
b.op.GeoM.Reset()
|
||||
b.op.GeoM.Translate(float64(5), float64(5))
|
||||
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-10, b.h-10))
|
||||
|
||||
baseImg := image.NewRGBA(image.Rect(0, 0, b.w-(b.horizontalBorderSize*2), b.h-(b.verticalBorderSize*2)))
|
||||
gc := draw2dimg.NewGraphicContext(baseImg)
|
||||
// Set some properties
|
||||
gc.SetFillColor(color.RGBA{0, 0, 0, 255})
|
||||
|
||||
// Bar
|
||||
box = image.NewRGBA(image.Rect(0, 0, b.barWidth, b.h))
|
||||
img = ebiten.NewImageFromImage(box)
|
||||
img.Fill(borderColor)
|
||||
b.op.GeoM.Reset()
|
||||
b.op.GeoM.Translate(float64((b.w/2)-(b.barWidth/2)), 0)
|
||||
b.backgroundImage.DrawImage(img, b.op)
|
||||
|
||||
// Draw triangles
|
||||
for i := 0; i < 2; i++ {
|
||||
triangleTip := float64(b.h / 2)
|
||||
if i == 0 {
|
||||
triangleTip -= 50
|
||||
triangleTip -= b.triangleOffset
|
||||
} else {
|
||||
triangleTip += 50
|
||||
triangleTip += b.triangleOffset
|
||||
}
|
||||
for j := 0; j < 12; j++ {
|
||||
gc.MoveTo(float64(100*j), float64(b.h*i))
|
||||
gc.LineTo(float64(100*j)+50, triangleTip)
|
||||
gc.LineTo(float64(100*j)+100, float64(b.h*i))
|
||||
colorA := j%2 == 0
|
||||
if i == 1 {
|
||||
colorA = !colorA
|
||||
}
|
||||
|
||||
if colorA {
|
||||
gc.SetFillColor(color.RGBA{219.0, 185, 113, 255})
|
||||
} else {
|
||||
gc.SetFillColor(color.RGBA{120.0, 17.0, 0, 255})
|
||||
}
|
||||
|
||||
tx := b.spaceWidth * j
|
||||
ty := b.h * i
|
||||
if j >= 6 {
|
||||
tx += b.barWidth
|
||||
}
|
||||
gc.MoveTo(float64(tx), float64(ty))
|
||||
gc.LineTo(float64(tx+b.spaceWidth/2), triangleTip)
|
||||
gc.LineTo(float64(tx+b.spaceWidth), float64(ty))
|
||||
gc.Close()
|
||||
gc.Fill()
|
||||
}
|
||||
|
@ -92,7 +143,7 @@ func (b *board) updateBackgroundImage() {
|
|||
img = ebiten.NewImageFromImage(baseImg)
|
||||
|
||||
b.op.GeoM.Reset()
|
||||
b.op.GeoM.Translate(float64(5), float64(5))
|
||||
b.op.GeoM.Translate(float64(b.horizontalBorderSize), float64(b.verticalBorderSize))
|
||||
b.backgroundImage.DrawImage(img, b.op)
|
||||
}
|
||||
|
||||
|
@ -105,7 +156,12 @@ func (b *board) draw(screen *ebiten.Image) {
|
|||
sprite := b.Sprites.sprites[i]
|
||||
b.op.GeoM.Reset()
|
||||
b.op.GeoM.Translate(float64(sprite.x), float64(sprite.y))
|
||||
screen.DrawImage(sprite.image, b.op)
|
||||
|
||||
if sprite.colorWhite {
|
||||
screen.DrawImage(imgCheckerWhite, b.op)
|
||||
} else {
|
||||
screen.DrawImage(imgCheckerBlack, b.op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,48 +172,102 @@ func (b *board) setRect(x, y, w, h int) {
|
|||
|
||||
b.x, b.y, b.w, b.h = x, y, w, h
|
||||
|
||||
b.spaceWidth = ((b.w - (b.horizontalBorderSize * 2)) - b.barWidth) / 12
|
||||
|
||||
loadAssets(b.spaceWidth)
|
||||
for i := 0; i < b.Sprites.num; i++ {
|
||||
s := b.Sprites.sprites[i]
|
||||
log.Printf("%d-%d", s.w, s.h)
|
||||
s.w, s.h = imgCheckerWhite.Size()
|
||||
log.Printf("NEW %d-%d", s.w, s.h)
|
||||
}
|
||||
|
||||
b.updateBackgroundImage()
|
||||
b.positionCheckers()
|
||||
}
|
||||
|
||||
func (b *board) offsetPosition(x, y int) (int, int) {
|
||||
const boardPadding = 7
|
||||
return b.x + x + boardPadding, b.y + y + boardPadding
|
||||
return b.x + x + b.horizontalBorderSize, b.y + y + b.verticalBorderSize
|
||||
}
|
||||
|
||||
func (b *board) positionCheckers() {
|
||||
// TODO slightly overlap to save space
|
||||
for i := 0; i < b.Sprites.num; i++ {
|
||||
s := b.Sprites.sprites[i]
|
||||
s.x, s.y = b.offsetPosition(s.w*(i%12), s.h*(i/12))
|
||||
if b.dragging == s {
|
||||
continue
|
||||
}
|
||||
|
||||
spaceIndex := i + 1
|
||||
|
||||
x, y := b.spacePosition(spaceIndex)
|
||||
s.x, s.y = b.offsetPosition(x, y)
|
||||
if (spaceIndex > 6 && spaceIndex < 13) || (spaceIndex > 18 && spaceIndex < 25) {
|
||||
s.x += b.barWidth
|
||||
}
|
||||
s.x += (b.spaceWidth - s.w) / 2
|
||||
|
||||
/* multiple pieces
|
||||
if i <= 12 {
|
||||
s.y += b.overlapSize
|
||||
} else {
|
||||
s.y -= b.overlapSize
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
func (b *board) spriteAt(x, y int) *Sprite {
|
||||
for i := 0; i < b.Sprites.num; i++ {
|
||||
s := b.Sprites.sprites[i]
|
||||
if x >= s.x && y >= s.y && x <= s.x+s.w && y <= s.y+s.h {
|
||||
// Bring sprite to front
|
||||
b.Sprites.sprites = append(b.Sprites.sprites[:i], b.Sprites.sprites[i+1:]...)
|
||||
b.Sprites.sprites = append(b.Sprites.sprites, s)
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *board) update() {
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
if b.dragging == nil {
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
|
||||
if b.dragging == nil {
|
||||
for i := 0; i < b.Sprites.num; i++ {
|
||||
s := b.Sprites.sprites[i]
|
||||
if x >= s.x && y >= s.y && x <= s.x+s.w && y <= s.y+s.h {
|
||||
if b.dragging == nil {
|
||||
s := b.spriteAt(x, y)
|
||||
if s != nil {
|
||||
b.dragging = s
|
||||
|
||||
// Bring sprite to front
|
||||
b.Sprites.sprites = append(b.Sprites.sprites[:i], b.Sprites.sprites[i+1:]...)
|
||||
b.Sprites.sprites = append(b.Sprites.sprites, s)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.touchIDs = inpututil.AppendJustPressedTouchIDs(b.touchIDs[:0])
|
||||
for _, id := range b.touchIDs {
|
||||
x, y := ebiten.TouchPosition(id)
|
||||
s := b.spriteAt(x, y)
|
||||
if s != nil {
|
||||
b.dragging = s
|
||||
b.dragTouchId = id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
||||
if b.dragTouchId == -1 {
|
||||
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
||||
b.dragging = nil
|
||||
}
|
||||
} else if inpututil.IsTouchJustReleased(b.dragTouchId) {
|
||||
b.dragging = nil
|
||||
}
|
||||
|
||||
if b.dragging != nil {
|
||||
x, y := ebiten.CursorPosition()
|
||||
if b.dragTouchId != -1 {
|
||||
x, y = ebiten.TouchPosition(b.dragTouchId)
|
||||
}
|
||||
|
||||
sprite := b.dragging
|
||||
sprite.x = x - (sprite.w / 2)
|
||||
sprite.y = y - (sprite.h / 2)
|
||||
|
|
56
game/game.go
56
game/game.go
|
@ -1,6 +1,7 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"image"
|
||||
|
@ -53,13 +54,17 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
imgCheckerWhite = loadAsset("assets/checker_white.png")
|
||||
imgCheckerBlack = loadAsset("assets/checker_black.png")
|
||||
loadAssets(0)
|
||||
|
||||
initializeFonts()
|
||||
}
|
||||
|
||||
func loadAsset(assetPath string) *ebiten.Image {
|
||||
func loadAssets(width int) {
|
||||
imgCheckerWhite = loadAsset("assets/checker_white.png", width)
|
||||
imgCheckerBlack = loadAsset("assets/checker_black.png", width)
|
||||
}
|
||||
|
||||
func loadAsset(assetPath string, width int) *ebiten.Image {
|
||||
f, err := assetsFS.Open(assetPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -70,9 +75,11 @@ func loadAsset(assetPath string) *ebiten.Image {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
imgResized := resize.Resize(100, 0, img, resize.Lanczos3)
|
||||
|
||||
return ebiten.NewImageFromImage(imgResized)
|
||||
if width > 0 {
|
||||
imgResized := resize.Resize(uint(width), 0, img, resize.Lanczos3)
|
||||
return ebiten.NewImageFromImage(imgResized)
|
||||
}
|
||||
return ebiten.NewImageFromImage(img)
|
||||
|
||||
}
|
||||
|
||||
|
@ -161,14 +168,12 @@ func line(x0, y0, x1, y1 float32, clr color.RGBA) ([]ebiten.Vertex, []uint16) {
|
|||
}
|
||||
|
||||
type Sprite struct {
|
||||
image *ebiten.Image
|
||||
w int
|
||||
h int
|
||||
x int
|
||||
y int
|
||||
vx int
|
||||
vy int
|
||||
angle int
|
||||
image *ebiten.Image
|
||||
w int
|
||||
h int
|
||||
x int
|
||||
y int
|
||||
colorWhite bool
|
||||
}
|
||||
|
||||
func (s *Sprite) Update() {
|
||||
|
@ -198,6 +203,8 @@ type Game struct {
|
|||
board *board
|
||||
|
||||
screenW, screenH int
|
||||
|
||||
drawBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
|
@ -270,8 +277,22 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
debugBox := image.NewRGBA(image.Rect(10, 20, 200, 200))
|
||||
debugImg := ebiten.NewImageFromImage(debugBox)
|
||||
|
||||
msg := fmt.Sprintf("FPS %0.0f\nTPS %0.0f\n%s", ebiten.CurrentTPS(), ebiten.CurrentFPS(), debugExtra)
|
||||
ebitenutil.DebugPrint(debugImg, msg)
|
||||
g.drawBuffer.Reset()
|
||||
|
||||
g.drawBuffer.Write([]byte(fmt.Sprintf("FPS %0.0f\nTPS %0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS())))
|
||||
|
||||
scaleFactor := ebiten.DeviceScaleFactor()
|
||||
if scaleFactor != 1.0 {
|
||||
g.drawBuffer.WriteRune('\n')
|
||||
g.drawBuffer.Write([]byte(fmt.Sprintf("SCA %0.1f", scaleFactor)))
|
||||
}
|
||||
|
||||
if debugExtra != nil {
|
||||
g.drawBuffer.WriteRune('\n')
|
||||
g.drawBuffer.Write(debugExtra)
|
||||
}
|
||||
|
||||
ebitenutil.DebugPrint(debugImg, g.drawBuffer.String())
|
||||
|
||||
g.resetImageOptions()
|
||||
g.op.GeoM.Translate(3, 0)
|
||||
|
@ -286,8 +307,9 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||
|
||||
g.screenW, g.screenH = outsideWidth, outsideHeight
|
||||
|
||||
g.board.setRect(300, 25, g.screenW-325, g.screenH-50)
|
||||
g.board.setRect(300, 0, g.screenW-300, g.screenH)
|
||||
|
||||
// TODO use scale factor
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -16,7 +16,7 @@ func main() {
|
|||
ebiten.SetWindowTitle("Boxcars")
|
||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||
ebiten.SetWindowResizable(true)
|
||||
ebiten.SetMaxTPS(144) // TODO tune
|
||||
ebiten.SetMaxTPS(60) // TODO allow users to set custom value
|
||||
ebiten.SetRunnableOnUnfocused(true) // Note - this currently does nothing in ebiten
|
||||
|
||||
//ebiten.SetWindowClosingHandled(true) TODO implement
|
||||
|
|
Loading…
Reference in New Issue