summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-08-24 21:34:48 -0700
committerRose Hogenson <rosehogenson@posteo.net>2024-08-24 21:34:48 -0700
commita2e1ec9472d7ff1d3387dc9d059dcee019a84996 (patch)
tree9fe251af8607c4844cf2432e9b9c5e1a221417e7
downloadpongo-a2e1ec9472d7ff1d3387dc9d059dcee019a84996.tar.zst
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--flake.lock26
-rw-r--r--flake.nix73
-rw-r--r--go.mod11
-rw-r--r--go.sum8
-rw-r--r--ping.aup3bin0 -> 57344 bytes
-rw-r--r--ping.mp3bin0 -> 4016 bytes
-rw-r--r--pongo.go215
8 files changed, 335 insertions, 0 deletions
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
--- /dev/null
+++ b/ping.aup3
Binary files differ
diff --git a/ping.mp3 b/ping.mp3
new file mode 100644
index 0000000..87145b8
--- /dev/null
+++ b/ping.mp3
Binary files 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()
+ }
+}