/
organism.cpp
385 lines (360 loc) · 14.4 KB
/
organism.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
#include "organism.hpp"
#include "dna.cpp"
#include <random>
#include <chrono>
#define DEBUG 1
std::bernoulli_distribution breeding_probability(0.1);
std::bernoulli_distribution attack_probability(0.3);
sista::Field* field = nullptr;
#if DEBUG
std::ofstream debug("debug.txt");
#endif
struct Range {
int start;
int stop;
int step;
};
Entity::Entity(char symbol_, sista::Coordinates coordinates_, ANSI::Settings settings_):
sista::Pawn(symbol_, coordinates_, settings_) {
is_food = false;
}
Entity::Entity(char symbol_, sista::Coordinates coordinates_, ANSI::Settings& settings_, bool by_reference_):
sista::Pawn(symbol_, coordinates_, settings_, by_reference_) {
is_food = false;
}
Entity::~Entity() {}
Food::Food(sista::Coordinates coordinates_):
Entity('@', coordinates_, ANSI::Settings(ANSI::ForegroundColor::F_GREEN, ANSI::BackgroundColor::B_BLACK, ANSI::Attribute::BRIGHT)) {
foods.push_back(this);
is_food = true;
energy = random_engine() % 10 + 1;
}
Food::~Food() {}
Organism::Organism(char symbol_, sista::Coordinates coordinates_, ANSI::Settings settings_, DNA* dna_, Statistics stats_):
Entity(symbol_, coordinates_, settings_), dna(dna_), stats(stats_) {
Organism::organisms.push_back(this);
health = dna->genes.at(Gene::STRENGTH)->value*10;
left = std::pow(dna->genes.at(Gene::LIFESPAN)->value, 2)*100;
has_given_birth = false;
this->id = id_counter++;
stats.age = 0;
}
Organism::Organism(char symbol_, sista::Coordinates coordinates_, ANSI::Settings& settings_, DNA* dna_, Statistics stats_, bool by_reference_):
Entity(symbol_, coordinates_, settings_, by_reference_), dna(dna_), stats(stats_) {
Organism::organisms.push_back(this);
health = dna->genes.at(Gene::STRENGTH)->value*10;
left = dna->genes.at(Gene::LIFESPAN)->value;
has_given_birth = false;
this->id = id_counter++;
stats.age = 0;
}
Organism::~Organism() {
delete dna;
}
void Organism::move() {
std::bernoulli_distribution moving_probability(0.2*dna->genes.at(Gene::SPEED)->value);
if (moving_probability(random_engine)) {
sista::Coordinates new_coordinates = coordinates;
do {
new_coordinates = coordinates;
switch (random_engine() % 4) {
case 0: {
new_coordinates.x++;
break;
}
case 1: {
new_coordinates.x--;
break;
}
case 2: {
new_coordinates.y++;
break;
}
case 3: {
new_coordinates.y--;
break;
}
}
} while (field->isOutOfBounds(new_coordinates) || new_coordinates == coordinates);
if (field->isOccupied(new_coordinates)) {
Entity* other = nullptr;
for (Food* food : Food::foods) {
if (food->getCoordinates() == new_coordinates) {
other = (Entity*)food;
break;
}
}
for (Organism* organism : Organism::organisms) {
if (organism->getCoordinates() == new_coordinates) {
other = (Entity*)organism;
break;
}
}
if (other == nullptr) {
return;
}
this->meet(other);
} else {
field->movePawn(this, new_coordinates);
coordinates = new_coordinates;
}
}
}
void Organism::meet(Entity* other) {
if (other == nullptr) {
return;
}
if (other->is_food) {
this->eat((Food*)other);
} else {
this->meet((Organism*)other);
}
}
void Organism::meet(Organism* other) {
#if DEBUG
debug << this << " is trying to meet with " << other << std::endl;
debug << "\t" << this << " stats: {" << this->stats.age << ", " << this->stats.generation << ", ";
debug << "{" << this->stats.parents[0] << ", " << this->stats.parents[1] << "}}";
debug << " output: {'" << this->symbol << "', {" << this->getCoordinates().y << ", " << this->getCoordinates().x << "}}\n";
debug << "\t" << other << " stats: {" << other->stats.age << ", " << other->stats.generation << ", ";
debug << "{" << other->stats.parents[0] << ", " << other->stats.parents[1] << "}}";
debug << " output: {'" << other->symbol << "', {" << other->getCoordinates().y << ", " << other->getCoordinates().x << "}}\n";
debug << std::flush;
#endif
if (attack_probability(random_engine)) {
this->attack(other);
} else if (breeding_probability(random_engine)) {
this->breed(other);
}
}
void Organism::breed(Organism* other) {
if (stats.age < 10 || other->stats.age < 10)
return;
// The youngest organism is more likely to breed
if (stats.age > other->stats.age && (random_engine() % 3 != 0)) {
debug << this << " is younger than " << other << std::endl;
return other->breed(this);
}
if (!breedable(other))
return;
if (this->has_given_birth || other->has_given_birth)
return; // Can't give birth more than once for each frame
this->has_given_birth = true;
#if DEBUG
debug << "BREED!";
debug << this << " with " << other << std::endl;
debug << "\t" << "'" << this->symbol << "' ";
this->dna->printInline(debug);
debug << std::endl;
debug << "\t" << "'" << other->symbol << "' ";
other->dna->printInline(debug);
debug << std::endl;
#endif
std::vector<Organism*> children;
for (int i = 0; i < dna->genes.at(Gene::FERTILITY)->value; i++) {
// Combine the DNA of the two organisms
DNA* new_dna = dna->combine(other->dna);
// Create a new organism with the new DNA
Organism* child = new Organism(
symbol, coordinates, settings, new_dna, // Coordinates will be changed later
{0, stats.generation + 1, {this, other}, {}}
);
// Add the child to the parents' children
stats.children.push_back(child);
other->stats.children.push_back(child);
children.push_back(child);
#if DEBUG
debug << "\t" << child << " is born with ";
child->dna->printInline(debug);
debug << std::endl;
#endif
}
// Now we have to place the children in the field
for (Organism* child : children) {
sista::Coordinates new_coordinates = coordinates;
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
int random = rand() % 4;
for (int i = -10; i < 10; i++) {
for (int j = -10; j < 10; j++) {
if (i == 0 && j == 0) {
continue;
}
if (random == 0) {
i = -i;
} else if (random == 1) {
j = -j;
} else if (random == 2) {
i = -i;
j = -j;
}
new_coordinates.y = coordinates.y + i;
new_coordinates.x = coordinates.x + j;
if (field->isOutOfBounds(new_coordinates)) {
continue;
}
if (field->isFree(new_coordinates)) {
#if DEBUG
debug << "\t" << child << " is placed at delta {" << i << ", " << j << "}" << std::endl;
#endif
goto found;
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::duration<double>>(end - begin).count() > 0.1) {
goto not_found;
}
}
}
goto not_found;
found:
child->coordinates = new_coordinates;
field->addPawn((sista::Pawn*)child);
continue;
not_found:
#if DEBUG
debug << "\t" << child << " couldn't find a place to be born" << std::endl;
#endif
organisms.erase(std::find(organisms.begin(), organisms.end(), child));
children.erase(std::find(children.begin(), children.end(), child));
delete child;
return; // There's no space for other children
}
}
void Organism::attack(Organism* other) {
// First we get the nature of the organisms
Nature this_nature = (Nature)dna->genes.at(Gene::NATURE)->value;
Nature other_nature = (Nature)other->dna->genes.at(Gene::NATURE)->value;
// Then we check if the attack is possible and if it is, we do it
if (this_nature == Nature::NEUTRAL) { // Won't attack
return;
} else if (this_nature == Nature::PASSIVE) { // May have provoked the other
if (other_nature == Nature::AGGRESSIVE) {
other->attack(this);
return;
}
} else if (this_nature == Nature::AGGRESSIVE) { // Will attack
#if DEBUG
debug << "ATTACK!";
debug << this << " attacks " << other << ", whom nature is " << other_nature << std::endl;
#endif
int this_attack = dna->genes.at(Gene::ATTACK)->value;
int this_defense = dna->genes.at(Gene::DEFENSE)->value;
int other_defense = other->dna->genes.at(Gene::DEFENSE)->value;
int other_attack = other->dna->genes.at(Gene::ATTACK)->value;
if (other_nature == Nature::PASSIVE) { // Will only defend
int damage_done = std::max(this_attack - other_defense, 0);
int damage_taken = std::max(other_defense, 0);
health -= damage_taken;
other->health -= damage_done;
} else if (other_nature == Nature::AGGRESSIVE) { // Will both attack and defend
int damage_done = std::max(this_attack - other_defense, 0);
int damage_taken = std::max(other_attack - this_defense, 0);
damage_done += std::max(other_defense, 0);
damage_taken += std::max(this_defense, 0);
while (health > 0 && other->health > 0) {
health -= damage_taken;
other->health -= damage_done;
} // At least one of the two will be dead, they're fighting to the death
} else if (other_nature == Nature::NEUTRAL) { // Won't fight back
int damage_done = std::max(this_attack, 0);
other->health -= damage_done;
} else if (other_nature > Nature::NEUTRAL) { // So peaceful that it won't take nor give damage
return;
}
} else if (this_nature < (int)Nature::AGGRESSIVE) { // Will attack only if the other is aggressive or over...
// ...then the most aggressive organism will win without taking any damage
if (other_nature <= Nature::AGGRESSIVE) {
if (this_nature > other_nature) {
other->health = 0;
} else {
health = 0;
}
}
}
// Check if someone died
if (health <= 0) {
#if DEBUG
debug << "\t" << this << " was defeated" << std::endl;
#endif
dead_organisms.push_back(this);
}
if (other->health <= 0) {
#if DEBUG
debug << "\t" << other << " was defeated" << std::endl;
#endif
dead_organisms.push_back(other);
if (other_nature != Nature::AGGRESSIVE) {
// Eat the other organism
#if DEBUG
debug << "\t" << this << " gains " << other->dna->genes.at(Gene::STRENGTH)->value*5 << " health points eating " << other << std::endl;
#endif
health += other->dna->genes.at(Gene::STRENGTH)->value*5;
}
}
}
void Organism::eat(Food* food) {
if (food == nullptr) {
return;
}
#if DEBUG
debug << "EAT! ";
debug << this << " eats " << food << std::endl;
#endif
health += food->energy;
health = std::min(dna->genes.at(Gene::STRENGTH)->value*10, health);
field->removePawn(food);
Food::foods.erase(std::find(Food::foods.begin(), Food::foods.end(), food));
delete food;
}
bool Organism::breedable(const Organism* other) const {
if (other == nullptr) {
#if DEBUG
debug << "\t" << this << " can't breed with " << other << " because it's nullptr" << std::endl;
#endif
return false;
}
if (this->id == other->id) {
return false;
}
if (this->symbol == other->symbol) {
if (this->stats.parents[0] == other || this->stats.parents[1] == other)
return false; // Can't breed with its parent
if (other->stats.parents[0] == this || other->stats.parents[1] == this)
return false; // Can't breed with its child
if (this->stats.parents[0] == other->stats.parents[0] || this->stats.parents[0] == other->stats.parents[1])
return false; // Can't breed with its sibling
if (this->stats.parents[1] == other->stats.parents[0] || this->stats.parents[1] == other->stats.parents[1])
return false; // Can't breed with its sibling
if (random_device() % 2 && random_device() % 2)
return false; // Reduce to 25% the probability of breeding with an organism of the same strain (or, at least, with the same external appearance)
}
DNA* other_dna = other->dna;
#if DEBUG
debug << "\t" << this << " is trying to breed with " << other << std::endl;
debug << "\t\t";
this->dna->printInline(debug);
debug << "\n\t\t";
other_dna->printInline(debug);
debug << std::endl;
#endif
int too_different_alleles = 0;
for (Allele* allele : dna->alleles) {
int other_value = other_dna->genes.at(allele->name)->value;
if (!std::max(allele->value, other_value)) {
continue;
}
int distance_coefficient = std::abs(allele->value - other_value) / std::max(allele->value, other_value);
if (distance_coefficient > 0.5) {
too_different_alleles++;
}
}
if (too_different_alleles >= 3) {
#if DEBUG
debug << "\t" << this << " can't breed with " << other << " because they have " << too_different_alleles << " too different alleles" << std::endl;
#endif
} else {
#if DEBUG
debug << "\t" << this << " can breed with " << other << " because they have only " << too_different_alleles << " too different alleles" << std::endl;
#endif
}
return too_different_alleles < 3;
}