boxcars/game/game.go

319 lines
6.0 KiB
Go

package game
import (
"bytes"
"embed"
"fmt"
"image"
"image/color"
"log"
"math"
"math/rand"
"time"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"golang.org/x/image/font/opentype"
"github.com/nfnt/resize"
// Asset decoding
_ "image/png"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"golang.org/x/image/font"
)
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const maxAngle = 256
//go:embed assets
var assetsFS embed.FS
var debugExtra []byte
var (
imgCheckerWhite *ebiten.Image
imgCheckerBlack *ebiten.Image
mplusNormalFont font.Face
mplusBigFont font.Face
)
func init() {
loadAssets(0)
initializeFonts()
}
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)
}
img, _, err := image.Decode(f)
if err != nil {
log.Fatal(err)
}
if width > 0 {
imgResized := resize.Resize(uint(width), 0, img, resize.Lanczos3)
return ebiten.NewImageFromImage(imgResized)
}
return ebiten.NewImageFromImage(img)
}
func initializeFonts() {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 24,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 32,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
}
// TODO copied
func line(x0, y0, x1, y1 float32, clr color.RGBA) ([]ebiten.Vertex, []uint16) {
const width = 1
theta := math.Atan2(float64(y1-y0), float64(x1-x0))
theta += math.Pi / 2
dx := float32(math.Cos(theta))
dy := float32(math.Sin(theta))
r := float32(clr.R) / 0xff
g := float32(clr.G) / 0xff
b := float32(clr.B) / 0xff
a := float32(clr.A) / 0xff
return []ebiten.Vertex{
{
DstX: x0 - width*dx/2,
DstY: y0 - width*dy/2,
SrcX: 1,
SrcY: 1,
ColorR: r,
ColorG: g,
ColorB: b,
ColorA: a,
},
{
DstX: x0 + width*dx/2,
DstY: y0 + width*dy/2,
SrcX: 1,
SrcY: 1,
ColorR: r,
ColorG: g,
ColorB: b,
ColorA: a,
},
{
DstX: x1 - width*dx/2,
DstY: y1 - width*dy/2,
SrcX: 1,
SrcY: 1,
ColorR: r,
ColorG: g,
ColorB: b,
ColorA: a,
},
{
DstX: x1 + width*dx/2,
DstY: y1 + width*dy/2,
SrcX: 1,
SrcY: 1,
ColorR: r,
ColorG: g,
ColorB: b,
ColorA: a,
},
}, []uint16{0, 1, 2, 1, 2, 3}
}
type Sprite struct {
image *ebiten.Image
w int
h int
x int
y int
colorWhite bool
}
func (s *Sprite) Update() {
return // TODO
}
type Sprites struct {
sprites []*Sprite
num int
}
func (s *Sprites) Update() {
for i := 0; i < s.num; i++ {
s.sprites[i].Update()
}
}
const (
MinSprites = 0
MaxSprites = 50000
)
type Game struct {
touchIDs []ebiten.TouchID
sprites Sprites
op *ebiten.DrawImageOptions
board *board
screenW, screenH int
drawBuffer bytes.Buffer
}
func NewGame() *Game {
rand.Seed(time.Now().UnixNano())
go func() {
// TODO fetch HTTP request, set debugExtra
}()
return &Game{
op: &ebiten.DrawImageOptions{
Filter: ebiten.FilterNearest,
},
board: NewBoard(),
}
}
func (g *Game) leftTouched() bool {
for _, id := range g.touchIDs {
x, _ := ebiten.TouchPosition(id)
/*if x < screenWidth/2 {
return true
}*/
_ = x
}
return false
}
func (g *Game) rightTouched() bool {
for _, id := range g.touchIDs {
x, _ := ebiten.TouchPosition(id)
/*if x >= screenWidth/2 {
return true
}*/
_ = x
}
return false
}
func (g *Game) Update() error {
g.touchIDs = ebiten.AppendTouchIDs(g.touchIDs[:0])
// Decrease the number of the sprites.
if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || g.leftTouched() {
g.sprites.num -= 20
if g.sprites.num < MinSprites {
g.sprites.num = MinSprites
}
}
// Increase the number of the sprites.
if ebiten.IsKeyPressed(ebiten.KeyArrowRight) || g.rightTouched() {
g.sprites.num += 20
if MaxSprites < g.sprites.num {
g.sprites.num = MaxSprites
}
}
g.board.update()
//g.sprites.Update()
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{0, 102, 51, 255})
g.board.draw(screen)
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())))
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)
g.op.GeoM.Scale(2, 2)
screen.DrawImage(debugImg, g.op)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
if g.screenW == outsideWidth && g.screenH == outsideHeight {
return outsideWidth, outsideHeight
}
g.screenW, g.screenH = outsideWidth, outsideHeight
g.board.setRect(300, 0, g.screenW-300, g.screenH)
// TODO use scale factor
return outsideWidth, outsideHeight
}
func (g *Game) resetImageOptions() {
g.op.GeoM.Reset()
}