Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Part 4: AI Opponent

Let’s add an AI opponent so single players can enjoy the game.

What You’ll Learn

  • Simple AI that follows the ball
  • Checking player count with player_count()
  • Making AI beatable (not perfect)

Add Player Count FFI

#![allow(unused)]
fn main() {
#[link(wasm_import_module = "env")]
extern "C" {
    // ... existing imports ...
    fn player_count() -> u32;
}
}

Track Game Mode

Add a state variable to track whether we’re in two-player mode:

#![allow(unused)]
fn main() {
static mut IS_TWO_PLAYER: bool = false;
}

Simple AI Logic

Create an AI update function:

#![allow(unused)]
fn main() {
fn update_ai(paddle_y: &mut f32) {
    unsafe {
        // AI follows the ball
        let paddle_center = *paddle_y + PADDLE_HEIGHT / 2.0;
        let ball_center = BALL_Y + BALL_SIZE / 2.0;

        let diff = ball_center - paddle_center;

        // Only move if difference is significant (dead zone)
        if diff.abs() > 5.0 {
            // AI moves slower than max speed to be beatable
            let ai_speed = PADDLE_SPEED * 0.7;

            if diff > 0.0 {
                *paddle_y += ai_speed;
            } else {
                *paddle_y -= ai_speed;
            }
        }

        // Clamp to screen bounds
        *paddle_y = clamp(*paddle_y, 0.0, SCREEN_HEIGHT - PADDLE_HEIGHT);
    }
}
}

You’ll also need this helper:

#![allow(unused)]
fn main() {
fn abs(v: f32) -> f32 {
    if v < 0.0 { -v } else { v }
}
}

Update the Game Loop

Modify update() to use AI when appropriate:

#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn update() {
    unsafe {
        // Check if a second player is connected
        IS_TWO_PLAYER = player_count() >= 2;

        // Player 1 always uses input
        update_paddle(&mut PADDLE1_Y, 0);

        // Player 2: human or AI
        if IS_TWO_PLAYER {
            update_paddle(&mut PADDLE2_Y, 1);
        } else {
            update_ai(&mut PADDLE2_Y);
        }

        update_ball();
    }
}
}

Show Game Mode

Let’s display the current mode. Add text FFI:

#![allow(unused)]
fn main() {
#[link(wasm_import_module = "env")]
extern "C" {
    // ... existing imports ...
    fn draw_text(ptr: *const u8, len: u32, x: f32, y: f32, size: f32, color: u32);
}
}

Add to render():

#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn render() {
    unsafe {
        // ... existing drawing code ...

        // Show mode indicator
        if IS_TWO_PLAYER {
            let text = b"2 PLAYERS";
            draw_text(text.as_ptr(), text.len() as u32, 10.0, 10.0, 16.0, COLOR_GRAY);
        } else {
            let text = b"vs AI";
            draw_text(text.as_ptr(), text.len() as u32, 10.0, 10.0, 16.0, COLOR_GRAY);
        }
    }
}
}

How the AI Works

The AI is intentionally imperfect:

  1. Follows the ball - Moves toward where the ball is
  2. Slower speed - Only 70% of max paddle speed
  3. Dead zone - Doesn’t jitter when ball is near center
  4. No prediction - Doesn’t anticipate where ball will go

This makes the AI beatable but still challenging.

Making AI Harder or Easier

#![allow(unused)]
fn main() {
// Easier AI (50% speed)
let ai_speed = PADDLE_SPEED * 0.5;

// Harder AI (90% speed)
let ai_speed = PADDLE_SPEED * 0.9;

// Perfect AI (instant tracking) - not fun!
*paddle_y = ball_center - PADDLE_HEIGHT / 2.0;
}

The Magic: Automatic Multiplayer

Here’s the key insight: when a second player connects, the game automatically becomes two-player. You don’t need to do anything special!

This works because:

  1. We check player_count() every frame
  2. Player 2 input is always read (even if unused)
  3. The switch from AI to human is seamless

When a friend joins your game online via Emberware’s netcode, player_count() increases and they take control of paddle 2.

Build and Test

cargo build --target wasm32-unknown-unknown --release
ember run target/wasm32-unknown-unknown/release/paddle.wasm

With one controller:

  • You control player 1
  • AI controls player 2
  • “vs AI” appears in corner

Connect a second controller:

  • Both players are human
  • “2 PLAYERS” appears

Next: Part 5: Multiplayer - Understanding the rollback netcode magic.