├── .darglint ├── .env ├── .flake8 ├── .github └── workflows │ ├── bump-version.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile.game ├── Dockerfile.view ├── Dockerfile.view.dockerignore ├── LICENSE ├── Makefile ├── README.md ├── mypy.ini ├── poetry.lock ├── pyproject.toml └── src ├── assets ├── demo.gif └── logo.png ├── game ├── public │ └── .gitkeep ├── resource │ ├── convert.py │ ├── newfont.png │ ├── packfont.py │ └── sprites.png └── src │ ├── logic │ ├── game.c │ ├── game.h │ ├── game_alien.c │ ├── game_alien.h │ ├── game_bullet.c │ ├── game_bullet.h │ ├── game_object.c │ ├── game_object.h │ ├── game_player.c │ ├── game_player.h │ ├── game_powerup.c │ ├── game_powerup.h │ ├── game_title.c │ └── game_title.h │ ├── main.c │ ├── sprites.h │ └── system │ ├── system.c │ ├── system.h │ ├── system_base.c │ ├── system_base.h │ ├── system_input.c │ ├── system_input.h │ ├── system_video.c │ └── system_video.h ├── train_invaders ├── __init__.py ├── index.js ├── start.py ├── stop.py └── view.txt └── view ├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── App.vue ├── assets │ ├── PressStart2P.ttf │ ├── aporia-logo.png │ ├── bg.png │ ├── game.txt │ ├── heart.png │ └── success.png ├── components │ ├── CloseButton.vue │ ├── Leaderboard.vue │ ├── LeaderboardRow.vue │ └── Spinner.vue ├── main.js └── styles.css └── vue.config.js /.darglint: -------------------------------------------------------------------------------- 1 | [darglint] 2 | strictness = short 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=${PYTHONPATH}${pathSeparator}./src 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | select = ANN,B,B9,BLK,C,D,DAR,E,F,I,S,W 3 | ignore = E203,E501,W503,ANN002,ANN003,ANN101,D100,D104,DAR401,S104,C901 4 | max-line-length = 100 5 | max-complexity = 10 6 | import-order-style = google 7 | docstring-convention = google 8 | per-file-ignores = tests/*:D101,D102,D103,D107,S101,S106 9 | suppress_none_returning = True 10 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Aporia 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | bump-version: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@master 12 | with: 13 | ref: ${{ github.ref }} 14 | fetch-depth: 0 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | - name: Configure git 18 | run: | 19 | git config --global user.email "camparibot@aporia.com" 20 | git config --global user.name "camparibot" 21 | git config --global push.followTags true 22 | 23 | - name: Install dependencies 24 | run: make install-deps 25 | 26 | - name: Bump Version 27 | id: bump-version 28 | run: make bump-version 29 | env: 30 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 31 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 32 | AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} 33 | AWS_ECR_ACCOUNT_URL: ${{ secrets.AWS_ECR_ACCOUNT_URL }} 34 | CAMPARIBOT_TOKEN: ${{ secrets.CAMPARIBOT_TOKEN }} 35 | 36 | - name: Create check run 37 | id: create-check-run 38 | run: | 39 | CHECK_RUN_ID=`curl -X POST https://api.github.com/repos/${{ github.repository }}/check-runs \ 40 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 41 | -H "Accept:application/vnd.github.antiope-preview+json" \ 42 | -d "{\"name\": \"Aporia / deploy (push)\", \"head_sha\": \"${{ steps.bump-version.outputs.bumped_version_commit_hash }}\", \"status\": \"in_progress\", \"details_url\": \"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\", \"output\": {\"title\": \"Versioned Commit\", \"summary\": \"This is a versioned commit. To see the full GitHub Action, [click here](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).\"}}" \ 43 | | jq .id`; 44 | echo "::set-output name=check_run_id::$CHECK_RUN_ID"; 45 | 46 | - name: Scan dependencies for insecure packages. 47 | run: make dependencies-safety 48 | 49 | - name: Update check run to success 50 | run: | 51 | curl -X PATCH https://api.github.com/repos/${{ github.repository }}/check-runs/${{ steps.create-check-run.outputs.check_run_id }} \ 52 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 53 | -H "Accept:application/vnd.github.antiope-preview+json" \ 54 | -d "{\"status\": \"completed\", \"conclusion\": \"success\"}"; 55 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | release: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Setup python 11 | uses: actions/setup-python@v2 12 | with: 13 | python-version: '3.8' 14 | architecture: x64 15 | - run: make build 16 | - run: pip3 install poetry 17 | - run: poetry build 18 | - run: poetry publish --username=__token__ --password=${{ secrets.PYPI_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # VS Code 131 | .vscode 132 | 133 | # Jetbrains 134 | .idea/ 135 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: local 9 | hooks: 10 | - id: isort 11 | name: sort imports 12 | entry: poetry run isort --filter-files 13 | language: system 14 | types: [python] 15 | - id: black 16 | name: black 17 | entry: python -m black 18 | language: system 19 | types: [python] 20 | - id: flake8 21 | name: flake8 22 | entry: poetry run flake8 23 | language: system 24 | types: [python] 25 | - id: mypy 26 | name: mypy 27 | entry: poetry run mypy 28 | language: system 29 | types: [python] 30 | -------------------------------------------------------------------------------- /Dockerfile.game: -------------------------------------------------------------------------------- 1 | # Build game 2 | FROM ubuntu as game_builder 3 | RUN apt-get update && \ 4 | apt-get install -y git python3 && \ 5 | apt-get install libatomic1 && \ 6 | mkdir /build && \ 7 | cd /build && \ 8 | git clone https://github.com/emscripten-core/emsdk.git 9 | RUN cd /build/emsdk && \ 10 | ./emsdk install latest && \ 11 | ./emsdk activate latest 12 | RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y libxml2-dev 13 | RUN apt-get install -y make 14 | COPY ./src/game /project 15 | COPY ./Makefile /project 16 | ENV PATH="${PATH}:/build/emsdk/upstream/bin" 17 | RUN cd /project && \ 18 | chmod +x /build/emsdk/emsdk_env.sh && \ 19 | make compile-game 20 | RUN cd /project/public && \ 21 | base64 game.wasm -w 0 > game.txt 22 | 23 | # Export game 24 | FROM scratch AS export 25 | COPY --from=game_builder /project/public/game.txt . 26 | -------------------------------------------------------------------------------- /Dockerfile.view: -------------------------------------------------------------------------------- 1 | # Build view 2 | FROM node:12 as view_builder 3 | RUN mkdir -p /app 4 | WORKDIR /app 5 | COPY ./src/view/package.json /app 6 | COPY ./src/view/package-lock.json /app 7 | RUN npm install 8 | COPY ./src/view /app 9 | RUN npm run build 10 | RUN cd /app/dist && \ 11 | base64 index.html -w 0 > view.txt 12 | 13 | # Export view.txt 14 | FROM scratch AS export 15 | COPY --from=view_builder /app/dist/view.txt . 16 | -------------------------------------------------------------------------------- /Dockerfile.view.dockerignore: -------------------------------------------------------------------------------- 1 | src/assets 2 | src/game 3 | src/train_invaders 4 | src/view/node_modules 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | DEFAULT_VERSION=1.0.0 4 | 5 | # Compile wasm game 6 | compile-game: 7 | cd src && \ 8 | clang \ 9 | --target=wasm32 \ 10 | -nostdlib \ 11 | -Wl,--no-entry \ 12 | -Wl,--allow-undefined \ 13 | -Wl,--strip-all \ 14 | -Wl,--export-dynamic \ 15 | -Os \ 16 | -fvisibility=hidden \ 17 | -Wl,--initial-memory=8388608 \ 18 | -Wl,--lto-O3 \ 19 | -Wl,-z,stack-size=1048576 \ 20 | -I ./system/ \ 21 | -I ./logic/ \ 22 | -o ../public/game.wasm \ 23 | main.c system/system*.c logic/game*.c 24 | 25 | # Build game 26 | build-game: 27 | DOCKER_BUILDKIT=1 docker build --file Dockerfile.game --output ./src/view/src/assets . 28 | 29 | # Build view 30 | build-view: 31 | DOCKER_BUILDKIT=1 docker build --file Dockerfile.view --output ./src/train_invaders . 32 | 33 | # Build TrainInvaders 34 | build: build-game build-view 35 | 36 | # Install dependencies 37 | install-deps: 38 | @echo [!] Installing Semver 39 | @sudo wget https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver -O /usr/bin/semver 40 | @sudo chmod +x /usr/bin/semver 41 | 42 | @echo [!] Installing yq 43 | @sudo wget https://github.com/mikefarah/yq/releases/download/v4.6.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq 44 | 45 | @echo [!] Installing Poetry 46 | @sudo apt install python3-setuptools 47 | @sudo pip3 install poetry --upgrade 48 | 49 | 50 | # Scan dependencies for insecure packages. 51 | dependencies-safety: 52 | @sudo pip3 install safety 53 | @poetry export --format=requirements.txt --without-hashes --output=requirements.txt 54 | @safety check --file=requirements.txt --full-report 55 | 56 | # Bump version 57 | bump-version: 58 | $(eval CURRENT_VERSION=$(shell git for-each-ref --sort=-v:refname --count=1 refs/tags/[0-9]*.[0-9]*.[0-9]* refs/tags/v[0-9]*.[0-9]*.[0-9]* | cut -d / -f 3-)) 59 | $(eval NEW_VERSION=v$(shell \ 60 | if [ -z $(CURRENT_VERSION) ]; then \ 61 | echo $(DEFAULT_VERSION); \ 62 | else \ 63 | semver bump patch $(CURRENT_VERSION); \ 64 | fi; \ 65 | )) 66 | 67 | @git log -1 --pretty="%B" > /tmp/commit-message 68 | @sed -i '1s/^/\[$(NEW_VERSION)] /' /tmp/commit-message 69 | 70 | @echo [!] Bumping version from $(CURRENT_VERSION) to $(NEW_VERSION) 71 | 72 | @poetry version $(NEW_VERSION) || true 73 | @git add pyproject.toml || true 74 | 75 | git commit -F /tmp/commit-message --amend --no-edit 76 | 77 | git tag -a -m "Version $(NEW_VERSION)" $(NEW_VERSION) 78 | 79 | @BRANCH_PROTECTION=`curl https://api.github.com/repos/$(GITHUB_REPOSITORY)/branches/master/protection \ 80 | -H "Authorization: token $(CAMPARIBOT_TOKEN)" -H "Accept:application/vnd.github.luke-cage-preview+json" -X GET -s`; \ 81 | if [ "`echo $$BRANCH_PROTECTION | jq -r '.message'`" != "Branch not protected" ]; \ 82 | then \ 83 | echo [!] Disabling GitHub master branch protection; \ 84 | curl https://api.github.com/repos/$(GITHUB_REPOSITORY)/branches/master/protection \ 85 | -H "Authorization: token $(CAMPARIBOT_TOKEN)" -H "Accept:application/vnd.github.luke-cage-preview+json" -X DELETE; \ 86 | trap '\ 87 | echo [!] Re-enabling GitHub master branch protection; \ 88 | curl https://api.github.com/repos/$(GITHUB_REPOSITORY)/branches/master/protection -H "Authorization: token $(CAMPARIBOT_TOKEN)" \ 89 | -H "Accept:application/vnd.github.luke-cage-preview+json" -X PUT -d "{\"required_status_checks\":{\"strict\":false,\"contexts\":`echo $$BRANCH_PROTECTION | jq '.required_status_checks.contexts'`},\"restrictions\":{\"users\":[],\"teams\":[],\"apps\":[]},\"required_pull_request_reviews\":{\"dismiss_stale_reviews\":false,\"require_code_owner_reviews\":false},\"enforce_admins\":true,\"required_linear_history\":false,\"allow_force_pushes\":true,\"allow_deletions\":false}"; \ 90 | ' EXIT; \ 91 | fi; \ 92 | echo [!] Git Push; \ 93 | git push --force; 94 | 95 | echo "::set-output name=bumped_version_commit_hash::`git log --pretty=format:'%H' -n 1`"; 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | TrainInvaders 4 |
5 | Train Invaders 6 | 7 | 8 | 9 |

10 | 11 |

Jupyter Notebook + Space Invaders!?

12 | 13 |

14 | Python version 16 | Python version 18 | License 20 |

21 | 22 |

23 | Why? • 24 | Getting started • 25 | How it works • 26 | FAQ • 27 | Drawbacks • 28 | Contribute • 29 | Thanks to • 30 | You may also like... 31 |

32 | 33 | ![Demo](https://raw.githubusercontent.com/aporia-ai/TrainInvaders/main/src/assets/demo.gif) 34 | 35 | ## Why❓ 36 | *Training a model can be a long long process!* 37 | 38 | In the meantime, have a bit of fun with a **competitive space invaders game**. 39 | 40 | See if you can get your name to the top of the *leaderboard*. 41 | 42 | ## Getting started 🏁 43 | 1. Install the game: 44 | `!pip3 install train_invaders --upgrade` 45 | 2. Import the game in your notebook: 46 | `import train_invaders.start` 47 | 3. Start training your model. The game will automatically pop up when the process begins. 48 | 4. **Play the game!** *You'll get notified when your training is finished*. 49 | 5. Want to stop the game from popping up when the model is being trained? 50 | `import train_invaders.stop` will do the work. 51 | 52 | ## How it works ⚙️ 53 | **Tons of magic**... Just kidding :) 54 | 55 | When importing the `start` module, its code will be executed. 56 | 57 | The code utilizes python's `settrace` function which follows the functions' call stack. 58 | 59 | When a function named `fit` `train` or `train_on_batch` is called - using Jupyter notebook's kernel, aka, `IPython`, a javascript view code will be injected inside the output block as an `iframe` to keep it completely **isolated from your code**. 60 | 61 | When importing the `stop` module, the `settrace` function will be canceled and the function hooks will be removed. 62 | 63 | ## FAQ 🙋 64 | ### Will it interfere with the training process somehow? 65 | 66 | NO. The game will start and be played **in parallel to the training** and will even *let you know when the training is finished*. 67 | 68 | ## Drawbacks 🥺 69 | * Training stop indication is only in Jupyter Notebook. Want to get notified by email or text? Try [MLNotify](https://mlnotify.aporia.com/?utm_source=train-invaders&utm_medium=docs&utm_campaign=train-invaders) 70 | * Authentication, and therefore, saving your score can only be done from `localhost` and port `8888 - 8891 / 8080 / 8081` 71 | 72 | ## Contribute 🤝 73 | Have an awesome idea for a new feature? PRs are more than welcome! 74 | 75 | 1. Clone the project 76 | 2. Run `make build-game` to get a local and compiled copy of the game (if not exists) 77 | 2. Enter `src/view` directory and run `npm run serve` to run the local environment 78 | 2. Implement your ideas 79 | 3. Made changes in the game (C files)? Re-run `make build-game` from root dir and check them out 80 | 5. Enter root directory, run `make build`, `pip install . --upgrade` and test the changes in your notebook 81 | 6. PR us! 82 | 83 | ## Thanks to 🙏 84 | [JanSiebert](https://github.com/JanSiebert/wasm-space-invaders) for the WebAssembly game. 85 | 86 | [Cody Boisclair](https://github.com/codeman38) for the PressStart2P font. 87 | 88 | [Vue](https://github.com/vuejs/vue) for the awesome FE framework. 89 | 90 | ## You may also ❤️ 91 | [Aporia](https://www.aporia.com?utm_source=github&utm_medium=github&utm_campaign=train-invaders) - Customized monitoring for your ML models. 92 | 93 | [MLNotify](https://www.mlnotify.aporia.com?utm_source=github&utm_medium=github&utm_campaign=train-invaders) - Get notified when training is complete. 94 | 95 | [MLOps Toys](https://www.mlops.toys?utm_source=github&utm_medium=github&utm_campaign=train-invaders) - A curated list of useful MLOps tools. 96 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | show_error_codes = True 3 | ignore_missing_imports = True 4 | 5 | [mypy-nox.*,pytest,pytest_mock,_pytest.*,aioresponses,async_generator,simpleflake,numpy,pandas,pandas.*] 6 | ignore_missing_imports = True 7 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 4 | name = "appdirs" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.4.4" 8 | 9 | [[package]] 10 | category = "main" 11 | description = "Disable App Nap on macOS >= 10.9" 12 | marker = "sys_platform == \"darwin\"" 13 | name = "appnope" 14 | optional = false 15 | python-versions = "*" 16 | version = "0.1.2" 17 | 18 | [[package]] 19 | category = "dev" 20 | description = "Classes Without Boilerplate" 21 | name = "attrs" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | version = "20.3.0" 25 | 26 | [package.extras] 27 | dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 28 | docs = ["furo", "sphinx", "zope.interface"] 29 | tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 30 | tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 31 | 32 | [[package]] 33 | category = "main" 34 | description = "Specifications for callback functions passed in to an API" 35 | name = "backcall" 36 | optional = false 37 | python-versions = "*" 38 | version = "0.2.0" 39 | 40 | [[package]] 41 | category = "dev" 42 | description = "Security oriented static analyser for python code." 43 | name = "bandit" 44 | optional = false 45 | python-versions = ">=3.5" 46 | version = "1.7.0" 47 | 48 | [package.dependencies] 49 | GitPython = ">=1.0.1" 50 | PyYAML = ">=5.3.1" 51 | colorama = ">=0.3.9" 52 | six = ">=1.10.0" 53 | stevedore = ">=1.20.0" 54 | 55 | [[package]] 56 | category = "dev" 57 | description = "The uncompromising code formatter." 58 | name = "black" 59 | optional = false 60 | python-versions = ">=3.6" 61 | version = "19.10b0" 62 | 63 | [package.dependencies] 64 | appdirs = "*" 65 | attrs = ">=18.1.0" 66 | click = ">=6.5" 67 | pathspec = ">=0.6,<1" 68 | regex = "*" 69 | toml = ">=0.9.4" 70 | typed-ast = ">=1.4.0" 71 | 72 | [package.extras] 73 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 74 | 75 | [[package]] 76 | category = "dev" 77 | description = "Composable command line interface toolkit" 78 | name = "click" 79 | optional = false 80 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 81 | version = "7.1.2" 82 | 83 | [[package]] 84 | category = "main" 85 | description = "Cross-platform colored terminal text." 86 | marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" 87 | name = "colorama" 88 | optional = false 89 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 90 | version = "0.4.4" 91 | 92 | [[package]] 93 | category = "dev" 94 | description = "A utility for ensuring Google-style docstrings stay up to date with the source code." 95 | name = "darglint" 96 | optional = false 97 | python-versions = ">=3.6,<4.0" 98 | version = "1.8.0" 99 | 100 | [[package]] 101 | category = "main" 102 | description = "Decorators for Humans" 103 | name = "decorator" 104 | optional = false 105 | python-versions = ">=3.5" 106 | version = "5.0.7" 107 | 108 | [[package]] 109 | category = "dev" 110 | description = "the modular source code checker: pep8 pyflakes and co" 111 | name = "flake8" 112 | optional = false 113 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 114 | version = "3.9.1" 115 | 116 | [package.dependencies] 117 | mccabe = ">=0.6.0,<0.7.0" 118 | pycodestyle = ">=2.7.0,<2.8.0" 119 | pyflakes = ">=2.3.0,<2.4.0" 120 | 121 | [package.dependencies.importlib-metadata] 122 | python = "<3.8" 123 | version = "*" 124 | 125 | [[package]] 126 | category = "dev" 127 | description = "Flake8 Type Annotation Checks" 128 | name = "flake8-annotations" 129 | optional = false 130 | python-versions = ">=3.6,<4.0" 131 | version = "2.0.1" 132 | 133 | [package.dependencies] 134 | flake8 = ">=3.7.9,<4.0.0" 135 | 136 | [package.dependencies.typed-ast] 137 | python = "<3.8" 138 | version = ">=1.4,<2.0" 139 | 140 | [[package]] 141 | category = "dev" 142 | description = "Automated security testing with bandit and flake8." 143 | name = "flake8-bandit" 144 | optional = false 145 | python-versions = "*" 146 | version = "2.1.2" 147 | 148 | [package.dependencies] 149 | bandit = "*" 150 | flake8 = "*" 151 | flake8-polyfill = "*" 152 | pycodestyle = "*" 153 | 154 | [[package]] 155 | category = "dev" 156 | description = "flake8 plugin to call black as a code style validator" 157 | name = "flake8-black" 158 | optional = false 159 | python-versions = "*" 160 | version = "0.1.2" 161 | 162 | [package.dependencies] 163 | black = ">=19.3b0" 164 | flake8 = ">=3.0.0" 165 | 166 | [[package]] 167 | category = "dev" 168 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." 169 | name = "flake8-bugbear" 170 | optional = false 171 | python-versions = ">=3.6" 172 | version = "20.11.1" 173 | 174 | [package.dependencies] 175 | attrs = ">=19.2.0" 176 | flake8 = ">=3.0.0" 177 | 178 | [package.extras] 179 | dev = ["coverage", "black", "hypothesis", "hypothesmith"] 180 | 181 | [[package]] 182 | category = "dev" 183 | description = "Extension for flake8 which uses pydocstyle to check docstrings" 184 | name = "flake8-docstrings" 185 | optional = false 186 | python-versions = "*" 187 | version = "1.6.0" 188 | 189 | [package.dependencies] 190 | flake8 = ">=3" 191 | pydocstyle = ">=2.1" 192 | 193 | [[package]] 194 | category = "dev" 195 | description = "Flake8 and pylama plugin that checks the ordering of import statements." 196 | name = "flake8-import-order" 197 | optional = false 198 | python-versions = "*" 199 | version = "0.18.1" 200 | 201 | [package.dependencies] 202 | pycodestyle = "*" 203 | setuptools = "*" 204 | 205 | [[package]] 206 | category = "dev" 207 | description = "Polyfill package for Flake8 plugins" 208 | name = "flake8-polyfill" 209 | optional = false 210 | python-versions = "*" 211 | version = "1.0.2" 212 | 213 | [package.dependencies] 214 | flake8 = "*" 215 | 216 | [[package]] 217 | category = "dev" 218 | description = "Git Object Database" 219 | name = "gitdb" 220 | optional = false 221 | python-versions = ">=3.4" 222 | version = "4.0.7" 223 | 224 | [package.dependencies] 225 | smmap = ">=3.0.1,<5" 226 | 227 | [[package]] 228 | category = "dev" 229 | description = "Python Git Library" 230 | name = "gitpython" 231 | optional = false 232 | python-versions = ">=3.5" 233 | version = "3.1.15" 234 | 235 | [package.dependencies] 236 | gitdb = ">=4.0.1,<5" 237 | typing-extensions = ">=3.7.4.0" 238 | 239 | [[package]] 240 | category = "dev" 241 | description = "Read metadata from Python packages" 242 | marker = "python_version < \"3.8\"" 243 | name = "importlib-metadata" 244 | optional = false 245 | python-versions = ">=3.6" 246 | version = "4.0.1" 247 | 248 | [package.dependencies] 249 | zipp = ">=0.5" 250 | 251 | [package.dependencies.typing-extensions] 252 | python = "<3.8" 253 | version = ">=3.6.4" 254 | 255 | [package.extras] 256 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 257 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 258 | 259 | [[package]] 260 | category = "main" 261 | description = "IPython: Productive Interactive Computing" 262 | name = "ipython" 263 | optional = false 264 | python-versions = ">=3.3" 265 | version = "6.5.0" 266 | 267 | [package.dependencies] 268 | appnope = "*" 269 | backcall = "*" 270 | colorama = "*" 271 | decorator = "*" 272 | jedi = ">=0.10" 273 | pexpect = "*" 274 | pickleshare = "*" 275 | prompt-toolkit = ">=1.0.15,<2.0.0" 276 | pygments = "*" 277 | setuptools = ">=18.5" 278 | simplegeneric = ">0.8" 279 | traitlets = ">=4.2" 280 | 281 | [package.extras] 282 | all = ["requests", "nbformat", "ipykernel", "ipyparallel", "qtconsole", "testpath", "pygments", "notebook", "nose (>=0.10.1)", "Sphinx (>=1.3)", "ipywidgets", "nbconvert"] 283 | doc = ["Sphinx (>=1.3)"] 284 | kernel = ["ipykernel"] 285 | nbconvert = ["nbconvert"] 286 | nbformat = ["nbformat"] 287 | notebook = ["notebook", "ipywidgets"] 288 | parallel = ["ipyparallel"] 289 | qtconsole = ["qtconsole"] 290 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy"] 291 | 292 | [[package]] 293 | category = "main" 294 | description = "Vestigial utilities from IPython" 295 | name = "ipython-genutils" 296 | optional = false 297 | python-versions = "*" 298 | version = "0.2.0" 299 | 300 | [[package]] 301 | category = "dev" 302 | description = "A Python utility / library to sort Python imports." 303 | name = "isort" 304 | optional = false 305 | python-versions = ">=3.6,<4.0" 306 | version = "5.8.0" 307 | 308 | [package.extras] 309 | colors = ["colorama (>=0.4.3,<0.5.0)"] 310 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 311 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 312 | 313 | [[package]] 314 | category = "main" 315 | description = "An autocompletion tool for Python that can be used for text editors." 316 | name = "jedi" 317 | optional = false 318 | python-versions = ">=3.6" 319 | version = "0.18.0" 320 | 321 | [package.dependencies] 322 | parso = ">=0.8.0,<0.9.0" 323 | 324 | [package.extras] 325 | qa = ["flake8 (3.8.3)", "mypy (0.782)"] 326 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] 327 | 328 | [[package]] 329 | category = "dev" 330 | description = "McCabe checker, plugin for flake8" 331 | name = "mccabe" 332 | optional = false 333 | python-versions = "*" 334 | version = "0.6.1" 335 | 336 | [[package]] 337 | category = "dev" 338 | description = "Optional static typing for Python" 339 | name = "mypy" 340 | optional = false 341 | python-versions = ">=3.5" 342 | version = "0.761" 343 | 344 | [package.dependencies] 345 | mypy-extensions = ">=0.4.3,<0.5.0" 346 | typed-ast = ">=1.4.0,<1.5.0" 347 | typing-extensions = ">=3.7.4" 348 | 349 | [package.extras] 350 | dmypy = ["psutil (>=4.0)"] 351 | 352 | [[package]] 353 | category = "dev" 354 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 355 | name = "mypy-extensions" 356 | optional = false 357 | python-versions = "*" 358 | version = "0.4.3" 359 | 360 | [[package]] 361 | category = "main" 362 | description = "A Python Parser" 363 | name = "parso" 364 | optional = false 365 | python-versions = ">=3.6" 366 | version = "0.8.2" 367 | 368 | [package.extras] 369 | qa = ["flake8 (3.8.3)", "mypy (0.782)"] 370 | testing = ["docopt", "pytest (<6.0.0)"] 371 | 372 | [[package]] 373 | category = "dev" 374 | description = "Utility library for gitignore style pattern matching of file paths." 375 | name = "pathspec" 376 | optional = false 377 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 378 | version = "0.8.1" 379 | 380 | [[package]] 381 | category = "dev" 382 | description = "Python Build Reasonableness" 383 | name = "pbr" 384 | optional = false 385 | python-versions = ">=2.6" 386 | version = "5.6.0" 387 | 388 | [[package]] 389 | category = "main" 390 | description = "Pexpect allows easy control of interactive console applications." 391 | marker = "sys_platform != \"win32\"" 392 | name = "pexpect" 393 | optional = false 394 | python-versions = "*" 395 | version = "4.8.0" 396 | 397 | [package.dependencies] 398 | ptyprocess = ">=0.5" 399 | 400 | [[package]] 401 | category = "main" 402 | description = "Tiny 'shelve'-like database with concurrency support" 403 | name = "pickleshare" 404 | optional = false 405 | python-versions = "*" 406 | version = "0.7.5" 407 | 408 | [[package]] 409 | category = "main" 410 | description = "Library for building powerful interactive command lines in Python" 411 | name = "prompt-toolkit" 412 | optional = false 413 | python-versions = "*" 414 | version = "1.0.18" 415 | 416 | [package.dependencies] 417 | six = ">=1.9.0" 418 | wcwidth = "*" 419 | 420 | [[package]] 421 | category = "main" 422 | description = "Run a subprocess in a pseudo terminal" 423 | marker = "sys_platform != \"win32\"" 424 | name = "ptyprocess" 425 | optional = false 426 | python-versions = "*" 427 | version = "0.7.0" 428 | 429 | [[package]] 430 | category = "dev" 431 | description = "Python style guide checker" 432 | name = "pycodestyle" 433 | optional = false 434 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 435 | version = "2.7.0" 436 | 437 | [[package]] 438 | category = "dev" 439 | description = "Python docstring style checker" 440 | name = "pydocstyle" 441 | optional = false 442 | python-versions = ">=3.6" 443 | version = "6.0.0" 444 | 445 | [package.dependencies] 446 | snowballstemmer = "*" 447 | 448 | [[package]] 449 | category = "dev" 450 | description = "passive checker of Python programs" 451 | name = "pyflakes" 452 | optional = false 453 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 454 | version = "2.3.1" 455 | 456 | [[package]] 457 | category = "main" 458 | description = "Pygments is a syntax highlighting package written in Python." 459 | name = "pygments" 460 | optional = false 461 | python-versions = ">=3.5" 462 | version = "2.8.1" 463 | 464 | [[package]] 465 | category = "dev" 466 | description = "YAML parser and emitter for Python" 467 | name = "pyyaml" 468 | optional = false 469 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 470 | version = "5.4.1" 471 | 472 | [[package]] 473 | category = "dev" 474 | description = "Alternative regular expression module, to replace re." 475 | name = "regex" 476 | optional = false 477 | python-versions = "*" 478 | version = "2021.4.4" 479 | 480 | [[package]] 481 | category = "main" 482 | description = "Simple generic functions (similar to Python's own len(), pickle.dump(), etc.)" 483 | name = "simplegeneric" 484 | optional = false 485 | python-versions = "*" 486 | version = "0.8.1" 487 | 488 | [[package]] 489 | category = "main" 490 | description = "Python 2 and 3 compatibility utilities" 491 | name = "six" 492 | optional = false 493 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 494 | version = "1.15.0" 495 | 496 | [[package]] 497 | category = "dev" 498 | description = "A pure Python implementation of a sliding window memory map manager" 499 | name = "smmap" 500 | optional = false 501 | python-versions = ">=3.5" 502 | version = "4.0.0" 503 | 504 | [[package]] 505 | category = "dev" 506 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 507 | name = "snowballstemmer" 508 | optional = false 509 | python-versions = "*" 510 | version = "2.1.0" 511 | 512 | [[package]] 513 | category = "dev" 514 | description = "Manage dynamic plugins for Python applications" 515 | name = "stevedore" 516 | optional = false 517 | python-versions = ">=3.6" 518 | version = "3.3.0" 519 | 520 | [package.dependencies] 521 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 522 | 523 | [package.dependencies.importlib-metadata] 524 | python = "<3.8" 525 | version = ">=1.7.0" 526 | 527 | [[package]] 528 | category = "dev" 529 | description = "Python Library for Tom's Obvious, Minimal Language" 530 | name = "toml" 531 | optional = false 532 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 533 | version = "0.10.2" 534 | 535 | [[package]] 536 | category = "main" 537 | description = "Traitlets Python config system" 538 | name = "traitlets" 539 | optional = false 540 | python-versions = "*" 541 | version = "4.3.3" 542 | 543 | [package.dependencies] 544 | decorator = "*" 545 | ipython-genutils = "*" 546 | six = "*" 547 | 548 | [package.extras] 549 | test = ["pytest", "mock"] 550 | 551 | [[package]] 552 | category = "dev" 553 | description = "a fork of Python 2 and 3 ast modules with type comment support" 554 | name = "typed-ast" 555 | optional = false 556 | python-versions = "*" 557 | version = "1.4.3" 558 | 559 | [[package]] 560 | category = "dev" 561 | description = "Backported and Experimental Type Hints for Python 3.5+" 562 | name = "typing-extensions" 563 | optional = false 564 | python-versions = "*" 565 | version = "3.7.4.3" 566 | 567 | [[package]] 568 | category = "main" 569 | description = "Measures the displayed width of unicode strings in a terminal" 570 | name = "wcwidth" 571 | optional = false 572 | python-versions = "*" 573 | version = "0.2.5" 574 | 575 | [[package]] 576 | category = "dev" 577 | description = "Backport of pathlib-compatible object wrapper for zip files" 578 | marker = "python_version < \"3.8\"" 579 | name = "zipp" 580 | optional = false 581 | python-versions = ">=3.6" 582 | version = "3.4.1" 583 | 584 | [package.extras] 585 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 586 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 587 | 588 | [metadata] 589 | content-hash = "ea73fe51141b3aeddd629aa44544019ad9fbc569ee2a4e07fcff333c24162ee7" 590 | python-versions = "^3.6" 591 | 592 | [metadata.files] 593 | appdirs = [ 594 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 595 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 596 | ] 597 | appnope = [ 598 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, 599 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, 600 | ] 601 | attrs = [ 602 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 603 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 604 | ] 605 | backcall = [ 606 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 607 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 608 | ] 609 | bandit = [ 610 | {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, 611 | {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, 612 | ] 613 | black = [ 614 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 615 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 616 | ] 617 | click = [ 618 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 619 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 620 | ] 621 | colorama = [ 622 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 623 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 624 | ] 625 | darglint = [ 626 | {file = "darglint-1.8.0-py3-none-any.whl", hash = "sha256:ac6797bcc918cd8d8f14c168a4a364f54e1aeb4ced59db58e7e4c6dfec2fe15c"}, 627 | {file = "darglint-1.8.0.tar.gz", hash = "sha256:aa605ef47817a6d14797d32b390466edab621768ea4ca5cc0f3c54f6d8dcaec8"}, 628 | ] 629 | decorator = [ 630 | {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"}, 631 | {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"}, 632 | ] 633 | flake8 = [ 634 | {file = "flake8-3.9.1-py2.py3-none-any.whl", hash = "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a"}, 635 | {file = "flake8-3.9.1.tar.gz", hash = "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378"}, 636 | ] 637 | flake8-annotations = [ 638 | {file = "flake8-annotations-2.0.1.tar.gz", hash = "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9"}, 639 | {file = "flake8_annotations-2.0.1-py3-none-any.whl", hash = "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659"}, 640 | ] 641 | flake8-bandit = [ 642 | {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, 643 | ] 644 | flake8-black = [ 645 | {file = "flake8-black-0.1.2.tar.gz", hash = "sha256:b79d8d868bd42dc2c1f27469b92a984ecab3579ad285a8708ea5f19bf6c1f3a2"}, 646 | ] 647 | flake8-bugbear = [ 648 | {file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"}, 649 | {file = "flake8_bugbear-20.11.1-py36.py37.py38-none-any.whl", hash = "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703"}, 650 | ] 651 | flake8-docstrings = [ 652 | {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, 653 | {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, 654 | ] 655 | flake8-import-order = [ 656 | {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"}, 657 | {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"}, 658 | ] 659 | flake8-polyfill = [ 660 | {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, 661 | {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, 662 | ] 663 | gitdb = [ 664 | {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, 665 | {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, 666 | ] 667 | gitpython = [ 668 | {file = "GitPython-3.1.15-py3-none-any.whl", hash = "sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867"}, 669 | {file = "GitPython-3.1.15.tar.gz", hash = "sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e"}, 670 | ] 671 | importlib-metadata = [ 672 | {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"}, 673 | {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"}, 674 | ] 675 | ipython = [ 676 | {file = "ipython-6.5.0-py3-none-any.whl", hash = "sha256:007dcd929c14631f83daff35df0147ea51d1af420da303fd078343878bd5fb62"}, 677 | {file = "ipython-6.5.0.tar.gz", hash = "sha256:b0f2ef9eada4a68ef63ee10b6dde4f35c840035c50fd24265f8052c98947d5a4"}, 678 | ] 679 | ipython-genutils = [ 680 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 681 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 682 | ] 683 | isort = [ 684 | {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, 685 | {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, 686 | ] 687 | jedi = [ 688 | {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, 689 | {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, 690 | ] 691 | mccabe = [ 692 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 693 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 694 | ] 695 | mypy = [ 696 | {file = "mypy-0.761-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6"}, 697 | {file = "mypy-0.761-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36"}, 698 | {file = "mypy-0.761-cp35-cp35m-win_amd64.whl", hash = "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72"}, 699 | {file = "mypy-0.761-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2"}, 700 | {file = "mypy-0.761-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0"}, 701 | {file = "mypy-0.761-cp36-cp36m-win_amd64.whl", hash = "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474"}, 702 | {file = "mypy-0.761-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a"}, 703 | {file = "mypy-0.761-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749"}, 704 | {file = "mypy-0.761-cp37-cp37m-win_amd64.whl", hash = "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1"}, 705 | {file = "mypy-0.761-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7"}, 706 | {file = "mypy-0.761-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1"}, 707 | {file = "mypy-0.761-cp38-cp38-win_amd64.whl", hash = "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b"}, 708 | {file = "mypy-0.761-py3-none-any.whl", hash = "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217"}, 709 | {file = "mypy-0.761.tar.gz", hash = "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf"}, 710 | ] 711 | mypy-extensions = [ 712 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 713 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 714 | ] 715 | parso = [ 716 | {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, 717 | {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, 718 | ] 719 | pathspec = [ 720 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 721 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 722 | ] 723 | pbr = [ 724 | {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, 725 | {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, 726 | ] 727 | pexpect = [ 728 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 729 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 730 | ] 731 | pickleshare = [ 732 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 733 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 734 | ] 735 | prompt-toolkit = [ 736 | {file = "prompt_toolkit-1.0.18-py2-none-any.whl", hash = "sha256:f7eec66105baf40eda9ab026cd8b2e251337eea8d111196695d82e0c5f0af852"}, 737 | {file = "prompt_toolkit-1.0.18-py3-none-any.whl", hash = "sha256:37925b37a4af1f6448c76b7606e0285f79f434ad246dda007a27411cca730c6d"}, 738 | {file = "prompt_toolkit-1.0.18.tar.gz", hash = "sha256:dd4fca02c8069497ad931a2d09914c6b0d1b50151ce876bc15bde4c747090126"}, 739 | ] 740 | ptyprocess = [ 741 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 742 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 743 | ] 744 | pycodestyle = [ 745 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, 746 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, 747 | ] 748 | pydocstyle = [ 749 | {file = "pydocstyle-6.0.0-py3-none-any.whl", hash = "sha256:d4449cf16d7e6709f63192146706933c7a334af7c0f083904799ccb851c50f6d"}, 750 | {file = "pydocstyle-6.0.0.tar.gz", hash = "sha256:164befb520d851dbcf0e029681b91f4f599c62c5cd8933fd54b1bfbd50e89e1f"}, 751 | ] 752 | pyflakes = [ 753 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, 754 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, 755 | ] 756 | pygments = [ 757 | {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, 758 | {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, 759 | ] 760 | pyyaml = [ 761 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, 762 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, 763 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, 764 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, 765 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, 766 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, 767 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, 768 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, 769 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, 770 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, 771 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, 772 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, 773 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, 774 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, 775 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, 776 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, 777 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, 778 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, 779 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, 780 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, 781 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, 782 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, 783 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, 784 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, 785 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, 786 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, 787 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, 788 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, 789 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, 790 | ] 791 | regex = [ 792 | {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, 793 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, 794 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, 795 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, 796 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, 797 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, 798 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, 799 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, 800 | {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, 801 | {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, 802 | {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, 803 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, 804 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, 805 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, 806 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, 807 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, 808 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, 809 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, 810 | {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, 811 | {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, 812 | {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, 813 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, 814 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, 815 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, 816 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, 817 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, 818 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, 819 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, 820 | {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, 821 | {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, 822 | {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, 823 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, 824 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, 825 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, 826 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, 827 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, 828 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, 829 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, 830 | {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, 831 | {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, 832 | {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, 833 | ] 834 | simplegeneric = [ 835 | {file = "simplegeneric-0.8.1.zip", hash = "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"}, 836 | ] 837 | six = [ 838 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 839 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 840 | ] 841 | smmap = [ 842 | {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, 843 | {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, 844 | ] 845 | snowballstemmer = [ 846 | {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, 847 | {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, 848 | ] 849 | stevedore = [ 850 | {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, 851 | {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, 852 | ] 853 | toml = [ 854 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 855 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 856 | ] 857 | traitlets = [ 858 | {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, 859 | {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, 860 | ] 861 | typed-ast = [ 862 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 863 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 864 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 865 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 866 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 867 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 868 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 869 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 870 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 871 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 872 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 873 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 874 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 875 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 876 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 877 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 878 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 879 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 880 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 881 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 882 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 883 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 884 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 885 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 886 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 887 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 888 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 889 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 890 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 891 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 892 | ] 893 | typing-extensions = [ 894 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 895 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 896 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 897 | ] 898 | wcwidth = [ 899 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 900 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 901 | ] 902 | zipp = [ 903 | {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, 904 | {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, 905 | ] 906 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "train_invaders" 3 | version = "v1.0.9" 4 | description = "Jupyter Notebook + Space Invaders!?" 5 | authors = ["Aporia"] 6 | readme = "README.md" 7 | repository = "https://github.com/aporia-ai/TrainInvaders" 8 | classifiers = [ 9 | "Framework :: Jupyter", 10 | "Topic :: Games/Entertainment :: Arcade", 11 | "Programming Language :: JavaScript", 12 | ] 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.6" 16 | ipython = "<=7.16" 17 | 18 | [tool.poetry.dev-dependencies] 19 | flake8 = "^3.7.9" 20 | black = "^19.10b0" 21 | flake8-black = "^0.1.1" 22 | flake8-import-order = "^0.18.1" 23 | flake8-bugbear = "^20.1.2" 24 | flake8-bandit = "^2.1.2" 25 | mypy = "^0.761" 26 | flake8-annotations = "^2.0.0" 27 | flake8-docstrings = "^1.5.0" 28 | darglint = "^1.1.2" 29 | isort = "^5.7.0" 30 | 31 | [tool.black] 32 | line-length = 100 33 | 34 | [tool.isort] 35 | profile = "black" 36 | force_sort_within_sections = true 37 | lexicographical = true 38 | order_by_type = false 39 | group_by_package = true 40 | no_lines_before = ['LOCALFOLDER'] 41 | 42 | [build-system] 43 | requires = ["poetry>=0.12"] 44 | build-backend = "poetry.masonry.api" 45 | -------------------------------------------------------------------------------- /src/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/assets/demo.gif -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/assets/logo.png -------------------------------------------------------------------------------- /src/game/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/game/public/.gitkeep -------------------------------------------------------------------------------- /src/game/resource/convert.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | colors = [ 4 | 0xFF000000, 5 | 0xFF555555, 6 | 0xFFAAAAAA, 7 | 0xFFFFFFFF, 8 | 0xFF5555FF, 9 | 0xFF0000AA, 10 | 0xFF0055AA, 11 | 0xFF55FFFF, 12 | 0xFF55FF55, 13 | 0xFF00AA00, 14 | 0xFFFFFF55, 15 | 0xFFAAAA00, 16 | 0xFFFF55FF, 17 | 0xFFFF5555, 18 | 0xFFAA00AA, 19 | 0xFFAA0000, 20 | ] 21 | 22 | palette = {((c >> 0) & 255, (c >> 8) & 255, (c >> 16) & 255): i for i, c in enumerate(colors)} 23 | 24 | 25 | def toColorIndex(c): 26 | return palette[c] 27 | 28 | 29 | img = Image.open("sprites.png") 30 | # print("loaded image ({}x{})".format(*img.size)) 31 | pixmap = img.load() 32 | width = img.size[0] 33 | height = img.size[1] 34 | data = [] 35 | for y in range(height): 36 | for x in range(0, width, 2): 37 | p1 = toColorIndex(pixmap[x, y]) 38 | p2 = toColorIndex(pixmap[x + 1, y]) 39 | c = (p1 << 4) | p2 40 | data.append(hex(c)) 41 | 42 | print(", ".join(data)) 43 | -------------------------------------------------------------------------------- /src/game/resource/newfont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/game/resource/newfont.png -------------------------------------------------------------------------------- /src/game/resource/packfont.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | 3 | from PIL import Image 4 | 5 | img = Image.open(argv[1]) 6 | pixmap = img.load() 7 | width = img.size[0] 8 | height = img.size[1] 9 | 10 | if width != 65 or height != 37: 11 | raise Exception("Expected 65x37px font file.") 12 | 13 | 14 | def isWhite(rgb): 15 | r, g, b = rgb 16 | return r > 127 or g > 127 or b > 127 17 | 18 | 19 | data = [] 20 | for letter in range(32, 128): 21 | img_x = 1 + 4 * ((letter - 32) % 16) 22 | img_y = 1 + 6 * ((letter - 32) // 16) 23 | letter_short = 0 24 | bitpos = 32768 25 | for y in range(5): 26 | for x in range(3): 27 | pixel = pixmap[img_x + x, img_y + y] 28 | if isWhite(pixel): 29 | letter_short |= bitpos 30 | bitpos >>= 1 31 | data.append(hex(letter_short)) 32 | if len(data) == 16: 33 | print(", ".join(data) + ",") 34 | data = [] 35 | -------------------------------------------------------------------------------- /src/game/resource/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/game/resource/sprites.png -------------------------------------------------------------------------------- /src/game/src/logic/game.c: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | 4 | // 5 | // WAVE 6 | // 7 | 8 | static void draw_status_panel(GameState *state) { 9 | // Draw Score 10 | char score_buffer[21]; 11 | int maxlen = 20; 12 | char *start = score_buffer; 13 | start = put_string("Score: ", start, &maxlen); 14 | start = put_number(state->score, start, &maxlen); 15 | rectfill(0, 0, SCREEN_WIDTH, LETTER_HEIGHT + 4, 0); 16 | rectfill(0, LETTER_HEIGHT+4, SCREEN_WIDTH, 1, 3); 17 | text(score_buffer, 2, 2, 3); 18 | 19 | // Draw lives 20 | for (int i = 0; i < 3; i++) { 21 | spr(state->player->player_data.lives >i ? 6 : 7, 1, 1, SCREEN_WIDTH - (3*8) + i*8, 0); 22 | } 23 | } 24 | 25 | static void game_update_common(GameState *state) { 26 | game_objects_update(state); 27 | cls(0); 28 | game_objects_draw(state); 29 | draw_status_panel(state); 30 | } 31 | 32 | static void game_update_wave(GameState *state) { 33 | game_update_common(state); 34 | 35 | if (state->player->player_data.lives <= 0) { 36 | state->next_scene = SCENE_GAMEOVER; 37 | } else if(state->alien_counter == 0 && state->scene_ticks > 30 * 3) { 38 | int bosses = 1 + state->wave/3; 39 | if (bosses > 6) 40 | bosses = 6; 41 | for(int i = 0; i < bosses; i++) { 42 | game_init_alien_boss(state); 43 | } 44 | state->next_scene = SCENE_BOSS; 45 | } 46 | } 47 | 48 | void game_init_wave(GameState *state) { 49 | GameObject *title = game_init_title(state); 50 | int len = TITLE_MAX_LENGTH; 51 | char *s = title->title_data.text; 52 | s = put_string("Wave ", s, &len); 53 | s = put_number(state->wave, s, &len); 54 | 55 | title->title_data.ticks = 30 * 2; // 2 seconds 56 | title->title_data.next = game_init_alien_armada; 57 | } 58 | 59 | // 60 | // BOSS 61 | // 62 | 63 | static void game_update_boss(GameState *state) { 64 | game_update_common(state); 65 | if(state->alien_counter == 0) { 66 | state->wave++; 67 | game_init_wave(state); 68 | state->next_scene = SCENE_WAVE; 69 | } 70 | 71 | if (state->player->player_data.lives <= 0) { 72 | state->next_scene = SCENE_GAMEOVER; 73 | } 74 | } 75 | 76 | // 77 | // MAIN MENU 78 | // 79 | 80 | static void game_start_new(GameState *state) { 81 | game_objects_clear(state); 82 | state->player = game_init_player(state); 83 | state->wave = 1; 84 | state->alien_counter = 0; 85 | state->score = 0; 86 | game_init_wave(state); 87 | state->next_scene = SCENE_WAVE; 88 | } 89 | 90 | static void game_update_mainmenu(GameState *state) { 91 | cls(0); 92 | 93 | text("Train invaders", (SCREEN_WIDTH-14*(LETTER_WIDTH+1))/2, 25, 3); 94 | if ((state->scene_ticks / 10) % 2 == 0) { 95 | text("PRESS X to start", (SCREEN_WIDTH-16*(LETTER_WIDTH+1))/2, SCREEN_HEIGHT-32, 4); 96 | } 97 | text("move with arrow keys", (SCREEN_WIDTH-20*(LETTER_WIDTH+1))/2, SCREEN_HEIGHT-32+LETTER_HEIGHT+2, 2); 98 | if (btnp(KEY_A)) { 99 | game_start_new(state); 100 | } 101 | } 102 | 103 | // 104 | // GAME OVER 105 | // 106 | 107 | static void game_update_gameover(GameState *state) { 108 | int boxw = 9*(LETTER_WIDTH+1) - 1 + 10; 109 | int boxh = LETTER_HEIGHT + 6; 110 | rectfill((SCREEN_WIDTH-boxw)/2, (SCREEN_HEIGHT-boxh)/2, boxw, boxh, 3); 111 | rectfill((SCREEN_WIDTH-boxw)/2 + 1, (SCREEN_HEIGHT-boxh)/2 + 1, boxw - 2, boxh - 2, 0); 112 | if ((state->scene_ticks / 10) % 2 == 0) { 113 | text("Game over", (SCREEN_WIDTH-9*(LETTER_WIDTH+1))/2, (SCREEN_HEIGHT-LETTER_HEIGHT)/2, 3); 114 | } 115 | 116 | if (state->scene_ticks > 30 * 5 - 1) { // > 5 seconds 117 | 118 | state->next_scene = SCENE_MAINMENU; 119 | } 120 | } 121 | 122 | // 123 | // MAIN UPDATE 124 | // 125 | 126 | GameState *game_init() { 127 | GameState *state = (GameState *) malloc(sizeof(GameState)); 128 | copyzero((void *)state, sizeof(GameState)); 129 | 130 | state->player = game_init_player(state); 131 | state->scene = SCENE_MAINMENU; 132 | state->next_scene = 0; 133 | 134 | return state; 135 | } 136 | 137 | void game_update(GameState *state) { 138 | switch(state->scene) { 139 | case SCENE_MAINMENU: 140 | game_update_mainmenu(state); 141 | break; 142 | case SCENE_WAVE: 143 | game_update_wave(state); 144 | break; 145 | case SCENE_GAMEOVER: 146 | game_update_gameover(state); 147 | break; 148 | case SCENE_BOSS: 149 | game_update_boss(state); 150 | break; 151 | default: 152 | cls(1); 153 | } 154 | 155 | // Change scene? 156 | if (state->next_scene != 0) { 157 | state->scene = state->next_scene; 158 | state->next_scene = 0; 159 | state->scene_ticks = 0; 160 | } else { 161 | state->scene_ticks++; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/game/src/logic/game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "system.h" 3 | 4 | #define STATUS_BAR_HEIGHT LETTER_HEIGHT + 4 + 1 5 | #define MAX_OBJECTS 512 6 | typedef struct _GameState GameState; 7 | 8 | // The Game has 4 scenes 9 | // We have the same map in JS 10 | #define SCENE_MAINMENU 1 11 | #define SCENE_WAVE 2 12 | #define SCENE_BOSS 3 13 | #define SCENE_GAMEOVER 4 14 | 15 | #include "game_object.h" 16 | #include "game_player.h" 17 | #include "game_alien.h" 18 | 19 | struct _GameState { 20 | struct _GameObject objects[MAX_OBJECTS]; 21 | int score; 22 | int wave; // <- correlated with difficulty 23 | int alien_counter; // <- the boss-ufo will show up, if this is 0 24 | int scene; 25 | int next_scene; 26 | uint64_t scene_ticks; 27 | GameObject *player; 28 | }; 29 | 30 | GameState *game_init(); 31 | void game_update(GameState *state); 32 | -------------------------------------------------------------------------------- /src/game/src/logic/game_alien.c: -------------------------------------------------------------------------------- 1 | #include "game.h" 2 | 3 | static int no_other_alien_below(GameObject *alien, GameState *state) { 4 | int offset = 0; 5 | GameObject *obj; 6 | while((obj = game_object_find(OBJECT_ALIEN, state, &offset))) { 7 | if (game_object_in_rect(obj, alien->x - 16, alien->y + 10, 40, SCREEN_HEIGHT)) return 0; 8 | } 9 | return 1; 10 | } 11 | 12 | 13 | static void alien_hit(GameObject *bullet, GameState *state, GameObject *alien) { 14 | alien->alien_data.lives -= bullet->bullet_data.damage; 15 | if (alien->alien_data.lives > 0) 16 | alien->flicker = 3; 17 | else { 18 | if (randn(12 - 2 * (3-state->player->player_data.lives)) == 0 && no_other_alien_below(alien, state)) { 19 | game_init_powerup(state, alien->x, alien->y); 20 | } 21 | game_object_delete(alien); 22 | state->alien_counter--; 23 | state->score += 1; 24 | } 25 | } 26 | 27 | static int alien_update_offset(GameObject *alien) { 28 | if (alien->alien_data.offset > 0) { 29 | int dy = 3 <= alien->alien_data.offset ? 3 : alien->alien_data.offset; 30 | alien->y += dy; 31 | alien->alien_data.offset -= dy; 32 | return 1; 33 | } 34 | return 0; 35 | } 36 | 37 | static void alien_update(GameObject *alien, GameState *state) { 38 | if(alien_update_offset(alien)) return; 39 | 40 | alien->x += alien->alien_data.direction; 41 | if ( 42 | (alien->x + 8 >= SCREEN_WIDTH) || 43 | (alien->x < 1) 44 | ) { 45 | int offset = 0; 46 | GameObject *obj; 47 | while((obj = game_object_find(OBJECT_ALIEN, state, &offset))) { 48 | if (obj <= alien) { // Has been updated before 49 | obj->x -= 2*alien->alien_data.direction; 50 | } 51 | obj->y += 2; 52 | obj->alien_data.direction *= -1.0; 53 | } 54 | } 55 | 56 | if (game_object_collide(alien, state->player)) { 57 | state->player->player_data.lives = 0; 58 | } 59 | 60 | if (no_other_alien_below(alien, state) && randn(alien->alien_data.shoot_prob) == 0) { 61 | game_init_alien_bullet(state, alien->x, alien->y + 3); 62 | } 63 | } 64 | 65 | static void alien_boss_update(GameObject *alien, GameState *state) { 66 | if(alien_update_offset(alien)) return; 67 | if (randn(alien->alien_data.shoot_prob) == 0) { 68 | if (state->wave < 3) 69 | game_init_alien_bullet(state, alien->x + 4, alien->y + 3); 70 | else { 71 | game_init_alien_bullet(state, alien->x, alien->y + 3); 72 | game_init_alien_bullet(state, alien->x + 8, alien->y + 3); 73 | } 74 | } 75 | 76 | if (randn(150) == 0) { 77 | double speed = randn(state->wave) / 2; 78 | if (speed > 4) speed = 4; 79 | if (alien->alien_data.direction < 0) { 80 | alien->alien_data.direction = -1 * speed; 81 | } else { 82 | alien->alien_data.direction = 1 * speed; 83 | } 84 | 85 | } 86 | 87 | alien->x += alien->alien_data.direction; 88 | 89 | if ( 90 | (alien->x + 16 >= SCREEN_WIDTH) || 91 | (alien->x < 1) 92 | ) { 93 | alien->x -= 2*alien->alien_data.direction; 94 | alien->alien_data.direction *= -1; 95 | } 96 | } 97 | 98 | GameObject *game_init_alien(GameState *state, int x, int y) { 99 | GameObject *alien = game_object_construct(state, OBJECT_ALIEN, 16, 1, 1, x, y); 100 | alien->update = alien_update; 101 | alien->alien_data.onBulletHit = alien_hit; 102 | alien->alien_data.lives = state->wave/3; 103 | if (alien->alien_data.lives > 5) 104 | alien->alien_data.lives = 5; 105 | alien->alien_data.direction = 0.5; 106 | 107 | alien->animation.n = 1; 108 | alien->animation.delay = 6; 109 | alien->animation.sprites[0].id = 17; 110 | 111 | state->alien_counter++; 112 | 113 | return alien; 114 | } 115 | 116 | void game_init_alien_armada(GameState *state) { 117 | int rows = 1 + state->wave/2; 118 | int cols = 4 + state->wave/4; 119 | if (rows > 5) rows = 5; 120 | if (cols > 6) cols = 6; 121 | 122 | for(int y = 0; y < rows; y++) { 123 | for(int x = 0; x < cols; x++) { 124 | GameObject *alien = game_init_alien(state, 4 + x*16, rows*-16 + y*16); 125 | alien->alien_data.offset = rows*16 + STATUS_BAR_HEIGHT + 4; 126 | alien->alien_data.direction = 0.1 + 0.05 * state->wave; 127 | alien->alien_data.shoot_prob = 230 - state->wave * 10; 128 | if (alien->alien_data.shoot_prob < 50) 129 | alien->alien_data.shoot_prob = 50; 130 | } 131 | } 132 | } 133 | 134 | void game_init_alien_boss(GameState *state) { 135 | GameObject *alien = game_init_alien(state, 10 + randn(SCREEN_WIDTH-20-16), -8*16); 136 | alien->sprite.id = 19; 137 | alien->animation.n = 0; 138 | alien->sw = 2; 139 | alien->alien_data.offset = STATUS_BAR_HEIGHT + 8*16 + 16; 140 | alien->update = alien_boss_update; 141 | alien->alien_data.shoot_prob = 100 - state->wave * 8; 142 | alien->alien_data.lives = 2 + state->wave; 143 | if (alien->alien_data.shoot_prob < 20) 144 | alien->alien_data.shoot_prob = 20; 145 | } 146 | -------------------------------------------------------------------------------- /src/game/src/logic/game_alien.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct _Alien Alien; 4 | 5 | #include "game_object.h" 6 | 7 | struct _Alien { 8 | int lives; 9 | double direction; 10 | int offset; 11 | int shoot_prob; 12 | void (*onBulletHit)(GameObject *bullet, GameState *state, GameObject *obj); 13 | }; 14 | 15 | GameObject *game_init_alien(GameState *state, int x, int y); 16 | void game_init_alien_armada(GameState *state); 17 | void game_init_alien_boss(GameState *state); 18 | -------------------------------------------------------------------------------- /src/game/src/logic/game_bullet.c: -------------------------------------------------------------------------------- 1 | #include "game.h" 2 | 3 | static void check_alien_collision(GameObject *bullet, GameState *state) { 4 | for(int i = 0; i < MAX_OBJECTS; i++) { 5 | GameObject *obj = &state->objects[i]; 6 | if (obj->valid && obj->type == OBJECT_ALIEN) { 7 | if (game_object_collide(bullet, obj)) { 8 | obj->alien_data.onBulletHit(bullet, state, obj); 9 | game_object_delete(bullet); 10 | } 11 | } 12 | } 13 | } 14 | 15 | static void check_player_collision(GameObject *bullet, GameState *state) { 16 | if (game_object_collide(bullet, state->player)) { 17 | state->player->player_data.onCollision(bullet, state, state->player); 18 | game_object_delete(bullet); 19 | } 20 | } 21 | 22 | static void game_bullet_update(GameObject *bullet, GameState *state) { 23 | bullet->y += bullet->bullet_data.speed; 24 | if (bullet->y <= 0 || bullet->y >= SCREEN_HEIGHT) { 25 | game_object_delete(bullet); 26 | return; 27 | } 28 | 29 | if (bullet->type == OBJECT_PLAYER_BULLET) { 30 | check_alien_collision(bullet, state); 31 | } else if(bullet->type == OBJECT_ALIEN_BULLET) { 32 | check_player_collision(bullet, state); 33 | } 34 | } 35 | 36 | static GameObject *game_init_bullet(GameState *state, int x, int y) { 37 | GameObject *bullet = game_object_construct(state, OBJECT_PLAYER_BULLET, 8, 1, 1, x, y); 38 | bullet->bullet_data.damage = 1; 39 | bullet->bullet_data.speed = -3; 40 | bullet->hitbox.margin_top = 3; 41 | bullet->hitbox.margin_left = 3; 42 | bullet->hitbox.margin_right = 3; 43 | bullet->hitbox.margin_bottom = 2; 44 | bullet->update = game_bullet_update; 45 | return bullet; 46 | } 47 | 48 | void game_init_player_bullet(GameState *state, int x, int y) { 49 | GameObject *bullet = game_init_bullet(state, x, y); 50 | } 51 | 52 | void game_init_alien_bullet(GameState *state, int x, int y) { 53 | GameObject *bullet = game_init_bullet(state, x, y); 54 | bullet->type = OBJECT_ALIEN_BULLET; 55 | bullet->sprite.id = 9; 56 | bullet->bullet_data.speed = 3; 57 | } 58 | -------------------------------------------------------------------------------- /src/game/src/logic/game_bullet.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | typedef struct _Bullet Bullet; 5 | 6 | #include "game_object.h" 7 | 8 | struct _Bullet { 9 | int damage; 10 | double speed; 11 | }; 12 | 13 | void game_init_player_bullet(GameState *state, int x, int y); 14 | void game_init_alien_bullet(GameState *state, int x, int y); 15 | -------------------------------------------------------------------------------- /src/game/src/logic/game_object.c: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | 4 | GameObject *game_object_alloc(GameState *state) { 5 | GameObject *object = state->objects; 6 | int i = MAX_OBJECTS; 7 | while(i--) { 8 | if (!object->valid) { 9 | copyzero(object, sizeof(GameObject)); 10 | object->valid = 1; 11 | return object; 12 | } 13 | object++; 14 | } 15 | 16 | return NULL; 17 | } 18 | 19 | void game_objects_clear(GameState *state) { 20 | copyzero(state->objects, MAX_OBJECTS * sizeof(GameObject)); 21 | } 22 | 23 | GameObject *game_object_construct(GameState *state, uint8_t type, uint8_t sprite_id, int sw, int sh, double x, double y) { 24 | GameObject *obj = game_object_alloc(state); 25 | obj->type = type; 26 | obj->visible = 1; 27 | obj->sprite.id = sprite_id; 28 | obj->x = x; 29 | obj->y = y; 30 | obj->sw = sw; 31 | obj->sh = sh; 32 | obj->draw = generic_draw; 33 | obj->update = generic_noaction; 34 | 35 | return obj; 36 | } 37 | 38 | GameObject *game_object_find(uint8_t type, GameState *state, int *offset) { 39 | for(int i = *offset; i < MAX_OBJECTS; i++) { 40 | if (state->objects[i].valid && state->objects[i].type == type) { 41 | *offset = i+1; 42 | return &state->objects[i]; 43 | } 44 | } 45 | return NULL; 46 | } 47 | 48 | void game_objects_update(GameState *state) { 49 | GameObject *object = state->objects; 50 | int i = MAX_OBJECTS; 51 | while(i--) { 52 | if (object->valid) 53 | object->update(object, state); 54 | object++; 55 | } 56 | } 57 | 58 | void game_objects_draw(GameState *state) { 59 | GameObject *object = state->objects; 60 | int i = MAX_OBJECTS; 61 | while(i--) { 62 | if (object->valid && object->visible) 63 | object->draw(object, state); 64 | object++; 65 | } 66 | } 67 | 68 | void generic_noaction(GameObject *obj, GameState *state) {} 69 | 70 | void generic_draw(GameObject *obj, GameState *state) { 71 | uint8_t sprite_id = obj->sprite.id; 72 | if (obj->animation.n > 0) { 73 | obj->animation.current_count++; 74 | if (obj->animation.current_count == obj->animation.delay) { 75 | obj->animation.current_count = 0; 76 | obj->animation.current_index++; 77 | if (obj->animation.current_index > obj->animation.n) 78 | obj->animation.current_index = 0; 79 | } 80 | if (obj->animation.current_index > 0) { 81 | sprite_id = obj->animation.sprites[obj->animation.current_index-1].id; 82 | } 83 | } 84 | 85 | if (!(obj->flicker&1)) { 86 | spr(sprite_id, 87 | obj->sw, 88 | obj->sh, 89 | obj->x, 90 | obj->y); 91 | } 92 | 93 | obj->flicker >>= 1; 94 | } 95 | 96 | void game_object_delete(GameObject *obj) { 97 | obj->valid = 0; 98 | } 99 | 100 | typedef struct { 101 | int x; 102 | int y; 103 | int w; 104 | int h; 105 | } Rect; 106 | 107 | static void game_object_hitbox(GameObject *obj, Rect *box) { 108 | box->x = obj->x + obj->hitbox.margin_left; 109 | box->y = obj->y + obj->hitbox.margin_top; 110 | box->w = (obj->sw*8) - obj->hitbox.margin_left - obj->hitbox.margin_right; 111 | box->h = (obj->sh*8) - obj->hitbox.margin_top - obj->hitbox.margin_bottom; 112 | } 113 | 114 | static int rect_overlap(Rect *a, Rect *b) { 115 | if ( 116 | (a->x >= b->x && a->x < b->x+b->w) || 117 | (b->x >= a->x && b->x < a->x+a->w)) { 118 | if ( 119 | (a->y >= b->y && a->y < b->y+b->h) || 120 | (b->y >= a->y && b->y < a->y+a->h)) { 121 | return 1; 122 | } 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | int game_object_in_rect(GameObject *a, int bx, int by, int bw, int bh) { 129 | if(a->visible == 0) return 0; 130 | Rect arect; 131 | game_object_hitbox(a, &arect); 132 | Rect brect = {bx, by, bw, bh}; 133 | 134 | return rect_overlap(&arect, &brect); 135 | } 136 | 137 | int game_object_collide(GameObject *a, GameObject *b) { 138 | // Only visible objects can collide 139 | if(a->visible == 0 || b->visible == 0) return 0; 140 | 141 | Rect arect, brect; 142 | game_object_hitbox(a, &arect); 143 | game_object_hitbox(b, &brect); 144 | 145 | return rect_overlap(&arect, &brect); 146 | } 147 | -------------------------------------------------------------------------------- /src/game/src/logic/game_object.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | typedef struct _GameObject GameObject; 5 | 6 | #define FLICKER_HIDE_2_TICKS 3 7 | #define FLICKER_HIDE_1_TICK 1 8 | 9 | #define OBJECT_PLAYER 1 10 | #define OBJECT_PLAYER_BULLET 2 11 | #define OBJECT_ALIEN_BULLET 3 12 | #define OBJECT_ALIEN 4 13 | #define OBJECT_TITLE 5 14 | #define OBJECT_POWERUP 6 15 | 16 | #include "game_player.h" 17 | #include "game_bullet.h" 18 | #include "game_alien.h" 19 | #include "game_title.h" 20 | #include "game_powerup.h" 21 | 22 | typedef struct { 23 | int margin_top; 24 | int margin_bottom; 25 | int margin_left; 26 | int margin_right; 27 | } Hitbox; 28 | 29 | typedef struct { 30 | uint8_t id; 31 | int flip_x; 32 | int flip_y; 33 | } Sprite; 34 | 35 | typedef struct { 36 | uint8_t n; 37 | Sprite sprites[3]; 38 | int delay; 39 | int current_count; 40 | int current_index; 41 | } Animation; 42 | 43 | // Every object in the game is represented by a GameObject. 44 | // GameObjects contain state information about the object 45 | // and pointers to functions to update an draw the object. 46 | struct _GameObject { 47 | // Only valid game objects are updated and rendered. 48 | // If an object is not valid it might me replaced by 49 | // the next object entering the game. 50 | uint8_t valid; 51 | 52 | // Every kind of object is identified by a specific type 53 | uint8_t type; 54 | 55 | // Only visible objects will be drawn. 56 | uint8_t visible; 57 | 58 | // Flicker is a bit mask used while drawing the object. 59 | // The least significant bit says if drawing is skipped (=1). 60 | // Flicker is shifted to the right one bit each tick. 61 | // This can be used to realize flickering sprites. 62 | uint32_t flicker; 63 | 64 | // Which sprite should be drawn? 65 | Sprite sprite; 66 | Animation animation; 67 | 68 | // What is the position 69 | double x; 70 | double y; 71 | 72 | // Width and height in units of 8 pixels 73 | int sw; 74 | int sh; 75 | 76 | // The hitbox is defined relative to spritebox 77 | // and used for detecting collisions. 78 | Hitbox hitbox; 79 | 80 | uint32_t tick; 81 | void(*update)(struct _GameObject *obj, struct _GameState *state); 82 | void(*draw)(struct _GameObject *obj, struct _GameState *state); 83 | 84 | // Depending on the type, specific data about the 85 | // object can be accessed 86 | union { 87 | Player player_data; 88 | Bullet bullet_data; 89 | Alien alien_data; 90 | Title title_data; 91 | PowerUp powerup_data; 92 | }; 93 | 94 | }; 95 | 96 | 97 | GameObject *game_object_alloc(GameState *state); 98 | void game_objects_clear(GameState *state); 99 | void game_object_delete(GameObject *obj); 100 | GameObject *game_object_construct(GameState *state, uint8_t type, uint8_t sprite_id, int sw, int sh, double x, double y); 101 | GameObject *game_object_find(uint8_t type, GameState *state, int *offset); 102 | 103 | void game_objects_update(GameState *state); 104 | void game_objects_draw(GameState *state); 105 | 106 | void generic_draw(GameObject *obj, GameState *state); 107 | void generic_noaction(GameObject *obj, GameState *state); 108 | 109 | int game_object_collide(GameObject *a, GameObject *b); 110 | int game_object_in_rect(GameObject *obj, int bx, int by, int bw, int bh); 111 | -------------------------------------------------------------------------------- /src/game/src/logic/game_player.c: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | 4 | static void player_fire(GameObject *player, GameState *state) { 5 | if (player->player_data.level == 1 || player->player_data.level == 3) { 6 | game_init_player_bullet(state, ((int)player->x) + 4, SCREEN_HEIGHT - 10 - 2); 7 | } 8 | 9 | if (player->player_data.level > 1) { 10 | game_init_player_bullet(state, ((int)player->x) - 2, SCREEN_HEIGHT - 10); 11 | game_init_player_bullet(state, ((int)player->x) + 8 + 2, SCREEN_HEIGHT - 10); 12 | } 13 | } 14 | 15 | static void game_player_update(GameObject *player, GameState *state) { 16 | player->player_data.velocity *= 0.65; 17 | 18 | if (btn(KEY_RIGHT)) { 19 | player->player_data.velocity += 2.0; 20 | } 21 | 22 | if(btn(KEY_LEFT)) { 23 | player->player_data.velocity -= 2.0; 24 | } 25 | 26 | player->x += player->player_data.velocity; 27 | 28 | if (player->x+16 > SCREEN_WIDTH) { 29 | player->x = SCREEN_WIDTH-16; 30 | player->player_data.velocity = 0; 31 | } 32 | 33 | if (player->x < 0) { 34 | player->x = 0; 35 | player->player_data.velocity = 0; 36 | } 37 | 38 | if (btnp(KEY_A)) { 39 | player_fire(player, state); 40 | } 41 | } 42 | 43 | static void player_levelup(GameObject *player) { 44 | if (player->player_data.level >= 3) return; 45 | player->flicker = 0b0101010101; 46 | 47 | player->player_data.level++; 48 | player->sprite.id += 2; 49 | } 50 | 51 | static void player_leveldown(GameObject *player) { 52 | if (player->player_data.level == 1) return; 53 | 54 | player->player_data.level--; 55 | player->sprite.id -= 2; 56 | } 57 | 58 | static int player_can_be_hit(GameObject *player) { 59 | return player->flicker == 0; 60 | } 61 | 62 | static void player_hit_by_alien_bullet(GameObject *bullet, GameState *state, GameObject *player) { 63 | if(!player_can_be_hit(player)) return; 64 | 65 | player_leveldown(player); 66 | player->player_data.lives -= bullet->bullet_data.damage; 67 | player->flicker = 0b110011; 68 | } 69 | 70 | static void player_powerup(GameObject *powerup, GameState *state, GameObject *player) { 71 | if(powerup->powerup_data.kind == POWER_UP_LIVE) { 72 | player->player_data.lives++; 73 | if (player->player_data.lives > 3) player->player_data.lives = 3; 74 | } else if (powerup->powerup_data.kind == POWER_UP_UPGRADE) { 75 | player_levelup(player); 76 | } 77 | } 78 | 79 | static void player_onCollision(GameObject *other, GameState *state, GameObject *player) { 80 | if (other->type == OBJECT_ALIEN_BULLET) { 81 | player_hit_by_alien_bullet(other, state, player); 82 | } else if(other->type == OBJECT_POWERUP) { 83 | player_powerup(other, state, player); 84 | } 85 | } 86 | 87 | GameObject *game_init_player(GameState *state) { 88 | GameObject *player = game_object_construct(state, 89 | OBJECT_PLAYER, 90 | 0, 2, 1, 91 | (SCREEN_WIDTH-16) / 2, SCREEN_HEIGHT - 8 - 2); 92 | player->update = game_player_update; 93 | player->player_data.velocity = 0; 94 | player->player_data.level = 1; 95 | player->player_data.lives = 3; 96 | player->player_data.onCollision = player_onCollision; 97 | return player; 98 | } 99 | -------------------------------------------------------------------------------- /src/game/src/logic/game_player.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | typedef struct _Player Player; 5 | 6 | #include "game_object.h" 7 | 8 | struct _Player { 9 | double velocity; 10 | void (*onCollision)(GameObject *other, GameState *state, GameObject *player); 11 | int level; 12 | int lives; 13 | }; 14 | 15 | GameObject *game_init_player(GameState *state); 16 | -------------------------------------------------------------------------------- /src/game/src/logic/game_powerup.c: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | 4 | #define POWERUP_SPEED 0.5 5 | 6 | static void powerup_update(GameObject *powerup, GameState *state) { 7 | powerup->y += POWERUP_SPEED; 8 | if (game_object_collide(powerup, state->player)) { 9 | state->player->player_data.onCollision(powerup, state, state->player); 10 | game_object_delete(powerup); 11 | } else if(powerup->y >= SCREEN_HEIGHT) { 12 | game_object_delete(powerup); 13 | } 14 | } 15 | 16 | void game_init_powerup(GameState *state, int x, int y) { 17 | int kind, sprite; 18 | 19 | if (state->player->player_data.lives < 3) { 20 | kind = POWER_UP_LIVE; 21 | sprite = 10; 22 | } else if(state->player->player_data.level < 3) { 23 | kind = POWER_UP_UPGRADE; 24 | sprite = 11; 25 | } else { 26 | return; // no powerup else 27 | } 28 | 29 | GameObject *obj = game_object_construct(state, OBJECT_POWERUP, sprite, 1, 1, x, y); 30 | obj->powerup_data.kind = kind; 31 | obj->update = powerup_update; 32 | } 33 | -------------------------------------------------------------------------------- /src/game/src/logic/game_powerup.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | typedef struct _PowerUp PowerUp; 5 | #define POWER_UP_LIVE 1 6 | #define POWER_UP_UPGRADE 2 7 | 8 | #include "game_object.h" 9 | 10 | struct _PowerUp { 11 | int kind; 12 | }; 13 | 14 | void game_init_powerup(GameState *state, int x, int y); 15 | -------------------------------------------------------------------------------- /src/game/src/logic/game_title.c: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | 4 | static void text_draw(GameObject *obj, GameState *state) { 5 | int l = strlen(obj->title_data.text); 6 | int x = (SCREEN_WIDTH - l*(LETTER_WIDTH+1))/2 - 1; 7 | int y = SCREEN_HEIGHT/2 - 1; 8 | int c = 3; 9 | if (obj->title_data.ticks % 3 == 1) c = 4; 10 | else if (obj->title_data.ticks % 3 == 2) c = 7; 11 | 12 | text(obj->title_data.text, x+1, y+1, 0); 13 | text(obj->title_data.text, x, y, c); 14 | } 15 | 16 | static void text_update(GameObject *obj, GameState *state) { 17 | obj->title_data.ticks--; 18 | if (obj->title_data.ticks == 0) { 19 | if (obj->title_data.next != NULL) 20 | obj->title_data.next(state); 21 | game_object_delete(obj); 22 | } 23 | } 24 | 25 | GameObject *game_init_title(GameState *state) { 26 | GameObject *text = game_object_alloc(state); 27 | text->visible = 1; 28 | text->update = text_update; 29 | text->draw = text_draw; 30 | return text; 31 | } 32 | -------------------------------------------------------------------------------- /src/game/src/logic/game_title.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | typedef struct _Title Title; 5 | #define TITLE_MAX_LENGTH 40 6 | 7 | #include "game_object.h" 8 | 9 | struct _Title { 10 | void (*next)(GameState *state); 11 | char text[TITLE_MAX_LENGTH+1]; 12 | int ticks; 13 | }; 14 | 15 | GameObject *game_init_title(GameState *state); 16 | -------------------------------------------------------------------------------- /src/game/src/main.c: -------------------------------------------------------------------------------- 1 | #include "system.h" 2 | #include "game.h" 3 | #include "sprites.h" 4 | 5 | static GameState *state; 6 | 7 | // This function is a JS method that was imported using WASM 8 | extern void get_game_state(int scene, int score); 9 | 10 | // _init is called from JavaScript when the WebAssembly-Module was loaded. 11 | EXPORT void _init() { 12 | SystemConfiguration cfg; 13 | 14 | cfg.palette = (uint32_t *)vga_colors; 15 | cfg.spritemem = SPRITE_DATA; 16 | cfg.sprite_w = 128; 17 | cfg.sprite_h = 32; 18 | 19 | system_init(&cfg); 20 | state = game_init(); 21 | } 22 | 23 | 24 | // _update is called from JavaScript 30 times per second. 25 | // It updates the game logic and the pixelbuffer. 26 | EXPORT void _update() { 27 | get_game_state(state->scene, state->score); 28 | game_update(state); 29 | } 30 | -------------------------------------------------------------------------------- /src/game/src/sprites.h: -------------------------------------------------------------------------------- 1 | 2 | static uint8_t SPRITE_DATA[] = { 3 | 0x0, 0x0, 0x0, 0x6, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xdd, 0xda, 0x0, 0xa, 0xdd, 0xda, 0x0, 0xa, 0xdd, 0xda, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xad, 0x40, 0x4d, 0xa0, 0xad, 0x0, 0xd, 0xa0, 0xad, 0x5, 0xd, 0xa0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x22, 0x22, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x22, 0x22, 0x11, 0x0, 0x0, 0x5, 0x50, 0x55, 0x0, 0x5, 0x50, 0x55, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd4, 0x44, 0x44, 0xd0, 0xd0, 0x6, 0x0, 0xd0, 0xd0, 0x60, 0x40, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x22, 0x22, 0x21, 0x0, 0x0, 0x6, 0x60, 0x11, 0x12, 0x21, 0x11, 0x6, 0x60, 0x6, 0x60, 0x12, 0x22, 0x22, 0x21, 0x6, 0x60, 0x54, 0x44, 0x44, 0x50, 0x50, 0x5, 0x0, 0x50, 0x0, 0x4, 0x40, 0x0, 0x0, 0x9, 0x90, 0x0, 0xd4, 0x44, 0x44, 0xd0, 0xd0, 0x22, 0x20, 0xd0, 0xd5, 0x7, 0x7, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x22, 0x21, 0x12, 0x22, 0x10, 0x0, 0x12, 0x21, 0x22, 0x22, 0x22, 0x22, 0x12, 0x21, 0x12, 0x21, 0x22, 0x22, 0x22, 0x22, 0x12, 0x21, 0x54, 0x44, 0x44, 0x50, 0x50, 0x0, 0x0, 0x50, 0x0, 0x4, 0x40, 0x0, 0x0, 0x8, 0x80, 0x0, 0xd0, 0x44, 0x40, 0xd0, 0xd2, 0x22, 0x22, 0xd0, 0xd0, 0x40, 0x50, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x22, 0x10, 0x1, 0x22, 0x21, 0x0, 0x12, 0x22, 0x22, 0x21, 0x12, 0x22, 0x22, 0x21, 0x12, 0x22, 0x22, 0x21, 0x12, 0x22, 0x22, 0x21, 0x5, 0x44, 0x45, 0x0, 0x5, 0x0, 0x5, 0x0, 0x0, 0x5, 0x50, 0x0, 0x0, 0x8, 0x80, 0x0, 0xad, 0x4, 0xd, 0xa0, 0xad, 0x0, 0xd, 0xa0, 0xad, 0x6, 0xd, 0xa0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x22, 0x22, 0x10, 0x1, 0x22, 0x22, 0x10, 0x11, 0x22, 0x22, 0x10, 0x1, 0x22, 0x22, 0x11, 0x11, 0x22, 0x22, 0x10, 0x1, 0x22, 0x22, 0x11, 0x0, 0x54, 0x50, 0x0, 0x0, 0x50, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xdd, 0xda, 0x0, 0xa, 0xdd, 0xda, 0x0, 0xa, 0xdd, 0xda, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11, 0x11, 0x0, 0x0, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0, 0x0, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0, 0x0, 0x11, 0x11, 0x11, 0x0, 0x5, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x90, 0x0, 0x9, 0x90, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0x9, 0x0, 0x0, 0x90, 0x9, 0x0, 0x0, 0x98, 0x90, 0x0, 0x0, 0x0, 0xf, 0xfd, 0xdf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x54, 0x45, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x99, 0x99, 0x0, 0x0, 0x99, 0x99, 0x0, 0x0, 0x98, 0x90, 0x0, 0x0, 0xf, 0xfd, 0xdd, 0xad, 0xaf, 0xf0, 0x0, 0x0, 0x5, 0x54, 0x44, 0x34, 0x35, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x98, 0x89, 0x90, 0x9, 0x98, 0x89, 0x90, 0x9, 0x99, 0x99, 0x0, 0xf, 0xfd, 0xdd, 0xda, 0xda, 0xda, 0xdf, 0xf0, 0x5, 0x54, 0x44, 0x43, 0x43, 0x43, 0x45, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0x99, 0x99, 0x9, 0x90, 0x99, 0x99, 0x9, 0x99, 0x9, 0x9, 0x90, 0xfd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdf, 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0x90, 0x9, 0x9, 0x90, 0x90, 0x9, 0x9, 0x99, 0x99, 0x99, 0x90, 0x12, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x21, 0x12, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x90, 0x0, 0x0, 0x9, 0x90, 0x0, 0x99, 0x90, 0x99, 0x90, 0x1, 0x14, 0x14, 0x17, 0x71, 0x41, 0x41, 0x10, 0x1, 0x18, 0x18, 0x11, 0x11, 0x81, 0x81, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0x90, 0x90, 0x90, 0x0, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0, 0x0, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 4 | 5 | }; 6 | 7 | static const uint32_t vga_colors[16] = { 0xff000000,0xff555555,0xffaaaaaa,0xffffffff,0xff5555ff,0xff0000aa,0xff0055aa,0xff55ffff,0xff55ff55,0xff00aa00,0xffffff55,0xffaaaa00,0xffff55ff,0xffff5555,0xffaa00aa,0xffaa0000}; 8 | -------------------------------------------------------------------------------- /src/game/src/system/system.c: -------------------------------------------------------------------------------- 1 | 2 | #include "system.h" 3 | 4 | void system_init(SystemConfiguration *configuration) { 5 | system_base_init(); 6 | system_video_init(configuration); 7 | system_input_init(); 8 | } 9 | -------------------------------------------------------------------------------- /src/game/src/system/system.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "system_base.h" 5 | 6 | typedef struct { 7 | uint32_t *palette; 8 | uint8_t *spritemem; 9 | int sprite_w; 10 | int sprite_h; 11 | } SystemConfiguration; 12 | 13 | #include "system_video.h" 14 | #include "system_input.h" 15 | 16 | void system_init(SystemConfiguration *configuration); 17 | -------------------------------------------------------------------------------- /src/game/src/system/system_base.c: -------------------------------------------------------------------------------- 1 | 2 | #include "system.h" 3 | 4 | // Since this program does not use the the c stdlib 5 | // this is a very simple replacement for malloc. 6 | // When asked for n bytes of memory, it returns the next 7 | // n bytes from the heap. The next request will return the 8 | // following n bytes, etc. Memory is never reused/freed. 9 | // The heap size is never changed. 10 | 11 | extern uint32_t __heap_base; 12 | static unsigned int current_heap_pointer = (uint32_t)&__heap_base; 13 | 14 | void* malloc(unsigned long n) { 15 | // Make memory 4-byte-aligned 16 | if (n % 4 != 0) 17 | n += 4-(n%4); 18 | 19 | uint32_t tmp = current_heap_pointer; 20 | current_heap_pointer += n; 21 | return (void *) tmp; 22 | } 23 | 24 | void free(void* mem) { 25 | // Memory is not freed 26 | } 27 | 28 | 29 | // This program exposes memory locations for the current time 30 | // and a random seed. This values are set from JavaScript after initialization. 31 | static uint32_t *_time_memory; 32 | static uint32_t *_seed_memory; 33 | 34 | 35 | void system_base_init() { 36 | _time_memory = (uint32_t *) malloc(4); 37 | _seed_memory = (uint32_t *) malloc(4); 38 | } 39 | 40 | uint32_t time() { 41 | return *_time_memory; 42 | } 43 | 44 | // xorshift PRNG, taken from Marsaglia 45 | uint32_t randn(uint32_t n) { 46 | uint32_t x = *_seed_memory; 47 | x ^= x << 13; 48 | x ^= x >> 17; 49 | x ^= x << 5; 50 | *_seed_memory = x; 51 | return x % n; 52 | } 53 | 54 | void memcpy_8(void *from, void *to, uint32_t len) { 55 | uint64_t *src = from; 56 | uint64_t *dst = to; 57 | len /= 8; 58 | while(len) { 59 | *dst++ = *src++; 60 | len--; 61 | } 62 | } 63 | 64 | void copy(void *from, void *to, unsigned long len) { 65 | if (len % 8 == 0) { 66 | memcpy_8(from, to, len); 67 | } 68 | uint8_t *src = from; 69 | uint8_t *dst = to; 70 | while(len) { 71 | *dst++ = *src++; 72 | len--; 73 | } 74 | } 75 | 76 | void *memset(void *dst, int v, unsigned long len) { 77 | if (len % 8 == 0) { 78 | uint64_t *_dst = dst; 79 | len /= 8; 80 | while(len--) *_dst++ = v; 81 | } else if (len % 4 == 0) { 82 | uint32_t *_dst = dst; 83 | len /= 4; 84 | while(len--) *_dst++ = v; 85 | } else { 86 | uint8_t *_dst = dst; 87 | while(len--) *_dst++ = v; 88 | } 89 | return dst; 90 | } 91 | 92 | void copyzero(void *dst, unsigned long len) { 93 | memset(dst, 0, len); 94 | } 95 | 96 | 97 | EXPORT void *_get_time_ref() { 98 | return _time_memory; 99 | } 100 | 101 | EXPORT void *_get_seed_ref() { 102 | return _seed_memory; 103 | } 104 | 105 | static void reverse(char *start, char *end) { 106 | char tmp; 107 | while(start < end) { 108 | tmp = *end; 109 | *end = *start; 110 | *start = tmp; 111 | 112 | start++; 113 | end--; 114 | } 115 | } 116 | 117 | char *put_string(const char *str, char *dst, int *maxlen) { 118 | if (*maxlen < 1) return dst; 119 | 120 | while(*maxlen > 1 && *str) { 121 | *dst++ = *str++; 122 | *maxlen = (*maxlen) - 1; 123 | } 124 | *dst = 0; 125 | 126 | return dst; 127 | } 128 | 129 | char *put_number(int n, char *dst, int *maxlen) { 130 | if (*maxlen < 1) return dst; 131 | if (n < 0) { 132 | *dst++ = '-'; 133 | *maxlen = (*maxlen) - 1; 134 | n *= -1; 135 | } else if(n == 0 && *maxlen > 1) { 136 | *dst++ = '0'; 137 | *dst = 0; 138 | *maxlen = (*maxlen) - 2; 139 | return dst; 140 | } 141 | 142 | char *number_start = dst; 143 | while(*maxlen > 1 && n > 0) { 144 | *dst++ = (n%10) + '0'; 145 | n /= 10; 146 | *maxlen = (*maxlen) - 1; 147 | } 148 | 149 | *dst = 0; 150 | 151 | reverse(number_start, dst-1); 152 | 153 | return dst; 154 | } 155 | 156 | 157 | unsigned long strlen(const char *s) { 158 | int i = 0; 159 | while(*s++) i++; 160 | return i; 161 | } 162 | -------------------------------------------------------------------------------- /src/game/src/system/system_base.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #define NULL ((void *)0) 5 | #include 6 | 7 | #define EXPORT __attribute__ ((visibility ("default"))) 8 | 9 | // malloc return n bytes of free memory that can be used. 10 | void* malloc(unsigned long n); 11 | 12 | // free does nothing in this implementation 13 | void free(void* mem); 14 | 15 | // system_base_init initializes memory locations 16 | // for time and random-seed 17 | void system_base_init(); 18 | 19 | // access memory locations from JavaScript 20 | void *_get_time_ref(); 21 | void *_get_seed_ref(); 22 | 23 | // randn return a random number in the interval [0,n) 24 | uint32_t randn(uint32_t n); 25 | 26 | // time return the time since the start of the program in ms. 27 | uint32_t time(); 28 | 29 | // Methods for memory initialization 30 | void copy(void *from, void *to, unsigned long len); 31 | void copyzero(void *dst, unsigned long len); 32 | void *memset(void *dst, int v, unsigned long len); 33 | 34 | // String manipulation 35 | 36 | // put_string copies bytes from str to dst, but maxlen bytes at most. 37 | // dst is null-terminated and a pointer to the last byte in dst is returned. 38 | char *put_string(const char *str, char *dst, int *maxlen); 39 | 40 | // put_number converts n to a string. At most maxlen bytes are written. 41 | // dst is null-terminated and a pointer to the last byte in dst is returned. 42 | char *put_number(int n, char *dst, int *maxlen); 43 | 44 | unsigned long strlen(const char *s); 45 | -------------------------------------------------------------------------------- /src/game/src/system/system_input.c: -------------------------------------------------------------------------------- 1 | 2 | #include "system.h" 3 | 4 | // This program exposes memory locations for the current 5 | // state of the keyboard. 6 | static uint8_t *_controller_memory; 7 | static uint8_t _controller_memory_last_frame; 8 | static uint8_t _controller_memory_pressed; 9 | static uint32_t _last_time; 10 | 11 | void system_input_init() { 12 | _controller_memory = (uint8_t *) malloc(1); 13 | _controller_memory_last_frame = 0; 14 | } 15 | 16 | uint8_t btn(uint8_t key) { 17 | return (*_controller_memory)&key; 18 | } 19 | 20 | uint8_t btnp(uint8_t key) { 21 | uint32_t t = time(); 22 | if(t != _last_time) { 23 | _last_time = t; 24 | _controller_memory_pressed = (*_controller_memory) & (~_controller_memory_last_frame); 25 | _controller_memory_last_frame = *_controller_memory; 26 | } 27 | return _controller_memory_pressed&key; 28 | } 29 | 30 | EXPORT void *_get_input_ref() { 31 | return _controller_memory; 32 | } 33 | -------------------------------------------------------------------------------- /src/game/src/system/system_input.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #define KEY_LEFT 1 5 | #define KEY_UP 2 6 | #define KEY_RIGHT 4 7 | #define KEY_DOWN 8 8 | #define KEY_A 16 9 | #define KEY_B 32 10 | #define KEY_START 64 11 | #define KEY_SELECT 128 12 | 13 | // Initialize the memory buffer for the keyboard. 14 | void system_input_init(); 15 | 16 | // Return location of keyboard buffer. 17 | void *_get_input_ref(); 18 | 19 | // Return 1, if key is pressed. 0 otherwise. 20 | uint8_t btn(uint8_t key); 21 | 22 | // Return 1, if key is pressed and was not pressed when 23 | // this function was called the last time. 0 otherwise. 24 | uint8_t btnp(uint8_t key); 25 | -------------------------------------------------------------------------------- /src/game/src/system/system_video.c: -------------------------------------------------------------------------------- 1 | 2 | #include "system.h" 3 | 4 | static uint32_t *_screen_memory; 5 | static uint8_t *_sprite_memory; 6 | static uint16_t _sprite_memory_width; 7 | static uint16_t _sprite_memory_height; 8 | static const uint32_t *_palette; 9 | static uint32_t _current_palette[16]; 10 | static const uint32_t _default_palette[16] = { 0xff000000,0xff555555,0xffaaaaaa,0xffffffff,0xff5555ff,0xff0000aa,0xff0055aa,0xff55ffff,0xff55ff55,0xff00aa00,0xffffff55,0xffaaaa00,0xffff55ff,0xffff5555,0xffaa00aa,0xffaa0000}; 11 | 12 | static int clip_x = 0; 13 | static int clip_y = 0; 14 | static int clip_width = SCREEN_WIDTH; 15 | static int clip_height = SCREEN_HEIGHT; 16 | 17 | void system_video_init(SystemConfiguration *configuration) { 18 | _screen_memory = (uint32_t *) malloc(SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(uint32_t)); 19 | _sprite_memory = configuration->spritemem; 20 | _sprite_memory_width = configuration->sprite_w; 21 | _sprite_memory_height = configuration->sprite_h; 22 | 23 | // We keep a copy of the pallet to allow color translations 24 | _palette = configuration->palette != NULL ? configuration->palette : _default_palette; 25 | copy((void *)_palette,(void *)_current_palette, 64); 26 | } 27 | 28 | EXPORT void *_get_screen_ref() { 29 | return _screen_memory; 30 | } 31 | 32 | // Drawing functions 33 | 34 | static uint8_t _transparent_color = 0; 35 | 36 | inline static void set_pixel(int x, int y, int c) { 37 | _screen_memory[y * SCREEN_WIDTH + x] = _current_palette[c]; 38 | } 39 | 40 | void pset(int x, int y, uint8_t c) { 41 | if (c == _transparent_color) return; 42 | if (x < 0) return; 43 | if (x >= SCREEN_WIDTH) return; 44 | if (y < 0) return; 45 | if (y >= SCREEN_HEIGHT) return; 46 | set_pixel(x, y, c); 47 | } 48 | 49 | void cls(uint8_t pc) { 50 | uint32_t c = _current_palette[pc]; 51 | uint64_t two = c | ((uint64_t)c<<32); 52 | uint64_t *screen_ptr = (uint64_t *)_screen_memory; 53 | uint64_t *screen_end_ptr = screen_ptr + (SCREEN_WIDTH*SCREEN_HEIGHT)/2; 54 | while(screen_ptr < screen_end_ptr) *screen_ptr++ = two; 55 | 56 | clip_x = 0; 57 | clip_y = 0; 58 | clip_width = SCREEN_WIDTH; 59 | clip_height = SCREEN_HEIGHT; 60 | } 61 | 62 | void palt(uint8_t c) { 63 | _transparent_color = c; 64 | } 65 | 66 | void rectfill(int x1, int y1, int w, int h, uint8_t pc) { 67 | uint32_t c = _current_palette[pc]; 68 | if (x1 < 0) x1 = 0; 69 | if (y1 < 0) y1 = 0; 70 | int x2 = x1 + w; 71 | int y2 = y1 + h; 72 | if (x2 > SCREEN_WIDTH) x2 = SCREEN_WIDTH; 73 | if (y2 > SCREEN_HEIGHT) y2 = SCREEN_HEIGHT; 74 | 75 | int dx = x2 - x1; 76 | int dy = y2 - y1; 77 | uint32_t *screen_ptr = &_screen_memory[SCREEN_WIDTH*y1+x1]; 78 | for(int y = y1; y < y2; y++) { 79 | for(int x = x1; x < x2; x++) { 80 | *screen_ptr++ = c; 81 | } 82 | screen_ptr -= dx; 83 | screen_ptr += SCREEN_WIDTH; 84 | } 85 | } 86 | 87 | typedef struct { 88 | int dst_x; 89 | int dst_y; 90 | int src_x; 91 | int src_y; 92 | int w; 93 | int h; 94 | } BlitInfo; 95 | 96 | // Extract color for pixel x,y. Since the sprite map 97 | // contains 2 pixels per byte (remember: 16 colors), 98 | // we need some bitshifting here. 99 | inline static uint8_t get_sprite_pixel(int x, int y) { 100 | uint8_t col = _sprite_memory[y * _sprite_memory_width/2 + x/2]; 101 | return x&1 ? col&0x0F : col>>4; 102 | } 103 | 104 | void blit(BlitInfo *info) { 105 | uint8_t col; 106 | 107 | for(int y = 0; y < info->h; y++) { 108 | for(int x = 0; x < info->w; x++) { 109 | col = get_sprite_pixel(info->src_x+x, info->src_y+y); 110 | if (col != _transparent_color) 111 | set_pixel(info->dst_x+x, info->dst_y+y, col); 112 | } 113 | } 114 | } 115 | 116 | void spr(int n, int w, int h, int x, int y) { 117 | // Check boundaries and continue with blit(...). 118 | BlitInfo info; 119 | 120 | info.src_x = 8 * (n % 16); 121 | info.src_y = 8 * (n / 16); 122 | info.w = w*8; 123 | info.h = h*8; 124 | 125 | if (x < 0) { 126 | info.dst_x = 0; 127 | info.src_x += -x; 128 | info.w -= -x; 129 | } else { 130 | info.dst_x = x; 131 | } 132 | 133 | if (y < 0) { 134 | info.dst_y = 0; 135 | info.src_y += -y; 136 | info.h -= -y; 137 | } else { 138 | info.dst_y = y; 139 | } 140 | 141 | if (info.dst_x + info.w > SCREEN_WIDTH) { 142 | info.w = SCREEN_WIDTH - info.dst_x; 143 | } 144 | 145 | if (info.dst_y + info.h > SCREEN_HEIGHT) { 146 | info.h = SCREEN_HEIGHT - info.dst_y; 147 | } 148 | 149 | 150 | blit(&info); 151 | } 152 | 153 | 154 | // Font 155 | 156 | // We have 96 letters (ASCII 32-127). Each letter has size 3x5. 157 | // Each two byte contain data for a letter (15 pixel). The most 158 | // significant bit is unused. 159 | const static uint16_t font_data[96] = { 160 | 0x0, 0x4904, 0xb400, 0x0, 0x0, 0x0, 0x0, 0x9000, 0x7246, 0xc49c, 0xb55a, 0xba0, 0x48, 0x380, 0x8, 0x2548, 161 | 0xf6de, 0x4924, 0xe7ce, 0xe79e, 0x9792, 0xf39e, 0x93de, 0xe492, 0xf7de, 0xf79e, 0x820, 0x828, 0x2a22, 0x1c70, 0x88a8, 0xf482, 162 | 0x76c6, 0xf7da, 0xd75e, 0xf24e, 0xd6dc, 0xf3ce, 0xf348, 0xf25e, 0xb7da, 0xe92e, 0xe4de, 0xb75a, 0x924e, 0xfeda, 0xd6da, 0x76dc, 163 | 0xf7c8, 0x56a2, 0xf75a, 0xf39e, 0xe924, 0xb6de, 0xb6d4, 0xb6fe, 0xb55a, 0xb6a4, 0xe54e, 0xf24e, 0x8922, 0xe49e, 0x5400, 0xe, 164 | 0x8800, 0xf7da, 0xd75e, 0xf24e, 0xd6dc, 0xf3ce, 0xf348, 0xf25e, 0xb7da, 0xe92e, 0xe4de, 0xb75a, 0x924e, 0xfeda, 0xd6da, 0x76dc, 165 | 0xf7c8, 0x56a2, 0xf75a, 0xf39e, 0xe924, 0xb6de, 0xb6d4, 0xb6fe, 0xb55a, 0xb6a4, 0xe54e, 0x6a26, 0x4924, 0xc8ac, 0x7c0, 0x100, 166 | }; 167 | 168 | static void blit_letter(char l, int x, int y, uint32_t color) { 169 | if (l <= 32 || l > 127) return; 170 | l -= 32; 171 | 172 | uint32_t *dst = _get_screen_ref(); 173 | uint16_t letter_data = font_data[l]; 174 | int bitMask = 32768; // = 2^15 175 | 176 | int y_limit = y+5 >= SCREEN_HEIGHT ? SCREEN_HEIGHT-1 : y+5; 177 | int x_limit = x+3 >= SCREEN_WIDTH ? SCREEN_WIDTH-1 : x+3; 178 | for(int dst_y = y; dst_y < y_limit; dst_y++) { 179 | for(int dst_x = x; dst_x < x_limit; dst_x++) { 180 | if(letter_data&bitMask) dst[SCREEN_WIDTH*dst_y+dst_x] = color; 181 | bitMask >>= 1; 182 | } 183 | } 184 | } 185 | 186 | void text(const char *text, int x, int y, uint8_t c) { 187 | if(y >= SCREEN_HEIGHT) return; 188 | 189 | for(int i = 0; text[i]; i++) { 190 | if (x >= SCREEN_WIDTH) return; 191 | if (x < 0) continue; 192 | blit_letter(text[i], x, y, _current_palette[c]); 193 | x += 4; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/game/src/system/system_video.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "system.h" 5 | 6 | #define SCREEN_WIDTH 128 7 | #define SCREEN_HEIGHT 128 8 | #define NO_TRANSPARENCY 16 9 | #define LETTER_WIDTH 3 10 | #define LETTER_HEIGHT 5 11 | 12 | // The screen-buffer is an 2 dimensional array that uses 13 | // 4 bytes per pixel. 14 | 15 | // Initializes the screen buffer 16 | void system_video_init(SystemConfiguration *configuration); 17 | // Returns location of screen buffer 18 | void *_get_screen_ref(); 19 | 20 | // Clears the screen with color c. 21 | void cls(uint8_t c); 22 | // Draw color c as transparent in following calls. 23 | void palt(uint8_t c); 24 | // Draw a filled rect. 25 | void rectfill(int x1, int y1, int w, int h, uint8_t c); 26 | // Draw sprite with id n 27 | void spr(int n, int w, int h, int x, int y); 28 | // Set pixel 29 | void pset(int x, int y, uint8_t c); 30 | 31 | // Print text on the screen 32 | void text(const char *text, int x, int y, uint8_t c); 33 | -------------------------------------------------------------------------------- /src/train_invaders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/train_invaders/__init__.py -------------------------------------------------------------------------------- /src/train_invaders/index.js: -------------------------------------------------------------------------------- 1 | const getGameIframe = () => document.querySelector(`.game-iframe`); 2 | 3 | // If Jupyter notebook 4 | if (typeof window !== 'undefined' && 'Jupyter' in window) { 5 | const outputArea = this 6 | const cellElement = outputArea.element.parents('.cell'); 7 | const cellIndex = Jupyter.notebook.get_cell_elements().index(cellElement); 8 | const cell = Jupyter.notebook.get_cell(cellIndex); 9 | const cellId = cell.cell_id 10 | 11 | // Expand output fully 12 | outputArea.collapse(); outputArea.expand(); 13 | 14 | if (!cell.__gameInitiated) { 15 | cell.__isRunning = true 16 | 17 | // On cell execution stop 18 | Jupyter.notebook.events.on('finished_execute.CodeCell', function (evt, data) { 19 | if (data.cell.cell_id === cellId) { 20 | const gameIframe = getGameIframe() 21 | if (gameIframe) gameIframe.contentWindow.postMessage({ event: 'finishedTraining' }, '*') 22 | cell.__isRunning = false 23 | } 24 | }); 25 | 26 | // On cell execution start 27 | Jupyter.notebook.events.on('execute.CodeCell', function (evt, data) { 28 | if (data.cell.cell_id === cellId) { 29 | cell.__isRunning = true 30 | } 31 | }); 32 | 33 | // Notify view when training is finished 34 | window.addEventListener("message", event => { 35 | if (event.data.event === `gameTrainingState`) { 36 | if (!cell.__isRunning) { 37 | const gameIframe = getGameIframe() 38 | if (gameIframe) gameIframe.contentWindow.postMessage({ event: 'finishedTraining' }, '*') 39 | } 40 | } 41 | }); 42 | 43 | cell.__gameInitiated = true 44 | } 45 | } 46 | 47 | // Handle game closing 48 | if(!window.__isThereGameCloseListener) { 49 | function gameCloseHandler(event) { 50 | if (event.data.event === `gameClose`) { 51 | const gameIframe = getGameIframe() 52 | if(gameIframe) getGameIframe().remove() 53 | window.removeEventListener("message", gameCloseHandler) 54 | window.__isThereGameCloseListener = false 55 | } 56 | } 57 | window.addEventListener("message", gameCloseHandler); 58 | window.__isThereGameCloseListener = true 59 | } 60 | 61 | // Inject base64 view to the iframe 62 | getGameIframe().contentDocument.write(atob("$$GAME_HTML_BASE64$$")) 63 | getGameIframe().focus() 64 | -------------------------------------------------------------------------------- /src/train_invaders/start.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import sys 3 | from types import FrameType 4 | from typing import Any, Dict, Optional 5 | 6 | from IPython.display import display, HTML, Javascript 7 | 8 | train_invaders_dir = Path(__file__).parent 9 | iframe_html = """ 10 | 18 | """ 19 | 20 | 21 | def _get_template(path: Path, mapping: Optional[Dict[str, Any]] = None) -> str: 22 | """Gets the file and injects the values of the keys from the mapping.""" 23 | with open(path, mode="r") as template_file: 24 | file = template_file.read() 25 | if mapping is not None: 26 | for key in mapping: 27 | file = file.replace(f"$${key}$$", mapping[key]) 28 | return file 29 | 30 | 31 | def _call_tracer(frame: FrameType, event: str, arg: Any): 32 | """Hook fit / train / train_on_batch methods and run the game on their call.""" 33 | if event == "call" and ( 34 | frame.f_code.co_name == "fit" 35 | or frame.f_code.co_name == "train" 36 | or frame.f_code.co_name == "train_on_batch" 37 | ): 38 | 39 | # Inject iframe 40 | display(HTML(iframe_html)) 41 | 42 | # Inject view inside the logic script 43 | with open(train_invaders_dir / "view.txt", mode="r") as view: 44 | script_str = _get_template( 45 | path=train_invaders_dir / "index.js", 46 | mapping={"GAME_HTML_BASE64": view.read()}, 47 | ) 48 | 49 | # Inject the logic 50 | display(Javascript(script_str)) 51 | 52 | 53 | # Start tracing 54 | sys.settrace(_call_tracer) 55 | 56 | # Remove `stop` module cache 57 | if "train_invaders.stop" in sys.modules: 58 | del sys.modules["train_invaders.stop"] 59 | -------------------------------------------------------------------------------- /src/train_invaders/stop.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # Stop settrace of `start` module 4 | sys.settrace(None) 5 | 6 | # Remove `start` module cache 7 | if "train_invaders.start" in sys.modules: 8 | del sys.modules["train_invaders.start"] 9 | -------------------------------------------------------------------------------- /src/view/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /src/view/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /src/view/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/airbnb', 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint', 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/view/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /src/view/README.md: -------------------------------------------------------------------------------- 1 | # TrainInvaders 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /src/view/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /src/view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TrainInvaders", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "vue": "^2.6.11" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "~4.5.0", 16 | "@vue/cli-plugin-eslint": "~4.5.0", 17 | "@vue/cli-plugin-vuex": "~4.5.0", 18 | "@vue/cli-service": "~4.5.0", 19 | "@vue/eslint-config-airbnb": "^5.0.2", 20 | "babel-eslint": "^10.1.0", 21 | "base64-inline-loader": "^2.0.1", 22 | "eslint": "^6.7.2", 23 | "eslint-plugin-import": "^2.20.2", 24 | "eslint-plugin-vue": "^6.2.2", 25 | "html-webpack-inline-source-plugin": "1.0.0-beta.2", 26 | "html-webpack-plugin": "^4.5.2", 27 | "lint-staged": "^9.5.0", 28 | "raw-loader": "^4.0.2", 29 | "sass": "^1.26.5", 30 | "sass-loader": "^8.0.2", 31 | "vue-template-compiler": "^2.6.11" 32 | }, 33 | "gitHooks": { 34 | "pre-commit": "lint-staged" 35 | }, 36 | "lint-staged": { 37 | "*.{js,jsx,vue}": [ 38 | "vue-cli-service lint", 39 | "git add" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/view/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/view/src/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 230 | 231 | 324 | -------------------------------------------------------------------------------- /src/view/src/assets/PressStart2P.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/view/src/assets/PressStart2P.ttf -------------------------------------------------------------------------------- /src/view/src/assets/aporia-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/view/src/assets/aporia-logo.png -------------------------------------------------------------------------------- /src/view/src/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/view/src/assets/bg.png -------------------------------------------------------------------------------- /src/view/src/assets/game.txt: -------------------------------------------------------------------------------- 1 | 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AoABQQFrDgIAAQILIAJBkAFqIgAgACgCACIAQQFqQQMgAEEDSBs2AgAPCyACQYwBaigCACIAQQJKDQAgAkHVAjYCBCACIABBAWo2AowBIAIgAi0ACEECajoACAsLWwAgACAAKwNQRAAAAAAAAOA/oDkDUAJAAkAgACABKAKg4AUQIwRAIAAgASABKAKg4AUiASABQYgBaigCABEBAAwBCyAAKwNQRAAAAAAAAGBAZkUNAQsgABAhCwt8AQR/IABBsAFqKAIAIQEgAEGEAWoiAEGAASAAIgItAAAEfyACQQFqIQNBACECA0AgAiADaiACQQFqIgUhAi0AAA0ACyAFBUEAC0ECdGtBAm0iAkHAAEEAEBEgACACQQFrQT9BBEEHQQMgAUEDbyIAQQJGGyAAQQFGGxARCzUBAX8gAEGwAWoiAiACKAIAQQFrIgI2AgAgAkUEQCAAKAKAASICBEAgASACEQIACyAAECELCwuaCBgAQYMIC4MB/1VVVf+qqqr///////9VVf+qAAD/qlUA////Vf9V/1X/AKoA/1X///8Aqqr//1X//1VV//+qAKr/AACq/wAAAP9VVVX/qqqq////////VVX/qgAA/6pVAP///1X/Vf9V/wCqAP9V////AKqq//9V//9VVf//qgCq/wAAqv8AAARJALQAQY8JC/0BkEZynMRataALSACAAwgASCXe9iRJzuee55KXnvPek5Lk3vee9yAIKAgiKnAcqIiC9MZ22vde107y3NbO80jzXvLaty7p3uRat06S2v7a1tx2yPeiVlr3nvMk6d621Lb+tlq1pLZO5U7yIome5ABUDgAAiNr3XtdO8tzWzvNI817y2rcu6d7kWrdOktr+2tbcdsj3olZa957zJOnettS2/rZataS2TuUmaiRJrMjABwABUFJFU1MgWCB0byBzdGFydABtb3ZlIHdpdGggYXJyb3cga2V5cwBUcmFpbiBpbnZhZGVycwBHYW1lIG92ZXIAV2F2ZSAAU2NvcmU6IABBkwsLAgZgAEGjCwsCBmAAQbgLCwsK3doACt3aAArd2gBB0wsLEhIhAAAAAAAAARAAAAAAAAASIQBB+AsLDK1ATaCtAA2grQUNoABBkgwLHQEiIhAAAAAAABIhAAAAAAARIiIRAAAFUFUABVBVAEG4DAsM1ERE0NAGANDQYEDQAEHSDAsyEiIiIQAABmAREiERBmAGYBIiIiEGYFRERFBQBQBQAARAAAAJkADURETQ0CIg0NUHB9AAQZENCzMBIiESIhAAEiEiIiIiEiESISIiIiISIVRERFBQAABQAARAAAAIgADQREDQ0iIi0NBAUNAAQdENCzMSIhABIiEAEiIiIRIiIiESIiIhEiIiIQVERQAFAAUAAAVQAAAIgACtBA2grQANoK0GDaAAQZAOCx8BIiIQASIiEBEiIhABIiIRESIiEAEiIhEAVFAAAFBQAEG4DgsLCt3aAArd2gAK3doAQdAOCx4REREAABEREREREQAAERERERERAAAREREABQAAAAUAQZAPCxkJAACQAAmQAAAJAAAAAAAP8AAAAAAAAAVQAEHRDwsZkAkAAJAJAACYkAAAAA/93/AAAAAABVRFUABBkRALGpmZAACZmQAAmJAAAA/93a2v8AAABVRENDVQAEHQEAscCZiJkAmYiZAJmZkAD/3d2tra3/AFVERDQ0NFUABBkBELHJCZmQmQmZkJmQkJkP3d3d3d3d3fVEREREREREUAQdARCxyQkAkJkJAJCZmZmZASIiIiIiIiIRIiIiIiIiIhAEGREgsbCZAAAAmQAJmQmZABFBQXcUFBEAEYGBERgYEQAEHYEgsTkJCQkAAREREREREAABEREREREQBBkBsLAyAOEA== -------------------------------------------------------------------------------- /src/view/src/assets/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/view/src/assets/heart.png -------------------------------------------------------------------------------- /src/view/src/assets/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aporia-ai/TrainInvaders/a67f23d331979aae1e71c91eed3c19241c7e260c/src/view/src/assets/success.png -------------------------------------------------------------------------------- /src/view/src/components/CloseButton.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /src/view/src/components/Leaderboard.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 340 | 341 | 423 | -------------------------------------------------------------------------------- /src/view/src/components/LeaderboardRow.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 40 | 41 | 111 | -------------------------------------------------------------------------------- /src/view/src/components/Spinner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /src/view/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | import './styles.css'; 5 | 6 | Vue.config.productionTip = false; 7 | 8 | new Vue({ 9 | render: (h) => h(App), 10 | }).$mount('#app'); 11 | -------------------------------------------------------------------------------- /src/view/src/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Press Start 2P"; 3 | src: local("Press Start 2P"), 4 | url(./assets/PressStart2P.ttf) format("truetype"); 5 | } 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | padding: 0; 13 | margin: 0; 14 | font-family: "Press Start 2P", cursive; 15 | font-size: 14px; 16 | line-height: 1.6; 17 | } 18 | 19 | button { 20 | all: unset; 21 | cursor: pointer; 22 | } 23 | 24 | input { 25 | all: unset; 26 | border-bottom: solid 1px currentColor; 27 | } 28 | 29 | @keyframes spin { 30 | 0% { 31 | transform: rotate(0deg); 32 | } 33 | 34 | 100% { 35 | transform: rotate(360deg); 36 | } 37 | } 38 | 39 | @keyframes blink { 40 | 50% { 41 | opacity: 0; 42 | } 43 | } -------------------------------------------------------------------------------- /src/view/vue.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); 3 | 4 | const URL_LOADER_LIMIT = 1000000; // 1000 kb 5 | 6 | module.exports = { 7 | lintOnSave: false, 8 | css: { 9 | extract: false, 10 | }, 11 | filenameHashing: false, 12 | chainWebpack: (config) => { 13 | // Show emojies 14 | config.optimization.minimizer("terser").tap(args => { 15 | const options = args[0] 16 | const terserOptions = options.terserOptions 17 | terserOptions.output = terserOptions.output || {} 18 | terserOptions.output.ascii_only = true 19 | return args 20 | }) 21 | 22 | // Images to base64 23 | config.module 24 | .rule('images') 25 | .use('url-loader') 26 | .loader('url-loader') 27 | .tap((options) => { 28 | options.limit = URL_LOADER_LIMIT; 29 | return options; 30 | }); 31 | 32 | // Fonts to base64 33 | config.module 34 | .rule('fonts') 35 | .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i) 36 | .use('url-loader') 37 | .loader('url-loader') 38 | .tap((options) => { 39 | options.limit = URL_LOADER_LIMIT; 40 | return options; 41 | }); 42 | }, 43 | configureWebpack: { 44 | optimization: { 45 | splitChunks: false, 46 | }, 47 | plugins: [ 48 | new HtmlWebpackPlugin({ 49 | title: 'TrainInvaders', 50 | template: 'public/index.html', // this is important - a template file to use for insertion 51 | inlineSource: '.(js|css)$', // embed all javascript and css inline 52 | }), 53 | new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin), 54 | ], 55 | }, 56 | }; 57 | --------------------------------------------------------------------------------