Your first room
Two lines turn a normal game into a multiplayer one. net.join() picks the room, and net.me() tells everyone where you are.
import game, net
net.join("my-first-room") # anyone with this name plays with you
game.window(480, 360)
me = game.sprite("car", 240, 180, 44)
while game.playing():
if game.pressed("left"): me.x -= 5
if game.pressed("right"): me.x += 5
if game.pressed("up"): me.y -= 5
if game.pressed("down"): me.y += 5
net.me(me) # show my car to everyone
net.others() # ...and put their cars on my screen
game.frame(30)
How to test it on your own. Open PyWebLib in a second browser tab and run the same code there. Each tab is a separate player, so you can drive both and watch them move on each other's screens. Pick a room name nobody else will guess, or you may find a stranger already driving around in it.
Everyone else
net.others() hands you a list of the other players. Each one already has a sprite, created, moved and (when they leave) removed for you. You do not build it and you do not tidy it up.
for p in net.others():
print(p.name, "is at", p.x, p.y)
if me.touches(p): # collisions work exactly like a sprite
game.game_over("Crash!")
A player is a normal thing to poke at:
p.id— who they are, and it never changes while they are playing.p.name— what to call them on screen.p.x,p.y— where they are.p.sprite— the sprite drawing them, if you want to fiddle with it.me.touches(p)— the same collision check you already know.
You can send extra facts about yourself, and read them back off everybody else:
net.me(me, hp=3, ready=True) # ride along with my position
for p in net.others():
if p.get("hp", 0) <= 0:
print(p.name, "is out!")
Sharing one fact
Positions are per player. Sometimes the whole room needs to agree on one thing: who has the bomb, whose turn it is, what the score is. That is net.set() and net.get().
net.set("bomb", net.id) # I have the bomb
if net.get("bomb") == net.id:
print("Run!")
It holds anything you could print: text, numbers, True/False, lists and dictionaries. The rule is last write wins — if two players write the same key at the same moment, one of them quietly loses. The next section is about making sure that never happens.
Bomb tag
Everything above, as a real game. Drive into somebody to hand them the bomb.
import game, net, random
net.join("bomb-tag")
game.window(480, 360)
game.background("#101828")
CAR = 83 # the two Asset-studio sprites: a plain car...
BOMB_CAR = 84 # ...and the same car carrying the bomb
game.preload(CAR, BOMB_CAR)
me = game.sprite(CAR, random.randint(60, 420), random.randint(60, 300), 44, asset=True)
info = game.label("", 240, 24, 16)
COOLDOWN = 30 # frames you must hold it for: 1 second at 30 fps
KNOCKBACK = 46 # pixels the two of you are shoved apart on a tag
def back_off(x, y):
# Push me away from a point. Without this the two cars are still touching
# the instant the bomb changes hands, so it would come straight back.
dx, dy = me.x - x, me.y - y
gap = (dx * dx + dy * dy) ** 0.5
if gap < 1: # dead centre on top of each other
dx, dy, gap = 1.0, 0.0, 1.0
me.x += dx / gap * KNOCKBACK
me.y += dy / gap * KNOCKBACK
cooldown = 0
had_bomb = False
while game.playing():
if game.pressed("left"): me.x -= 5
if game.pressed("right"): me.x += 5
if game.pressed("up"): me.y -= 5
if game.pressed("down"): me.y += 5
players = net.others()
holder = net.get("bomb")
here = [p.id for p in players] + [net.id]
# Nobody has the bomb, OR whoever had it has left the room? The smallest id
# still here claims it. Every browser sees the same room and agrees, so no
# one has to be the referee. Without the "left the room" half, a holder who
# closes their tab leaves the bomb pointing at a player who is gone, and
# nobody can ever be it again.
if net.online() and holder not in here:
if net.id == min(here):
net.set("bomb", net.id)
holder = net.id
mine = (holder == net.id)
# Just been handed it? Jump back off whoever tagged me and start the
# cooldown, so it cannot bounce between two cars that are touching.
if mine and not had_bomb:
cooldown = COOLDOWN
for p in players:
if me.touches(p):
back_off(p.x, p.y)
break
had_bomb = mine
if cooldown > 0:
cooldown -= 1
want = BOMB_CAR if mine else CAR
if me.asset != want:
me.asset = want
# Only whoever HOLDS the bomb ever writes down who has it next, so two
# players can never disagree about where it is.
if mine and cooldown == 0:
for p in players:
if me.touches(p):
net.set("bomb", p.id)
back_off(p.x, p.y)
break
me.x = max(22, min(458, me.x))
me.y = max(22, min(338, me.y))
net.me(me)
if not net.online():
info.content = "Connecting..."
elif mine and cooldown > 0:
info.content = "You have the bomb! Hold it for " + str(cooldown // 30 + 1) + "..."
elif mine:
info.content = "You have the bomb! Run into someone."
else:
info.content = "Players: " + str(net.count()) + " - keep away from the bomb!"
game.frame(30)
Ideas to take it further: a countdown that ends the game for whoever is holding it, a score kept in net.set("scores", ...), or making the bomb holder faster than everyone else.
Who decides?
This is the one genuinely new idea in multiplayer, and bomb tag shows it twice.
Every browser is running its own copy of your program. They cannot all be in charge of the same fact, or they will disagree — two players would each think they had passed the bomb on. So for every shared fact, decide who is allowed to write it:
- Give one player the pen. Only the player holding the bomb writes who gets it next. Everyone else only reads. There is nothing to disagree about, because only one browser ever writes.
- Or let everyone work it out identically. Nobody owns the bomb at the start, so the rule is "smallest id takes it". Every browser sorts the same list of ids and reaches the same answer without anyone being asked.
The trap to avoid. Writing a shared value every frame from every player, like net.set("scores", ...) in the main loop for everybody. They will fight, the value will flicker, and the room floods with messages. Let one player own each key.
Every call
net.join(room, name=None, rate=5)— join a room. Returns straight away and connects in the background.rateis how many times a second your position may be sent (1–20). The default 5 looks smooth at 30 fps; raise it only for something twitchy.net.me(sprite, **extras)— publish my position and skin. Call it once per frame. An unchanged position is not resent.net.others()— the other players, each with a sprite already on screen.net.set(key, value)/net.get(key, default=None)— one fact the whole room shares.net.id— my player id. Fixed for this browser tab.net.count()— how many players are here, me included.net.online()—Trueonce I am actually in the room.net.status()—"offline","joining","joined", or"unavailable"when this copy of PyWebLib has no multiplayer backend set up.net.room()— the room name I ended up in (tidied: spaces and punctuation become dashes).net.leave()— leave, and take everyone else's sprites off my screen.
What it cannot do
- It is not cheat-proof. Every browser is trusted, so a determined student can write whatever position they like. That is fine for tag and terrible for anything competitive.
- Rooms are public. Anyone who guesses the name can join. Pick an odd one.
- No history. A room remembers nothing: join late and you see the room as it is now, not what happened before. Scores that must survive belong in
game.save()or a published game's leaderboard. - Up to 24 players are reported in a room, and everyone is dropped about four seconds after they go quiet.
- It needs the community backend. On a copy of PyWebLib without Supabase configured,
net.status()returns"unavailable"and every call quietly does nothing, so a program written withnetstill runs — alone.