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() } }