Initial commit with a mostly working engine.

Basic commands are being parsed. I think the only weird part is the
'use' command because it needs to possibly target two things. A tiny
test example is provided in __main__, this will probably be broken out
later.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2021-11-18 11:31:21 -08:00
commit dd2128beb1
15 changed files with 1650 additions and 0 deletions

190
.gitignore vendored Normal file
View File

@@ -0,0 +1,190 @@
# Created by https://www.toptal.com/developers/gitignore/api/vim,python
# Edit at https://www.toptal.com/developers/gitignore?templates=vim,python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# End of https://www.toptal.com/developers/gitignore/api/vim,python
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
!.vscode/*.code-snippets
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode

3
.pylintrc Normal file
View File

@@ -0,0 +1,3 @@
[MASTER]
disable=wildcard-import,
unused-wildcard-import

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.pythonPath": "/home/alek/.local/share/virtualenvs/adventuregame-Kcj1_Ep-/bin/python",
"python.languageServer": "Jedi",
"python.linting.mypyEnabled": true
}

18
Pipfile Normal file
View File

@@ -0,0 +1,18 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
black = "==21.9b0"
mypy = "*"
pylint = "*"
pytest = "*"
[requires]
python_version = "3.9"
[pipenv]
allow_prereleases = true

338
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,338 @@
{
"_meta": {
"hash": {
"sha256": "2782f015f061a958c6a0bbb02c739c5a6967777ccfa578d2a8de9b996dcd0ff4"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {
"astroid": {
"hashes": [
"sha256:11f7356737b624c42e21e71fe85eea6875cb94c03c82ac76bd535a0ff10b0f25",
"sha256:abc423a1e85bc1553954a14f2053473d2b7f8baf32eae62a328be24f436b5107"
],
"markers": "python_version ~= '3.6'",
"version": "==2.8.5"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"black": {
"hashes": [
"sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115",
"sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"
],
"index": "pypi",
"version": "==21.9b0"
},
"click": {
"hashes": [
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.3"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"isort": {
"hashes": [
"sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
"sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.10.1"
},
"lazy-object-proxy": {
"hashes": [
"sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653",
"sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61",
"sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2",
"sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837",
"sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3",
"sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43",
"sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726",
"sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3",
"sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587",
"sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8",
"sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a",
"sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd",
"sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f",
"sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad",
"sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4",
"sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b",
"sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf",
"sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981",
"sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741",
"sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e",
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.6.0"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"mypy": {
"hashes": [
"sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9",
"sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a",
"sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9",
"sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e",
"sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2",
"sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212",
"sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b",
"sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885",
"sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150",
"sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703",
"sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072",
"sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457",
"sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e",
"sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0",
"sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb",
"sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97",
"sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8",
"sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811",
"sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6",
"sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de",
"sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504",
"sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921",
"sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"
],
"index": "pypi",
"version": "==0.910"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966",
"sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"
],
"markers": "python_version >= '3.6'",
"version": "==21.2"
},
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"platformdirs": {
"hashes": [
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
],
"markers": "python_version >= '3.6'",
"version": "==2.4.0"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
"hashes": [
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.11.0"
},
"pylint": {
"hashes": [
"sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
"sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
],
"index": "pypi",
"version": "==2.11.1"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
],
"index": "pypi",
"version": "==6.2.5"
},
"regex": {
"hashes": [
"sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f",
"sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc",
"sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4",
"sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4",
"sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8",
"sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f",
"sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a",
"sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef",
"sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f",
"sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc",
"sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50",
"sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d",
"sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d",
"sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733",
"sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36",
"sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345",
"sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0",
"sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12",
"sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646",
"sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667",
"sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244",
"sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29",
"sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec",
"sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf",
"sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4",
"sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449",
"sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a",
"sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d",
"sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb",
"sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e",
"sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83",
"sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e",
"sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a",
"sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94",
"sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc",
"sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e",
"sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965",
"sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0",
"sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36",
"sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec",
"sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23",
"sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7",
"sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe",
"sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6",
"sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b",
"sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb",
"sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b",
"sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30",
"sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"
],
"version": "==2021.11.10"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"tomli": {
"hashes": [
"sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee",
"sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"
],
"markers": "python_version >= '3.6'",
"version": "==1.2.2"
},
"typing-extensions": {
"hashes": [
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
],
"markers": "python_version < '3.10'",
"version": "==3.10.0.2"
},
"wrapt": {
"hashes": [
"sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179",
"sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096",
"sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374",
"sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df",
"sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185",
"sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785",
"sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7",
"sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909",
"sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918",
"sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33",
"sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068",
"sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829",
"sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af",
"sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79",
"sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce",
"sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc",
"sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36",
"sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade",
"sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca",
"sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32",
"sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125",
"sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e",
"sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709",
"sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f",
"sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b",
"sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb",
"sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb",
"sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489",
"sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640",
"sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb",
"sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851",
"sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d",
"sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44",
"sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13",
"sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2",
"sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb",
"sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b",
"sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9",
"sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755",
"sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c",
"sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a",
"sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf",
"sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3",
"sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229",
"sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e",
"sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de",
"sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554",
"sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10",
"sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80",
"sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056",
"sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.13.3"
}
}
}

0
agame/__init__.py Normal file
View File

115
agame/__main__.py Normal file
View File

@@ -0,0 +1,115 @@
from agame.action import *
from agame.game import *
from agame.item import *
from agame.room import *
from agame.trigger import *
# TODO - take a custom module name that has:
# .database
# .game
# so that you can just RUN it
# Something similar to wsgi using "app" in the passed module
# This is the *game* here
database = Database()
database.add_items(
Item(
id="glowing_rock",
name="glowing rock",
desc="This rock is glowing.",
synonyms=("rock",),
room_desc="You see a ((glowing rock)). You have **got** to have it.",
triggers={
GET: [
PrintAction(
"You try to pick up the rock, but it slips out of your greasy hands.",
"Maybe you should wash your hands, you disgusting little man.",
)
],
LOOK: [PrintAction("Man, that rock looks awesome.")],
},
),
Item(
id="cell_door",
name="door",
room_desc="A ((door)) sits on the far wall.",
triggers={
GET: [PrintAction("The door is pretty attached to its wall.")],
OPEN: [
CheckVarAction(
"cell_door_open",
Compare.EQUALS,
True,
yes=[
PrintAction(
"It's already open. You push on the door even //more//, just in case."
),
SleepAction(1.0),
PrintAction("..."),
SleepAction(1.0),
PrintAction("Yup, still open."),
],
no=[
SetVarAction("cell_door_open", True),
PrintAction("The door swings open, thanks to you."),
],
)
],
CLOSE: [
CheckVarAction(
"cell_door_open",
Compare.EQUALS,
True,
yes=[
PrintAction("You close that door. Nice job."),
SetVarAction("cell_door_open", False),
],
no=[PrintAction("The door is already closed.")],
)
],
LOOK: [
CheckVarAction(
"cell_door_open",
Compare.EQUALS,
True,
yes=[PrintAction("It's a door, wide open, because you opened it.")],
no=[PrintAction("A closed door. You can change this.")],
)
],
},
),
)
database.add_rooms(
Room(
id="start",
name="Test room",
desc="You're in ((Todd's Test Cell)).",
items=[
database.items["glowing_rock"].create_inst(),
database.items["cell_door"].create_inst(),
],
),
)
# Build the game state
game = Game(
database=database,
room=database.rooms["start"],
vars={
"cell_door_open": False,
},
)
game.print_room()
while True:
try:
game.say()
line = input("> ")
game.say()
game.run_command(line)
except (KeyboardInterrupt, EOFError):
game.say()
game.say("Bye.")
break

294
agame/action.py Normal file
View File

@@ -0,0 +1,294 @@
import dataclasses
import time
from typing import Any, Optional, Sequence, Union, TYPE_CHECKING
import enum
if TYPE_CHECKING:
from agame.game import Game
__all__ = (
"Action",
"SleepAction",
"PrintAction",
"TeleportAction",
"GetAction",
"CheckInvItemsAction",
"CheckRoomItemsAction",
"Compare",
"Var",
"CheckVarAction",
"SetVarAction",
)
class Action:
"""
An action that the game engine can take.
This is the base class. It can be instantiated as a "dummy" action if
needed.
"""
def act(self, game: "Game"):
"""
Complete this action.
"""
@dataclasses.dataclass
class SleepAction(Action):
"""
An action that delays the amount of time, in seconds. Decimal values are
allowed.
"""
secs: float
def act(self, game: "Game"):
time.sleep(self.secs)
@dataclasses.dataclass
class PrintAction(Action):
"""
Prints a message to the screen.
"""
lines: Sequence[str]
def __init__(self, *lines: str):
self.lines = lines
def act(self, game: "Game"):
if not self.lines:
return
line = self.lines[0]
game.say(line)
for line in self.lines[1:]:
game.say()
game.say(line)
@dataclasses.dataclass
class TeleportAction(Action):
"""
Moves the player to another room.
"""
room_id: str
def act(self, game: "Game"):
# TODO
raise NotImplementedError("TODO - implement teleport action")
@dataclasses.dataclass
class GetAction(Action):
"""
Removes an item from the current room and puts it in the player's inventory.
"""
item_id: str
pickup_text: Optional[str] = None
# def __init__(self, item_id: str, pickup_text: )
def act(self, game: "Game"):
# Find the first instance of the item in the room and remove it
item = game.room.remove(self.item_id)
assert (
item is not None
), f"attempted to remove an item (id: {self.item_id}) that does not exist in the current room; this is a game logic error/bug"
if item.id in game.inventory:
# Item in inventory already? Update count
game.inventory[item.id].count += item.count
else:
# Otherwise just add it
game.inventory[item.id] = item
# Print text
if self.pickup_text is None:
game.say(f"You pick up (({item.name})).")
else:
game.say(self.pickup_text)
@dataclasses.dataclass
class CheckInvItemsAction(Action):
"""
Checks if all supplied items are present in the inventory, and executes the
appropriate action sequence.
"""
item_ids: Union[str, Sequence[str]]
yes: Sequence[Action]
no: Sequence[Action]
def act(self, game: "Game"):
# If the item_ids are just a string, use that as a list.
items = [self.item_ids] if isinstance(self.item_ids, str) else self.item_ids
for item_id in items:
if item_id not in game.inventory:
game.do_actions(self.no)
return
game.do_actions(self.yes)
@dataclasses.dataclass
class CheckRoomItemsAction(Action):
"""
Checks if all supplied items are present in the current room, and executes the
appropriate action sequence.
"""
item_ids: str
yes: Sequence[Action]
no: Sequence[Action]
def act(self, game: "Game"):
items = [self.item_ids] if isinstance(self.item_ids, str) else self.item_ids
for item_id in items:
if item_id not in game.room.items:
game.do_actions(self.no)
return
game.do_actions(self.yes)
class Compare(enum.Enum):
"""
A comparison for a value.
"""
# Does what it says on the tin.
#
# This is type-sensitive and literally just does `==` in Python. Seriously.
EQUALS = enum.auto()
# Also does what it says on the tin.
#
# This is type-sensitive and literally just does `!=` in Python. Seriously.
NOT_EQUALS = enum.auto()
# Checks if a value is less than to another value.
#
# This uses the `cmp` to check the values.
LESS_THAN = enum.auto()
# Checks if a value is less than or equal to another value.
#
# This uses the `cmp` to check the values.
LESS_THAN_EQUALS = enum.auto()
# Checks if a value is greater than to another value.
#
# This uses the `cmp` to check the values.
GREATER_THAN = enum.auto()
# Checks if a value is greater than or equal to another value.
#
# This uses the `cmp` to check the values.
GREATER_THAN_EQUALS = enum.auto()
# Checks if the string-ified version of the value matches the other value,
# as a regex.
MATCHES = enum.auto()
@dataclasses.dataclass
class Var:
id: str
@dataclasses.dataclass
class CheckVarAction(Action):
"""
Check a variable's value using some kind of comparison.
If you want to check one variable against another, use the `action.Var`
type.
"""
# The variable to check the value of.
var_id: str
# The comparison operation to use. See `Compare` for comparisons.
compare: Compare
# The constant value to check against.
against: Any
# The action to execute when this is true.
yes: Sequence[Action]
# The action to execute when this is false.
no: Sequence[Action]
def act(self, game: "Game"):
val = game.vars.get(self.var_id, None)
compare_val = None
if isinstance(self.against, Var):
compare_val = game.vars.get(self.against.id, None)
else:
compare_val = self.against
result = False
if self.compare == Compare.EQUALS:
result = val == compare_val
elif self.compare == Compare.NOT_EQUALS:
result = val != compare_val
elif self.compare == Compare.LESS_THAN:
if isinstance(val, str) or isinstance(compare_val, str):
result = str(val) < str(compare_val)
elif (isinstance(val, int) or isinstance(val, float)) and (
isinstance(compare_val, int) or isinstance(compare_val, float)
):
result = val < compare_val
elif self.compare == Compare.LESS_THAN_EQUALS:
if isinstance(val, str) or isinstance(compare_val, str):
result = str(val) <= str(compare_val)
elif (isinstance(val, int) or isinstance(val, float)) and (
isinstance(compare_val, int) or isinstance(compare_val, float)
):
result = val <= compare_val
elif self.compare == Compare.GREATER_THAN:
if isinstance(val, str) or isinstance(compare_val, str):
result = str(val) > str(compare_val)
elif (isinstance(val, int) or isinstance(val, float)) and (
isinstance(compare_val, int) or isinstance(compare_val, float)
):
result = val > compare_val
elif self.compare == Compare.GREATER_THAN_EQUALS:
if isinstance(val, str) or isinstance(compare_val, str):
result = str(val) >= str(compare_val)
elif (isinstance(val, int) or isinstance(val, float)) and (
isinstance(compare_val, int) or isinstance(compare_val, float)
):
result = val >= compare_val
elif self.compare == Compare.MATCHES:
pass
else:
assert False, f"{self.compare} isn't a Compare value, ya doink"
# Check result, do action
if result:
game.do_actions(self.yes)
else:
game.do_actions(self.no)
@dataclasses.dataclass
class SetVarAction(Action):
"""
Set a variable to a specific value.
"""
var_id: str
value: Any
def act(self, game: "Game"):
value = (
game.vars.get(self.value.id) if isinstance(self.value, Var) else self.value
)
game.vars[self.var_id] = value

39
agame/color.py Normal file
View File

@@ -0,0 +1,39 @@
import re
__all__ = ("colorize",)
BOLD_PAT = re.compile(r"\*\*(.+?)\*\*", re.MULTILINE)
ITALIC_PAT = re.compile(r"//(.+?)//", re.MULTILINE)
INTEREST_PAT = re.compile(r"\(\((.+?)\)\)", re.MULTILINE)
SHADOW_PAT = re.compile(r"\{\{(.+?)\}\}", re.MULTILINE)
BOLD_COL = "\u001b[1m"
ITALIC_COL = "\u001b[3m"
INTEREST_COL = "\u001b[34;1m"
SHADOW_COL = "\u001b[30;1m"
RESET_COL = "\u001b[0m"
def colorize(text: str) -> str:
"""
Colorizes text for output on an ANSI terminal.
This will use escape codes to replace things.
Style guide:
((This)) is "interest" styling. This will make the text blue.
{{This}} is "shadow" styling. This will make the text a dark grey (or at
least, more subtle.)
"""
replacements = [
(INTEREST_PAT, INTEREST_COL),
(SHADOW_PAT, SHADOW_COL),
(BOLD_PAT, BOLD_COL),
(ITALIC_PAT, ITALIC_COL),
]
for (pat, col) in replacements:
text = pat.sub(col + r"\1" + RESET_COL, text)
return text

113
agame/game.py Normal file
View File

@@ -0,0 +1,113 @@
import dataclasses
import textwrap
from typing import Any, MutableMapping, Match, Optional, Sequence
from agame.action import Action
from agame.color import colorize
from agame.item import Item, ItemInst
from agame.room import Room
from agame.trigger import *
__all__ = (
"Database",
"Game",
)
@dataclasses.dataclass
class Database:
# All items available to the game.
items: MutableMapping[str, Item] = dataclasses.field(default_factory=dict)
# All rooms available to the game.
rooms: MutableMapping[str, Room] = dataclasses.field(default_factory=dict)
def add_item(self, item: Item):
self.items[item.id] = item
def add_items(self, *items: Item):
for item in items:
self.add_item(item)
def add_room(self, room: Room):
self.rooms[room.id] = room
def add_rooms(self, *rooms: Room):
for room in rooms:
self.add_room(room)
@dataclasses.dataclass
class Game:
# Game room/items database
database: Database
# Current room.
room: Room
# Player inventory.
inventory: MutableMapping[str, ItemInst] = dataclasses.field(default_factory=dict)
# Variables.
vars: MutableMapping[str, Any] = dataclasses.field(default_factory=dict)
def run_command(self, line: str):
line = line.strip()
if not line:
return
triggers: Sequence[Trigger] = [
GetTrigger(),
UseTrigger(),
PutTrigger(),
LookTrigger(),
OpenTrigger(),
CloseTrigger(),
GoTrigger(),
]
trigger = None
match: Optional[Match] = None
for t in triggers:
match = t.pattern().fullmatch(line)
if match:
trigger = t
break
if not trigger:
self.say("I'm not sure what you mean.")
return
assert match, "why were no patterns matched?"
trigger.trigger(self, match)
def do_actions(self, actions: Sequence[Action]):
"Executes the supplied actions."
for action in actions:
action.act(self)
def print_room(self):
"Prints this room's description."
self.say(self.room.name)
self.say()
self.say(self.room.desc)
# Look at revealed text
for item in self.room.items.values():
if not item.revealed or item.room_desc is None:
continue
if item.room_desc == "":
# TODO - pluralization, 'a' vs 'an'
self.say(f"You see a (({item.name})).")
else:
self.say(item.room_desc)
def say(self, message: Optional[str] = None):
"Format, colorize, wrap, and print the message."
message = message or ""
message = textwrap.fill(message)
print(colorize(message))
@property
def rooms(self):
"Shortcut property for `game.database.rooms`."
return self.database.rooms
@property
def items(self):
"Shortcut property for `game.database.items`."
return self.database.items

131
agame/item.py Normal file
View File

@@ -0,0 +1,131 @@
import dataclasses
from typing import Mapping, Optional, Sequence
from agame.action import Action
__all__ = (
"ItemInst",
"Item",
)
@dataclasses.dataclass
class ItemInst:
"""
An instance of an item in the game.
"""
# Reference to the global item that this is an instance of.
item: "Item"
# Gets whether this item can be taken.
fixed: bool = False
# Gets how many of this item instance are present in this stack.
count: int = 1
# Gets whether this item is revealed or not.
revealed: bool = True
@property
def id(self) -> str:
return self.item.id
@property
def name(self) -> str:
return self.item.name
@property
def desc(self) -> Optional[str]:
return self.item.desc
@property
def synonyms(self) -> Sequence[str]:
return self.item.synonyms
@property
def room_desc(self) -> Optional[str]:
return self.item.room_desc
@property
def triggers(self) -> Mapping[str, Sequence[Action]]:
return self.item.triggers
@property
def use_actions(self) -> Mapping[str, Sequence[Action]]:
return self.item.use_actions
@dataclasses.dataclass
class Item:
"""
A game item.
"""
# The ID of this item. This is how items are looked up in the game.
id: str
# The printable name of this item.
name: str
# A long description for this item.
desc: Optional[str] = None
# A list of all synonyms for this item.
synonyms: Sequence[str] = dataclasses.field(default_factory=list)
# The description that is used in the context of a room's `look` command.
#
# When someone wants to look at the entire room, all items that have been
# revealed will also be displayed.
#
# If you want to disable this behavior entirely, `room_desc` should be set
# to `None`.
#
# If you want to use the default text, "You see a (({item.name}))",
# `room_desc` should be set to the blank string, `""`.
#
# Otherwise, the `room_desc` string will be colorized and printed as
# written.
room_desc: Optional[str] = None
# A list of triggers that a game may use. Since this is just a mapping of
# strings to action sequences, only one set of actions is allowed per
# trigger.
#
# Valid triggers include:
# * get
# * use
# * put
# * look
# * open
# * close
# ...more to come
triggers: Mapping[str, Sequence[Action]] = dataclasses.field(default_factory=dict)
# A mapping of other items that this item may be used with, specifically on
# the USE command.
#
# USE is a strange beast, because instead of just one implicit target (which
# is verified to exist by the trigger, of all things) we have *two* targets:
# the subject and the direct object. And not always!
#
# For example, we have an item, paintbrush. These are some options:
# use paintbrush # <- on what?
# use paintbrush on canvas # <- you paint a beautiful masterpiece.
# use paintbrush on car # <- that's not allowed (custom text)
# use paintbrush on fake item # <- that item doesn't exist
#
# We can't really represent this with the current str -> sequence[action]
# stuff we have in place right now, so a special field will be good enough
# until a more insane/robust solution is implemented.
use_actions: Mapping[str, Sequence[Action]] = dataclasses.field(
default_factory=dict
)
def create_inst(self, *args, **kwargs):
"""
Creates a new item instance, passing the supplied args and kwargs to the
ItemInst constructor.
"""
return ItemInst(item=self, *args, **kwargs)

46
agame/room.py Normal file
View File

@@ -0,0 +1,46 @@
import dataclasses
from typing import MutableMapping, Optional, Sequence, Union, TYPE_CHECKING
from agame.item import ItemInst
from agame.util import search_item_name
__all__ = ("Room",)
@dataclasses.dataclass
class Room:
id: str
name: str
desc: str
items: MutableMapping[str, ItemInst]
def __init__(
self,
id: str,
name: str,
desc: str,
items: Union[Sequence[ItemInst], MutableMapping[str, ItemInst]],
):
self.id = id
self.name = name
self.desc = desc
if isinstance(items, MutableMapping):
self.items = items
else:
self.items = {item.id: item for item in items}
def search_item_name(self, item_name: str) -> Optional[ItemInst]:
"""
Searches all item instances in the room for the given item name, also
checking synonyms. Returns the first item instance found, or none if no
synonyms or names were found to match.
"""
return search_item_name(self.items.values(), item_name)
def remove(self, item_id: str) -> Optional[ItemInst]:
"""
Removes an item with the given ID from the room, returning it.
If it's not present in the room, `None` is returned.
"""
return self.items.pop(item_id, None)

247
agame/trigger.py Normal file
View File

@@ -0,0 +1,247 @@
import abc
import re
from typing import Match, Pattern, TYPE_CHECKING
from agame.util import search_item_name
if TYPE_CHECKING:
from agame.game import Game
# Triggers:
# get/take/grab/pick up [a[n]/the] x
# use [a[n]/the] x [with/on [a[n]/the] y]
# put [a[n]/the] x on/in [a[n]/the] y
# look [[at] [a[n]/the] x]
# open x
# close x
# go/(go to)/leave/exit x
# give [a[n]/the] x to [a[n]/the] y
# push [a[n]/the] x
# pull [a[n]/the] x
# (put down)/drop [a[n]/the] x
__all__ = (
"Trigger",
"GetTrigger",
"UseTrigger",
"PutTrigger",
"LookTrigger",
"OpenTrigger",
"CloseTrigger",
"GoTrigger",
"GET",
"USE",
"PUT",
"LOOK",
"OPEN",
"CLOSE",
"GO",
)
GET = "get"
USE = "use"
PUT = "put"
LOOK = "look"
OPEN = "open"
CLOSE = "close"
GO = "go"
class Trigger(metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def pattern() -> Pattern:
pass
@abc.abstractmethod
def trigger(self, game: "Game", match: Match):
pass
class GetTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>get|take|grab|pick[ ]*up)
(([ ]+(an?|the))?[ ]+(?P<item>.+))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
otrigger = match["trigger"].lower().capitalize()
game.say(f"{otrigger} what?")
return
item = game.room.search_item_name(item_name.lower())
if item and GET in item.triggers:
actions = item.triggers[GET]
# if there are any actions, do them. else do the default action
game.do_actions(actions)
else:
game.say("Can't get that.")
class UseTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
# TODO(low) - wouldn't it be cool to specify "use" actions?
# e.g. you have a gun item and you want to be allowed to use "shoot" in order to
# use the gun.
return re.compile(
r"""
(?P<trigger>use)
(([ ]+(an?|the))?[ ]+(?P<item>.+?)
([ ]+(with|on)([ ]+(an?|the))?[ ]+(?P<target>.+))?)?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Use what?")
return
target_name = match["target"]
# Get the item from inventory or room
item = game.room.search_item_name(item_name) or search_item_name(
game.inventory.values(), item_name.lower()
)
if not item:
game.say(f"I'm not sure what you mean by {item_name}.")
return
target = None
if target_name:
# Get the target from inventory or room
target = game.room.search_item_name(target_name) or search_item_name(
game.inventory.values(), target_name.lower()
)
if not target:
game.say(f"I'm not sure what you mean by {target_name}.")
# Check if the target can be used on something
elif target.id in item.use_actions:
game.do_actions(item.use_actions[target.id])
else:
game.say("I'm not sure how to do that.")
elif USE in item.triggers:
# Check if the item can be used by itself
game.do_actions(item.triggers[USE])
elif item.use_actions:
# This item can be used with *something*, but we don't know what.
game.say(f"Use (({item_name})) with what?")
else:
# This can't be used.
game.say("I can't really use that.")
class PutTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>put)
([ ]+((an?|the)[ ]+)?(?P<item>.+?)
((on|in)[ ]+)?((an?|the)[ ]+)?[ ]+(?P<target>.+))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
class LookTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>look)
([ ]+(at[ ]+)?((an?|the)[ ]+)?(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.print_room()
return
item = game.room.search_item_name(item_name.lower())
if item and LOOK in item.triggers:
actions = item.triggers[LOOK]
game.do_actions(actions)
else:
game.say("Can't see that.")
class OpenTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>open)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Open what?")
return
item = game.room.search_item_name(item_name.lower())
if item and OPEN in item.triggers:
actions = item.triggers[OPEN]
game.do_actions(actions)
else:
game.say("Can't open that.")
class CloseTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>close)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Close what?")
return
item = game.room.search_item_name(item_name.lower())
if item and CLOSE in item.triggers:
actions = item.triggers[CLOSE]
game.do_actions(actions)
else:
game.say("Can't close that.")
class GoTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>go|go[ ]*to|leave|exit)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
otrigger = match["trigger"].lower().capitalize()
game.say(f"{otrigger} where?")
return
item = game.room.search_item_name(item_name.lower())
if item and GO in item.triggers:
actions = item.triggers[GO]
game.do_actions(actions)

11
agame/util.py Normal file
View File

@@ -0,0 +1,11 @@
from typing import Iterable, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from agame.item import ItemInst
def search_item_name(seq: Iterable["ItemInst"], item_name: str) -> Optional["ItemInst"]:
for item in seq:
if item.name == item_name or item_name in item.synonyms:
return item
return None

View File

@@ -0,0 +1,98 @@
from agame.trigger import *
def test_get_item():
cases = [
("get item", "item"),
("take item", "item"),
("grab item", "item"),
("pick up item", "item"),
("pickup item", "item"),
("get the item", "item"),
("take the item", "item"),
("grab the item", "item"),
("pick up the item", "item"),
("pickup the item", "item"),
("get a item", "item"),
("take a item", "item"),
("grab a item", "item"),
("pick up a item", "item"),
("pickup a item", "item"),
("get an item", "item"),
("take an item", "item"),
("grab an item", "item"),
("pick up an item", "item"),
("pickup an item", "item"),
("get item", "item"),
("take item", "item"),
("grab item", "item"),
("pick up item", "item"),
("pickup item", "item"),
("get the item", "item"),
("take the item", "item"),
("grab the item", "item"),
("pick up the item", "item"),
("pickup the item", "item"),
("get a item", "item"),
("take a item", "item"),
("grab a item", "item"),
("pick up a item", "item"),
("pickup a item", "item"),
("get an item", "item"),
("take an item", "item"),
("grab an item", "item"),
("pick up an item", "item"),
("pickup an item", "item"),
]
pat = GetTrigger.pattern()
for (line, expected) in cases:
line = line.strip()
match = pat.fullmatch(line)
assert match is not None
assert match["item"] == expected
def test_use_item():
cases = [
("use item", "item"),
("use the item", "item"),
("use a item", "item"),
("use an item", "item"),
("use item", "item"),
("use the item", "item"),
("use a item", "item"),
("use an item", "item"),
("use an item", "item"),
]
pat = UseTrigger.pattern()
for (line, expected) in cases:
line = line.strip()
match = pat.fullmatch(line)
assert match is not None
assert match["item"] == expected
def test_use_item_with():
cases = [
("use item with other item", "item", "other item"),
("use the item with an other item", "item", "other item"),
("use a item with a other item", "item", "other item"),
("use an item with the other item", "item", "other item"),
("use item with other item", "item", "other item"),
("use the item with an other item", "item", "other item"),
("use a item with a other item", "item", "other item"),
("use an item with the other item", "item", "other item"),
]
pat = UseTrigger.pattern()
for (line, item, other_item) in cases:
match = pat.fullmatch(line)
assert match is not None
assert match["item"] == item
assert match["target"] == other_item
# TODO : put
# TODO : look