summaryrefslogtreecommitdiffstats
path: root/pongo.go
diff options
context:
space:
mode:
Diffstat (limited to 'pongo.go')
-rw-r--r--pongo.go215
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()
+ }
+}