From b58f6439cb65dd39c9af0848450d0df76a3ba013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Tue, 28 Nov 2023 07:43:22 +0100 Subject: [PATCH] feat: render paddles --- .golangci.yml | 2 +- internal/ball.go | 47 +++++++++++------ internal/game.go | 47 +++++++++++++---- internal/paddle.go | 125 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 internal/paddle.go diff --git a/.golangci.yml b/.golangci.yml index 94699cc..8c27c22 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -258,7 +258,7 @@ linters-settings: - name: function-result-limit severity: warning disabled: false - arguments: [3] + arguments: [4] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length - name: function-length severity: warning diff --git a/internal/ball.go b/internal/ball.go index 0b105f2..bc220fa 100644 --- a/internal/ball.go +++ b/internal/ball.go @@ -17,15 +17,15 @@ type ball struct { } const ( - ballBaselRadius = 5 - ballBaseSpeedX = 1 - ballBaseSpeedY = 1 + ballBaseRadius = 5 + ballBaseSpeedX = 1 + ballBaseSpeedY = 1 ) func newBall(screenWidth, screenHeight int) *ball { fScreenWidth := float32(screenWidth) fScreenHeight := float32(screenHeight) - r := ballBaselRadius * fScreenHeight / BaseHeight + r := ballBaseRadius * fScreenHeight / BaseHeight return &ball{ x: fScreenWidth/2.0 - r, speedX: ballBaseSpeedX * fScreenWidth / BaseWidth, @@ -42,20 +42,37 @@ func (b *ball) draw(img *ebiten.Image) { } 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 - + b.x, b.y, b.speedX, b.speedY = b.nextPosAndSpeed(bounds) return nil } +//nolint:nonamedreturns +func (b *ball) nextPos(bounds image.Rectangle) (x, y float32) { + x, y, _, _ = b.nextPosAndSpeed(bounds) + return x, y +} + +//nolint:nonamedreturns +func (b *ball) nextPosAndSpeed(bounds image.Rectangle) (x, y, speedX, speedY float32) { + speedX, speedY = b.nextSpeed(bounds) + return b.x + speedX, b.y + speedY, speedX, speedY +} + +//nolint:nonamedreturns +func (b *ball) nextSpeed(bounds image.Rectangle) (speedX, speedY float32) { + speedX = b.speedX + if b.x+b.r >= float32(bounds.Max.X) || b.x-b.r <= float32(bounds.Min.X) { + speedX *= -1 + } + + speedY = b.speedY + if b.y+b.r >= float32(bounds.Max.Y) || b.y-b.r <= float32(bounds.Min.Y) { + speedY *= -1 + } + + return speedX, speedY +} + func (b *ball) resize(x, y float32) { b.x *= x b.speedX *= x diff --git a/internal/game.go b/internal/game.go index 0242250..17109c2 100644 --- a/internal/game.go +++ b/internal/game.go @@ -15,26 +15,44 @@ const ( ) type Game struct { - width int - height int - ball *ball - debug bool - img *ebiten.Image + width int + height int + ball *ball + leftPaddle *paddle + rightPaddle *paddle + 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, + width: width, + height: height, + ball: newBall(width, height), + leftPaddle: newLeftPaddle(width, height, true), + rightPaddle: newRightPaddle(width, height, false), + debug: debug, } } func (g *Game) Update() error { - return g.ball.update(image.Rect(0, 0, g.width, g.height)) + bounds := image.Rect(0, 0, g.width, g.height) + + if err := g.ball.update(bounds); err != nil { + return fmt.Errorf("couldn't update ball: %w", err) + } + + if err := g.leftPaddle.update(bounds, g.ball); err != nil { + return fmt.Errorf("couldn't update left paddle: %w", err) + } + + if err := g.rightPaddle.update(bounds, g.ball); err != nil { + return fmt.Errorf("couldn't update right paddle: %w", err) + } + + return nil } var backgroundColor = color.Black @@ -47,6 +65,8 @@ func (g *Game) Draw(screen *ebiten.Image) { g.img.Fill(backgroundColor) g.ball.draw(g.img) + g.leftPaddle.draw(g.img) + g.rightPaddle.draw(g.img) if g.debug { g.drawDebug() @@ -74,7 +94,12 @@ func (g *Game) drawDebug() { //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)) + ratioWidth := float32(outsideWidth) / float32(g.width) + ratioHeight := float32(outsideHeight) / float32(g.height) + + g.ball.resize(ratioWidth, ratioHeight) + g.leftPaddle.resize(ratioWidth, ratioHeight) + g.rightPaddle.resize(ratioWidth, ratioHeight) if g.img != nil { g.img.Dispose() diff --git a/internal/paddle.go b/internal/paddle.go new file mode 100644 index 0000000..9d7c604 --- /dev/null +++ b/internal/paddle.go @@ -0,0 +1,125 @@ +package internal + +import ( + "image" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" +) + +type paddle struct { + x float32 // top left corner - X + y float32 // top left corner - Y + initX float32 + initY float32 + width float32 + height float32 + speed float32 + isControlledByPlayer bool +} + +func newRightPaddle(screenWidth, screenHeight int, isControlledByPlayer bool) *paddle { + return newPaddle( + screenWidth, + screenHeight, + isControlledByPlayer, + func(paddleWidth, paddleHeight, screenWidth, screenHeight float32) (float32, float32) { + return screenWidth - paddleWidth, screenHeight/2 - paddleHeight/2 + }, + ) +} + +func newLeftPaddle(screenWidth, screenHeight int, isControlledByPlayer bool) *paddle { + return newPaddle( + screenWidth, + screenHeight, + isControlledByPlayer, + func(paddleWidth, paddleHeight, screenWidth, screenHeight float32) (float32, float32) { + return 0, screenHeight/2 - paddleHeight/2 + }, + ) +} + +const ( + paddleBaseWidth = 10 + paddleBaseHeight = 100 + paddleBaseSpeed = 1 +) + +func newPaddle( + screenWidth, screenHeight int, + isControlledByPlayer bool, + getPos func(paddleWidth, paddleHeight, screenWidth, screenHeight float32) (x, y float32), +) *paddle { + fScreenWidth := float32(screenWidth) + fScreenHeight := float32(screenHeight) + p := &paddle{ + width: paddleBaseWidth * fScreenWidth / BaseWidth, + height: paddleBaseHeight * fScreenHeight / BaseHeight, + isControlledByPlayer: isControlledByPlayer, + speed: paddleBaseSpeed * fScreenHeight / BaseHeight, + } + p.x, p.y = getPos(p.width, p.height, fScreenWidth, fScreenHeight) + p.initX, p.initY = p.x, p.y + return p +} + +var paddleColor = color.White + +func (p *paddle) draw(img *ebiten.Image) { + vector.DrawFilledRect(img, p.x, p.y, p.width, p.height, paddleColor, false) +} + +// nolint:Revive,cyclomatic +func (p *paddle) update(bounds image.Rectangle, b *ball) error { + newY := p.y + + if p.isControlledByPlayer && ebiten.IsFocused() { + _, cursorY := ebiten.CursorPosition() + newY = float32(cursorY) - p.height/2 + } + + ballNextX, _ := b.nextPos(bounds) + isBallGettingCloser := p.x-ballNextX < p.x-b.x + + // move the paddle according to the position of the ball + if !p.isControlledByPlayer && isBallGettingCloser { + if newY+p.height/2 > b.y { + newY -= p.speed + } else if newY-p.height/2 < b.y { + newY += p.speed + } + } + + // move the paddle to the init position + if !p.isControlledByPlayer && !isBallGettingCloser { + if newY > p.initY { + newY -= min(newY-p.initY, p.speed) + } else if newY < p.initY { + newY += min(p.initY-newY, p.speed) + } + } + + if minY := float32(bounds.Min.Y); newY <= minY { + newY = minY + } + + if maxY := float32(bounds.Max.Y); newY+p.height >= maxY { + newY = maxY - p.height + } + + p.y = newY + + return nil +} + +func (p *paddle) resize(x, y float32) { + p.x *= x + p.width *= x + p.initX *= x + p.y *= y + p.height *= y + p.speed *= y + p.initY *= y +} -- 2.45.1