diff options
Diffstat (limited to 'pongo.go')
| -rw-r--r-- | pongo.go | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/pongo.go b/pongo.go new file mode 100644 index 0000000..f4dc038 --- /dev/null +++ b/pongo.go @@ -0,0 +1,215 @@ +package main + +import ( + _ "embed" + "github.com/gen2brain/raylib-go/raylib" + "math" + "strconv" +) + +const ( + screenWidth = 1920 + screenHeight = 1080 + + ballSpeed = 5 + + paddleSpeed = 5 +) + +func printCenter(text string, size int32, color rl.Color) { + rl.DrawText(text, screenWidth/2-rl.MeasureText(text, size), screenHeight/2, size, color) +} + +type rect rl.Rectangle + +func (r rect) bottom() float32 { + return r.Y + r.Height +} + +func (r rect) right() float32 { + return r.X + r.Width +} + +func (r rect) collides(s rect) bool { + return rl.CheckCollisionRecs(rl.Rectangle(r), rl.Rectangle(s)) +} + +func (r rect) draw(color rl.Color) { + rl.DrawRectangleRec(rl.Rectangle(r), color) +} + +type ball struct { + pos rl.Vector2 + velocity rl.Vector2 +} + +func (b *ball) bounds() rect { + const ballSize = 30 + return rect{ + X: b.pos.X, + Y: b.pos.Y, + Width: ballSize, + Height: ballSize, + } +} + +func (b *ball) predictIntersection(targetX float32) (float32, bool) { + if b.velocity.X <= 0 { + return 0, false + } + x := b.pos.X + y := b.pos.Y + velocity := b.velocity + dist := targetX - x + prediction := y + velocity.Y*dist/velocity.X + if prediction < 0 { + bounces := int(-prediction / screenHeight) + if bounces%2 == 0 { + return -prediction - float32(screenHeight*bounces), true + } + return prediction + float32(screenHeight*(bounces+1)), true + } + if prediction > screenHeight { + bounces := int(prediction / screenHeight) + if bounces%2 == 0 { + return prediction - float32(screenHeight*bounces), true + } + return -prediction + float32(screenHeight*(bounces+1)), true + } + return prediction, true +} + +type side int + +const ( + left side = iota + right +) + +func (b *ball) reset(side side) { + b.pos = rl.Vector2{X: screenWidth / 2, Y: screenHeight / 2} + b.velocity = rl.Vector2{X: ballSpeed} + if side == left { + b.velocity.X = -b.velocity.X + } +} + +type paddle struct { + pos float32 + side side +} + +func (p *paddle) bounds() rect { + const ( + paddleHeight = 200 + thickness = 10 + padding = 100 + ) + r := rect{ + X: padding - thickness/2, + Y: p.pos - paddleHeight/2, + Width: thickness, + Height: paddleHeight, + } + if p.side == right { + r.X = screenWidth - padding - thickness/2 + } + return r +} + +//go:embed ping.mp3 +var pingMP3 []byte + +func main() { + rl.InitWindow(screenWidth, screenHeight, "pongo") + defer rl.CloseWindow() + rl.SetTargetFPS(120) + + rl.InitAudioDevice() + defer rl.CloseAudioDevice() + + pingWave := rl.LoadWaveFromMemory(".mp3", pingMP3, int32(len(pingMP3))) + defer rl.UnloadWave(pingWave) + ping := rl.LoadSoundFromWave(pingWave) + defer rl.UnloadSound(ping) + + playerScore := 0 + aiScore := 0 + + var ball ball + ball.reset(left) + + paddle1 := paddle{pos: screenHeight / 2, side: left} + paddle2 := paddle{pos: screenHeight / 2, side: right} + + for !rl.WindowShouldClose() { + switch { + case rl.IsKeyDown(rl.KeyUp) && paddle1.bounds().Y >= 0: + paddle1.pos -= paddleSpeed + case rl.IsKeyDown(rl.KeyDown) && paddle1.bounds().bottom() <= screenHeight: + paddle1.pos += paddleSpeed + } + + if prediction, ok := ball.predictIntersection(paddle2.bounds().X); ok { + switch { + case paddle2.pos < prediction && paddle2.bounds().bottom() <= screenHeight: + paddle2.pos += paddleSpeed + case paddle2.pos > prediction && paddle2.bounds().Y >= 0: + paddle2.pos -= paddleSpeed + } + } + + ball.pos = rl.Vector2Add(ball.pos, ball.velocity) + switch { + case ball.bounds().collides(paddle1.bounds()): + rl.PlaySound(ping) + d := ball.pos.Y - paddle1.pos + if d < 0 { + d = -d + } + angle := d / (paddle1.bounds().Height/2 + ball.bounds().Height) * (75 * 2 * math.Pi / 360) + if ball.pos.Y < paddle1.pos { + angle = -angle + } + speed := ballSpeed * (1 + 4*d/(paddle1.bounds().Height/2+ball.bounds().Height)) + ball.velocity = rl.Vector2{X: speed * float32(math.Cos(float64(angle))), Y: speed * float32(math.Sin(float64(angle)))} + case ball.bounds().collides(paddle2.bounds()): + rl.PlaySound(ping) + d := ball.pos.Y - paddle2.pos + if d < 0 { + d = -d + } + angle := d / (paddle2.bounds().Height/2 + ball.bounds().Height) * (75 * 2 * math.Pi / 360) + if ball.pos.Y < paddle2.pos { + angle = -angle + } + angle += math.Pi + speed := ballSpeed * (1 + 4*d/(paddle2.bounds().Height/2+ball.bounds().Height)) + ball.velocity = rl.Vector2{X: speed * float32(math.Cos(float64(angle))), Y: speed * float32(math.Sin(float64(angle)))} + case ball.bounds().right() <= paddle1.bounds().X: + aiScore++ + ball.reset(right) + case ball.bounds().X >= paddle2.bounds().X: + playerScore++ + ball.reset(left) + case ball.bounds().Y <= 0 || ball.bounds().bottom() >= screenHeight: + ball.velocity.Y = -ball.velocity.Y + rl.PlaySound(ping) + } + + rl.BeginDrawing() + + rl.ClearBackground(rl.Black) + rl.DrawLine(screenWidth/2, 0, screenWidth/2, screenHeight, rl.Gray) + + paddle1.bounds().draw(rl.RayWhite) + paddle2.bounds().draw(rl.RayWhite) + ball.bounds().draw(rl.RayWhite) + + const fontSize = 120 + rl.DrawText(strconv.Itoa(playerScore), int32(paddle1.bounds().X)+20, 30, fontSize, rl.RayWhite) + rl.DrawText(strconv.Itoa(aiScore), int32(paddle2.bounds().X)-rl.MeasureText("9999", fontSize), 30, fontSize, rl.RayWhite) + + rl.EndDrawing() + } +} |
