From 827f639912529589e651da5c35e28881c8e08c1f Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sun, 25 Aug 2024 12:05:01 -0700 Subject: Port to SDL. --- pongo.go | 240 +++++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 149 insertions(+), 91 deletions(-) (limited to 'pongo.go') diff --git a/pongo.go b/pongo.go index f4dc038..b632626 100644 --- a/pongo.go +++ b/pongo.go @@ -2,79 +2,76 @@ package main import ( _ "embed" - "github.com/gen2brain/raylib-go/raylib" + "fmt" + "github.com/veandco/go-sdl2/mix" + "github.com/veandco/go-sdl2/sdl" + "gitlab.com/rhogenson/pongo/numbers" "math" - "strconv" + "os" + "time" ) const ( screenWidth = 1920 screenHeight = 1080 - ballSpeed = 5 - + 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 rectRight(r *sdl.Rect) int32 { + return r.X + r.W } -func (r rect) right() float32 { - return r.X + r.Width +func rectBottom(r *sdl.Rect) int32 { + return r.Y + r.H } -func (r rect) collides(s rect) bool { - return rl.CheckCollisionRecs(rl.Rectangle(r), rl.Rectangle(s)) +type vector2 struct { + x, y float64 } -func (r rect) draw(color rl.Color) { - rl.DrawRectangleRec(rl.Rectangle(r), color) +func (v vector2) add(w vector2) vector2 { + return vector2{v.x + w.x, v.y + w.y} } type ball struct { - pos rl.Vector2 - velocity rl.Vector2 + pos vector2 + velocity vector2 } -func (b *ball) bounds() rect { +func (b *ball) bounds() *sdl.Rect { const ballSize = 30 - return rect{ - X: b.pos.X, - Y: b.pos.Y, - Width: ballSize, - Height: ballSize, + return &sdl.Rect{ + X: int32(b.pos.x - ballSize/2), + Y: int32(b.pos.y - ballSize/2), + W: ballSize, + H: ballSize, } } -func (b *ball) predictIntersection(targetX float32) (float32, bool) { - if b.velocity.X <= 0 { +func (b *ball) predictIntersection(targetX float64) (float64, bool) { + if b.velocity.x <= 0 { return 0, false } - x := b.pos.X - y := b.pos.Y + x := b.pos.x + y := b.pos.y velocity := b.velocity dist := targetX - x - prediction := y + velocity.Y*dist/velocity.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 - float64(screenHeight*bounces), true } - return prediction + float32(screenHeight*(bounces+1)), true + return prediction + float64(screenHeight*(bounces+1)), true } if prediction > screenHeight { bounces := int(prediction / screenHeight) if bounces%2 == 0 { - return prediction - float32(screenHeight*bounces), true + return prediction - float64(screenHeight*bounces), true } - return -prediction + float32(screenHeight*(bounces+1)), true + return -prediction + float64(screenHeight*(bounces+1)), true } return prediction, true } @@ -87,29 +84,29 @@ const ( ) func (b *ball) reset(side side) { - b.pos = rl.Vector2{X: screenWidth / 2, Y: screenHeight / 2} - b.velocity = rl.Vector2{X: ballSpeed} + b.pos = vector2{screenWidth / 2, screenHeight / 2} + b.velocity = vector2{x: ballSpeed} if side == left { - b.velocity.X = -b.velocity.X + b.velocity.x = -b.velocity.x } } type paddle struct { - pos float32 + pos float64 side side } -func (p *paddle) bounds() rect { +func (p *paddle) bounds() *sdl.Rect { const ( paddleHeight = 200 thickness = 10 padding = 100 ) - r := rect{ - X: padding - thickness/2, - Y: p.pos - paddleHeight/2, - Width: thickness, - Height: paddleHeight, + r := &sdl.Rect{ + X: padding - thickness/2, + Y: int32(p.pos - paddleHeight/2), + W: thickness, + H: paddleHeight, } if p.side == right { r.X = screenWidth - padding - thickness/2 @@ -117,21 +114,35 @@ func (p *paddle) bounds() rect { return r } -//go:embed ping.mp3 -var pingMP3 []byte +//go:embed ping.wav +var pingWAV []byte -func main() { - rl.InitWindow(screenWidth, screenHeight, "pongo") - defer rl.CloseWindow() - rl.SetTargetFPS(120) +func pong() error { + const fps = 60 - rl.InitAudioDevice() - defer rl.CloseAudioDevice() + if err := sdl.Init(sdl.INIT_AUDIO & sdl.INIT_VIDEO & sdl.INIT_EVENTS); err != nil { + return err + } + defer sdl.Quit() + if err := mix.OpenAudio(44100, mix.DEFAULT_FORMAT, 2, 4096); err != nil { + return err + } + defer mix.CloseAudio() - pingWave := rl.LoadWaveFromMemory(".mp3", pingMP3, int32(len(pingMP3))) - defer rl.UnloadWave(pingWave) - ping := rl.LoadSoundFromWave(pingWave) - defer rl.UnloadSound(ping) + ping, err := mix.QuickLoadWAV(pingWAV) + if err != nil { + return err + } + defer ping.Free() + + window, err := sdl.CreateWindow("pongo", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, screenWidth, screenHeight, sdl.WINDOW_SHOWN) + if err != nil { + return err + } + defer window.Destroy() + + upPressed := false + downPressed := false playerScore := 0 aiScore := 0 @@ -142,74 +153,121 @@ func main() { paddle1 := paddle{pos: screenHeight / 2, side: left} paddle2 := paddle{pos: screenHeight / 2, side: right} - for !rl.WindowShouldClose() { + frameTargetTime := time.Now().Add(time.Second / fps) + + for { + for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + switch t := event.(type) { + case *sdl.QuitEvent: + return nil + case *sdl.KeyboardEvent: + switch t.Keysym.Sym { + case sdl.K_UP: + upPressed = t.State == sdl.PRESSED + case sdl.K_DOWN: + downPressed = t.State == sdl.PRESSED + } + } + } + switch { - case rl.IsKeyDown(rl.KeyUp) && paddle1.bounds().Y >= 0: + case upPressed && paddle1.bounds().Y >= 0: paddle1.pos -= paddleSpeed - case rl.IsKeyDown(rl.KeyDown) && paddle1.bounds().bottom() <= screenHeight: + case downPressed && rectBottom(paddle1.bounds()) <= screenHeight: paddle1.pos += paddleSpeed } - if prediction, ok := ball.predictIntersection(paddle2.bounds().X); ok { + if prediction, ok := ball.predictIntersection(float64(paddle2.bounds().X)); ok { switch { - case paddle2.pos < prediction && paddle2.bounds().bottom() <= screenHeight: + case paddle2.pos < prediction && rectBottom(paddle2.bounds()) <= screenHeight: paddle2.pos += paddleSpeed case paddle2.pos > prediction && paddle2.bounds().Y >= 0: paddle2.pos -= paddleSpeed } } - ball.pos = rl.Vector2Add(ball.pos, ball.velocity) + ball.pos = ball.pos.add(ball.velocity) switch { - case ball.bounds().collides(paddle1.bounds()): - rl.PlaySound(ping) - d := ball.pos.Y - paddle1.pos + case ball.bounds().HasIntersection(paddle1.bounds()): + ping.Play(-1, 0) + 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 := d / (float64(paddle1.bounds().H)/2 + float64(ball.bounds().H)) * (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 + speed := ballSpeed * (1 + 4*d/(float64(paddle1.bounds().H)/2+float64(ball.bounds().H))) + ball.velocity = vector2{speed * math.Cos(angle), speed * math.Sin(angle)} + case ball.bounds().HasIntersection(paddle2.bounds()): + ping.Play(-1, 0) + 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 := d / (float64(paddle2.bounds().H)/2 + float64(ball.bounds().H)) * (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: + speed := ballSpeed * (1 + 4*d/(float64(paddle2.bounds().H)/2+float64(ball.bounds().H))) + ball.velocity = vector2{speed * math.Cos(angle), speed * math.Sin(angle)} + case rectRight(ball.bounds()) <= 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) + case ball.bounds().Y <= 0 || rectBottom(ball.bounds()) >= screenHeight: + ping.Play(-1, 0) + ball.velocity.y = -ball.velocity.y } - rl.BeginDrawing() + surface, err := window.GetSurface() + if err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } - rl.ClearBackground(rl.Black) - rl.DrawLine(screenWidth/2, 0, screenWidth/2, screenHeight, rl.Gray) + gray := sdl.MapRGB(surface.Format, 130, 130, 130) + offWhite := sdl.MapRGB(surface.Format, 245, 245, 245) - paddle1.bounds().draw(rl.RayWhite) - paddle2.bounds().draw(rl.RayWhite) - ball.bounds().draw(rl.RayWhite) + if err := surface.FillRect(nil, 0); err != nil { + fmt.Fprintln(os.Stderr, err) + } + if err := surface.FillRect(&sdl.Rect{X: screenWidth / 2, Y: 0, W: 1, H: screenHeight}, gray); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + if err := surface.FillRect(paddle1.bounds(), offWhite); err != nil { + fmt.Fprintln(os.Stderr, err) + } + if err := surface.FillRect(paddle2.bounds(), offWhite); err != nil { + fmt.Fprintln(os.Stderr, err) + } + if err := surface.FillRect(ball.bounds(), offWhite); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + if err := numbers.Draw(surface, playerScore, paddle1.bounds().X+20, 30); err != nil { + fmt.Fprintln(os.Stderr, err) + } + if err := numbers.Draw(surface, aiScore, paddle2.bounds().X-500, 30); err != nil { + fmt.Fprintln(os.Stderr, err) + } - 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) + if err := window.UpdateSurface(); err != nil { + fmt.Fprintln(os.Stderr, err) + } - rl.EndDrawing() + time.Sleep(time.Until(frameTargetTime)) + frameTargetTime = frameTargetTime.Add(time.Second / fps) + } +} + +func main() { + if err := pong(); err != nil { + fmt.Fprintln(os.Stderr, err) } } -- cgit v1.3.1