summaryrefslogtreecommitdiffstats
path: root/pacman-c++/server/server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pacman-c++/server/server.cpp')
-rw-r--r--pacman-c++/server/server.cpp971
1 files changed, 971 insertions, 0 deletions
diff --git a/pacman-c++/server/server.cpp b/pacman-c++/server/server.cpp
new file mode 100644
index 0000000..c9e4fff
--- /dev/null
+++ b/pacman-c++/server/server.cpp
@@ -0,0 +1,971 @@
1#include "server.h"
2#include "util.h"
3#include "pacman.pb.h"
4#include "block.h"
5#include "anyoption.h"
6#include "bonuspoint.h"
7#include "point.h"
8#include <QTextStream>
9#include <algorithm>
10
11Server::Server(QWidget *parent)
12 : SceneHolder(parent), m_host(NULL), m_bindaddress(QHostAddress::Any),
13 m_port(Constants::Networking::port), m_numbots(0),
14 m_rounds(3), m_curRound(0), m_running(false), m_finishRound(false)
15{
16 /* determine max players by using order array */
17 for(m_maxplayers = 0; Color::order[m_maxplayers] != Color::none; ++m_maxplayers);
18}
19
20Server::~Server()
21{
22 if (m_host != NULL)
23 {
24 foreach(ENetPeer *peer, m_clientConnections.keys())
25 enet_peer_disconnect(peer, 0);
26
27 /* allow up to 3 seconds for the disconnect to succeed
28 * and drop any packets received packets
29 */
30 ENetEvent event;
31 while (m_clientConnections.count() > 0
32 && enet_host_service(m_host, &event, 3000) > 0)
33 {
34 switch (event.type)
35 {
36 case ENET_EVENT_TYPE_RECEIVE:
37 enet_packet_destroy(event.packet);
38 break;
39 case ENET_EVENT_TYPE_DISCONNECT:
40 m_clientConnections.remove(event.peer);
41 return;
42 default:
43 break;
44 }
45 }
46
47 enet_host_destroy(m_host);
48 }
49}
50
51bool Server::run()
52{
53 /* create eating order list first */
54 for(unsigned int i = 0; i < m_maxplayers; ++i)
55 m_eatingorder.append(Color::order[i]);
56 m_eatingorder.append(m_eatingorder.at(0));
57
58 m_tickTimer = new QTimer(this);
59 m_tickTimer->setInterval(Constants::tick);
60 connect(m_tickTimer, SIGNAL(timeout()), this, SLOT(tick()));
61
62 std::cout << "[Server] Running server..." << std::endl
63 << "[Server] Max players: " << m_maxplayers << std::endl
64 << "[Server] Number of bots: " << m_numbots << std::endl
65 << "[Server] Number of rounds: " << m_rounds << std::endl;
66 if (!waitForClientConnections())
67 return false;
68
69 connect(this, SIGNAL(allPointsRemoved()), this, SLOT(setFinishRound()));
70 initRoundMap();
71 return true;
72}
73
74void Server::tick()
75{
76 //qDebug() << "[Tick] Doing server update";
77 if (m_finishRound)
78 stopGame(true);
79 if (!m_running)
80 {
81 Transmission::map_t map = Util::createEmptyMap();
82 sendUpdate(map);
83 Util::deleteMap(map);
84 return;
85 }
86
87 /* fetch key updates */
88 keyPressUpdate();
89
90 /* let the bots move */
91 foreach (Color::Color color, m_bots)
92 botCalculate(m_actors[color]);
93
94 /* move on the virtual map */
95 Transmission::map_t map = calculateUpdates();
96 updateMap(map);
97
98 /* add a random bonus point */
99 QPoint pos = addRandomPoint(map, Transmission::bonuspoint);
100 if (!pos.isNull())
101 updateMap(map, pos.x(), pos.y());
102
103 /* add/remove random colorized block */
104 if (this->property("coloredblocks").toBool())
105 colorizeBlocks(map);
106
107 sendUpdate(map);
108 Util::deleteMap(map);
109}
110
111Transmission::map_t Server::calculateUpdates()
112{
113 QMap<Color::Color, QPair<QPoint, QPoint> > movements;
114 Transmission::map_t map = Util::createEmptyMap();
115
116 QMutableMapIterator<Color::Color, Actor::Movement> i(m_actorMovements);
117 while (i.hasNext())
118 {
119 i.next();
120 Color::Color color = i.key();
121 Actor *actor = m_actors[color];
122 QPoint mapPosition = CoordToMapPosition(actor->pos().toPoint());
123 Actor::Movement direction = i.value();
124 int turn = 0;
125
126invalid_direction:
127 ++turn;
128 qDebug() << "[Calc] Actor wants to move: color=" << color
129 << "pos=" << mapPosition << "direction=" << direction;
130
131 QPoint newMapPosition = mapPosition + Actor::movementToPoint(direction);
132 if (newMapPosition.x() < 0)
133 newMapPosition.setX(0);
134 if (newMapPosition.x() >= visualMap.size())
135 newMapPosition.setX(visualMap.size() - 1);
136 if (newMapPosition.y() < 0)
137 newMapPosition.setY(0);
138 if (newMapPosition.y() >= visualMap[newMapPosition.x()].size())
139 newMapPosition.setY(visualMap[newMapPosition.x()].size() - 1);
140
141 /* check if there's an item at new location of actor */
142 GameEntity *item = visualMap[newMapPosition.x()][newMapPosition.y()];
143 GameEntity *oldItem = visualMap[mapPosition.x()][mapPosition.y()];
144 if (item != NULL && oldItem != item)
145 {
146 qDebug() << "[Calc] Found item at new actor location";
147 if (!item->checkEnter(actor))
148 {
149 /* movement invalid. e.g. move against wall */
150 newMapPosition = mapPosition;
151 qDebug() << "[Calc] Item blocks actor";
152 }
153 else
154 {
155 /* apply actions of entering this field */
156 GameEntity::EnteredState survive = item->enter(actor);
157 if (survive == GameEntity::DestroyedEntity)
158 map[newMapPosition.x()][newMapPosition.y()] = Transmission::empty | actor->color();
159 else if (survive == GameEntity::DestroyedActor)
160 {
161 map[newMapPosition.x()][newMapPosition.y()] = Transmission::death | actor->color();
162 m_actors[item->color()]->addRoundPoints(actor->getRoundPoints());
163 actor->finishRound(true);
164 setFinishRound();
165 }
166 }
167 }
168
169 /* movement didn't work - e.g. was blocked */
170 if (mapPosition == newMapPosition)
171 {
172 /* check turn */
173 if (turn == 1 && direction != actor->direction())
174 {
175 /* set direction back to last known direction and try again */
176 qDebug() << "[Calc] Movement was blocked. Trying last known actor direction";
177 direction = actor->direction();
178 goto invalid_direction;
179 }
180 else
181 {
182 /* second turn didn't work too -> stop movement */
183 direction = Actor::None;
184 qDebug() << "[Calc] No good direction known. Movement stopped";
185 }
186 }
187
188 /* store movement for collision */
189 movements.insert(color, QPair<QPoint, QPoint>(mapPosition, newMapPosition));
190
191 /* set direction (used for collision detection) */
192 actor->setDirection(direction);
193
194 /* DEBUG: uncomments to disable auto-movement */
195 //direction = Actor::None;
196
197 /* actor is not moving anymore: remove from list */
198 if (direction == Actor::None)
199 i.remove();
200 }
201
202 /* check for actor collision */
203 QList<Actor *> blocked;
204 foreach(Color::Color color, movements.keys())
205 {
206 Actor *actor = m_actors[color];
207 QPoint oldpos = movements[color].first;
208 QPoint newpos = movements[color].second;
209 QPoint scenepos = actor->pos().toPoint();
210
211 /* first move actor to new position */
212 actor->move(actor->direction());
213
214 /* next check for collisions */
215 Actor *orderer = NULL;
216 Actor *meal = NULL;
217 foreach(Actor *other, m_actors)
218 {
219 if (actor == other)
220 continue;
221 if (!actor->collidesWithItem(other))
222 continue;
223 /* both move in the same direction */
224 if (actor->direction() == other->direction())
225 continue;
226
227 if (other->canEat(actor, m_eatingorder))
228 {
229 qDebug() << "[Collision] Actor" << actor->color() << "got EATEN by" << other->color();
230 orderer = other;
231 meal = actor;
232 break;
233 }
234 else if (actor->canEat(other, m_eatingorder))
235 {
236 qDebug() << "[Collision] Actor" << actor->color() << "EATS" << other->color();
237 orderer = actor;
238 meal = other;
239 blocked.append(other);
240 break;
241 }
242 else
243 {
244 qDebug() << "[Collision] Actor" << actor->color() << "got BLOCKED by" << other->color();
245 blocked.append(actor);
246 /* no break here */
247 }
248 }
249
250 /* update map depending on collision */
251 if (orderer != NULL && meal != NULL)
252 {
253 map[newpos.x()][newpos.y()] |= Transmission::pacman | Transmission::death
254 | orderer->color() | meal->color();
255 orderer->addRoundPoints(meal->getRoundPoints());
256 meal->finishRound(true);
257 setFinishRound();
258 }
259 else if (blocked.contains(actor))
260 {
261 /* move actor back early cause he won't move */
262 actor->setPos(scenepos);
263 map[oldpos.x()][oldpos.y()] |= Transmission::pacman | color;
264 }
265 else
266 {
267 map[newpos.x()][newpos.y()] |= Transmission::pacman | color;
268 }
269 }
270
271 /* move all actors back to their origin position */
272 foreach(Color::Color color, movements.keys())
273 m_actors[color]->setPos(mapPositionToCoord(movements[color].first));
274
275#ifndef QT_NO_DEBUG
276 /* collision sanity check: colors must be unique */
277 foreach(Color::Color col, m_eatingorder.toSet())
278 {
279 QList<QPoint> found;
280 for (unsigned int x = 0; x < Constants::map_size.width; ++x)
281 {
282 for (unsigned int y = 0; y < Constants::map_size.height; ++y)
283 {
284 if ((map[x][y] & Transmission::color_mask) & col)
285 found.append(QPoint(x, y));
286 }
287 }
288 if (found.count() > 1)
289 qCritical() << "[Collision] found" << found.count() << "fields with color=" << col;
290 }
291#endif
292
293 return map;
294}
295
296QPoint Server::addRandomPoint(Transmission::map_t map, Transmission::field_t type)
297{
298 int chance = Constants::Game::bouns_point_chance
299 - Constants::Game::bouns_point_chance_playerfactor * (m_maxplayers - 1);
300 int rand = (int) (chance * (qrand() / (RAND_MAX + 1.0)));
301 if (rand != 0)
302 return QPoint();
303
304 /* get list of valid positions */
305 QList<QPoint> validpos;
306 for (unsigned int x = 0; x < Constants::map_size.width; ++x)
307 {
308 for (unsigned int y = 0; y < Constants::map_size.height; ++y)
309 {
310 if (visualMap[x][y] == NULL)
311 validpos.append(QPoint(x, y));
312 }
313 }
314
315 /* remove actors positions from list
316 * performance would be better if actors would be listed in visualMap too
317 * but this isn't possible that easily. see comment in updateMap(map)
318 */
319 foreach (Actor *actor, m_actors)
320 {
321 QPoint actorpos = CoordToMapPosition(actor->pos().toPoint());
322 validpos.removeAll(actorpos);
323 }
324
325 if (validpos.empty())
326 return QPoint();
327
328 rand = (int) (validpos.size() * (qrand() / (RAND_MAX + 1.0)));
329 QPoint pos = validpos.at(rand);
330 map[pos.x()][pos.y()] = type;
331 return pos;
332}
333
334void Server::colorizeBlocks(Transmission::map_t map)
335{
336 /* decrement tickcount of colored blocks */
337 if (!m_coloredBlocks.empty())
338 {
339 QMutableMapIterator<QPoint, unsigned int> i(m_coloredBlocks);
340 while(i.hasNext())
341 {
342 i.next();
343 unsigned val = i.value();
344 if (val > 0)
345 i.setValue(--val);
346 else
347 {
348 QPoint block = i.key();
349 /* check for actor collision */
350 bool skip = false;
351 foreach (Actor *actor, m_actors)
352 {
353 if (CoordToMapPosition(actor->pos().toPoint()) == block)
354 skip = true;
355 if (skip)
356 break;
357 }
358 if (skip)
359 continue;
360
361 map[block.x()][block.y()] |= Transmission::block | Color::none;
362 updateMap(map, block.x(), block.y());
363 m_blocks.append(block);
364 i.remove();
365 }
366 }
367 }
368
369 /* colorize a random block */
370 int rand = (int) (Constants::Game::colorize_block_chance * (qrand() / (RAND_MAX + 1.0)));
371 if (rand == 0)
372 {
373 rand = (int) (m_blocks.size() * (qrand() / (RAND_MAX + 1.0)));
374 QPoint block = m_blocks.at(rand);
375 m_blocks.removeAt(rand);
376
377 unsigned int min = Constants::Game::colorize_block_tickcount_min;
378 unsigned int max = Constants::Game::colorize_block_tickcount_max;
379 int tickcount = min + (int) ((max - min + 1) * (qrand() / (RAND_MAX + 1.0)));
380 m_coloredBlocks.insert(block, tickcount);
381
382 unsigned int color = (int) (m_maxplayers * (qrand() / (RAND_MAX + 1.0)));
383 map[block.x()][block.y()] |= Transmission::block | Color::order[color];
384 updateMap(map, block.x(), block.y());
385 }
386}
387
388bool Server::waitForClientConnections()
389{
390 // server must stay alive as long as sockets (qt parent mem mechanism)
391 ENetAddress address;
392 address.host = ENET_HOST_ANY;
393 if (m_bindaddress != QHostAddress::Any)
394 enet_address_set_host(&address, qPrintable(m_bindaddress.toString()));
395 address.port = m_port;
396
397 m_host = enet_host_create(&address, m_maxplayers - m_numbots, 1, 0, 0);
398 if (m_host == NULL)
399 {
400 qCritical() << "An error occurred while trying to create an ENet server host";
401 return false;
402 }
403
404 char buf[1024];
405 enet_address_get_host_ip(&m_host->address, buf, 1024);
406 std::cout << "[Server] Listening on: "
407 << qPrintable(QString("%1:%2").arg(buf).arg(m_host->address.port)) << std::endl;
408
409 /* add bots first */
410 for (unsigned int i = (m_maxplayers - m_numbots); i < m_maxplayers; ++i)
411 {
412 m_bots.append(Color::order[i]);
413 m_actorMovements[Color::order[i]] = Actor::None;
414 }
415
416 std::cout << "[Server] Waiting for clients" << std::endl;
417 ProtoBuf::Init packet;
418 packet.set_maxplayers(m_maxplayers);
419 unsigned int i = 0;
420 while(m_clientConnections.count() < int(m_maxplayers - m_numbots))
421 {
422 ENetEvent event;
423 if (enet_host_service(m_host, &event, 3000) > 0)
424 {
425 switch (event.type)
426 {
427 case ENET_EVENT_TYPE_CONNECT:
428 {
429 /* assign color and notify client */
430 Color::Color color = Color::order[i++];
431 m_clientConnections[event.peer] = color;
432 packet.set_color(color);
433 Util::sendPacket(packet, event.peer, m_host);
434 std::cout << "[Connect] New Player: color=" << qPrintable(Util::colorToString(color)) << std::endl;
435 }
436 break;
437 case ENET_EVENT_TYPE_RECEIVE:
438 keyPressUpdate(&event);
439 break;
440 case ENET_EVENT_TYPE_DISCONNECT:
441 keyPressUpdate(&event);
442 break;
443 default:
444 break;
445 }
446 }
447 }
448
449 qDebug() << "[Server] All Clients connected";
450 return true;
451}
452
453void Server::sendUpdate(Transmission::map_t map, bool firstPacket)
454{
455 m_updatepacket.Clear();
456
457 for (unsigned int x = 0; x < Constants::map_size.width; ++x)
458 {
459 for (unsigned int y = 0; y < Constants::map_size.height; ++y)
460 m_updatepacket.add_field(map[x][y]);
461 }
462
463 for(unsigned int i = 0; i < m_maxplayers; ++i)
464 {
465 m_updatepacket.add_round_points(m_actors.value(Color::order[i])->getRoundPoints());
466 m_updatepacket.add_game_points(m_actors.value(Color::order[i])->getGamePoints());
467 }
468
469 /* we send the eating_order inside the first packet */
470 if (firstPacket)
471 {
472 foreach(Color::Color color, m_eatingorder)
473 m_updatepacket.add_eating_order(color);
474 }
475
476 QSharedPointer<QByteArray> data = Util::createPacket(m_updatepacket);
477 QMutableMapIterator<ENetPeer *, Color::Color> i(m_clientConnections);
478 while(i.hasNext())
479 {
480 i.next();
481 ENetPeer *peer = i.key();
482
483 if (!Util::sendPacket(data.data(), peer, m_host))
484 {
485 qWarning() << "[Connect] Error while sending data to client" << i.value() << "-> Disconnecting...";
486 std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(i.value()))
487 << " is now a bot" << std::endl;
488 m_bots.append(i.value());
489 ++m_numbots;
490 i.remove();
491 }
492 }
493
494 if (m_maxplayers == m_numbots)
495 {
496 std::cout << "[Connect] No more real players left. Exiting..." << std::endl;
497 qApp->quit();
498 }
499}
500
501void Server::botCalculate(Actor *actor)
502{
503 QPoint actorpos = CoordToMapPosition(actor->pos().toPoint());
504
505 /* first make list of possible directions based on current actor position */
506 QHash<Actor::Movement, unsigned int> directions;
507 if (actorpos.x() > 0)
508 directions.insert(Actor::Left, 0);
509 if (actorpos.x() < visualMap.size() - 1)
510 directions.insert(Actor::Right, 0);
511 if (actorpos.y() > 0)
512 directions.insert(Actor::Up, 0);
513 if (actorpos.y() < visualMap[actorpos.x()].size() - 1)
514 directions.insert(Actor::Down, 0);
515
516 /* check neighbours for blocks first */
517 QMutableHashIterator<Actor::Movement, unsigned int> i(directions);
518 while(i.hasNext())
519 {
520 i.next();
521 QPoint newpos = actorpos + Actor::movementToPoint(i.key());
522 if (newpos.x() < 0 || newpos.x() >= visualMap.size())
523 continue;
524 if (newpos.y() < 0 || newpos.y() >= visualMap[newpos.x()].size())
525 continue;
526 GameEntity *item = visualMap[newpos.x()][newpos.y()];
527
528 /* check if neighbour is a block */
529 Block *block = qgraphicsitem_cast<Block *>(item);
530 if (block != NULL && block->color() != actor->color())
531 i.remove();
532 }
533
534 /* we're enclosed by blocks */
535 if (directions.empty())
536 return;
537
538 /* determine if other actors are in range to be afraid/to hunt */
539 int mindistance = Constants::AI::player_minimum_distance;
540 QList<QPoint> pos_afraid;
541 QList<QPoint> pos_hunt;
542 foreach (Actor *other, m_actors)
543 {
544 if (actor == other)
545 continue;
546
547 QList<QPoint> *ptr = NULL;
548 if (other->canEat(actor, m_eatingorder))
549 ptr = &pos_afraid;
550 else if (actor->canEat(other, m_eatingorder))
551 ptr = &pos_hunt;
552 if (ptr == NULL)
553 continue;
554
555 QPoint otherpos = CoordToMapPosition(other->pos().toPoint());
556 QPoint distance = actorpos - otherpos;
557 if (distance.manhattanLength() < mindistance)
558 ptr->append(otherpos);
559 }
560
561 /* check new directions and change direction-weight */
562 i = directions;
563 while(i.hasNext())
564 {
565 i.next();
566 QPoint newpos = actorpos + Actor::movementToPoint(i.key());
567
568 /* check for new positions in afraid list */
569 foreach(QPoint otherpos, pos_afraid)
570 {
571 int olddistance = (actorpos - otherpos).manhattanLength();
572 int newdistance = (newpos - otherpos).manhattanLength();
573 if (newdistance >= olddistance)
574 i.setValue(i.value() + Constants::AI::weight_afraid);
575
576 /* check for blocks of own color: other pacman can't follow their */
577 GameEntity *item = visualMap[newpos.x()][newpos.y()];
578 Block *block = qgraphicsitem_cast<Block *>(item);
579 if (block != NULL && block->color() == actor->color())
580 i.setValue(i.value() + Constants::AI::weight_colorblock);
581 }
582
583 /* check for new positions in hunt list */
584 foreach(QPoint otherpos, pos_hunt)
585 {
586 int olddistance = (actorpos - otherpos).manhattanLength();
587 int newdistance = (newpos - otherpos).manhattanLength();
588 if (newdistance <= olddistance)
589 i.setValue(i.value() + Constants::AI::weight_hunt);
590 }
591
592 /* check for bonuspoint */
593 GameEntity *item = visualMap[newpos.x()][newpos.y()];
594 BonusPoint *bpoint = qgraphicsitem_cast<BonusPoint *>(item);
595 if (bpoint != NULL)
596 i.setValue(i.value() + Constants::AI::weight_bonus_point);
597
598 /* check for normal point */
599 Point *point = qgraphicsitem_cast<Point *>(item);
600 if (point != NULL)
601 i.setValue(i.value() + Constants::AI::weight_point);
602 }
603
604 /* sort directions */
605 QList<unsigned int> weightlist = directions.values();
606 qSort(weightlist.begin(), weightlist.end(), qGreater<unsigned int>());
607
608 /* remove directions with lesser weight */
609 unsigned int max = weightlist.at(0);
610 i = directions;
611 while(i.hasNext())
612 {
613 i.next();
614 if (i.value() < max)
615 i.remove();
616 }
617
618 QList<Actor::Movement> list = directions.keys();
619
620 /* default is no direction change */
621 if (list.contains(actor->direction()))
622 return;
623
624 /* random direction */
625 int rand = (int) (list.size() * (qrand() / (RAND_MAX + 1.0)));
626 m_actorMovements[actor->color()] = list.at(rand);
627}
628
629void Server::keyPressUpdate()
630{
631 ProtoBuf::KeyPressUpdate packet;
632 ENetEvent event;
633 while (enet_host_service(m_host, &event, 1) > 0)
634 keyPressUpdate(&event);
635}
636
637void Server::keyPressUpdate(ENetEvent *event)
638{
639 ProtoBuf::KeyPressUpdate packet;
640 switch(event->type)
641 {
642 case ENET_EVENT_TYPE_RECEIVE:
643 {
644 QSharedPointer<QByteArray> data = Util::receivePacket(event->packet);
645 enet_packet_destroy(event->packet);
646 bool worked = packet.ParseFromArray(data->data(), data->size());
647 Q_ASSERT(worked);
648 Q_UNUSED(worked);
649 Transmission::field_t direction = packet.newkey();
650 Color::Color color = m_clientConnections[event->peer];
651 qDebug() << "[KeyPress] actor=" << color << "direction=" << direction;
652 m_actorMovements[color] = Util::transmissionMovementToActor(direction);
653 }
654 break;
655 case ENET_EVENT_TYPE_DISCONNECT:
656 {
657 Color::Color color = m_clientConnections[event->peer];
658 std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(color))
659 << " is now a bot" << std::endl;
660 m_bots.append(color);
661 ++m_numbots;
662 m_clientConnections.remove(event->peer);
663 }
664 break;
665 default:
666 break;
667 }
668}
669
670void Server::initRoundMap()
671{
672 std::cout << "[Game] New round starts..." << std::endl;
673 m_tickTimer->stop();
674
675 /* reset scene and clean up items */
676 reset();
677
678 /* randomize color eating order */
679 m_eatingorder.removeLast();
680 random_shuffle(m_eatingorder.begin(), m_eatingorder.end());
681 m_eatingorder.append(m_eatingorder.at(0));
682
683 /* create new map */
684 Transmission::map_t map = Util::createDemoMap();
685 Util::placeActors(map, m_maxplayers, Color::order);
686 Util::fillPoints(map);
687
688#if 0 // actor eating actor tests - TODO: remove
689 m_actorMovements.clear();
690#if 0
691 //works
692 map[0][0] = Color::order[0] | Transmission::pacman;
693 map[0][1] = Color::order[1] | Transmission::pacman;
694 m_actorMovements.insert(Color::order[0], Actor::Down);
695#elif 0
696 //works
697 map[0][0] = Color::order[0] | Transmission::pacman;
698 map[0][3] = Color::order[1] | Transmission::pacman;
699 m_actorMovements.insert(Color::order[0], Actor::Down);
700#elif 0
701 //works
702 map[0][0] = Color::order[0] | Transmission::pacman;
703 map[0][4] = Color::order[1] | Transmission::pacman;
704 m_actorMovements.insert(Color::order[0], Actor::Down);
705#elif 0
706 //works
707 map[0][0] = Color::order[0] | Transmission::pacman;
708 map[0][5] = Color::order[1] | Transmission::pacman;
709 m_actorMovements.insert(Color::order[0], Actor::Down);
710#elif 0
711 //works
712 map[0][0] = Color::order[1] | Transmission::pacman;
713 map[0][5] = Color::order[0] | Transmission::pacman;
714 m_actorMovements.insert(Color::order[1], Actor::Down);
715#elif 0
716 //works
717 map[0][0] = Color::order[0] | Transmission::pacman;
718 map[0][5] = Color::order[1] | Transmission::pacman;
719 m_actorMovements.insert(Color::order[0], Actor::Down);
720 m_actorMovements.insert(Color::order[1], Actor::Up);
721#elif 0
722 //works
723 map[0][0] = Color::order[0] | Transmission::pacman;
724 map[0][6] = Color::order[1] | Transmission::pacman;
725 m_actorMovements.insert(Color::order[0], Actor::Down);
726 m_actorMovements.insert(Color::order[1], Actor::Up);
727#elif 0
728 //works
729 map[0][0] = Color::order[0] | Transmission::pacman;
730 map[0][7] = Color::order[1] | Transmission::pacman;
731 m_actorMovements.insert(Color::order[0], Actor::Down);
732 m_actorMovements.insert(Color::order[1], Actor::Up);
733#elif 1
734 //works
735 map[0][0] = Color::order[0] | Transmission::pacman;
736 map[0][1] = Color::order[1] | Transmission::pacman;
737 m_actorMovements.insert(Color::order[0], Actor::Down);
738 m_actorMovements.insert(Color::order[1], Actor::Down);
739#endif
740#endif
741
742 /* save positions of blocks for later usage */
743 m_blocks.clear();
744 for (unsigned int x = 0; x < Constants::map_size.width; ++x)
745 {
746 for (unsigned int y = 0; y < Constants::map_size.height; ++y)
747 {
748 Transmission::field_t &cur = map[x][y];
749 if (cur & Transmission::block)
750 m_blocks.append(QPoint(x, y));
751 }
752 }
753
754 updateMap(map);
755 sendUpdate(map, true);
756 Util::deleteMap(map);
757 map = NULL;
758
759 m_actorMovements.clear();
760
761 disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL);
762 connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(startGame()));
763 AudioManager::self()->play(Sound::Intro, true);
764 m_tickTimer->start();
765}
766
767void Server::startGame()
768{
769 m_running = true;
770}
771
772void Server::stopGame(bool delay)
773{
774 /* first finish previous round */
775 foreach(Actor *actor, m_actors)
776 actor->finishRound();
777 m_finishRound = false;
778 m_running = false;
779
780 /* add delay if requested */
781 if (delay)
782 {
783 disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL);
784 connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(stopGame()));
785 AudioManager::self()->play(Sound::Die, true);
786 return;
787 }
788
789 std::cout << "[Game] Round finished..." << std::endl;
790
791 /* do next-round work */
792 ++m_curRound;
793 if(m_rounds == 0 || m_curRound < m_rounds)
794 initRoundMap();
795 else
796 {
797 /* end of game */
798 std::cout << "[Game] All round finished. Exiting..." << std::endl;
799 qApp->quit();
800 }
801}
802
803void Server::setFinishRound()
804{
805 m_finishRound = true;
806}
807
808bool Server::parseCommandline()
809{
810 AnyOption opt;
811 opt.setVerbose();
812
813 /* usage strings must remain valid until parsing is done */
814 QString exec = QFileInfo(qApp->applicationFilePath()).fileName();
815 QByteArray usage;
816 QTextStream out(&usage, QIODevice::ReadWrite | QIODevice::Text);
817 out << "Usage: " << exec << " [OPTION]" << endl
818 << "Usage: " << exec << " -h" << endl
819 << endl;
820 out << " -b, --bind <bind_address>" << endl
821 << " Specifies the ip address on which the server listens for connections" << endl
822 << " Default: " << m_bindaddress.toString() << endl
823 << endl;
824 opt.setOption("bind", 'b');
825 out << " -p, --port <port>" << endl
826 << " Specifies the port on which the server listens for connections" << endl
827 << " Default: " << m_port << endl
828 << endl;
829 opt.setOption("port", 'p');
830 out << " -m, --maxplayers [1.." << m_maxplayers << "]" << endl
831 << " Specifies the maximum number of players/pacmans" << endl
832 << " Default: " << m_maxplayers << endl
833 << endl;
834 opt.setOption("maxplayers", 'm');
835 out << " --bots [0..maxplayers-1]" << endl
836 << " Specifies the number of AI pacmans/bots" << endl
837 << " Default: " << m_numbots << endl
838 << endl;
839 opt.setOption("bots");
840 out << " --nocolorblocks" << endl
841 << " Disable random colorized blocks" << endl
842 << endl;
843 opt.setOption("rounds", 'r');
844 out << " -r, --rounds [0 | 1..n]" << endl
845 << " Number of rounds to play" << endl
846 << " Default: " << m_rounds << endl
847 << endl;
848 opt.setFlag("nocolorblocks");
849 out << " -h, --help" << endl
850 << " Prints this help message" << endl;
851 opt.setFlag("help", 'h');
852 out.flush();
853 opt.addUsage(usage.constData());
854 opt.processCommandArgs(qApp->argc(), qApp->argv());
855
856 if (opt.getFlag("help") || opt.getFlag('h'))
857 {
858 opt.printUsage();
859 return false;
860 }
861
862 if (opt.getValue("port") != NULL)
863 {
864 bool ok;
865 m_port = QString(opt.getValue("port")).toUInt(&ok);
866 if (!ok || m_port < 1 || m_port > 65535)
867 {
868 qCritical() << "Invalid port-option:" << opt.getValue("port") << endl
869 << "Port must be between 1 and 65535";
870 return false;
871 }
872 }
873
874 if (opt.getValue("bind") != NULL)
875 {
876 m_bindaddress = opt.getValue("bind");
877 if (m_bindaddress.isNull())
878 {
879 qCritical() << "Invalid bind-option:" << opt.getValue("bind") << endl
880 << "Bind address must be an ip address";
881 return false;
882 }
883 }
884
885 if (opt.getValue("maxplayers") != NULL)
886 {
887 bool ok;
888 unsigned int maxplayers = QString(opt.getValue("maxplayers")).toUInt(&ok);
889 if (!ok || maxplayers < 1 || maxplayers > m_maxplayers)
890 {
891 qCritical() << "Invalid maxplayers-option:" << opt.getValue("maxplayers") << endl
892 << "Maxplayers must be between 1 and" << m_maxplayers;
893 return false;
894 }
895 m_maxplayers = maxplayers;
896 if (m_maxplayers == 2)
897 {
898 qCritical() << "2 player game is not supported (who wins if a player gets eaten?)";
899 return false;
900 }
901 }
902
903 if (opt.getValue("bots") != NULL)
904 {
905 bool ok;
906 unsigned int numbots = QString(opt.getValue("bots")).toUInt(&ok);
907 if (!ok || numbots >= m_maxplayers)
908 {
909 qCritical() << "Invalid numbots-options:" << opt.getValue("bots") << endl
910 << "AI pacmans/bots must be between 0 and" << m_maxplayers - 1;
911 return false;
912 }
913 m_numbots = numbots;
914 }
915
916 if (opt.getValue("rounds") != NULL)
917 {
918 bool ok;
919 unsigned int rounds = QString(opt.getValue("rounds")).toUInt(&ok);
920 if (!ok)
921 {
922 qCritical() << "Invalid number of rounds: " << opt.getValue("rounds") << endl;
923 return false;
924 }
925 m_rounds = rounds;
926 }
927
928 this->setProperty("coloredblocks", !opt.getFlag("nocolorblocks"));
929
930 return true;
931}
932
933bool operator<(const QPoint& lhs, const QPoint& rhs)
934{
935 if (lhs.x() < rhs.x())
936 return true;
937 else if (lhs.x() == rhs.x())
938 return lhs.y() < rhs.y();
939 else
940 return false;
941}
942
943bool Constants::server = true;
944
945int main(int argc, char **argv)
946{
947 /* Verify that the version of the library that we linked against is
948 * compatible with the version of the headers we compiled against.
949 */
950 GOOGLE_PROTOBUF_VERIFY_VERSION;
951
952 QApplication app(argc, argv, false);
953 app.setApplicationName("Pacman Server");
954 app.setWindowIcon(QIcon(":/appicon"));
955
956 qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
957
958 int ret = 0;
959 Server server;
960 if (!ret && !server.parseCommandline())
961 ret = 1;
962 if (!ret && !server.run())
963 ret = 1;
964 if (!ret)
965 ret = app.exec();
966
967 /* Delete all global objects allocated by libprotobuf */
968 google::protobuf::ShutdownProtobufLibrary();
969
970 return ret;
971}