Tutorial: Building Pong

Let's build a complete Pong game step by step.

Final Result

Two paddles, a ball, scoring, and AI opponent.

Step 1: Project Setup

mkdir pong && cd pong
go mod init pong
go get github.com/drpaneas/pigo8

Create main.go:

package main

import p8 "github.com/drpaneas/pigo8"

type Game struct{}

func (g *Game) Init()   {}
func (g *Game) Update() {}
func (g *Game) Draw()   { p8.Cls(0) }

func main() {
    p8.InsertGame(&Game{})
    p8.Play()
}

Step 2: Define Game Objects

type Paddle struct {
    x, y, width, height, speed float64
    color int
}

type Ball struct {
    x, y, size   float64
    dx, dy       float64
    color        int
}

type Game struct {
    player   Paddle
    computer Paddle
    ball     Ball
    playerScore   int
    computerScore int
}

Step 3: Initialize Positions

func (g *Game) Init() {
    // Player paddle on the left
    g.player = Paddle{
        x: 4, y: 54,
        width: 4, height: 20,
        speed: 2, color: 12,
    }
    
    // Computer paddle on the right
    g.computer = Paddle{
        x: 120, y: 54,
        width: 4, height: 20,
        speed: 1.5, color: 8,
    }
    
    // Ball in center
    g.ball = Ball{
        x: 62, y: 62,
        size: 4,
        dx: 2, dy: 1,
        color: 7,
    }
}

Step 4: Player Input

func (g *Game) Update() {
    // Player movement
    if p8.Btn(p8.UP) && g.player.y > 0 {
        g.player.y -= g.player.speed
    }
    if p8.Btn(p8.DOWN) && g.player.y+g.player.height < 128 {
        g.player.y += g.player.speed
    }
}

Step 5: Ball Movement and Wall Bouncing

func (g *Game) Update() {
    // ... player input ...
    
    // Move ball
    g.ball.x += g.ball.dx
    g.ball.y += g.ball.dy
    
    // Bounce off top/bottom walls
    if g.ball.y <= 0 || g.ball.y+g.ball.size >= 128 {
        g.ball.dy = -g.ball.dy
    }
}

Step 6: Paddle Collision

func collides(ball Ball, paddle Paddle) bool {
    return ball.x+ball.size >= paddle.x &&
           ball.x <= paddle.x+paddle.width &&
           ball.y+ball.size >= paddle.y &&
           ball.y <= paddle.y+paddle.height
}

func (g *Game) Update() {
    // ... previous code ...
    
    // Paddle collision
    if collides(g.ball, g.player) || collides(g.ball, g.computer) {
        g.ball.dx = -g.ball.dx
    }
}

Step 7: Scoring

func (g *Game) Update() {
    // ... previous code ...
    
    // Score when ball exits
    if g.ball.x < 0 {
        g.computerScore++
        g.resetBall()
    }
    if g.ball.x > 128 {
        g.playerScore++
        g.resetBall()
    }
}

func (g *Game) resetBall() {
    g.ball.x = 62
    g.ball.y = 62
    g.ball.dx = -g.ball.dx  // Serve toward last scorer
}

Step 8: AI Opponent

func (g *Game) Update() {
    // ... previous code ...
    
    // Simple AI: follow the ball
    if g.ball.dx > 0 {  // Ball moving toward AI
        mid := g.computer.y + g.computer.height/2
        if mid < g.ball.y && g.computer.y+g.computer.height < 128 {
            g.computer.y += g.computer.speed
        }
        if mid > g.ball.y && g.computer.y > 0 {
            g.computer.y -= g.computer.speed
        }
    }
}

Step 9: Drawing

func (g *Game) Draw() {
    p8.Cls(0)
    
    // Center line
    for y := 0; y < 128; y += 8 {
        p8.Line(64, float64(y), 64, float64(y+4), 5)
    }
    
    // Paddles
    p8.Rectfill(g.player.x, g.player.y,
        g.player.x+g.player.width, g.player.y+g.player.height,
        g.player.color)
    p8.Rectfill(g.computer.x, g.computer.y,
        g.computer.x+g.computer.width, g.computer.y+g.computer.height,
        g.computer.color)
    
    // Ball
    p8.Rectfill(g.ball.x, g.ball.y,
        g.ball.x+g.ball.size, g.ball.y+g.ball.size,
        g.ball.color)
    
    // Score
    p8.Print(g.playerScore, 32, 4, 12)
    p8.Print(g.computerScore, 92, 4, 8)
}

Complete Code

package main

import p8 "github.com/drpaneas/pigo8"

type Paddle struct {
    x, y, width, height, speed float64
    color                      int
}

type Ball struct {
    x, y, size float64
    dx, dy     float64
    color      int
}

type Game struct {
    player        Paddle
    computer      Paddle
    ball          Ball
    playerScore   int
    computerScore int
}

func (g *Game) Init() {
    g.player = Paddle{x: 4, y: 54, width: 4, height: 20, speed: 2, color: 12}
    g.computer = Paddle{x: 120, y: 54, width: 4, height: 20, speed: 1.5, color: 8}
    g.ball = Ball{x: 62, y: 62, size: 4, dx: 2, dy: 1, color: 7}
}

func (g *Game) Update() {
    // Player input
    if p8.Btn(p8.UP) && g.player.y > 0 {
        g.player.y -= g.player.speed
    }
    if p8.Btn(p8.DOWN) && g.player.y+g.player.height < 128 {
        g.player.y += g.player.speed
    }

    // AI
    if g.ball.dx > 0 {
        mid := g.computer.y + g.computer.height/2
        if mid < g.ball.y && g.computer.y+g.computer.height < 128 {
            g.computer.y += g.computer.speed
        }
        if mid > g.ball.y && g.computer.y > 0 {
            g.computer.y -= g.computer.speed
        }
    }

    // Ball movement
    g.ball.x += g.ball.dx
    g.ball.y += g.ball.dy

    // Wall bounce
    if g.ball.y <= 0 || g.ball.y+g.ball.size >= 128 {
        g.ball.dy = -g.ball.dy
    }

    // Paddle collision
    if collides(g.ball, g.player) || collides(g.ball, g.computer) {
        g.ball.dx = -g.ball.dx
    }

    // Scoring
    if g.ball.x < 0 {
        g.computerScore++
        g.resetBall()
    }
    if g.ball.x > 128 {
        g.playerScore++
        g.resetBall()
    }
}

func (g *Game) Draw() {
    p8.Cls(0)

    // Center line
    for y := 0; y < 128; y += 8 {
        p8.Line(64, float64(y), 64, float64(y+4), 5)
    }

    // Game objects
    p8.Rectfill(g.player.x, g.player.y, g.player.x+g.player.width, g.player.y+g.player.height, g.player.color)
    p8.Rectfill(g.computer.x, g.computer.y, g.computer.x+g.computer.width, g.computer.y+g.computer.height, g.computer.color)
    p8.Rectfill(g.ball.x, g.ball.y, g.ball.x+g.ball.size, g.ball.y+g.ball.size, g.ball.color)

    // Score
    p8.Print(g.playerScore, 32, 4, 12)
    p8.Print(g.computerScore, 92, 4, 8)
}

func (g *Game) resetBall() {
    g.ball.x = 62
    g.ball.y = 62
    g.ball.dx = -g.ball.dx
}

func collides(ball Ball, paddle Paddle) bool {
    return ball.x+ball.size >= paddle.x &&
        ball.x <= paddle.x+paddle.width &&
        ball.y+ball.size >= paddle.y &&
        ball.y <= paddle.y+paddle.height
}

func main() {
    settings := p8.NewSettings()
    settings.TargetFPS = 60
    settings.WindowTitle = "PIGO-8 Pong"
    p8.InsertGame(&Game{})
    p8.PlayGameWith(settings)
}

Next Steps

  • Add sound effects for paddle hits and scoring
  • Implement difficulty levels
  • Add a two-player mode
  • Create a title screen
  • Add ball speed increase over time

See examples/pong/main.go for the full implementation with sound effects.