From a2e1ec9472d7ff1d3387dc9d059dcee019a84996 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sat, 24 Aug 2024 21:34:48 -0700 Subject: Initial commit --- .gitignore | 2 + flake.lock | 26 ++++++++ flake.nix | 73 +++++++++++++++++++++ go.mod | 11 ++++ go.sum | 8 +++ ping.aup3 | Bin 0 -> 57344 bytes ping.mp3 | Bin 0 -> 4016 bytes pongo.go | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 335 insertions(+) create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ping.aup3 create mode 100644 ping.mp3 create mode 100644 pongo.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e72e976 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/pongo +/result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d26d37b --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1724224976, + "narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c374d94f1536013ca8e92341b540eba4c22f9c62", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b415c15 --- /dev/null +++ b/flake.nix @@ -0,0 +1,73 @@ +{ + description = "pong"; + + # Nixpkgs / NixOS version to use. + inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: + let + + # to work with older version of flakes + lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101"; + + # Generate a user-friendly version number. + version = builtins.substring 0 8 lastModifiedDate; + + # System types to support. + supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; + + # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + # 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 ]; + + in + { + + # Provide some binary packages for selected system types. + packages = forAllSystems (system: + let + pkgs = nixpkgsFor.${system}; + in + { + pongo = pkgs.buildGoModule { + pname = "pongo"; + inherit version; + # In 'nix develop', we don't need a copy of the source tree + # in the Nix store. + src = ./.; + + vendorHash = "sha256-ZWMZkwDMSUuuWCInVJ1YTd0W5GJedGrxYugKv1jhx2U="; + + buildInputs = deps pkgs; + + proxyVendor = true; + postFixup = '' + patchelf $out/bin/pongo --add-rpath ${pkgs.lib.makeLibraryPath [ pkgs.alsa-lib ]} + ''; + }; + }); + + # Add dependencies that are only needed for development + devShells = forAllSystems (system: + let + pkgs = nixpkgsFor.${system}; + in + { + default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ go gopls gotools go-tools ] ++ deps pkgs; + shellHook = '' + export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [ pkgs.alsa-lib ]} + ''; + }; + }); + + # The default package for 'nix build'. This makes sense if the + # flake provides only one package or there is a clear "main" + # package. + defaultPackage = forAllSystems (system: self.packages.${system}.pongo); + }; +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f1e3ac0 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7e6902d --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +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= diff --git a/ping.aup3 b/ping.aup3 new file mode 100644 index 0000000..da7c415 Binary files /dev/null and b/ping.aup3 differ diff --git a/ping.mp3 b/ping.mp3 new file mode 100644 index 0000000..87145b8 Binary files /dev/null and b/ping.mp3 differ 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() + } +} -- cgit v1.3.1