Running Python-Based Snake Game in the Browser with Pygbag and WebAssembly

WebAssembly (Wasm) enables near-native performance for applications running in web browsers. Combined with tools like pygbag, it allows Python code—specifically games built with Pygame—to run directly in the browser without requirring users to install Python locally.

Why Pygbag?

While alternatives like Pyodide exist, they often lack support for complex libraries such as Pygame or suffer from slow startup times. pygbag fills this gap by packaging Pygame-based applications into WebAssembly modules. It leverages Emscripten under the hood but abstracts away much of the complexity, providing a streamlined workflow for deevlopers.

Building a Snake Game

Start by implementing a basic Snake game using Pygame. The following example creates a playable version where the player controls a red circle that grows when consuming white food items:

import pygame
import random

pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0

head_pos = [pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)]
speed = 300
segment_radius = 30

food_location = pygame.Vector2(
    random.randint(0, screen.get_width()),
    random.randint(0, screen.get_height())
)
food_size = 15

movement = pygame.Vector2(0, 0)

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill("black")

    keys = pygame.key.get_pressed()
    if keys[pygame.K_w] or keys[pygame.K_UP]:
        movement = pygame.Vector2(0, -1)
    if keys[pygame.K_s] or keys[pygame.K_DOWN]:
        movement = pygame.Vector2(0, 1)
    if keys[pygame.K_a] or keys[pygame.K_LEFT]:
        movement = pygame.Vector2(-1, 0)
    if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
        movement = pygame.Vector2(1, 0)

    head_pos[0] += movement * speed * dt

    if head_pos[0].distance_to(food_location) < segment_radius + food_size:
        head_pos.append(head_pos[-1].copy())
        food_location = pygame.Vector2(
            random.randint(0, screen.get_width()),
            random.randint(0, screen.get_height())
        )

    for i in range(len(head_pos) - 1, 0, -1):
        head_pos[i] = head_pos[i - 1].copy()

    pygame.draw.circle(screen, "white", food_location, food_size)
    for pos in head_pos:
        pygame.draw.circle(screen, "red", pos, segment_radius)

    pygame.display.flip()
    dt = clock.tick(60) / 1000

pygame.quit()

Adapting for Pygbag

To run this game in the browser via pygbag, wrap the main loop in an asynchronous function and use asyncio.sleep(0) to yield control back to the browser evant loop. Save the file as main.py in your project directory:

import pygame
import random
import asyncio

pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0

head_pos = [pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)]
speed = 300
segment_radius = 30

food_location = pygame.Vector2(
    random.randint(0, screen.get_width()),
    random.randint(0, screen.get_height())
)
food_size = 15

movement = pygame.Vector2(0, 0)

async def game_loop():
    global running, dt, head_pos, food_location, movement
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        screen.fill("black")

        keys = pygame.key.get_pressed()
        if keys[pygame.K_w] or keys[pygame.K_UP]:
            movement = pygame.Vector2(0, -1)
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:
            movement = pygame.Vector2(0, 1)
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:
            movement = pygame.Vector2(-1, 0)
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            movement = pygame.Vector2(1, 0)

        head_pos[0] += movement * speed * dt

        if head_pos[0].distance_to(food_location) < segment_radius + food_size:
            head_pos.append(head_pos[-1].copy())
            food_location = pygame.Vector2(
                random.randint(0, screen.get_width()),
                random.randint(0, screen.get_height())
            )

        for i in range(len(head_pos) - 1, 0, -1):
            head_pos[i] = head_pos[i - 1].copy()

        pygame.draw.circle(screen, "white", food_location, food_size)
        for pos in head_pos:
            pygame.draw.circle(screen, "red", pos, segment_radius)

        pygame.display.flip()
        dt = clock.tick(60) / 1000
        await asyncio.sleep(0)

    pygame.quit()

if __name__ == "__main__":
    asyncio.run(game_loop())

Deployment with Pygbag

Install pygbag using pip:

pip install pygbag

From the parent directory of your game folder (e.g., if your game is in snake_game/, run from the directory containing snake_game/), execute:

python -m pygbag snake_game

This command starts a local development server at http://localhost:8000/. After a brief loading period, the game will appear in the browser, fully interactive and powered by WebAssembly.

Tags: WebAssembly Pygbag pygame python Asyncio

Posted on Sun, 23 Aug 2026 16:47:11 +0000 by ClyssaN