Field.cpp 9.21 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

Thomas Kolb's avatar
Thomas Kolb committed
92
93
	std::cerr << "Initializing Bot with ID " << bot->getGUID() << ", DB-ID " << bot->getDatabaseId() << ", Name: " << bot->getName() << std::endl;

94
95
	initErrorMessage = "";
	if (bot->init(initErrorMessage))
Hubert Denkmair's avatar
Hubert Denkmair committed
96
	{
97
		m_updateTracker->botLogMessage(bot->getViewerKey(), "starting bot");
Hubert Denkmair's avatar
Hubert Denkmair committed
98
99
100
		m_updateTracker->botSpawned(bot);
		m_bots.insert(bot);
	}
101
102
103
104
105
	else
	{
		m_updateTracker->botLogMessage(bot->getViewerKey(), "cannot start bot: " + initErrorMessage);
	}
	return bot;
106
107
}

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

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

Thomas Kolb's avatar
Thomas Kolb committed
129
130
void Field::consumeFood(void)
{
Hubert Denkmair's avatar
Hubert Denkmair committed
131
	size_t newStaticFood = 0;
132
	for (auto &b: m_bots) {
133
134
135
136
137
138
		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))
139
			{
140
				b->updateConsumeStats(fi);
141
142
143
				m_updateTracker->foodConsumed(fi, b);
				fi.markForRemove();
				if (fi.shallRegenerate())
144
				{
145
					newStaticFood++;
146
147
				}
			}
148
		}
149
150

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

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

164
	m_threadPool.waitForCompletion();
165

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

170
	std::unique_ptr<BotThreadPool::Job> job;
171
172
173
174
175
176
177
178
179
180
181
182
183
184
	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
185
	while((job = m_threadPool.getProcessedJob()) != NULL) {
186
187
		std::shared_ptr<Bot> victim = job->bot;
		std::size_t steps = job->steps;
188

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

191
192
193
194
195
196
197
198
199
200
		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);
			}
201
202
		} else {
			// no collision, bot still alive
203
			m_updateTracker->botMoved(victim, steps);
204
205
206
207
208
209
210
211
212
213
214
215

			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);
				}
			}
216
217
218

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

	updateSnakeSegmentMap();
223
224
}

Hubert Denkmair's avatar
Hubert Denkmair committed
225
226
227
228
229
230
231
232
233
234
235
236
237
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
238
void Field::tick()
239
{
Hubert Denkmair's avatar
Hubert Denkmair committed
240
241
	m_currentFrame++;
	m_updateTracker->tick(m_currentFrame);
242
243
}

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

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

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

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

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

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

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

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

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

		remainingValue -= value;
294
295
296
	}
}

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

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

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

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

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

318
	return {x, y};
319
320
}

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

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

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

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

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

342
	return {x, y};
343
}
344

345
346
Vector2D Field::unwrapRelativeCoords(const Vector2D& relativeCoords) const
{
Hubert Denkmair's avatar
Hubert Denkmair committed
347
348
	real_t x = fmod(relativeCoords.x(), m_width);
	real_t y = fmod(relativeCoords.y(), m_height);
349
350
351
352
353
354
355
	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 };
}

356
357
358
359
360
361
362
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
363
	// empty cells are dots
364
365
	std::fill(rep.begin(), rep.end(), '.');

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

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

			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
394

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

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

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

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

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