maeves-ecosphere 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maeves_ecosphere-0.1.2/PKG-INFO +5 -0
- maeves_ecosphere-0.1.2/README.md +108 -0
- maeves_ecosphere-0.1.2/pyproject.toml +24 -0
- maeves_ecosphere-0.1.2/setup.cfg +4 -0
- maeves_ecosphere-0.1.2/src/maeves_ecosphere.egg-info/PKG-INFO +5 -0
- maeves_ecosphere-0.1.2/src/maeves_ecosphere.egg-info/SOURCES.txt +13 -0
- maeves_ecosphere-0.1.2/src/maeves_ecosphere.egg-info/dependency_links.txt +1 -0
- maeves_ecosphere-0.1.2/src/maeves_ecosphere.egg-info/entry_points.txt +2 -0
- maeves_ecosphere-0.1.2/src/maeves_ecosphere.egg-info/top_level.txt +1 -0
- maeves_ecosphere-0.1.2/src/mecosphere/__init__.py +1 -0
- maeves_ecosphere-0.1.2/src/mecosphere/application.py +356 -0
- maeves_ecosphere-0.1.2/src/mecosphere/cli.py +31 -0
- maeves_ecosphere-0.1.2/src/mecosphere/components.py +323 -0
- maeves_ecosphere-0.1.2/src/mecosphere/simulation.py +160 -0
- maeves_ecosphere-0.1.2/src/mecosphere/tilemap.py +69 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Maeve's Ecosphere
|
|
2
|
+
|
|
3
|
+
<p><a href="https://ko-fi.com/molasses"><img src="https://img.shields.io/badge/donate-kofi-blue?style=for-the-badge&logo=ko-fi&color=e57578&logoColor=FFFFFF&labelColor=262a35" alt="Ko-Fi"></a>
|
|
4
|
+
<a href="https://www.patreon.com/molasseslover"><img src="https://img.shields.io/badge/donate-patreon-blue?style=for-the-badge&logo=patreon&color=e57578&logoColor=FFFFFF&labelColor=262a35" alt="Patreon"></a>
|
|
5
|
+
|
|
6
|
+
This is a simple procedural cellular automata simulation resembling an ecosystem.
|
|
7
|
+
I made this, in my boredom, to toy around with during a lengthy
|
|
8
|
+
[PEM](https://en.wikipedia.org/wiki/Post-exertional_malaise) flare-up.
|
|
9
|
+
|
|
10
|
+
The project makes use of rudimentary ECS-inspired framework, which makes use
|
|
11
|
+
of Python's dictionaries to manage data. Every tile (cell) is an object which
|
|
12
|
+
may contain another object as its `occupant`. These objects are referred to
|
|
13
|
+
as `SimulationObject`s and are this project's (much more bloated) equivalent
|
|
14
|
+
to entities. Every `SimulationObject` contains a list of components, stored as
|
|
15
|
+
dictionaries. Every component dictionary contains a callback function, which
|
|
16
|
+
serves as a system instance, as you'd see in a project using a conventional
|
|
17
|
+
ECS architecture. Lastly, `SimulationObject`s also contain a `component_data`
|
|
18
|
+
dictionary which stores data accessed by components, straying from real
|
|
19
|
+
ECS implementations where data is stored directly inside of components.
|
|
20
|
+
|
|
21
|
+
## Preview
|
|
22
|
+
|
|
23
|
+
Below is a screenshot of the simulation after about 1h30m:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
Below is a brief glimpse at the early stage of a simulation:
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
If you'd like a more extensive preview, take a look at this
|
|
32
|
+
[YouTube video](https://youtu.be/FK47rwov3aU).
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- Tiles which may be occupied by simulated objects
|
|
37
|
+
- Simulated objects which may:
|
|
38
|
+
- Forage to other tiles.
|
|
39
|
+
- Consume other objects.
|
|
40
|
+
- Starve to death if too hungry.
|
|
41
|
+
- Produce objects in neighbouring tiles by:
|
|
42
|
+
- Reproducing with partner objects.
|
|
43
|
+
- Propogating themselves on their own.
|
|
44
|
+
- Age into different life stages.
|
|
45
|
+
- Die after reaching a certain age.
|
|
46
|
+
- Decompose after death.
|
|
47
|
+
- Affect the nutrition of a tile on birth or death.
|
|
48
|
+
|
|
49
|
+
### Organisms
|
|
50
|
+
#### Trees
|
|
51
|
+
Trees take a significant amount of time to age into adulthood, and
|
|
52
|
+
consequently encroach on surrounding organisms over time.
|
|
53
|
+
|
|
54
|
+
#### Grass
|
|
55
|
+
Grass has a very short lifespan, and serves as the initial food source for
|
|
56
|
+
animals.
|
|
57
|
+
|
|
58
|
+
#### Mushrooms
|
|
59
|
+
Mushrooms take a long time to reach adulthood, spread prior to adulthood, and
|
|
60
|
+
serve as the main food source for animals.
|
|
61
|
+
|
|
62
|
+
#### Isopods
|
|
63
|
+
Isopods forage the map, eating and mating with partner isopods when they can.
|
|
64
|
+
|
|
65
|
+
## Cloning
|
|
66
|
+
This project makes use of the [Git LFS](https://git-lfs.com/) extension, please
|
|
67
|
+
make sure to install it prior to cloning.
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
➜ git lfs install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Already cloned the repository before setting Git LFS up? After you've
|
|
74
|
+
installed Git LFS, these commands should download all Git LFS files.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
➜ git lfs fetch --all
|
|
78
|
+
➜ git lfs pull
|
|
79
|
+
➜ git lfs checkout
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Credits
|
|
83
|
+
Image assets under the [`res/img/`](res/img) directory are taken from or
|
|
84
|
+
derivations of Pierre Vandermaesen' 1-bit asset pack,
|
|
85
|
+
found [here](https://pierre-vandermaesen.itch.io/1-bit-16x16-asset-packs).
|
|
86
|
+
|
|
87
|
+
## Running & Installing
|
|
88
|
+
### Nix
|
|
89
|
+
|
|
90
|
+
The recommended installation route is with use of Nix. This project provides a Nix
|
|
91
|
+
flake which can be installed via Home Manager, your NixOS configuration, and
|
|
92
|
+
a plethora of other solutions which support Nix flakes.
|
|
93
|
+
|
|
94
|
+
Alternatively the project can simply be run without installation using the following command:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
➜ nix build .; ./result/result/bin/maeves-ecosphere
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Python
|
|
101
|
+
|
|
102
|
+
Installation is required to run this project without Nix.
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
➜ pip install pygame cython
|
|
106
|
+
➜ pip install .
|
|
107
|
+
➜ python -m mecosphere.cli
|
|
108
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "maeves-ecosphere"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "A tile-based procedural ecosystem simulation, with an object component system."
|
|
5
|
+
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "MolassesLover"}
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[project.scripts]
|
|
11
|
+
maeves-ecosphere = "mecosphere:cli.main"
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["setuptools", "cython"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
ext-modules = [
|
|
18
|
+
{ name = "mecosphere", sources = [
|
|
19
|
+
"src/mecosphere/application.py",
|
|
20
|
+
"src/mecosphere/components.py",
|
|
21
|
+
"src/mecosphere/simulation.py",
|
|
22
|
+
"src/mecosphere/tilemap.py"
|
|
23
|
+
] }
|
|
24
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/maeves_ecosphere.egg-info/PKG-INFO
|
|
4
|
+
src/maeves_ecosphere.egg-info/SOURCES.txt
|
|
5
|
+
src/maeves_ecosphere.egg-info/dependency_links.txt
|
|
6
|
+
src/maeves_ecosphere.egg-info/entry_points.txt
|
|
7
|
+
src/maeves_ecosphere.egg-info/top_level.txt
|
|
8
|
+
src/mecosphere/__init__.py
|
|
9
|
+
src/mecosphere/application.py
|
|
10
|
+
src/mecosphere/cli.py
|
|
11
|
+
src/mecosphere/components.py
|
|
12
|
+
src/mecosphere/simulation.py
|
|
13
|
+
src/mecosphere/tilemap.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mecosphere
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import *
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import random
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import pygame
|
|
7
|
+
|
|
8
|
+
from mecosphere import components
|
|
9
|
+
from mecosphere.simulation import *
|
|
10
|
+
from mecosphere.tilemap import *
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def arguments_create() -> Namespace:
|
|
14
|
+
argument_parser = argparse.ArgumentParser(prog="Maeve's Ecosphere")
|
|
15
|
+
argument_parser.add_argument(
|
|
16
|
+
"-c",
|
|
17
|
+
"--cycles",
|
|
18
|
+
type=int,
|
|
19
|
+
help="Limit simulation to a number of cycles.",
|
|
20
|
+
)
|
|
21
|
+
argument_parser.add_argument(
|
|
22
|
+
"-x",
|
|
23
|
+
"--width",
|
|
24
|
+
default=64,
|
|
25
|
+
type=int,
|
|
26
|
+
help="Width in tiles, this value is multiplied by 16 for the display window's width.",
|
|
27
|
+
)
|
|
28
|
+
argument_parser.add_argument(
|
|
29
|
+
"-y",
|
|
30
|
+
"--height",
|
|
31
|
+
default=36,
|
|
32
|
+
type=int,
|
|
33
|
+
help="Height in tiles, this value is multiplied by 16 for the display window's height.",
|
|
34
|
+
)
|
|
35
|
+
argument_parser.add_argument(
|
|
36
|
+
"-z",
|
|
37
|
+
"--headless",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Height in tiles, this value is multiplied by 16 for the display window's height.",
|
|
40
|
+
)
|
|
41
|
+
arguments = argument_parser.parse_args()
|
|
42
|
+
|
|
43
|
+
return arguments
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ApplicationSettings:
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self.arguments = arguments_create()
|
|
49
|
+
|
|
50
|
+
if self.arguments.cycles:
|
|
51
|
+
self.cycles: int = self.arguments.cycles
|
|
52
|
+
else:
|
|
53
|
+
self.cycles = -1
|
|
54
|
+
|
|
55
|
+
self.headless: bool = self.arguments.headless
|
|
56
|
+
self.height: int = self.arguments.height
|
|
57
|
+
self.width: int = self.arguments.width
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EcosphereApplication:
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self.should_run = True
|
|
63
|
+
self.should_simulate = True
|
|
64
|
+
|
|
65
|
+
self.application_settings = ApplicationSettings()
|
|
66
|
+
|
|
67
|
+
if not self.application_settings.headless:
|
|
68
|
+
pygame.init()
|
|
69
|
+
|
|
70
|
+
self.pygame_display = pygame.display.set_mode(
|
|
71
|
+
(
|
|
72
|
+
self.application_settings.width * 16,
|
|
73
|
+
self.application_settings.height * 16,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if not self.application_settings.headless:
|
|
78
|
+
self.tile_dirt = pygame.image.load("res/img/dirt.bmp").convert()
|
|
79
|
+
|
|
80
|
+
self.ecosphere = Ecosphere(self.application_settings)
|
|
81
|
+
|
|
82
|
+
self.initialize()
|
|
83
|
+
|
|
84
|
+
def tiles_render(self):
|
|
85
|
+
col_index = 0
|
|
86
|
+
row_index = 0
|
|
87
|
+
|
|
88
|
+
for collumn in self.ecosphere.map:
|
|
89
|
+
for row in collumn:
|
|
90
|
+
tile = self.ecosphere.map[col_index][row_index]
|
|
91
|
+
|
|
92
|
+
self.pygame_display.blit(
|
|
93
|
+
self.tile_dirt, (row_index * 16, col_index * 16)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if tile.occupant:
|
|
97
|
+
self.pygame_display.blit(
|
|
98
|
+
tile.occupant.image, (row_index * 16, col_index * 16)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
row_index += 1
|
|
102
|
+
|
|
103
|
+
row_index = 0
|
|
104
|
+
col_index += 1
|
|
105
|
+
|
|
106
|
+
def run(self):
|
|
107
|
+
cycles: int = 0
|
|
108
|
+
|
|
109
|
+
print("Running application!")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
while self.should_run:
|
|
113
|
+
self.pygame_display.fill((0, 0, 0))
|
|
114
|
+
|
|
115
|
+
for event in pygame.event.get():
|
|
116
|
+
if event.type == pygame.QUIT:
|
|
117
|
+
self.should_run = False
|
|
118
|
+
|
|
119
|
+
if self.should_simulate:
|
|
120
|
+
if self.ecosphere.update() == 1:
|
|
121
|
+
self.should_simulate = False
|
|
122
|
+
elif not self.application_settings.cycles == -1:
|
|
123
|
+
if cycles >= self.application_settings.cycles:
|
|
124
|
+
self.should_simulate = False
|
|
125
|
+
self.should_run = False
|
|
126
|
+
|
|
127
|
+
cycles += 1
|
|
128
|
+
|
|
129
|
+
print(f"Simulation cycle {cycles}\r", end="")
|
|
130
|
+
|
|
131
|
+
self.tiles_render()
|
|
132
|
+
|
|
133
|
+
pygame.display.flip()
|
|
134
|
+
|
|
135
|
+
# time.sleep(0.1666)
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
print(f"Simulation cycle {cycles}\r", end="")
|
|
138
|
+
self.should_simulate = False
|
|
139
|
+
self.should_run = False
|
|
140
|
+
|
|
141
|
+
print(f"Exited application after {cycles-1} cycles.")
|
|
142
|
+
|
|
143
|
+
pygame.quit()
|
|
144
|
+
|
|
145
|
+
def run_headless(self):
|
|
146
|
+
cycles: int = 0
|
|
147
|
+
|
|
148
|
+
print("Running application in headless mode!")
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
while self.should_run:
|
|
152
|
+
if self.should_simulate:
|
|
153
|
+
if self.ecosphere.update() == 1:
|
|
154
|
+
self.should_simulate = False
|
|
155
|
+
elif not self.application_settings.cycles == -1:
|
|
156
|
+
if cycles >= self.application_settings.cycles:
|
|
157
|
+
self.should_simulate = False
|
|
158
|
+
self.should_run = False
|
|
159
|
+
|
|
160
|
+
cycles += 1
|
|
161
|
+
|
|
162
|
+
print(f"Simulation cycle {cycles}\r", end="")
|
|
163
|
+
|
|
164
|
+
except KeyboardInterrupt:
|
|
165
|
+
print(f"Simulation cycle {cycles}\r", end="")
|
|
166
|
+
self.should_run = False
|
|
167
|
+
|
|
168
|
+
print(f"Exited application after {cycles-1} cycles.")
|
|
169
|
+
|
|
170
|
+
def start(self):
|
|
171
|
+
if not self.application_settings.headless:
|
|
172
|
+
self.run()
|
|
173
|
+
else:
|
|
174
|
+
self.run_headless()
|
|
175
|
+
|
|
176
|
+
def initialize(self):
|
|
177
|
+
# Propogation
|
|
178
|
+
print("Propogating ecosphere...")
|
|
179
|
+
|
|
180
|
+
propogated_map = self.ecosphere.map.copy()
|
|
181
|
+
|
|
182
|
+
for tile, col_index, row_index in ecosphere_map_yield_tile(
|
|
183
|
+
self.ecosphere.map,
|
|
184
|
+
width=self.application_settings.width,
|
|
185
|
+
height=self.application_settings.height,
|
|
186
|
+
):
|
|
187
|
+
tile = propogated_map[col_index][row_index]
|
|
188
|
+
|
|
189
|
+
if tile == 1:
|
|
190
|
+
new_tree = SimulationObject(
|
|
191
|
+
components=[],
|
|
192
|
+
domain="flora",
|
|
193
|
+
age_maximum=random.randint(1825, 7250),
|
|
194
|
+
name="tree",
|
|
195
|
+
species="tree",
|
|
196
|
+
images={"image_egg": "seed"},
|
|
197
|
+
position_range_width=self.application_settings.width,
|
|
198
|
+
position_range_height=self.application_settings.height,
|
|
199
|
+
headless=self.application_settings.headless,
|
|
200
|
+
component_data={
|
|
201
|
+
"Aging": {
|
|
202
|
+
"is_egg": False,
|
|
203
|
+
"is_baby": False,
|
|
204
|
+
"age_baby": 180,
|
|
205
|
+
"age_adult": 356,
|
|
206
|
+
},
|
|
207
|
+
"ReproducingAsexually": {
|
|
208
|
+
"rate_percentage": 50,
|
|
209
|
+
"while_baby": False,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
position=[col_index, row_index],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
new_tree.components.append(components.component_age)
|
|
216
|
+
new_tree.components.append(components.component_reproduce_asexual)
|
|
217
|
+
|
|
218
|
+
new_tree.decomposition_maximum = 1000
|
|
219
|
+
|
|
220
|
+
propogated_map[col_index][row_index] = Tile(
|
|
221
|
+
occupant=new_tree,
|
|
222
|
+
nutritious=False,
|
|
223
|
+
contaminated=False,
|
|
224
|
+
pos_x=col_index,
|
|
225
|
+
pos_y=row_index,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
self.ecosphere.simulation_objects.append(new_tree)
|
|
229
|
+
elif tile >= 2 and tile < 5:
|
|
230
|
+
new_tree = SimulationObject(
|
|
231
|
+
components=[],
|
|
232
|
+
domain="flora",
|
|
233
|
+
age_maximum=random.randint(120, 270),
|
|
234
|
+
name="grass",
|
|
235
|
+
species="grass",
|
|
236
|
+
position_range_width=self.application_settings.width,
|
|
237
|
+
position_range_height=self.application_settings.height,
|
|
238
|
+
headless=self.application_settings.headless,
|
|
239
|
+
images={"image_egg": "seed"},
|
|
240
|
+
component_data={
|
|
241
|
+
"Aging": {
|
|
242
|
+
"is_egg": False,
|
|
243
|
+
"is_baby": False,
|
|
244
|
+
"age_baby": 5,
|
|
245
|
+
"age_adult": 15,
|
|
246
|
+
},
|
|
247
|
+
"ReproducingAsexually": {
|
|
248
|
+
"rate_percentage": 95,
|
|
249
|
+
"while_baby": True,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
position=[col_index, row_index],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
new_tree.components.append(components.component_age)
|
|
256
|
+
new_tree.components.append(components.component_reproduce_asexual)
|
|
257
|
+
|
|
258
|
+
propogated_map[col_index][row_index] = Tile(
|
|
259
|
+
occupant=new_tree,
|
|
260
|
+
nutritious=False,
|
|
261
|
+
contaminated=False,
|
|
262
|
+
pos_x=col_index,
|
|
263
|
+
pos_y=row_index,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
self.ecosphere.simulation_objects.append(new_tree)
|
|
267
|
+
elif tile == 5:
|
|
268
|
+
new_tree = SimulationObject(
|
|
269
|
+
components=[],
|
|
270
|
+
domain="fungus",
|
|
271
|
+
age_maximum=random.randint(1500, 3000),
|
|
272
|
+
name=f"mushroom",
|
|
273
|
+
species="mushroom",
|
|
274
|
+
position_range_width=self.application_settings.width,
|
|
275
|
+
position_range_height=self.application_settings.height,
|
|
276
|
+
headless=self.application_settings.headless,
|
|
277
|
+
images={"image_egg": "seed"},
|
|
278
|
+
component_data={
|
|
279
|
+
"Aging": {
|
|
280
|
+
"is_egg": False,
|
|
281
|
+
"is_baby": False,
|
|
282
|
+
"age_baby": 500,
|
|
283
|
+
"age_adult": 1000,
|
|
284
|
+
},
|
|
285
|
+
"ReproducingAsexually": {
|
|
286
|
+
"rate_percentage": 90,
|
|
287
|
+
"while_baby": True,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
position=[col_index, row_index],
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
new_tree.components.append(components.component_age)
|
|
294
|
+
new_tree.components.append(components.component_reproduce_asexual)
|
|
295
|
+
|
|
296
|
+
propogated_map[col_index][row_index] = Tile(
|
|
297
|
+
occupant=new_tree,
|
|
298
|
+
nutritious=False,
|
|
299
|
+
contaminated=False,
|
|
300
|
+
pos_x=col_index,
|
|
301
|
+
pos_y=row_index,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
self.ecosphere.simulation_objects.append(new_tree)
|
|
305
|
+
else:
|
|
306
|
+
propogated_map[col_index][row_index] = Tile(
|
|
307
|
+
occupant=None,
|
|
308
|
+
nutritious=False,
|
|
309
|
+
contaminated=False,
|
|
310
|
+
pos_x=col_index,
|
|
311
|
+
pos_y=row_index,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
row_index += 1
|
|
315
|
+
|
|
316
|
+
row_index = 0
|
|
317
|
+
col_index += 1
|
|
318
|
+
|
|
319
|
+
self.ecosphere.map = propogated_map
|
|
320
|
+
|
|
321
|
+
for i in range(50):
|
|
322
|
+
isopod = SimulationObject(
|
|
323
|
+
components=[],
|
|
324
|
+
domain="fauna",
|
|
325
|
+
age_maximum=random.randint(913, 1825),
|
|
326
|
+
name=f"isopod {i}",
|
|
327
|
+
species="isopod",
|
|
328
|
+
position_range_width=self.application_settings.width,
|
|
329
|
+
position_range_height=self.application_settings.height,
|
|
330
|
+
headless=self.application_settings.headless,
|
|
331
|
+
component_data={
|
|
332
|
+
"Eating": {
|
|
333
|
+
"diet": ["grass", "mushroom"],
|
|
334
|
+
"hunger": 0,
|
|
335
|
+
"hunger_max": 120,
|
|
336
|
+
},
|
|
337
|
+
"Aging": {
|
|
338
|
+
"is_egg": True,
|
|
339
|
+
"is_baby": False,
|
|
340
|
+
"age_baby": 30,
|
|
341
|
+
"age_adult": 60,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
images={"image_baby": "isopod-baby"},
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if not self.application_settings.headless:
|
|
348
|
+
isopod.image = isopod.image_baby
|
|
349
|
+
|
|
350
|
+
isopod.components.append(components.component_forage)
|
|
351
|
+
isopod.components.append(components.component_eat)
|
|
352
|
+
isopod.components.append(components.component_age)
|
|
353
|
+
isopod.components.append(components.component_reproduce_sexual_direct)
|
|
354
|
+
|
|
355
|
+
self.ecosphere.simulation_objects.append(isopod)
|
|
356
|
+
self.ecosphere.map[isopod.position[0]][isopod.position[1]].occupant = isopod
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/local/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
A simple ecosphere simulator I made while bored and sick.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import random
|
|
9
|
+
import csv
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
import pygame
|
|
13
|
+
|
|
14
|
+
from mecosphere.application import *
|
|
15
|
+
from mecosphere.simulation import *
|
|
16
|
+
from mecosphere.tilemap import *
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""
|
|
21
|
+
The entrypoint for the script.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
application = EcosphereApplication()
|
|
25
|
+
|
|
26
|
+
application.start()
|
|
27
|
+
application.should_run = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
import copy
|
|
5
|
+
|
|
6
|
+
from mecosphere.simulation import *
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_descendant(
|
|
10
|
+
simobject_instance, new_position, _position_range_width, _position_range_height
|
|
11
|
+
) -> SimulationObject:
|
|
12
|
+
simobject_descendant = SimulationObject(
|
|
13
|
+
name=f"{simobject_instance.name} Jr.",
|
|
14
|
+
species=simobject_instance.species,
|
|
15
|
+
domain=simobject_instance.domain,
|
|
16
|
+
components=copy.deepcopy(simobject_instance.components),
|
|
17
|
+
component_data=copy.deepcopy(simobject_instance.component_data),
|
|
18
|
+
age_maximum=simobject_instance.age_maximum,
|
|
19
|
+
images=simobject_instance.images,
|
|
20
|
+
position=new_position,
|
|
21
|
+
position_range_width=_position_range_width,
|
|
22
|
+
position_range_height=_position_range_height,
|
|
23
|
+
headless=simobject_instance.headless,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
simobject_descendant.age_current = 0
|
|
27
|
+
|
|
28
|
+
return simobject_descendant
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_component(component_name, callback: callable, is_active=True) -> dict:
|
|
32
|
+
"""
|
|
33
|
+
A simple constructor for components as dictionaries. This is not an ECS,
|
|
34
|
+
just borrowing the language.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
component_dictionary = {
|
|
38
|
+
"name": component_name,
|
|
39
|
+
"active": is_active,
|
|
40
|
+
"callback": callback,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return component_dictionary
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def system_age(
|
|
47
|
+
simobject_instance, component_instance, ecosphere_map, application_settings
|
|
48
|
+
):
|
|
49
|
+
if simobject_instance.is_dead:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
age_current = simobject_instance.age_current
|
|
53
|
+
age_baby = simobject_instance.component_data["Aging"]["age_baby"]
|
|
54
|
+
age_adult = simobject_instance.component_data["Aging"]["age_adult"]
|
|
55
|
+
|
|
56
|
+
if age_current < age_baby:
|
|
57
|
+
if not application_settings.headless:
|
|
58
|
+
simobject_instance.image = simobject_instance.image_egg
|
|
59
|
+
simobject_instance.component_data["Aging"]["is_egg"] = True
|
|
60
|
+
simobject_instance.component_data["Aging"]["is_baby"] = False
|
|
61
|
+
elif age_current >= age_adult:
|
|
62
|
+
if not application_settings.headless:
|
|
63
|
+
simobject_instance.image = simobject_instance.image_default
|
|
64
|
+
simobject_instance.component_data["Aging"]["is_egg"] = False
|
|
65
|
+
simobject_instance.component_data["Aging"]["is_baby"] = False
|
|
66
|
+
elif age_current > age_baby and age_current < age_adult:
|
|
67
|
+
if not application_settings.headless:
|
|
68
|
+
simobject_instance.image = simobject_instance.image_baby
|
|
69
|
+
simobject_instance.component_data["Aging"]["is_egg"] = False
|
|
70
|
+
simobject_instance.component_data["Aging"]["is_baby"] = True
|
|
71
|
+
else:
|
|
72
|
+
if not application_settings.headless:
|
|
73
|
+
simobject_instance.image = simobject_instance.image_default
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def system_forage(
|
|
77
|
+
simobject_instance, component_instance, ecosphere_map, application_settings
|
|
78
|
+
):
|
|
79
|
+
if "Aging" in simobject_instance.component_data:
|
|
80
|
+
if simobject_instance.component_data["Aging"]["is_egg"]:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
position_old_tile = ecosphere_map[simobject_instance.position[0]][
|
|
84
|
+
simobject_instance.position[1]
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
position_old_tile.occupant = simobject_instance
|
|
88
|
+
|
|
89
|
+
position_new = simobject_instance.position.copy()
|
|
90
|
+
|
|
91
|
+
position_new = [
|
|
92
|
+
position_new[0] + random.randint(-1, 1),
|
|
93
|
+
position_new[1] + random.randint(-1, 1),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
if position_new[0] > (application_settings.height - 1) or position_new[0] < 0:
|
|
97
|
+
return
|
|
98
|
+
elif position_new[1] > (application_settings.width - 1) or position_new[1] < 0:
|
|
99
|
+
return
|
|
100
|
+
else:
|
|
101
|
+
position_new_tile = ecosphere_map[position_new[0]][position_new[1]]
|
|
102
|
+
|
|
103
|
+
if not position_new_tile.occupant:
|
|
104
|
+
position_old_tile.occupant = None
|
|
105
|
+
|
|
106
|
+
simobject_instance.position = position_new
|
|
107
|
+
|
|
108
|
+
position_new_tile.occupant = simobject_instance
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def system_eat(
|
|
112
|
+
simobject_instance, component_instance, ecosphere_map, application_settings
|
|
113
|
+
):
|
|
114
|
+
def eat():
|
|
115
|
+
if (
|
|
116
|
+
search_tile.occupant.species
|
|
117
|
+
in simobject_instance.component_data[component_instance["name"]]["diet"]
|
|
118
|
+
):
|
|
119
|
+
search_tile.occupant.die()
|
|
120
|
+
|
|
121
|
+
hunger = simobject_instance.component_data[component_instance["name"]]["hunger"]
|
|
122
|
+
hunger_max = simobject_instance.component_data[component_instance["name"]][
|
|
123
|
+
"hunger_max"
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
hunger += 1
|
|
127
|
+
|
|
128
|
+
if hunger >= hunger_max:
|
|
129
|
+
simobject_instance.die()
|
|
130
|
+
return
|
|
131
|
+
else:
|
|
132
|
+
simobject_instance.component_data[component_instance["name"]]["hunger"] = hunger
|
|
133
|
+
|
|
134
|
+
for search_tile, pos_x, pos_y in simulation_object_yield_tile_neighbour(
|
|
135
|
+
simobject_instance,
|
|
136
|
+
ecosphere_map,
|
|
137
|
+
width=application_settings.width,
|
|
138
|
+
height=application_settings.height,
|
|
139
|
+
):
|
|
140
|
+
if search_tile.occupant:
|
|
141
|
+
if search_tile.occupant == simobject_instance:
|
|
142
|
+
return
|
|
143
|
+
elif search_tile.occupant.species == simobject_instance.species:
|
|
144
|
+
return
|
|
145
|
+
else:
|
|
146
|
+
eat()
|
|
147
|
+
simobject_instance.component_data[component_instance["name"]][
|
|
148
|
+
"hunger"
|
|
149
|
+
] = 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_hungry(_simobject_instance):
|
|
153
|
+
hungry = False
|
|
154
|
+
|
|
155
|
+
if "Eating" in _simobject_instance.component_data:
|
|
156
|
+
hunger = _simobject_instance.component_data["Eating"]["hunger"]
|
|
157
|
+
hunger_max = _simobject_instance.component_data["Eating"]["hunger_max"]
|
|
158
|
+
|
|
159
|
+
if hunger > (int(round(hunger_max / 2))):
|
|
160
|
+
hungry = True
|
|
161
|
+
return hungry
|
|
162
|
+
|
|
163
|
+
return hungry
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def system_reproduce_sexual_direct(
|
|
167
|
+
simobject_instance, component_instance, ecosphere_map, application_settings
|
|
168
|
+
):
|
|
169
|
+
reproduction_partner = None
|
|
170
|
+
|
|
171
|
+
if "Aging" in simobject_instance.component_data:
|
|
172
|
+
if simobject_instance.component_data["Aging"]["is_egg"]:
|
|
173
|
+
return
|
|
174
|
+
elif simobject_instance.component_data["Aging"]["is_baby"]:
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
if is_hungry(simobject_instance):
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
for search_tile, pos_x, pos_y in simulation_object_yield_tile_neighbour(
|
|
181
|
+
simobject_instance,
|
|
182
|
+
ecosphere_map,
|
|
183
|
+
width=application_settings.width,
|
|
184
|
+
height=application_settings.height,
|
|
185
|
+
):
|
|
186
|
+
if search_tile.occupant:
|
|
187
|
+
if not search_tile.occupant == simobject_instance:
|
|
188
|
+
if not search_tile.occupant.is_dead:
|
|
189
|
+
reproduction_partner = search_tile.occupant
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
if reproduction_partner:
|
|
193
|
+
if is_hungry(reproduction_partner):
|
|
194
|
+
return
|
|
195
|
+
else:
|
|
196
|
+
for search_tile, pos_x, pos_y in simulation_object_yield_tile_neighbour(
|
|
197
|
+
simobject_instance,
|
|
198
|
+
ecosphere_map,
|
|
199
|
+
width=application_settings.width,
|
|
200
|
+
height=application_settings.height,
|
|
201
|
+
):
|
|
202
|
+
if (
|
|
203
|
+
not search_tile.occupant
|
|
204
|
+
and not search_tile.metadata["contaminated"]
|
|
205
|
+
):
|
|
206
|
+
baby = create_descendant(
|
|
207
|
+
simobject_instance,
|
|
208
|
+
new_position=[pos_x, pos_y],
|
|
209
|
+
_position_range_width=application_settings.width,
|
|
210
|
+
_position_range_height=application_settings.height,
|
|
211
|
+
) # Awww they're so alike
|
|
212
|
+
|
|
213
|
+
baby.is_egg = True
|
|
214
|
+
baby.is_baby = False
|
|
215
|
+
|
|
216
|
+
if "Eating" in baby.component_data:
|
|
217
|
+
baby_hunger_max = baby.component_data["Eating"]["hunger_max"]
|
|
218
|
+
|
|
219
|
+
# Set the baby's hunger to 1/4 max hunger
|
|
220
|
+
baby.component_data["Eating"]["hunger"] = int(
|
|
221
|
+
round(baby_hunger_max / 4)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
baby.age_current = 0
|
|
225
|
+
|
|
226
|
+
ecosphere_map[pos_x][pos_y].occupant = baby
|
|
227
|
+
|
|
228
|
+
if "Eating" in search_tile.occupant.component_data:
|
|
229
|
+
search_tile.occupant.component_data["Eating"]["hunger"] += int(
|
|
230
|
+
round(
|
|
231
|
+
search_tile.occupant.component_data["Eating"][
|
|
232
|
+
"hunger_max"
|
|
233
|
+
]
|
|
234
|
+
/ 3
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
if "Eating" in simobject_instance.component_data:
|
|
238
|
+
simobject_instance.component_data["Eating"]["hunger"] += int(
|
|
239
|
+
round(
|
|
240
|
+
simobject_instance.component_data["Eating"][
|
|
241
|
+
"hunger_max"
|
|
242
|
+
]
|
|
243
|
+
/ 2
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def system_reproduce_asexual(
|
|
249
|
+
simobject_instance, component_instance, ecosphere_map, application_settings
|
|
250
|
+
):
|
|
251
|
+
while_baby = simobject_instance.component_data["ReproducingAsexually"]["while_baby"]
|
|
252
|
+
|
|
253
|
+
rate_percentage = simobject_instance.component_data["ReproducingAsexually"][
|
|
254
|
+
"rate_percentage"
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
rate_roll = random.randint(1, 100)
|
|
258
|
+
|
|
259
|
+
if rate_roll > (int(100 - round(rate_percentage))):
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
if not while_baby:
|
|
263
|
+
if "Aging" in simobject_instance.component_data:
|
|
264
|
+
if simobject_instance.component_data["Aging"]["is_egg"]:
|
|
265
|
+
return
|
|
266
|
+
if simobject_instance.component_data["Aging"]["is_baby"]:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
for search_tile, pos_x, pos_y in simulation_object_yield_tile_neighbour(
|
|
270
|
+
simobject_instance,
|
|
271
|
+
ecosphere_map,
|
|
272
|
+
width=application_settings.width,
|
|
273
|
+
height=application_settings.height,
|
|
274
|
+
):
|
|
275
|
+
if search_tile.occupant:
|
|
276
|
+
if search_tile.occupant == simobject_instance:
|
|
277
|
+
continue
|
|
278
|
+
elif not search_tile.occupant.is_dead:
|
|
279
|
+
continue
|
|
280
|
+
elif search_tile.occupant.species == simobject_instance.species:
|
|
281
|
+
continue
|
|
282
|
+
else:
|
|
283
|
+
clone = create_descendant(
|
|
284
|
+
simobject_instance,
|
|
285
|
+
new_position=[pos_x, pos_y],
|
|
286
|
+
_position_range_width=application_settings.width,
|
|
287
|
+
_position_range_height=application_settings.height,
|
|
288
|
+
) # Propogation
|
|
289
|
+
|
|
290
|
+
clone.is_egg = True
|
|
291
|
+
clone.is_baby = False
|
|
292
|
+
|
|
293
|
+
clone.age_current = 0
|
|
294
|
+
|
|
295
|
+
ecosphere_map[pos_x][pos_y].occupant = clone
|
|
296
|
+
|
|
297
|
+
if search_tile.metadata["contaminated"]:
|
|
298
|
+
ecosphere_map[pos_x][pos_y].metadata["contaminated"] = False
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
component_forage: dict = create_component(
|
|
302
|
+
component_name="Foraging", callback=system_forage
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
component_eat: dict = create_component(
|
|
306
|
+
component_name="Eating",
|
|
307
|
+
callback=system_eat,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
component_reproduce_sexual_direct: dict = create_component(
|
|
311
|
+
component_name="ReproducingSexuallyDirectly",
|
|
312
|
+
callback=system_reproduce_sexual_direct,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
component_reproduce_asexual: dict = create_component(
|
|
316
|
+
component_name="ReproducingAsexually",
|
|
317
|
+
callback=system_reproduce_asexual,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
component_age: dict = create_component(
|
|
321
|
+
component_name="Aging",
|
|
322
|
+
callback=system_age,
|
|
323
|
+
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pygame
|
|
6
|
+
|
|
7
|
+
from mecosphere.tilemap import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tile(object):
|
|
11
|
+
__slots__ = ("occupant", "metadata", "pos_x", "pos_y")
|
|
12
|
+
|
|
13
|
+
def __init__(self, occupant, nutritious, contaminated, pos_x, pos_y):
|
|
14
|
+
self.occupant: SimulationObject = occupant
|
|
15
|
+
self.pos_x = pos_x
|
|
16
|
+
self.pos_y = pos_y
|
|
17
|
+
self.metadata = self.metadata_create(nutritious, contaminated)
|
|
18
|
+
|
|
19
|
+
def metadata_create(self, nutritious=False, contaminated=False) -> dict:
|
|
20
|
+
metadata_dictionary = {"nutritious": nutritious, "contaminated": contaminated}
|
|
21
|
+
|
|
22
|
+
return metadata_dictionary
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SimulationObject:
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
name,
|
|
29
|
+
species,
|
|
30
|
+
domain,
|
|
31
|
+
position_range_width,
|
|
32
|
+
position_range_height,
|
|
33
|
+
components: list,
|
|
34
|
+
age_maximum=7,
|
|
35
|
+
component_data={},
|
|
36
|
+
images={},
|
|
37
|
+
position=[],
|
|
38
|
+
headless=False,
|
|
39
|
+
):
|
|
40
|
+
self.age_current = 0
|
|
41
|
+
self.age_maximum = age_maximum
|
|
42
|
+
self.decomposition_current = 0
|
|
43
|
+
self.decomposition_maximum = int(round(age_maximum / 8))
|
|
44
|
+
self.components: list = components
|
|
45
|
+
self.images = images
|
|
46
|
+
self.name = name
|
|
47
|
+
self.species = species
|
|
48
|
+
self.position_range_width = position_range_width
|
|
49
|
+
self.position_range_height = position_range_height
|
|
50
|
+
self.headless = headless
|
|
51
|
+
|
|
52
|
+
if not position:
|
|
53
|
+
self.position = [
|
|
54
|
+
random.randint(1, self.position_range_height - 1),
|
|
55
|
+
random.randint(1, self.position_range_width - 1),
|
|
56
|
+
]
|
|
57
|
+
else:
|
|
58
|
+
self.position = position
|
|
59
|
+
|
|
60
|
+
self.domain = domain
|
|
61
|
+
self.is_dead = False
|
|
62
|
+
|
|
63
|
+
if not self.headless:
|
|
64
|
+
self.image_default = pygame.image.load(f"res/img/{species}.bmp")
|
|
65
|
+
self.image_egg = pygame.image.load(f"res/img/egg.bmp")
|
|
66
|
+
self.image_dead = pygame.image.load(f"res/img/{species}-dead.bmp")
|
|
67
|
+
self.image_baby = self.image_default
|
|
68
|
+
|
|
69
|
+
if "image_baby" in images:
|
|
70
|
+
self.image_baby = pygame.image.load(
|
|
71
|
+
f"res/img/{images['image_baby']}.bmp"
|
|
72
|
+
)
|
|
73
|
+
elif "image_egg" in images:
|
|
74
|
+
self.image_egg = pygame.image.load(f"res/img/{images['image_egg']}.bmp")
|
|
75
|
+
|
|
76
|
+
self.image = self.image_default
|
|
77
|
+
|
|
78
|
+
# `self.component_data`` is a dict of component names matched to any other child dict
|
|
79
|
+
# Example:
|
|
80
|
+
# { "Eating": { "diet": [ "grass", "mushroom" ] }, }
|
|
81
|
+
|
|
82
|
+
self.component_data = component_data
|
|
83
|
+
|
|
84
|
+
def die(self):
|
|
85
|
+
if not self.headless:
|
|
86
|
+
self.image = self.image_dead
|
|
87
|
+
|
|
88
|
+
self.is_dead = True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Ecosphere:
|
|
92
|
+
def __init__(self, _application_settings):
|
|
93
|
+
self.simulation_objects = (
|
|
94
|
+
[]
|
|
95
|
+
) # Set on every update call, do not change manually.
|
|
96
|
+
self.fauna: list = []
|
|
97
|
+
self.flora: list = []
|
|
98
|
+
self.bacteria: list = []
|
|
99
|
+
self.viruses: list = []
|
|
100
|
+
self.time = 0.0
|
|
101
|
+
self.application_settings = _application_settings
|
|
102
|
+
self.map = ecosphere_map_generate(self.application_settings)
|
|
103
|
+
|
|
104
|
+
def update(self) -> int:
|
|
105
|
+
if self.simulation_objects:
|
|
106
|
+
# Old list may contain out of bounds entitities
|
|
107
|
+
# We set self.simulation_objects to this new list in order to remove them
|
|
108
|
+
new_entities_list = []
|
|
109
|
+
living_entities_count = 0
|
|
110
|
+
|
|
111
|
+
for tile, col_index, row_index in ecosphere_map_yield_tile(
|
|
112
|
+
self.map,
|
|
113
|
+
width=self.application_settings.width,
|
|
114
|
+
height=self.application_settings.height,
|
|
115
|
+
):
|
|
116
|
+
simulation_object = tile.occupant
|
|
117
|
+
|
|
118
|
+
if simulation_object:
|
|
119
|
+
new_entities_list.append(simulation_object)
|
|
120
|
+
|
|
121
|
+
if simulation_object.age_current > simulation_object.age_maximum:
|
|
122
|
+
if not simulation_object.is_dead:
|
|
123
|
+
simulation_object.die() # Messed up, man. :C
|
|
124
|
+
elif (
|
|
125
|
+
simulation_object.decomposition_current
|
|
126
|
+
>= simulation_object.decomposition_maximum
|
|
127
|
+
):
|
|
128
|
+
if simulation_object.species == "fauna":
|
|
129
|
+
self.map[col_index][row_index].contaminated = True
|
|
130
|
+
|
|
131
|
+
self.map[col_index][row_index].occupant = None
|
|
132
|
+
|
|
133
|
+
self.simulation_objects.remove(simulation_object)
|
|
134
|
+
|
|
135
|
+
del simulation_object
|
|
136
|
+
else:
|
|
137
|
+
simulation_object.decomposition_current += 1
|
|
138
|
+
else:
|
|
139
|
+
living_entities_count += 1
|
|
140
|
+
simulation_object.age_current += 1
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
simulation_object.components
|
|
144
|
+
and not simulation_object.is_dead
|
|
145
|
+
):
|
|
146
|
+
for component in simulation_object.components:
|
|
147
|
+
component["callback"](
|
|
148
|
+
simulation_object,
|
|
149
|
+
component,
|
|
150
|
+
self.map,
|
|
151
|
+
self.application_settings,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
self.simulation_objects = new_entities_list
|
|
155
|
+
else:
|
|
156
|
+
print("No more simulation objects, ending simulation.")
|
|
157
|
+
|
|
158
|
+
return 1
|
|
159
|
+
|
|
160
|
+
return 0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pygame
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def simulation_object_yield_tile_neighbour(
|
|
9
|
+
_simobject_instance, _ecosphere_map, width, height
|
|
10
|
+
):
|
|
11
|
+
directions = [[-1, 0], [1, 0], [0, 1], [0, -1]] # Left # Right # Up # Down
|
|
12
|
+
|
|
13
|
+
for direction in directions:
|
|
14
|
+
check_position_x = _simobject_instance.position[0] + direction[0]
|
|
15
|
+
check_position_y = _simobject_instance.position[1] + direction[1]
|
|
16
|
+
|
|
17
|
+
if check_position_y > (width - 1):
|
|
18
|
+
check_position_y = width - 1
|
|
19
|
+
elif check_position_y < 0:
|
|
20
|
+
check_position_y = 0
|
|
21
|
+
|
|
22
|
+
if check_position_x > (height - 1):
|
|
23
|
+
check_position_x = height - 1
|
|
24
|
+
elif check_position_x < 0:
|
|
25
|
+
check_position_x = 0
|
|
26
|
+
|
|
27
|
+
tile = _ecosphere_map[check_position_x][check_position_y]
|
|
28
|
+
|
|
29
|
+
yield tile, check_position_x, check_position_y
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ecosphere_map_yield_tile(ecosphere_map, width, height):
|
|
33
|
+
position_x = 0
|
|
34
|
+
position_y = 0
|
|
35
|
+
|
|
36
|
+
for collumn in range(height):
|
|
37
|
+
for row in range(width):
|
|
38
|
+
yield ecosphere_map[collumn][row], position_x, position_y
|
|
39
|
+
|
|
40
|
+
position_y += 1
|
|
41
|
+
|
|
42
|
+
position_y = 0
|
|
43
|
+
position_x += 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ecosphere_map_generate(_application_settings) -> list:
|
|
47
|
+
print("Generating map...")
|
|
48
|
+
new_map = []
|
|
49
|
+
position_x = 0
|
|
50
|
+
position_y = 0
|
|
51
|
+
|
|
52
|
+
for collumn in range(_application_settings.height):
|
|
53
|
+
new_collumn = []
|
|
54
|
+
|
|
55
|
+
for row in range(_application_settings.width):
|
|
56
|
+
place_vegetation = bool(random.getrandbits(1))
|
|
57
|
+
|
|
58
|
+
if place_vegetation:
|
|
59
|
+
new_collumn.append(random.randint(1, 5))
|
|
60
|
+
else:
|
|
61
|
+
new_collumn.append(0)
|
|
62
|
+
|
|
63
|
+
position_y += 1
|
|
64
|
+
|
|
65
|
+
new_map.append(new_collumn)
|
|
66
|
+
|
|
67
|
+
position_x += 1
|
|
68
|
+
|
|
69
|
+
return new_map
|