diff --git a/.golangci.yml b/.golangci.yml index 1f0e4de..94699cc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -107,7 +107,7 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant - name: add-constant severity: warning - disabled: false + disabled: true arguments: - maxLitCount: "3" allowStrs: "\"\"" diff --git a/internal/ball.go b/internal/ball.go new file mode 100644 index 0000000..0b105f2 --- /dev/null +++ b/internal/ball.go @@ -0,0 +1,65 @@ +package internal + +import ( + "image" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" +) + +type ball struct { + x float32 + speedX float32 + y float32 + speedY float32 + r float32 +} + +const ( + ballBaselRadius = 5 + ballBaseSpeedX = 1 + ballBaseSpeedY = 1 +) + +func newBall(screenWidth, screenHeight int) *ball { + fScreenWidth := float32(screenWidth) + fScreenHeight := float32(screenHeight) + r := ballBaselRadius * fScreenHeight / BaseHeight + return &ball{ + x: fScreenWidth/2.0 - r, + speedX: ballBaseSpeedX * fScreenWidth / BaseWidth, + y: fScreenHeight/2.0 - r, + speedY: ballBaseSpeedY * fScreenHeight / BaseHeight, + r: r, + } +} + +var ballColor = color.White + +func (b *ball) draw(img *ebiten.Image) { + vector.DrawFilledCircle(img, b.x, b.y, b.r, ballColor, false) +} + +func (b *ball) update(bounds image.Rectangle) error { + if b.x+b.r >= float32(bounds.Max.X) || b.x-b.r <= float32(bounds.Min.X) { + b.speedX *= -1 + } + + if b.y+b.r >= float32(bounds.Max.Y) || b.y-b.r <= float32(bounds.Min.Y) { + b.speedY *= -1 + } + + b.x += b.speedX + b.y += b.speedY + + return nil +} + +func (b *ball) resize(x, y float32) { + b.x *= x + b.speedX *= x + b.y *= y + b.speedY *= y + b.r *= y +} diff --git a/internal/game.go b/internal/game.go new file mode 100644 index 0000000..0242250 --- /dev/null +++ b/internal/game.go @@ -0,0 +1,89 @@ +package internal + +import ( + "fmt" + "image" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" +) + +const ( + BaseWidth = 640 + BaseHeight = 360 +) + +type Game struct { + width int + height int + ball *ball + debug bool + img *ebiten.Image +} + +var _ ebiten.Game = (*Game)(nil) + +func NewGame(width, height int, debug bool) *Game { + return &Game{ + width: width, + height: height, + ball: newBall(width, height), + debug: debug, + } +} + +func (g *Game) Update() error { + return g.ball.update(image.Rect(0, 0, g.width, g.height)) +} + +var backgroundColor = color.Black + +func (g *Game) Draw(screen *ebiten.Image) { + if g.img == nil { + g.img = ebiten.NewImage(g.width, g.height) + } + + g.img.Fill(backgroundColor) + + g.ball.draw(g.img) + + if g.debug { + g.drawDebug() + } + + screen.DrawImage(g.img, nil) +} + +func (g *Game) drawDebug() { + ebitenutil.DebugPrint( + g.img, + fmt.Sprintf( + "FPS: %0.2f"+ + "\nBall: x=%v, y=%v, speedX=%v, speedY=%v, radius=%v", + ebiten.ActualFPS(), + g.ball.x, + g.ball.y, + g.ball.speedX, + g.ball.speedY, + g.ball.r, + ), + ) +} + +//nolint:nonamedreturns +func (g *Game) Layout(outsideWidth, outsideHeight int) (_screenWidth, _screenHeight int) { + if g.width != outsideWidth || g.height != outsideHeight { + g.ball.resize(float32(outsideWidth)/float32(g.width), float32(outsideHeight)/float32(g.height)) + + if g.img != nil { + g.img.Dispose() + g.img = nil + } + + g.width = outsideWidth + g.height = outsideHeight + } + + return outsideWidth, outsideHeight +} diff --git a/main.go b/main.go index 00880c7..ba5b7ba 100644 --- a/main.go +++ b/main.go @@ -1,43 +1,30 @@ package main import ( + "flag" "log" + "gitea.dwysokinski.me/classic-games/tennis-game/internal" "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/ebitenutil" -) - -type Game struct{} - -var _ ebiten.Game = (*Game)(nil) - -func (g *Game) Update() error { - return nil -} - -func (g *Game) Draw(screen *ebiten.Image) { - ebitenutil.DebugPrint(screen, "Hello, World!") -} - -const ( - screenWidth = 320 - screenHeight = 240 -) - -//nolint:nonamedreturns -func (g *Game) Layout(_, _ int) (_screenWidth, _screenHeight int) { - return screenWidth, screenHeight -} - -const ( - windowWidth = 640 - windowHeight = 480 ) func main() { - ebiten.SetWindowSize(windowWidth, windowHeight) - ebiten.SetWindowTitle("Hello, World!") - if err := ebiten.RunGame(&Game{}); err != nil { + fullscreen := flag.Bool("fullscreen", true, "") + debug := flag.Bool("debug", false, "") + flag.Parse() + + ebiten.SetWindowTitle("Tennis game") + ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) + ebiten.SetWindowSizeLimits(internal.BaseWidth, internal.BaseHeight, -1, -1) + w, h := ebiten.ScreenSizeInFullscreen() + + if *fullscreen { + ebiten.SetFullscreen(true) + } else { + ebiten.SetWindowSize(w, h) + } + + if err := ebiten.RunGame(internal.NewGame(w, h, *debug)); err != nil { log.Fatal(err) } }