Field.cpp 9.2 KB
Newer Older
1
2
3
4
#include <iostream>
#include <vector>
#include <algorithm>

5
6
#include "Field.h"

7
Field::Field(real_t w, real_t h, std::size_t food_parts, std::unique_ptr<UpdateTracker> update_tracker)
8
9
	: m_width(w)
	, m_height(h)
10
	, m_updateTracker(std::move(update_tracker))
11
12
	, m_foodMap(static_cast<size_t>(w), static_cast<size_t>(h), config::SPATIAL_MAP_RESERVE_COUNT)
	, m_segmentInfoMap(static_cast<size_t>(w), static_cast<size_t>(h), config::SPATIAL_MAP_RESERVE_COUNT)
13
	, m_threadPool(std::thread::hardware_concurrency())
14
{
Thomas Kolb's avatar
Thomas Kolb committed
15
16
17
18
19
20
21
	setupRandomness();
	createStaticFood(food_parts);
}

void Field::createStaticFood(std::size_t count)
{
	for(std::size_t i = 0; i < count; i++) {
Hubert Denkmair's avatar
Hubert Denkmair committed
22
23
24
		real_t value = (*m_foodSizeDistribution)(*m_rndGen);
		real_t x     = (*m_positionXDistribution)(*m_rndGen);
		real_t y     = (*m_positionYDistribution)(*m_rndGen);
Thomas Kolb's avatar
Thomas Kolb committed
25

Hubert Denkmair's avatar
Hubert Denkmair committed
26
27
28
		Food food {true, Vector2D(x,y), value};
		m_updateTracker->foodSpawned(food);
		m_foodMap.addElement(food);
Thomas Kolb's avatar
Thomas Kolb committed
29
30
31
32
33
34
35
36
	}
}

void Field::setupRandomness(void)
{
	std::random_device rd;
	m_rndGen = std::make_unique<std::mt19937>(rd());

Hubert Denkmair's avatar
Hubert Denkmair committed
37
	m_foodSizeDistribution = std::make_unique< std::normal_distribution<real_t> >(
Thomas Kolb's avatar
Thomas Kolb committed
38
39
40
			config::FOOD_SIZE_MEAN, config::FOOD_SIZE_STDDEV);

	m_positionXDistribution =
Hubert Denkmair's avatar
Hubert Denkmair committed
41
		std::make_unique< std::uniform_real_distribution<real_t> >(0, m_width);
Hubert Denkmair's avatar
Hubert Denkmair committed
42

Thomas Kolb's avatar
Thomas Kolb committed
43
	m_positionYDistribution =
Hubert Denkmair's avatar
Hubert Denkmair committed
44
		std::make_unique< std::uniform_real_distribution<real_t> >(0, m_height);
45

46
	m_angleRadDistribution =
Hubert Denkmair's avatar
Hubert Denkmair committed
47
		std::make_unique< std::uniform_real_distribution<real_t> >(-M_PI, M_PI);
48
49

	m_simple0To1Distribution =
Hubert Denkmair's avatar
Hubert Denkmair committed
50
		std::make_unique< std::uniform_real_distribution<real_t> >(0, 1);
51
52
}

53
54
55
56
57
58
59
60
61
62
void Field::updateSnakeSegmentMap()
{
	m_segmentInfoMap.clear();
	for (auto &b : m_bots)
	{
		for(auto &s : b->getSnake()->getSegments())
		{
			m_segmentInfoMap.addElement({s, b});
		}
	}
63
64
}

65
66
67
68
69
void Field::updateMaxSegmentRadius(void)
{
	m_maxSegmentRadius = 0;

	for(auto &b: m_bots) {
Hubert Denkmair's avatar
Hubert Denkmair committed
70
		real_t segmentRadius = b->getSnake()->getSegmentRadius();
71
72
73
74
75
76
77

		if(segmentRadius > m_maxSegmentRadius) {
			m_maxSegmentRadius = segmentRadius;
		}
	}
}

78
std::shared_ptr<Bot> Field::newBot(std::unique_ptr<db::BotScript> data, std::string& initErrorMessage)
79
{
Hubert Denkmair's avatar
Hubert Denkmair committed
80
81
	real_t x = (*m_positionXDistribution)(*m_rndGen);
	real_t y = (*m_positionYDistribution)(*m_rndGen);
Hubert Denkmair's avatar
Hubert Denkmair committed
82
	real_t heading = (*m_angleRadDistribution)(*m_rndGen);
83

Hubert Denkmair's avatar
Hubert Denkmair committed
84
85
86
87
88
89
90
	std::shared_ptr<Bot> bot = std::make_shared<Bot>(
		this,
		getCurrentFrame(),
		std::move(data),
		Vector2D(x,y),
		heading
	);
91

92
93
	initErrorMessage = "";
	if (bot->init(initErrorMessage))
Hubert Denkmair's avatar
Hubert Denkmair committed
94
	{
Thomas Kolb's avatar
Thomas Kolb committed
95
		std::cerr << "Created Bot with ID " << bot->getGUID() << ", DB-ID " << bot->getDatabaseId() << ", Name: " << bot->getName() << std::endl;
96
		m_updateTracker->botLogMessage(bot->getViewerKey(), "starting bot");
Hubert Denkmair's avatar
Hubert Denkmair committed
97
98
99
		m_updateTracker->botSpawned(bot);
		m_bots.insert(bot);
	}
100
101
102
103
104
	else
	{
		m_updateTracker->botLogMessage(bot->getViewerKey(), "cannot start bot: " + initErrorMessage);
	}
	return bot;
105
106
}

Hubert Denkmair's avatar
Hubert Denkmair committed
107
void Field::decayFood(void)
Thomas Kolb's avatar
Thomas Kolb committed
108
{
109
	for (Food &item: m_foodMap)
Hubert Denkmair's avatar
Hubert Denkmair committed
110
111
112
113
114
115
116
	{
		if (item.decay()) {
			m_updateTracker->foodDecayed(item);
			if (item.shallRegenerate())
			{
				createStaticFood(1);
			}
Thomas Kolb's avatar
Thomas Kolb committed
117
		}
118
	};
Hubert Denkmair's avatar
Hubert Denkmair committed
119
}
Thomas Kolb's avatar
Thomas Kolb committed
120

Hubert Denkmair's avatar
Hubert Denkmair committed
121
122
void Field::removeFood()
{
123
124
125
	m_foodMap.erase_if([](const Food& item) {
		return item.shallBeRemoved();
	});
Thomas Kolb's avatar
Thomas Kolb committed
126
127
}

Thomas Kolb's avatar
Thomas Kolb committed
128
129
void Field::consumeFood(void)
{
Hubert Denkmair's avatar
Hubert Denkmair committed
130
	size_t newStaticFood = 0;
131
	for (auto &b: m_bots) {
132
133
134
135
136
137
		auto headPos = b->getSnake()->getHeadPosition();
		auto radius = b->getSnake()->getSegmentRadius() * config::SNAKE_CONSUME_RANGE;

		for (auto& fi: m_foodMap.getRegion(headPos, radius))
		{
			if (b->getSnake()->tryConsume(fi))
138
			{
139
				b->updateConsumeStats(fi);
140
141
142
				m_updateTracker->foodConsumed(fi, b);
				fi.markForRemove();
				if (fi.shallRegenerate())
143
				{
144
					newStaticFood++;
145
146
				}
			}
147
		}
148
149

		b->getSnake()->ensureSizeMatchesMass();
Thomas Kolb's avatar
Thomas Kolb committed
150
	}
Hubert Denkmair's avatar
Hubert Denkmair committed
151
	createStaticFood(newStaticFood);
152
	updateMaxSegmentRadius();
Thomas Kolb's avatar
Thomas Kolb committed
153
154
}

155
156
void Field::moveAllBots(void)
{
157
	// first round: move all bots
158
	for(auto &b : m_bots) {
159
		std::unique_ptr<BotThreadPool::Job> job(new BotThreadPool::Job(BotThreadPool::Move, b));
160
161
162
		m_threadPool.addJob(std::move(job));
	}

163
	m_threadPool.waitForCompletion();
164

165
166
167
168
	// FIXME: make this work without temporary vector
	std::vector< std::unique_ptr<BotThreadPool::Job> > tmpJobs;
	tmpJobs.reserve(m_bots.size());

169
	std::unique_ptr<BotThreadPool::Job> job;
170
171
172
173
174
175
176
177
178
179
180
181
182
183
	while((job = m_threadPool.getProcessedJob()) != NULL) {
		tmpJobs.push_back(std::move(job));
	}

	// second round: collision check
	for(auto &j : tmpJobs) {
		j->jobType = BotThreadPool::CollisionCheck;
		m_threadPool.addJob(std::move(j));
	}

	m_threadPool.waitForCompletion();


	// collision check for all bots
184
	while((job = m_threadPool.getProcessedJob()) != NULL) {
185
186
		std::shared_ptr<Bot> victim = job->bot;
		std::size_t steps = job->steps;
187

188
		std::shared_ptr<Bot> killer = job->killer;
189

190
191
192
193
194
195
196
197
198
199
		if (killer) {
			// size check on killer
			double killerMass = killer->getSnake()->getMass();
			double victimMass = victim->getSnake()->getMass();

			if(killerMass > (victimMass * config::KILLER_MIN_MASS_RATIO)) {
				// collision detected and killer is large enough
				// -> convert the colliding bot to food
				killBot(victim, killer);
			}
200
201
		} else {
			// no collision, bot still alive
202
			m_updateTracker->botMoved(victim, steps);
203
204
205
206
207
208
209
210
211
212
213
214

			if(victim->getSnake()->boostedLastMove()) {
				real_t lossValue =
					config::SNAKE_BOOST_LOSS_FACTOR * victim->getSnake()->getMass();

				victim->getSnake()->dropFood(lossValue);

				if(victim->getSnake()->getMass() < config::SNAKE_SELF_KILL_MASS_THESHOLD) {
					// Bot is now too small, so it dies
					killBot(victim, victim);
				}
			}
215
216
217

			// adjust size to new mass
			victim->getSnake()->ensureSizeMatchesMass();
218
		}
219
	}
220
221

	updateSnakeSegmentMap();
222
223
}

Hubert Denkmair's avatar
Hubert Denkmair committed
224
225
226
227
228
229
230
231
232
233
234
235
236
void Field::processLog()
{
	for (auto &b : m_bots)
	{
		for (auto &msg: b->getLogMessages())
		{
			m_updateTracker->botLogMessage(b->getViewerKey(), msg);
		}
		b->clearLogMessages();
		b->increaseLogCredit();
	}
}

Hubert Denkmair's avatar
Hubert Denkmair committed
237
void Field::tick()
238
{
Hubert Denkmair's avatar
Hubert Denkmair committed
239
240
	m_currentFrame++;
	m_updateTracker->tick(m_currentFrame);
241
242
}

243
void Field::sendStatsToStream(void)
244
245
246
247
248
249
{
	for(auto &bot: m_bots) {
		m_updateTracker->botStats(bot);
	}
}

Thomas Kolb's avatar
Thomas Kolb committed
250
const Field::BotSet& Field::getBots(void) const
251
252
253
254
{
	return m_bots;
}

255
256
257
258
259
260
261
262
263
264
265
266
std::shared_ptr<Bot> Field::getBotByDatabaseId(int id)
{
	for (auto& bot: m_bots)
	{
		if (bot->getDatabaseId() == id)
		{
			return bot;
		}
	}
	return nullptr;
}

267
268
void Field::createDynamicFood(real_t totalValue, const Vector2D &center, real_t radius,
		const std::shared_ptr<Bot> &hunter)
269
{
270
	real_t remainingValue = totalValue;
271

272
	while(remainingValue > 0) {
273
		real_t value;
274
		if(remainingValue > config::FOOD_SIZE_MEAN) {
275
276
			value = (*m_foodSizeDistribution)(*m_rndGen);
		} else {
277
			value = remainingValue;
278
		}
279

Hubert Denkmair's avatar
Hubert Denkmair committed
280
281
		real_t rndRadius = radius * (*m_simple0To1Distribution)(*m_rndGen);
		real_t rndAngle = (*m_angleRadDistribution)(*m_rndGen);
Thomas Kolb's avatar
Thomas Kolb committed
282
283
284

		Vector2D offset(cos(rndAngle), sin(rndAngle));
		offset *= rndRadius;
285

286
		Vector2D pos = wrapCoords(center + offset);
287

288
		Food food {false, pos, value, hunter};
Hubert Denkmair's avatar
Hubert Denkmair committed
289
290
		m_updateTracker->foodSpawned(food);
		m_foodMap.addElement(food);
291
292

		remainingValue -= value;
293
294
295
	}
}

296
Vector2D Field::wrapCoords(const Vector2D &v) const
297
{
Hubert Denkmair's avatar
Hubert Denkmair committed
298
299
	real_t x = v.x();
	real_t y = v.y();
300

301
302
	while(x < 0) {
		x += m_width;
303
304
	}

305
306
	while(x > m_width) {
		x -= m_width;
307
308
	}

309
310
	while(y < 0) {
		y += m_height;
311
312
	}

313
314
	while(y > m_height) {
		y -= m_height;
315
316
	}

317
	return {x, y};
318
319
}

320
Vector2D Field::unwrapCoords(const Vector2D &v, const Vector2D &ref) const
321
{
Hubert Denkmair's avatar
Hubert Denkmair committed
322
323
	real_t x = v.x();
	real_t y = v.y();
324

325
326
	while((x - ref.x()) < -m_width/2) {
		x += m_width;
327
328
	}

329
330
	while((x - ref.x()) > m_width/2) {
		x -= m_width;
331
332
	}

333
334
	while((y - ref.y()) < -m_height/2) {
		y += m_height;
335
336
	}

337
338
	while((y - ref.y()) > m_height/2) {
		y -= m_height;
339
340
	}

341
	return {x, y};
342
}
343

344
345
Vector2D Field::unwrapRelativeCoords(const Vector2D& relativeCoords) const
{
Hubert Denkmair's avatar
Hubert Denkmair committed
346
347
	real_t x = fmod(relativeCoords.x(), m_width);
	real_t y = fmod(relativeCoords.y(), m_height);
348
349
350
351
352
353
354
	if (x > m_width/2) { x -= m_width; }
	if (x < (-(int)m_width/2)) { x += m_width; }
	if (y > m_height/2) { y -= m_height; }
	if (y < (-(int)m_height/2)) { y += m_height; }
	return Vector2D { x, y };
}

355
356
357
358
359
360
361
void Field::debugVisualization(void)
{
	size_t intW = static_cast<size_t>(m_width);
	size_t intH = static_cast<size_t>(m_height);

	std::vector<char> rep(intW*intH);

Thomas Kolb's avatar
Thomas Kolb committed
362
	// empty cells are dots
363
364
	std::fill(rep.begin(), rep.end(), '.');

Thomas Kolb's avatar
Thomas Kolb committed
365
	// draw snakes (head = #, rest = +)
366
367
368
369
370
	for(auto &b: m_bots) {
		std::shared_ptr<Snake> snake = b->getSnake();

		bool first = true;
		for(auto &seg: snake->getSegments()) {
371
372
			size_t x = static_cast<size_t>(seg.pos().x());
			size_t y = static_cast<size_t>(seg.pos().y());
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392

			if(first) {
				rep[y*intW + x] = '#';
				first = false;
			} else {
				rep[y*intW + x] = '+';
			}
		}
	}

	for(std::size_t y = 0; y < intH; y++) {
		for(std::size_t x = 0; x < intW; x++) {
			std::cout << rep[y*intW + x];
		}

		std::cout << "\n";
	}

	std::cout << std::endl;
}
Thomas Kolb's avatar
Thomas Kolb committed
393

394
Vector2D Field::getSize(void) const
Thomas Kolb's avatar
Thomas Kolb committed
395
{
396
	return Vector2D(m_width, m_height);
Thomas Kolb's avatar
Thomas Kolb committed
397
}
398

Hubert Denkmair's avatar
Hubert Denkmair committed
399
real_t Field::getMaxSegmentRadius(void) const
400
401
402
{
	return m_maxSegmentRadius;
}
Hubert Denkmair's avatar
Hubert Denkmair committed
403
404
405
406
407
408

void Field::addBotKilledCallback(Field::BotKilledCallback callback)
{
	m_botKilledCallbacks.push_back(callback);
}

Hubert Denkmair's avatar
Hubert Denkmair committed
409
void Field::killBot(std::shared_ptr<Bot> victim, std::shared_ptr<Bot> killer)
Hubert Denkmair's avatar
Hubert Denkmair committed
410
{
411
	victim->getSnake()->convertToFood(killer);
Hubert Denkmair's avatar
Hubert Denkmair committed
412
413
414
415
	m_bots.erase(victim);
	m_updateTracker->botKilled(killer, victim);

	// bot will eventually be recreated in callbacks
Hubert Denkmair's avatar
Hubert Denkmair committed
416
417
418
419
420
	for (auto& callback: m_botKilledCallbacks)
	{
		callback(victim, killer);
	}
}