diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..26515d7 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,22 @@ +# https://pre-commit.com +# This GitHub Action assumes that the repo contains a valid .pre-commit-config.yaml file. +--- +name: pre-commit +on: + pull_request: + push: + branches: [master] + +permissions: + contents: read + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - run: pip install pre-commit + - run: pre-commit --version + - run: pre-commit install + - run: pre-commit run --all-files diff --git a/.github/workflows/pygbag.yml b/.github/workflows/pygbag.yml new file mode 100644 index 0000000..3f6f9c8 --- /dev/null +++ b/.github/workflows/pygbag.yml @@ -0,0 +1,21 @@ + +name: pygbag_build +on: [workflow_dispatch] + + +jobs: + build-pygbag: + name: Build for Emscripten pygbag runtime + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Checkout + run: | + python -m pip install pygbag + python -m pygbag --build $GITHUB_WORKSPACE/main.py + - name : "Upload to GitHub pages branch gh-pages" + uses: JamesIves/github-pages-deploy-action@4.1.7 + with: + branch: gh-pages + folder: build/web diff --git a/.gitignore b/.gitignore index cf76eb4..45a992e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,7 @@ gobblet/game/__PYCACHE__/* /gobblet/examples/example_cleanRL_batch.py /tests/test.py *.gif -*/__pycache__/ +gobblet/__pycache__/ +/build/ +/modules/ diff --git a/gobblet/__pycache__/__init__.cpython-39.pyc b/gobblet/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 8d6ec7a..0000000 Binary files a/gobblet/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/gobblet/__pycache__/gobblet_v1.cpython-38.pyc b/gobblet/__pycache__/gobblet_v1.cpython-38.pyc deleted file mode 100644 index db2b846..0000000 Binary files a/gobblet/__pycache__/gobblet_v1.cpython-38.pyc and /dev/null differ diff --git a/gobblet/examples/example_basic.py b/gobblet/examples/example_basic.py index 6af71c8..8362743 100644 --- a/gobblet/examples/example_basic.py +++ b/gobblet/examples/example_basic.py @@ -61,8 +61,7 @@ def get_args() -> argparse.Namespace: ) if args.render_mode == "human": - time.sleep( - 0.5 - ) # Wait .5 seconds between moves so the user can follow the sequence of moves + # Wait .5 seconds between moves so the user can follow the sequence of moves + time.sleep(0.5) env.step(action) diff --git a/install_wasm.sh b/install_wasm.sh new file mode 100755 index 0000000..eafc33b --- /dev/null +++ b/install_wasm.sh @@ -0,0 +1,8 @@ +#!/bin/bash +mkdir -p modules +cd modules + +python -m pip download pettingzoo==1.22.3 # tianshou==0.4.1 torch==1.13.1 + +unzip -o '*.whl' +rm *.whl \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..fcec2b4 --- /dev/null +++ b/main.py @@ -0,0 +1,41 @@ +import asyncio +import sys + +sys.path.append("modules") + +import numpy as np # noqa: E402 F401 +import time # noqa: E402 +from gobblet import gobblet_v1 # noqa: E402 + + +PLAYER = 0 +DEPTH = 2 +RENDER_MODE = "human" +RENDER_DELAY = 0.1 + +async def main() -> None: + env = gobblet_v1.env(render_mode="human", args=None) + env.reset() + env.render() # need to render the environment before pygame can take user input + + for agent in env.agent_iter(): + observation, reward, termination, truncation, info = env.last() + + if termination or truncation: + print(f"Agent: ({agent}), Reward: {reward}, info: {info}") + break + + action_mask = observation["action_mask"] + action = np.random.choice( + np.arange(len(action_mask)), p=action_mask / np.sum(action_mask) + ) + + # Wait .5 seconds between moves so the user can follow the sequence of moves + time.sleep(0.5) + env.step(action) + + await asyncio.sleep(0) # Very important, and keep it 0 + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tutorials/PygameWASM/pygame_wasm.md b/tutorials/PygameWASM/pygame_wasm.md new file mode 100644 index 0000000..5635bc2 --- /dev/null +++ b/tutorials/PygameWASM/pygame_wasm.md @@ -0,0 +1,39 @@ +# Tutorial +This tutorial provides a working example of using Pygame with WebAssembly to run and render a Gobblet environment locally in the browser. + +The script displays a game of chess between two random agents. +Features such as loading trained models or using interactive environments may be added in the future. + +It can be deployed to github-pages via a workflow (see `.github/workflows/pygbag.yml`). + +## Pygame & WebAssembly (WASM) + +[WebAssembly](https://webassembly.org/) (WASM) allows many languages to run locally in the browser with near-native performance. The python package [Pygbag](https://github.com/pygame-web/pygbag) allows us to run python scripts in the web using WebAssembly--including [Pygame](https://github.com/pygame/pygame), which is used for rendering PettingZoo environments. + +For more information and example scripts, see [pygame-web](https://github.com/pygame-web/pygame-web.github.io) + +Packages may not all work natively using Pygbag, so any issues can be reported to [pkg-porting-wasm](https://github.com/pygame-web/pkg-porting-wasm) + +Pygbag works via a virtual environment, and all dependencies must be downloaded locally as wheels and unzipped. `install.sh` script does this and moves the unzipped packages into `/modules` which needs to be added to the system path before importing. Pygbag only allows a single file `main.py` to be used, and requires a specific structure using `asyncio` (see [pygame-web](https://github.com/pygame-web/pygame-web.github.io)) + + + +## Usage: + +1. (Optional) Create a virtual environment: `conda create -n pygame-wasm python=3.10` +2. (Optional) Activate the virtual environment: `conda activate pygame-wasm` +3. Install requirements: `pip install -r requirements.txt` +4. Run `bash install.sh` in order to download and unzip dependencies to be used in the WASM virtual machine. +5. Change directory to parent of root project directory: `cd ../../..` +6. Run pygbag on this directory: pygbag PygameWASM ` +7. Open your browser and go to `http://localhost:8000/` (for debugging info: http://localhost:8000/#debug) + +## Modifying: +Dependencies and versions can be changed in `install.sh`, which can be re-run (although it is safest to delete `/modules` and ensure there are not multiple versions of the same package) + +## Debugging: + +- Python 3.11 is recommended for WASM, but is currently not supported by PettingZoo, which may lead to errors. + +- Calling certain sub-modules (e.g., `package.submodule`) without explicitly importing them can also cause errors. For example, in `connect_four.py`, line 281 ` observation = np.array(pygame.surfarray.pixels3d(self.screen))` raises an error. + - This can be solved by explicitly importing the sub-module: add the line `import pygame.surfarray` to `main.py` (no need to modify the original file it was called in) \ No newline at end of file diff --git a/tutorials/PygameWASM/requirements_wasm.txt b/tutorials/PygameWASM/requirements_wasm.txt new file mode 100644 index 0000000..ce71bce --- /dev/null +++ b/tutorials/PygameWASM/requirements_wasm.txt @@ -0,0 +1,5 @@ +numpy==1.23.5 +pettingzoo==1.22.3 +asyncio==3.4.3 +pygbag==0.7.1 +token-utils==0.1.8