diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2024-08-25 12:05:01 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2024-08-25 12:05:01 -0700 |
| commit | 827f639912529589e651da5c35e28881c8e08c1f (patch) | |
| tree | dbca88c4c3749c7286f54d326c81ad9f970734f6 | |
| parent | a2e1ec9472d7ff1d3387dc9d059dcee019a84996 (diff) | |
| download | pongo-827f639912529589e651da5c35e28881c8e08c1f.tar.zst | |
Port to SDL.
| -rw-r--r-- | flake.nix | 14 | ||||
| -rw-r--r-- | go.mod | 8 | ||||
| -rw-r--r-- | go.sum | 10 | ||||
| -rw-r--r-- | numbers/numbers.go | 88 | ||||
| -rw-r--r-- | ping.mp3 | bin | 4016 -> 0 bytes | |||
| -rw-r--r-- | ping.wav | bin | 0 -> 36044 bytes | |||
| -rw-r--r-- | pongo.go | 240 |
7 files changed, 247 insertions, 113 deletions
@@ -22,7 +22,7 @@ # Nixpkgs instantiated for supported system types. nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); - deps = pkgs: with pkgs; [ libGL xorg.libXi xorg.libXcursor xorg.libXrandr xorg.libXinerama wayland libxkbcommon ]; + deps = pkgs: with pkgs; [ SDL2_mixer ]; in { @@ -40,13 +40,13 @@ # in the Nix store. src = ./.; - vendorHash = "sha256-ZWMZkwDMSUuuWCInVJ1YTd0W5GJedGrxYugKv1jhx2U="; + vendorHash = "sha256-0GaAPAg940MKg4XOw+WqZBT8jT2gXJIAWsltcHMgzfw="; + nativeBuildInputs = [ pkgs.pkg-config ]; buildInputs = deps pkgs; - proxyVendor = true; - postFixup = '' - patchelf $out/bin/pongo --add-rpath ${pkgs.lib.makeLibraryPath [ pkgs.alsa-lib ]} + preBuild = '' + export CGO_CFLAGS=$(pkg-config --cflags --libs SDL2_mixer) ''; }; }); @@ -58,9 +58,9 @@ in { default = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ go gopls gotools go-tools ] ++ deps pkgs; + nativeBuildInputs = with pkgs; [ go gopls gotools go-tools pkg-config ] ++ deps pkgs; shellHook = '' - export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [ pkgs.alsa-lib ]} + export CGO_CFLAGS=$(pkg-config --cflags --libs SDL2_mixer) ''; }; }); @@ -2,10 +2,4 @@ module gitlab.com/rhogenson/pongo go 1.22 -require github.com/gen2brain/raylib-go/raylib v0.0.0-20240807111636-8861ee437da9 - -require ( - github.com/ebitengine/purego v0.7.1 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/sys v0.20.0 // indirect -) +require github.com/veandco/go-sdl2 v0.4.40 @@ -1,8 +1,2 @@ -github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= -github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= -github.com/gen2brain/raylib-go/raylib v0.0.0-20240807111636-8861ee437da9 h1:voUyZVwDxeiKv31gHmzHY95Oq/+0+ozj8zmKDiaag/o= -github.com/gen2brain/raylib-go/raylib v0.0.0-20240807111636-8861ee437da9/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U= +github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= diff --git a/numbers/numbers.go b/numbers/numbers.go new file mode 100644 index 0000000..21e5cb5 --- /dev/null +++ b/numbers/numbers.go @@ -0,0 +1,88 @@ +package numbers + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +const width = 60 + +func drawDigit(surface *sdl.Surface, d int, x, y int32) error { + const ( + height = 100 + thickness = 10 + ) + white := sdl.MapRGB(surface.Format, 245, 245, 245) + switch d { + case 0: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 1: + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + case 2: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 3: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 4: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + case 5: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y + height/2, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 6: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y + height/2, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 7: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + case 8: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + case 9: + surface.FillRect(&sdl.Rect{X: x, Y: y, W: thickness, H: height / 2}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x + width - thickness, Y: y, W: thickness, H: height}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height/2 - thickness/2, W: width, H: thickness}, white) + surface.FillRect(&sdl.Rect{X: x, Y: y + height - thickness, W: width, H: thickness}, white) + default: + panic("invalid digit") + } + return nil +} + +func Draw(surface *sdl.Surface, n int, x, y int32) error { + const padding = 20 + + if n == 0 { + return drawDigit(surface, 0, x, y) + } + digits := make([]int, 0, 19) + for ; n > 0; n /= 10 { + digits = append(digits, n%10) + } + for i := len(digits) - 1; i >= 0; i-- { + if err := drawDigit(surface, digits[i], x, y); err != nil { + return err + } + x += width + padding + } + return nil +} diff --git a/ping.mp3 b/ping.mp3 Binary files differdeleted file mode 100644 index 87145b8..0000000 --- a/ping.mp3 +++ /dev/null diff --git a/ping.wav b/ping.wav Binary files differnew file mode 100644 index 0000000..8ea7d00 --- /dev/null +++ b/ping.wav @@ -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) + } - 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 := 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) + } - rl.EndDrawing() + 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) + } + + if err := window.UpdateSurface(); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + time.Sleep(time.Until(frameTargetTime)) + frameTargetTime = frameTargetTime.Add(time.Second / fps) + } +} + +func main() { + if err := pong(); err != nil { + fmt.Fprintln(os.Stderr, err) } } |
