Refactor Camera and Entity classes for improved type hinting and documentation using typing and pydantic.
All checks were successful
Build Simulation and Test / Run All Tests (push) Successful in 34s

This commit is contained in:
Sam 2025-06-03 19:47:39 -05:00
parent 9e3c34fd3e
commit 3a4301f7cb
6 changed files with 479 additions and 159 deletions

11
main.py
View File

@ -141,8 +141,8 @@ def main():
# Initialize world # Initialize world
world = World() world = World()
world.add_object(DebugRenderObject(Position(0, 0))) world.add_object(DebugRenderObject(Position(x=0, y=0)))
world.add_object(DebugRenderObject(Position(20, 0))) world.add_object(DebugRenderObject(Position(x=20, y=0)))
# sets seed to 67 >_< # sets seed to 67 >_<
random.seed(67) random.seed(67)
@ -229,22 +229,19 @@ def main():
last_tick_time += tick_interval last_tick_time += tick_interval
tick_counter += 1 tick_counter += 1
total_ticks += 1 total_ticks += 1
# Add your tick-specific logic here
# gets every object in the world and returns amount of FoodObjects # gets every object in the world and returns amount of FoodObjects
objects = world.get_objects() objects = world.get_objects()
print(objects)
food = len([obj for obj in objects if isinstance(obj, FoodObject)]) food = len([obj for obj in objects if isinstance(obj, FoodObject)])
print(f"Food count: {food}")
if food < 10 and FOOD_SPAWNING == True: if food < 10 and FOOD_SPAWNING == True:
world.add_object(FoodObject(Position(random.randint(-200, 200), random.randint(-200, 200)))) world.add_object(FoodObject(Position(x=random.randint(-200, 200), y=random.randint(-200, 200))))
# ensure selected objects are still valid or have not changed position, if so, reselect them # ensure selected objects are still valid or have not changed position, if so, reselect them
selected_objects = [ selected_objects = [
obj for obj in selected_objects if obj in world.get_objects() obj for obj in selected_objects if obj in world.get_objects()
] ]
print("Tick logic executed")
world.tick_all() world.tick_all()
# Calculate TPS every second # Calculate TPS every second

View File

@ -5,6 +5,7 @@ description = "Add your description here"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"pre-commit>=4.2.0", "pre-commit>=4.2.0",
"pydantic>=2.11.5",
"pygame>=2.6.1", "pygame>=2.6.1",
"pytest>=8.3.5", "pytest>=8.3.5",
] ]

112
uv.lock generated
View File

@ -2,6 +2,15 @@ version = 1
revision = 2 revision = 2
requires-python = ">=3.11" requires-python = ">=3.11"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]] [[package]]
name = "cfgv" name = "cfgv"
version = "3.4.0" version = "3.4.0"
@ -35,6 +44,7 @@ version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "pydantic" },
{ name = "pygame" }, { name = "pygame" },
{ name = "pytest" }, { name = "pytest" },
] ]
@ -47,6 +57,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "pre-commit", specifier = ">=4.2.0" }, { name = "pre-commit", specifier = ">=4.2.0" },
{ name = "pydantic", specifier = ">=2.11.5" },
{ name = "pygame", specifier = ">=2.6.1" }, { name = "pygame", specifier = ">=2.6.1" },
{ name = "pytest", specifier = ">=8.3.5" }, { name = "pytest", specifier = ">=8.3.5" },
] ]
@ -133,6 +144,86 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
] ]
[[package]]
name = "pydantic"
version = "2.11.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
]
[[package]] [[package]]
name = "pygame" name = "pygame"
version = "2.6.1" version = "2.6.1"
@ -237,6 +328,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" }, { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" },
] ]
[[package]]
name = "typing-extensions"
version = "4.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.31.2" version = "20.31.2"

View File

@ -1,58 +1,108 @@
import random
from world.world import Position, BaseEntity from world.world import Position, BaseEntity
import pygame import pygame
from typing import Optional, List, Any
# returns desired yellow value for food decay
def food_decay_yellow(decay):
if decay < 128:
return decay
else:
return 255 - decay
class DebugRenderObject(BaseEntity): class DebugRenderObject(BaseEntity):
def __init__(self, position: Position, radius=5): """
super().__init__(position) Debug object that renders as a circle and counts its neighbors.
"""
self.neighbors = 0 def __init__(self, position: Position, radius: int = 5) -> None:
self.radius = radius """
self.max_visual_width = radius * 2 Initializes the debug render object.
self.interaction_radius = 50
self.flags = { :param position: The position of the object.
:param radius: The radius of the rendered circle.
"""
super().__init__(position)
self.neighbors: int = 0
self.radius: int = radius
self.max_visual_width: int = radius * 2
self.interaction_radius: int = 50
self.flags: dict[str, bool] = {
"death": False, "death": False,
"can_interact": True, "can_interact": True,
} }
def tick(self, interactable=None): def tick(self, interactable: Optional[List[BaseEntity]] = None) -> "DebugRenderObject":
"""
Updates the object, counting the number of interactable neighbors.
:param interactable: List of nearby entities.
:return: Self.
"""
if interactable is None: if interactable is None:
interactable = [] interactable = []
self.neighbors = len(interactable) self.neighbors = len(interactable)
return self return self
def render(self, camera, screen): def render(self, camera: Any, screen: Any) -> None:
"""
Renders the debug object as a circle, color intensity based on neighbors.
:param camera: The camera object for coordinate transformation.
:param screen: The Pygame screen surface.
"""
if camera.is_in_view(*self.position.get_position()): if camera.is_in_view(*self.position.get_position()):
pygame.draw.circle( pygame.draw.circle(
screen, screen,
(50, 50, min([255, (self.neighbors + 4) * 30])), (50, 50, min([255, (self.neighbors + 4) * 30])),
camera.world_to_screen(*self.position.get_position()), camera.world_to_screen(*self.position.get_position()),
self.radius * camera.zoom, int(self.radius * camera.zoom),
) )
def __repr__(self): def __repr__(self) -> str:
"""
Returns a string representation of the object.
:return: String representation.
"""
return f"DebugRenderObject({self.position}, neighbors={self.neighbors})" return f"DebugRenderObject({self.position}, neighbors={self.neighbors})"
class FoodObject(BaseEntity):
def __init__(self, position: Position):
super().__init__(position)
self.max_visual_width = 10 def food_decay_yellow(decay: int) -> int:
self.decay = 0 """
self.interaction_radius = 50 Returns the yellow color component for food decay visualization.
self.flags = {
:param decay: The current decay value (0-255).
:return: The yellow component (0-255).
"""
if decay < 128:
return decay
else:
return 255 - decay
class FoodObject(BaseEntity):
"""
Food object that decays over time and is rendered as a colored circle.
"""
def __init__(self, position: Position) -> None:
"""
Initializes the food object.
:param position: The position of the food.
"""
super().__init__(position)
self.max_visual_width: int = 10
self.decay: int = 0
self.interaction_radius: int = 50
self.flags: dict[str, bool] = {
"death": False, "death": False,
"can_interact": True, "can_interact": True,
} }
def tick(self, interactable=None): def tick(self, interactable: Optional[List[BaseEntity]] = None) -> Optional["FoodObject"]:
"""
Updates the food object, increasing decay and flagging for death if decayed.
:param interactable: List of nearby entities (unused).
:return: Self
"""
if interactable is None: if interactable is None:
interactable = [] interactable = []
@ -64,14 +114,25 @@ class FoodObject(BaseEntity):
return self return self
def render(self, camera, screen): def render(self, camera: Any, screen: Any) -> None:
"""
Renders the food object as a decaying colored circle.
:param camera: The camera object for coordinate transformation.
:param screen: The Pygame screen surface.
"""
if camera.is_in_view(*self.position.get_position()): if camera.is_in_view(*self.position.get_position()):
pygame.draw.circle( pygame.draw.circle(
screen, screen,
(255-self.decay,food_decay_yellow(self.decay),0), (255 - self.decay, food_decay_yellow(self.decay), 0),
camera.world_to_screen(*self.position.get_position()), camera.world_to_screen(*self.position.get_position()),
5 * camera.zoom int(5 * camera.zoom)
) )
def __repr__(self): def __repr__(self) -> str:
"""
Returns a string representation of the food object.
:return: String representation.
"""
return f"FoodObject({self.position}, decay={self.decay})" return f"FoodObject({self.position}, decay={self.decay})"

View File

@ -1,27 +1,47 @@
import pygame import pygame
from typing import Optional, Tuple, Sequence
class Camera: class Camera:
def __init__(self, screen_width, screen_height, render_buffer=50): """
self.x = 0 Camera class for handling world-to-screen transformations, panning, and zooming in a 2D simulation.
self.y = 0 """
self.target_x = 0
self.target_y = 0
self.zoom = 1.0
self.target_zoom = 1.0
self.smoothing = 0.15 # Higher = more responsive, lower = more smooth
self.speed = 700
self.zoom_smoothing = 0.2 # Higher = more responsive, lower = more smooth
self.is_panning = False
self.last_mouse_pos = None
self.screen_width = screen_width
self.screen_height = screen_height
self.render_buffer = (
render_buffer # Buffer for rendering objects outside the screen
)
def update(self, keys, deltatime): def __init__(
# Determine movement direction self,
screen_width: int,
screen_height: int,
render_buffer: int = 50,
) -> None:
"""
Initializes the Camera.
:param screen_width: Width of the screen in pixels.
:param screen_height: Height of the screen in pixels.
:param render_buffer: Buffer for rendering objects outside the screen.
"""
self.x: float = 0
self.y: float = 0
self.target_x: float = 0
self.target_y: float = 0
self.zoom: float = 1.0
self.target_zoom: float = 1.0
self.smoothing: float = 0.15 # Higher = more responsive, lower = more smooth
self.speed: float = 700
self.zoom_smoothing: float = 0.2 # Higher = more responsive, lower = more smooth
self.is_panning: bool = False
self.last_mouse_pos: Optional[Sequence[int]] = None
self.screen_width: int = screen_width
self.screen_height: int = screen_height
self.render_buffer: int = render_buffer
def update(self, keys: Sequence[bool], deltatime: float) -> None:
"""
Updates the camera position and zoom based on input and time.
:param keys: Sequence of boolean values representing pressed keys.
:param deltatime: Time elapsed since last update (in seconds).
"""
dx = 0 dx = 0
dy = 0 dy = 0
if keys[pygame.K_w]: if keys[pygame.K_w]:
@ -33,13 +53,11 @@ class Camera:
if keys[pygame.K_d]: if keys[pygame.K_d]:
dx += 1 dx += 1
# Normalize direction
length = (dx ** 2 + dy ** 2) ** 0.5 length = (dx ** 2 + dy ** 2) ** 0.5
if length > 0: if length > 0:
dx /= length dx /= length
dy /= length dy /= length
# Apply movement
self.target_x += dx * self.speed * deltatime / self.zoom self.target_x += dx * self.speed * deltatime / self.zoom
self.target_y += dy * self.speed * deltatime / self.zoom self.target_y += dy * self.speed * deltatime / self.zoom
@ -47,70 +65,91 @@ class Camera:
self.target_x = 0 self.target_x = 0
self.target_y = 0 self.target_y = 0
# Smooth camera movement with drift
smoothing_factor = 1 - pow(1 - self.smoothing, deltatime * 60) smoothing_factor = 1 - pow(1 - self.smoothing, deltatime * 60)
self.x += (self.target_x - self.x) * smoothing_factor self.x += (self.target_x - self.x) * smoothing_factor
self.y += (self.target_y - self.y) * smoothing_factor self.y += (self.target_y - self.y) * smoothing_factor
# Snap to target if within threshold
threshold = 0.5 threshold = 0.5
if abs(self.x - self.target_x) < threshold: if abs(self.x - self.target_x) < threshold:
self.x = self.target_x self.x = self.target_x
if abs(self.y - self.target_y) < threshold: if abs(self.y - self.target_y) < threshold:
self.y = self.target_y self.y = self.target_y
# Smooth zoom
zoom_smoothing_factor = 1 - pow(1 - self.zoom_smoothing, deltatime * 60) zoom_smoothing_factor = 1 - pow(1 - self.zoom_smoothing, deltatime * 60)
self.zoom += (self.target_zoom - self.zoom) * zoom_smoothing_factor self.zoom += (self.target_zoom - self.zoom) * zoom_smoothing_factor
# Snap zoom to target if within threshold
zoom_threshold = 0.001 zoom_threshold = 0.001
if abs(self.zoom - self.target_zoom) < zoom_threshold: if abs(self.zoom - self.target_zoom) < zoom_threshold:
self.zoom = self.target_zoom self.zoom = self.target_zoom
def handle_zoom(self, zoom_delta): def handle_zoom(self, zoom_delta: int) -> None:
# Zoom in/out with mouse wheel """
Adjusts the camera zoom level based on mouse wheel input.
:param zoom_delta: The amount of zoom change (positive for zoom in, negative for zoom out).
"""
zoom_factor = 1.1 zoom_factor = 1.1
if zoom_delta > 0: # Zoom in if zoom_delta > 0:
self.target_zoom *= zoom_factor self.target_zoom *= zoom_factor
elif zoom_delta < 0: # Zoom out elif zoom_delta < 0:
self.target_zoom /= zoom_factor self.target_zoom /= zoom_factor
# Clamp zoom levels
self.target_zoom = max(0.1, min(5.0, self.target_zoom)) self.target_zoom = max(0.1, min(5.0, self.target_zoom))
def start_panning(self, mouse_pos): def start_panning(self, mouse_pos: Sequence[int]) -> None:
"""
Begins panning the camera.
:param mouse_pos: The current mouse position as a sequence (x, y).
"""
self.is_panning = True self.is_panning = True
self.last_mouse_pos = mouse_pos self.last_mouse_pos = mouse_pos
def stop_panning(self): def stop_panning(self) -> None:
"""
Stops panning the camera.
"""
self.is_panning = False self.is_panning = False
self.last_mouse_pos = None self.last_mouse_pos = None
def pan(self, mouse_pos): def pan(self, mouse_pos: Sequence[int]) -> None:
"""
Pans the camera based on mouse movement.
:param mouse_pos: The current mouse position as a sequence (x, y).
"""
if self.is_panning and self.last_mouse_pos: if self.is_panning and self.last_mouse_pos:
dx = mouse_pos[0] - self.last_mouse_pos[0] dx = mouse_pos[0] - self.last_mouse_pos[0]
dy = mouse_pos[1] - self.last_mouse_pos[1] dy = mouse_pos[1] - self.last_mouse_pos[1]
self.x -= dx / self.zoom self.x -= dx / self.zoom
self.y -= dy / self.zoom self.y -= dy / self.zoom
self.target_x = self.x # Sync target position with actual position self.target_x = self.x
self.target_y = self.y self.target_y = self.y
self.last_mouse_pos = mouse_pos self.last_mouse_pos = mouse_pos
def get_real_coordinates(self, screen_x, screen_y): def get_real_coordinates(self, screen_x: int, screen_y: int) -> Tuple[float, float]:
# Convert screen coordinates to world coordinates """
Converts screen coordinates to world coordinates.
:param screen_x: X coordinate on the screen.
:param screen_y: Y coordinate on the screen.
:return: Tuple of (world_x, world_y).
"""
world_x = (screen_x - self.screen_width // 2 + self.x * self.zoom) / self.zoom world_x = (screen_x - self.screen_width // 2 + self.x * self.zoom) / self.zoom
world_y = (screen_y - self.screen_height // 2 + self.y * self.zoom) / self.zoom world_y = (screen_y - self.screen_height // 2 + self.y * self.zoom) / self.zoom
return world_x, world_y return world_x, world_y
def is_in_view(self, obj_x, obj_y, margin=0): def is_in_view(self, obj_x: float, obj_y: float, margin: float = 0) -> bool:
half_w = (self.screen_width + (self.render_buffer * self.zoom)) / ( """
2 * self.zoom Checks if a world coordinate is within the camera's view.
)
half_h = (self.screen_height + (self.render_buffer * self.zoom)) / ( :param obj_x: X coordinate in world space.
2 * self.zoom :param obj_y: Y coordinate in world space.
) :param margin: Additional margin to expand the view area.
:return: True if the object is in view, False otherwise.
"""
half_w = (self.screen_width + (self.render_buffer * self.zoom)) / (2 * self.zoom)
half_h = (self.screen_height + (self.render_buffer * self.zoom)) / (2 * self.zoom)
cam_left = self.x - half_w cam_left = self.x - half_w
cam_right = self.x + half_w cam_right = self.x + half_w
cam_top = self.y - half_h cam_top = self.y - half_h
@ -120,11 +159,23 @@ class Camera:
and cam_top - margin <= obj_y <= cam_bottom + margin and cam_top - margin <= obj_y <= cam_bottom + margin
) )
def world_to_screen(self, obj_x, obj_y): def world_to_screen(self, obj_x: float, obj_y: float) -> Tuple[int, int]:
"""
Converts world coordinates to screen coordinates.
:param obj_x: X coordinate in world space.
:param obj_y: Y coordinate in world space.
:return: Tuple of (screen_x, screen_y) in pixels.
"""
screen_x = (obj_x - self.x) * self.zoom + self.screen_width // 2 screen_x = (obj_x - self.x) * self.zoom + self.screen_width // 2
screen_y = (obj_y - self.y) * self.zoom + self.screen_height // 2 screen_y = (obj_y - self.y) * self.zoom + self.screen_height // 2
return int(screen_x), int(screen_y) return int(screen_x), int(screen_y)
def get_relative_size(self, world_size): def get_relative_size(self, world_size: float) -> int:
# Converts a world size (e.g., radius or width/height) to screen pixels """
Converts a world size (e.g., radius or width/height) to screen pixels.
:param world_size: Size in world units.
:return: Size in screen pixels.
"""
return int(world_size * self.zoom) return int(world_size * self.zoom)

View File

@ -1,34 +1,132 @@
from collections import defaultdict from collections import defaultdict
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Dict, Tuple, Optional, Any, TypeVar, Union
from pydantic import BaseModel, Field
T = TypeVar("T", bound="BaseEntity")
class Position(BaseModel):
"""
Represents a 2D position in the world.
"""
x: int = Field(..., description="X coordinate")
y: int = Field(..., description="Y coordinate")
def __str__(self) -> str:
return f"({self.x}, {self.y})"
def __repr__(self) -> str:
return f"Position({self.x}, {self.y})"
def set_position(self, x: int, y: int) -> None:
"""
Sets the position to the given coordinates.
:param x: New X coordinate.
:param y: New Y coordinate.
"""
self.x = x
self.y = y
def get_position(self) -> Tuple[int, int]:
"""
Returns the current position as a tuple.
:return: Tuple of (x, y).
"""
return self.x, self.y
class BaseEntity(ABC):
"""
Abstract base class for all entities in the world.
"""
def __init__(self, position: Position) -> None:
"""
Initializes the entity with a position.
:param position: The position of the entity.
"""
self.position: Position = position
self.interaction_radius: int = 0
self.flags: Dict[str, bool] = {
"death": False,
"can_interact": False,
}
self.world_callbacks: Dict[str, Any] = {}
self.max_visual_width: int = 0
@abstractmethod
def tick(self, interactable: Optional[List["BaseEntity"]] = None) -> Optional["BaseEntity"]:
"""
Updates the entity for a single tick.
:param interactable: List of entities this entity can interact with.
:return: The updated entity or None if it should be removed.
"""
return self
@abstractmethod
def render(self, camera: Any, screen: Any) -> None:
"""
Renders the entity on the screen.
:param camera: The camera object for coordinate transformation.
:param screen: The Pygame screen surface.
"""
pass
def flag_for_death(self) -> None:
"""
Flags the entity for removal from the world.
"""
self.flags["death"] = True
class World: class World:
def __init__(self, partition_size=10): """
self.partition_size = partition_size A world-class that contains and manages all objects in the game using spatial partitioning.
self.buffers = [defaultdict(list), defaultdict(list)] """
self.current_buffer = 0
def _hash_position(self, position): def __init__(self, partition_size: int = 10) -> None:
# Map world coordinates to cell coordinates """
return int(position.x // self.partition_size), int( Initializes the world with a partition size.
position.y // self.partition_size
)
def render_all(self, camera, screen): :param partition_size: The size of each partition cell in the world.
"""
self.partition_size: int = partition_size
self.buffers: List[Dict[Tuple[int, int], List[BaseEntity]]] = [defaultdict(list), defaultdict(list)]
self.current_buffer: int = 0
def _hash_position(self, position: Position) -> Tuple[int, int]:
"""
Hashes a position into a cell based on the partition size.
:param position: A Position object representing the position in the world.
:return: Tuple (cell_x, cell_y) representing the cell coordinates.
"""
return int(position.x // self.partition_size), int(position.y // self.partition_size)
def render_all(self, camera: Any, screen: Any) -> None:
"""
Renders all objects in the current buffer.
:param camera: The camera object for coordinate transformation.
:param screen: The Pygame screen surface.
"""
for obj_list in self.buffers[self.current_buffer].values(): for obj_list in self.buffers[self.current_buffer].values():
for obj in obj_list: for obj in obj_list:
obj.render(camera, screen) obj.render(camera, screen)
def tick_all(self): def tick_all(self) -> None:
next_buffer = 1 - self.current_buffer """
Advances all objects in the world by one tick, updating their state and handling interactions.
"""
next_buffer: int = 1 - self.current_buffer
self.buffers[next_buffer].clear() self.buffers[next_buffer].clear()
# print all objects in the current buffer
print(
f"Ticking objects in buffer {self.current_buffer}:",
self.buffers[self.current_buffer].values(),
)
for obj_list in self.buffers[self.current_buffer].values(): for obj_list in self.buffers[self.current_buffer].values():
for obj in obj_list: for obj in obj_list:
if obj.flags["death"]: if obj.flags["death"]:
@ -38,24 +136,44 @@ class World:
obj.position.x, obj.position.y, obj.interaction_radius obj.position.x, obj.position.y, obj.interaction_radius
) )
interactable.remove(obj) interactable.remove(obj)
print(f"Object {obj} interacting with {len(interactable)} objects.")
new_obj = obj.tick(interactable) new_obj = obj.tick(interactable)
else: else:
new_obj = obj.tick() new_obj = obj.tick()
if new_obj is None: if new_obj is None:
continue continue
cell = self._hash_position(new_obj.position)
self.buffers[next_buffer][cell].append(new_obj) # reproduction code
if isinstance(new_obj, list):
for item in new_obj:
if isinstance(item, BaseEntity):
cell = self._hash_position(item.position)
self.buffers[next_buffer][cell].append(item)
else:
cell = self._hash_position(new_obj.position)
self.buffers[next_buffer][cell].append(new_obj)
self.current_buffer = next_buffer self.current_buffer = next_buffer
def add_object(self, new_object): def add_object(self, new_object: BaseEntity) -> None:
"""
Adds a new object to the world in the appropriate cell.
:param new_object: The object to add.
"""
cell = self._hash_position(new_object.position) cell = self._hash_position(new_object.position)
self.buffers[self.current_buffer][cell].append(new_object) self.buffers[self.current_buffer][cell].append(new_object)
def query_objects_within_radius(self, x, y, radius): def query_objects_within_radius(self, x: float, y: float, radius: float) -> List[BaseEntity]:
result = [] """
Returns all objects within a given radius of a point.
:param x: X coordinate of the center.
:param y: Y coordinate of the center.
:param radius: Search radius.
:return: List of objects within the radius.
"""
result: List[BaseEntity] = []
cell_x, cell_y = int(x // self.partition_size), int(y // self.partition_size) cell_x, cell_y = int(x // self.partition_size), int(y // self.partition_size)
cells_to_check = [] cells_to_check: List[Tuple[int, int]] = []
r = int((radius // self.partition_size) + 1) r = int((radius // self.partition_size) + 1)
for dx in range(-r, r + 1): for dx in range(-r, r + 1):
for dy in range(-r, r + 1): for dy in range(-r, r + 1):
@ -69,8 +187,17 @@ class World:
result.append(obj) result.append(obj)
return result return result
def query_objects_in_range(self, x1, y1, x2, y2): def query_objects_in_range(self, x1: float, y1: float, x2: float, y2: float) -> List[BaseEntity]:
result = [] """
Returns all objects within a rectangular range.
:param x1: Minimum X coordinate.
:param y1: Minimum Y coordinate.
:param x2: Maximum X coordinate.
:param y2: Maximum Y coordinate.
:return: List of objects within the rectangle.
"""
result: List[BaseEntity] = []
cell_x1, cell_y1 = ( cell_x1, cell_y1 = (
int(x1 // self.partition_size), int(x1 // self.partition_size),
int(y1 // self.partition_size), int(y1 // self.partition_size),
@ -87,9 +214,16 @@ class World:
result.append(obj) result.append(obj)
return result return result
def query_closest_object(self, x, y): def query_closest_object(self, x: float, y: float) -> Optional[BaseEntity]:
closest_obj = None """
closest_distance = float("inf") Returns the closest object to a given point.
:param x: X coordinate of the point.
:param y: Y coordinate of the point.
:return: The closest object or None if no objects exist.
"""
closest_obj: Optional[BaseEntity] = None
closest_distance: float = float("inf")
for obj_list in self.buffers[self.current_buffer].values(): for obj_list in self.buffers[self.current_buffer].values():
for obj in obj_list: for obj in obj_list:
obj_x, obj_y = obj.position.get_position() obj_x, obj_y = obj.position.get_position()
@ -101,49 +235,13 @@ class World:
closest_obj = obj closest_obj = obj
return closest_obj return closest_obj
def get_objects(self): def get_objects(self) -> List[BaseEntity]:
all_objects = [] """
Returns a list of all objects currently in the world.
:return: List of all objects.
"""
all_objects: List[BaseEntity] = []
for obj_list in self.buffers[self.current_buffer].values(): for obj_list in self.buffers[self.current_buffer].values():
all_objects.extend(obj_list) all_objects.extend(obj_list)
print("All objects: ", all_objects)
return all_objects return all_objects
class BaseEntity(ABC):
def __init__(self, position: "Position"):
self.position = position
self.interaction_radius = 0
self.flags = {
"death": False,
"can_interact": False,
}
self.world_callbacks = {}
self.max_visual_width = 0
@abstractmethod
def tick(self, interactable=None):
return self
@abstractmethod
def render(self, camera, screen):
pass
def flag_for_death(self):
self.flags["death"] = True
class Position:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Position({self.x}, {self.y})"
def set_position(self, x, y):
self.x = x
self.y = y
def get_position(self):
return self.x, self.y