Pong – complete code¶
The finished script from the Build Multiplayer Pong tutorial, ready to compare against your own build.
-- ============================================================
-- Constants and local state
-- ============================================================
W, H = 320, 180
PAD_W = 4
PAD_H = 28
PAD_SPEED = 3
BALL_SIZE = 4
WIN_SCORE = 5
COL_BG = 0 -- black
COL_LEFT = 12 -- blue
COL_RIGHT = 8 -- red
COL_BALL = 7 -- white
state = "menu" -- "menu" | "waiting" | "playing" | "over"
is_host = false
side = nil -- "left" (host) or "right" (joiner)
function _init()
state = "menu"
is_host = false
side = nil
print("Press H to host a game, J to join one")
end
function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
-- ============================================================
-- Session menu
-- ============================================================
function update_menu()
if key_pressed("h") then
state = "waiting"
is_host = true
net.host({ max_players = 2, title = "Pong" }, on_connected)
elseif key_pressed("j") then
state = "waiting"
is_host = false
net.join(on_connected)
end
end
function update_waiting()
-- Cancelling a dialog fires no callback, so the game stays here. Let the
-- player re-open host/join directly, or press M to return to the menu.
if key_pressed("m") then
state = "menu"
print("Press H to host a game, J to join one")
elseif key_pressed("h") then
is_host = true
net.host({ max_players = 2, title = "Pong" }, on_connected)
elseif key_pressed("j") then
is_host = false
net.join(on_connected)
end
end
-- ============================================================
-- Session setup (runs once, on the successful host/join)
-- ============================================================
function on_connected()
side = is_host and "left" or "right"
if is_host then
net.state.pads = { left = (H - PAD_H) / 2, right = (H - PAD_H) / 2 }
net.state.score = { left = 0, right = 0 }
net.state.playing = false
reset_ball(1)
net.on("peer.joined", function(playerId)
net.state.playing = true
print("Player " .. playerId .. " joined -- game on!")
end)
net.on("peer.left", function(playerId)
net.state.playing = false
print("Player " .. playerId .. " left -- waiting for a new opponent")
end)
end
net.on("event:point", function(from, scorer)
print("Point for the " .. scorer .. " side!")
end)
net.on("ended", function()
state = "over"
print("The host closed the session. Press M for the menu.")
end)
state = "playing"
print("Connected! You are the " .. side .. " paddle (arrow keys).")
end
function reset_ball(direction)
net.state.ball = {
x = (W - BALL_SIZE) / 2,
y = (H - BALL_SIZE) / 2,
dx = 2 * direction,
dy = 1.5,
}
end
-- ============================================================
-- Gameplay: own paddle, host-simulated ball
-- ============================================================
function update_paddle()
local pads = net.state.pads
if not pads then
return -- state not replicated yet
end
local y = pads[side]
if key_pressed("ArrowUp") then
y = y - PAD_SPEED
end
if key_pressed("ArrowDown") then
y = y + PAD_SPEED
end
pads[side] = clamp(y, 0, H - PAD_H)
end
function update_ball()
if not net.state.playing or net.state.winner then
return
end
local ball = net.state.ball
local pads = net.state.pads
ball.x = ball.x + ball.dx
ball.y = ball.y + ball.dy
-- Bounce off the top and bottom
if ball.y <= 0 or ball.y >= H - BALL_SIZE then
ball.dy = -ball.dy
end
-- Bounce off the paddles
if ball.dx < 0 and ball.x <= 8 + PAD_W
and ball.y + BALL_SIZE >= pads.left and ball.y <= pads.left + PAD_H then
ball.dx = -ball.dx
elseif ball.dx > 0 and ball.x + BALL_SIZE >= W - 8 - PAD_W
and ball.y + BALL_SIZE >= pads.right and ball.y <= pads.right + PAD_H then
ball.dx = -ball.dx
end
-- Out on the left: right scores (and vice versa)
if ball.x < -BALL_SIZE then
score_point("right", 1)
elseif ball.x > W then
score_point("left", -1)
end
end
function score_point(scorer, serve_direction)
net.state.score[scorer] = net.state.score[scorer] + 1
net.emit("point", scorer)
print("Point for the " .. scorer .. " side!")
if net.state.score[scorer] >= WIN_SCORE then
net.state.winner = scorer
else
reset_ball(serve_direction)
end
end
-- ============================================================
-- Game loop
-- ============================================================
function update_playing()
update_paddle()
if is_host then
update_ball()
end
if net.state.winner then
state = "over"
print("The " .. net.state.winner .. " side wins! Press M for the menu.")
end
end
function update_over()
if key_pressed("m") then
net.leave()
_init()
end
end
function _update()
if state == "menu" then
update_menu()
elseif state == "waiting" then
update_waiting()
elseif state == "playing" then
update_playing()
elseif state == "over" then
update_over()
end
end
-- ============================================================
-- Drawing (everything comes from net.state)
-- ============================================================
function draw_score()
local score = net.state.score
if not score then
return
end
for i = 1, score.left do
fill_rect(COL_LEFT, 8 + (i - 1) * 6, 6, 4, 4)
end
for i = 1, score.right do
fill_rect(COL_RIGHT, W - 8 - (i - 1) * 6 - 4, 6, 4, 4)
end
end
function draw_game()
local pads = net.state.pads
local ball = net.state.ball
if pads then
fill_rect(COL_LEFT, 8, pads.left, PAD_W, PAD_H)
fill_rect(COL_RIGHT, W - 8 - PAD_W, pads.right, PAD_W, PAD_H)
end
if ball and net.state.playing and not net.state.winner then
fill_rect(COL_BALL, ball.x, ball.y, BALL_SIZE, BALL_SIZE)
end
draw_score()
end
function _draw()
clear(COL_BG)
if state == "playing" then
draw_game()
elseif state == "over" and net.state.winner then
-- Fill the screen with the winner's color
local col = net.state.winner == "left" and COL_LEFT or COL_RIGHT
fill_rect(col, 0, 0, W, H)
else
-- Menu / waiting: a centered "net" between two idle paddles
fill_rect(COL_LEFT, 8, (H - PAD_H) / 2, PAD_W, PAD_H)
fill_rect(COL_RIGHT, W - 8 - PAD_W, (H - PAD_H) / 2, PAD_W, PAD_H)
for y = 0, H - 8, 12 do
fill_rect(5, W / 2 - 1, y, 2, 8)
end
end
end