Skip to content

Example for deploying a FastHTTP application into MetaCall FaaS. The app is an implementation of Game of Life.

License

Notifications You must be signed in to change notification settings

metacall/fasthtml-example

Repository files navigation

Game of Life in FastHTML

This project implements Conway's Game of Life using FastHTML, showcasing real-time updates and multi-client synchronization through WebSockets.

Game of Life Animation

This project has been taken from: https://github.com/AnswerDotAI/fasthtml-example

We have used this example in order to show how it can be deployed into MetaCall FaaS

Features

  • Interactive Game of Life grid
  • Real-time updates across multiple clients
  • WebSocket integration for live synchronization
  • Simple controls: Run, Pause, and Reset

Technology Stack

  • FastHTML: A Python framework for building dynamic web applications
  • WebSockets: For real-time communication between server and clients
  • HTMX: For seamless client-side updates without full page reloads

Implementation Highlights

Game Logic

The core Game of Life logic is implemented in the update_grid function courtesy of ChatGPT:

def update_grid(grid: list[list[int]]) -> list[list[int]]:
    new_grid = [[0 for _ in range(20)] for _ in range(20)]
    def count_neighbors(x, y):
        directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
        count = 0
        for dx, dy in directions:
            nx, ny = x + dx, y + dy
            if 0 <= nx < len(grid) and 0 <= ny < len(grid[0]): count += grid[nx][ny]
        return count
    for i in range(len(grid)):
        for j in range(len(grid[0])):
            neighbors = count_neighbors(i, j)
            if grid[i][j] == 1:
                if neighbors < 2 or neighbors > 3: new_grid[i][j] = 0
                else: new_grid[i][j] = 1
            elif neighbors == 3: new_grid[i][j] = 1
    return new_grid

This function determines how the game world evolves over time by applying the rules of Conway's Game of Life to the cells in the grid.

Grid Rendering and User Interaction

The grid is rendered using FastHTML components:

def Grid():
    cells = []
    for y, row in enumerate(game_state['grid']):
        for x, cell in enumerate(row):
            cell_class = 'alive' if cell else 'dead'
            cell = Div(cls=f'cell {cell_class}', hx_put='/update', hx_vals={'x': x, 'y': y}, hx_swap='none', hx_target='#gol', hx_trigger='click')
            cells.append(cell)
    return Div(*cells, id='grid')

@rt('/update')
async def put(x: int, y: int):
    game_state['grid'][y][x] = 1 if game_state['grid'][y][x] == 0 else 0
    await update_players()

Above is a component for representing the game's state that the user can interact with and update on the server using cool HTMX features such as hx_vals for determining which cell was clicked to make it dead or alive. We use hx_put to send a PUT request to the server to update the game state rather than a POST request, which would have returned a new Grid component with the updated state since we want to handle that via websockets so all clients can see the changes rather than only the client that initiated the change.

FastHTML and WebSocket Integration

FastHTML handles WebSocket connections with ease:

@app.ws('/gol', conn=on_connect, disconn=on_disconnect)
async def ws(msg:str, send): pass

player_queue = []
async def on_connect(send): player_queue.append(send)
async def on_disconnect(send): await update_players()

The @app.ws decorator sets up the WebSocket endpoint, while on_connect and on_disconnect manage the player queue. Similar to all of HTMX, you send HTML snippets, or in our case FastHTML components, to the client to update the page. There is only one difference with standard HTMX updating of HTML on the client and how it is done via websockets, that being all swaps are OOB. You can find more information on the HTMX websocket extension documentation page here.

Real-time Updates

A background task continuously updates the game state and notifies clients:

async def update_players():
    for i, player in enumerate(player_queue):
        try: await player(Grid())
        except: player_queue.pop(i)

async def background_task():
    while True:
        if game_state['running'] and len(player_queue) > 0:
            game_state['grid'] = update_grid(game_state['grid'])
            await update_players()
        await asyncio.sleep(1.0)

background_task_coroutine = asyncio.create_task(background_task())

update_players() sends the current game state to all connected clients, updating their visuals in real-time and removing any players that have disconnected.

Running Locally

Uvicorn CLI

To run the app locally with uvicorn CLI:

  1. Clone the repository
  2. Install the dependencies: pip install -r requirements.txt
  3. Run the server: uvicorn main:app --reload

MetaCall CLI

To run the app locally with metacall CLI:

  1. Clone the repository
  2. Install metacall CLI: curl -sL https://raw.githubusercontent.com/metacall/install/master/install.sh | sh
  3. Install the dependencies: metacall pip3 install -r requirements.txt
  4. Run the server: metacall server.py

Docker

To run the app locally with docker:

  1. Clone the repository.
  2. Build the image: docker build -t metacall/fasthtml-example .
  3. Run the container: docker run --rm -p 5000:5000 -it metacall/fasthtml-example

Deploy

For deploying, install metacall CLI as shown before. Then run metacall deploy.

About

Example for deploying a FastHTTP application into MetaCall FaaS. The app is an implementation of Game of Life.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published