From ce48af53646cd9e7ec762fc1ac176b3aa620b11d Mon Sep 17 00:00:00 2001 From: manuel Date: Thu, 5 May 2011 00:57:07 +0200 Subject: - refactorized the whole project and made a few subprojects - replaced tcp with enet - added connect dialog - some smaller bugfixes --- pacman-c++/server/server.cpp | 971 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 971 insertions(+) create mode 100644 pacman-c++/server/server.cpp (limited to 'pacman-c++/server/server.cpp') 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 @@ +#include "server.h" +#include "util.h" +#include "pacman.pb.h" +#include "block.h" +#include "anyoption.h" +#include "bonuspoint.h" +#include "point.h" +#include +#include + +Server::Server(QWidget *parent) + : SceneHolder(parent), m_host(NULL), m_bindaddress(QHostAddress::Any), + m_port(Constants::Networking::port), m_numbots(0), + m_rounds(3), m_curRound(0), m_running(false), m_finishRound(false) +{ + /* determine max players by using order array */ + for(m_maxplayers = 0; Color::order[m_maxplayers] != Color::none; ++m_maxplayers); +} + +Server::~Server() +{ + if (m_host != NULL) + { + foreach(ENetPeer *peer, m_clientConnections.keys()) + enet_peer_disconnect(peer, 0); + + /* allow up to 3 seconds for the disconnect to succeed + * and drop any packets received packets + */ + ENetEvent event; + while (m_clientConnections.count() > 0 + && enet_host_service(m_host, &event, 3000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + m_clientConnections.remove(event.peer); + return; + default: + break; + } + } + + enet_host_destroy(m_host); + } +} + +bool Server::run() +{ + /* create eating order list first */ + for(unsigned int i = 0; i < m_maxplayers; ++i) + m_eatingorder.append(Color::order[i]); + m_eatingorder.append(m_eatingorder.at(0)); + + m_tickTimer = new QTimer(this); + m_tickTimer->setInterval(Constants::tick); + connect(m_tickTimer, SIGNAL(timeout()), this, SLOT(tick())); + + std::cout << "[Server] Running server..." << std::endl + << "[Server] Max players: " << m_maxplayers << std::endl + << "[Server] Number of bots: " << m_numbots << std::endl + << "[Server] Number of rounds: " << m_rounds << std::endl; + if (!waitForClientConnections()) + return false; + + connect(this, SIGNAL(allPointsRemoved()), this, SLOT(setFinishRound())); + initRoundMap(); + return true; +} + +void Server::tick() +{ + //qDebug() << "[Tick] Doing server update"; + if (m_finishRound) + stopGame(true); + if (!m_running) + { + Transmission::map_t map = Util::createEmptyMap(); + sendUpdate(map); + Util::deleteMap(map); + return; + } + + /* fetch key updates */ + keyPressUpdate(); + + /* let the bots move */ + foreach (Color::Color color, m_bots) + botCalculate(m_actors[color]); + + /* move on the virtual map */ + Transmission::map_t map = calculateUpdates(); + updateMap(map); + + /* add a random bonus point */ + QPoint pos = addRandomPoint(map, Transmission::bonuspoint); + if (!pos.isNull()) + updateMap(map, pos.x(), pos.y()); + + /* add/remove random colorized block */ + if (this->property("coloredblocks").toBool()) + colorizeBlocks(map); + + sendUpdate(map); + Util::deleteMap(map); +} + +Transmission::map_t Server::calculateUpdates() +{ + QMap > movements; + Transmission::map_t map = Util::createEmptyMap(); + + QMutableMapIterator i(m_actorMovements); + while (i.hasNext()) + { + i.next(); + Color::Color color = i.key(); + Actor *actor = m_actors[color]; + QPoint mapPosition = CoordToMapPosition(actor->pos().toPoint()); + Actor::Movement direction = i.value(); + int turn = 0; + +invalid_direction: + ++turn; + qDebug() << "[Calc] Actor wants to move: color=" << color + << "pos=" << mapPosition << "direction=" << direction; + + QPoint newMapPosition = mapPosition + Actor::movementToPoint(direction); + if (newMapPosition.x() < 0) + newMapPosition.setX(0); + if (newMapPosition.x() >= visualMap.size()) + newMapPosition.setX(visualMap.size() - 1); + if (newMapPosition.y() < 0) + newMapPosition.setY(0); + if (newMapPosition.y() >= visualMap[newMapPosition.x()].size()) + newMapPosition.setY(visualMap[newMapPosition.x()].size() - 1); + + /* check if there's an item at new location of actor */ + GameEntity *item = visualMap[newMapPosition.x()][newMapPosition.y()]; + GameEntity *oldItem = visualMap[mapPosition.x()][mapPosition.y()]; + if (item != NULL && oldItem != item) + { + qDebug() << "[Calc] Found item at new actor location"; + if (!item->checkEnter(actor)) + { + /* movement invalid. e.g. move against wall */ + newMapPosition = mapPosition; + qDebug() << "[Calc] Item blocks actor"; + } + else + { + /* apply actions of entering this field */ + GameEntity::EnteredState survive = item->enter(actor); + if (survive == GameEntity::DestroyedEntity) + map[newMapPosition.x()][newMapPosition.y()] = Transmission::empty | actor->color(); + else if (survive == GameEntity::DestroyedActor) + { + map[newMapPosition.x()][newMapPosition.y()] = Transmission::death | actor->color(); + m_actors[item->color()]->addRoundPoints(actor->getRoundPoints()); + actor->finishRound(true); + setFinishRound(); + } + } + } + + /* movement didn't work - e.g. was blocked */ + if (mapPosition == newMapPosition) + { + /* check turn */ + if (turn == 1 && direction != actor->direction()) + { + /* set direction back to last known direction and try again */ + qDebug() << "[Calc] Movement was blocked. Trying last known actor direction"; + direction = actor->direction(); + goto invalid_direction; + } + else + { + /* second turn didn't work too -> stop movement */ + direction = Actor::None; + qDebug() << "[Calc] No good direction known. Movement stopped"; + } + } + + /* store movement for collision */ + movements.insert(color, QPair(mapPosition, newMapPosition)); + + /* set direction (used for collision detection) */ + actor->setDirection(direction); + + /* DEBUG: uncomments to disable auto-movement */ + //direction = Actor::None; + + /* actor is not moving anymore: remove from list */ + if (direction == Actor::None) + i.remove(); + } + + /* check for actor collision */ + QList blocked; + foreach(Color::Color color, movements.keys()) + { + Actor *actor = m_actors[color]; + QPoint oldpos = movements[color].first; + QPoint newpos = movements[color].second; + QPoint scenepos = actor->pos().toPoint(); + + /* first move actor to new position */ + actor->move(actor->direction()); + + /* next check for collisions */ + Actor *orderer = NULL; + Actor *meal = NULL; + foreach(Actor *other, m_actors) + { + if (actor == other) + continue; + if (!actor->collidesWithItem(other)) + continue; + /* both move in the same direction */ + if (actor->direction() == other->direction()) + continue; + + if (other->canEat(actor, m_eatingorder)) + { + qDebug() << "[Collision] Actor" << actor->color() << "got EATEN by" << other->color(); + orderer = other; + meal = actor; + break; + } + else if (actor->canEat(other, m_eatingorder)) + { + qDebug() << "[Collision] Actor" << actor->color() << "EATS" << other->color(); + orderer = actor; + meal = other; + blocked.append(other); + break; + } + else + { + qDebug() << "[Collision] Actor" << actor->color() << "got BLOCKED by" << other->color(); + blocked.append(actor); + /* no break here */ + } + } + + /* update map depending on collision */ + if (orderer != NULL && meal != NULL) + { + map[newpos.x()][newpos.y()] |= Transmission::pacman | Transmission::death + | orderer->color() | meal->color(); + orderer->addRoundPoints(meal->getRoundPoints()); + meal->finishRound(true); + setFinishRound(); + } + else if (blocked.contains(actor)) + { + /* move actor back early cause he won't move */ + actor->setPos(scenepos); + map[oldpos.x()][oldpos.y()] |= Transmission::pacman | color; + } + else + { + map[newpos.x()][newpos.y()] |= Transmission::pacman | color; + } + } + + /* move all actors back to their origin position */ + foreach(Color::Color color, movements.keys()) + m_actors[color]->setPos(mapPositionToCoord(movements[color].first)); + +#ifndef QT_NO_DEBUG + /* collision sanity check: colors must be unique */ + foreach(Color::Color col, m_eatingorder.toSet()) + { + QList found; + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + if ((map[x][y] & Transmission::color_mask) & col) + found.append(QPoint(x, y)); + } + } + if (found.count() > 1) + qCritical() << "[Collision] found" << found.count() << "fields with color=" << col; + } +#endif + + return map; +} + +QPoint Server::addRandomPoint(Transmission::map_t map, Transmission::field_t type) +{ + int chance = Constants::Game::bouns_point_chance + - Constants::Game::bouns_point_chance_playerfactor * (m_maxplayers - 1); + int rand = (int) (chance * (qrand() / (RAND_MAX + 1.0))); + if (rand != 0) + return QPoint(); + + /* get list of valid positions */ + QList validpos; + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + if (visualMap[x][y] == NULL) + validpos.append(QPoint(x, y)); + } + } + + /* remove actors positions from list + * performance would be better if actors would be listed in visualMap too + * but this isn't possible that easily. see comment in updateMap(map) + */ + foreach (Actor *actor, m_actors) + { + QPoint actorpos = CoordToMapPosition(actor->pos().toPoint()); + validpos.removeAll(actorpos); + } + + if (validpos.empty()) + return QPoint(); + + rand = (int) (validpos.size() * (qrand() / (RAND_MAX + 1.0))); + QPoint pos = validpos.at(rand); + map[pos.x()][pos.y()] = type; + return pos; +} + +void Server::colorizeBlocks(Transmission::map_t map) +{ + /* decrement tickcount of colored blocks */ + if (!m_coloredBlocks.empty()) + { + QMutableMapIterator i(m_coloredBlocks); + while(i.hasNext()) + { + i.next(); + unsigned val = i.value(); + if (val > 0) + i.setValue(--val); + else + { + QPoint block = i.key(); + /* check for actor collision */ + bool skip = false; + foreach (Actor *actor, m_actors) + { + if (CoordToMapPosition(actor->pos().toPoint()) == block) + skip = true; + if (skip) + break; + } + if (skip) + continue; + + map[block.x()][block.y()] |= Transmission::block | Color::none; + updateMap(map, block.x(), block.y()); + m_blocks.append(block); + i.remove(); + } + } + } + + /* colorize a random block */ + int rand = (int) (Constants::Game::colorize_block_chance * (qrand() / (RAND_MAX + 1.0))); + if (rand == 0) + { + rand = (int) (m_blocks.size() * (qrand() / (RAND_MAX + 1.0))); + QPoint block = m_blocks.at(rand); + m_blocks.removeAt(rand); + + unsigned int min = Constants::Game::colorize_block_tickcount_min; + unsigned int max = Constants::Game::colorize_block_tickcount_max; + int tickcount = min + (int) ((max - min + 1) * (qrand() / (RAND_MAX + 1.0))); + m_coloredBlocks.insert(block, tickcount); + + unsigned int color = (int) (m_maxplayers * (qrand() / (RAND_MAX + 1.0))); + map[block.x()][block.y()] |= Transmission::block | Color::order[color]; + updateMap(map, block.x(), block.y()); + } +} + +bool Server::waitForClientConnections() +{ + // server must stay alive as long as sockets (qt parent mem mechanism) + ENetAddress address; + address.host = ENET_HOST_ANY; + if (m_bindaddress != QHostAddress::Any) + enet_address_set_host(&address, qPrintable(m_bindaddress.toString())); + address.port = m_port; + + m_host = enet_host_create(&address, m_maxplayers - m_numbots, 1, 0, 0); + if (m_host == NULL) + { + qCritical() << "An error occurred while trying to create an ENet server host"; + return false; + } + + char buf[1024]; + enet_address_get_host_ip(&m_host->address, buf, 1024); + std::cout << "[Server] Listening on: " + << qPrintable(QString("%1:%2").arg(buf).arg(m_host->address.port)) << std::endl; + + /* add bots first */ + for (unsigned int i = (m_maxplayers - m_numbots); i < m_maxplayers; ++i) + { + m_bots.append(Color::order[i]); + m_actorMovements[Color::order[i]] = Actor::None; + } + + std::cout << "[Server] Waiting for clients" << std::endl; + ProtoBuf::Init packet; + packet.set_maxplayers(m_maxplayers); + unsigned int i = 0; + while(m_clientConnections.count() < int(m_maxplayers - m_numbots)) + { + ENetEvent event; + if (enet_host_service(m_host, &event, 3000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + /* assign color and notify client */ + Color::Color color = Color::order[i++]; + m_clientConnections[event.peer] = color; + packet.set_color(color); + Util::sendPacket(packet, event.peer, m_host); + std::cout << "[Connect] New Player: color=" << qPrintable(Util::colorToString(color)) << std::endl; + } + break; + case ENET_EVENT_TYPE_RECEIVE: + keyPressUpdate(&event); + break; + case ENET_EVENT_TYPE_DISCONNECT: + keyPressUpdate(&event); + break; + default: + break; + } + } + } + + qDebug() << "[Server] All Clients connected"; + return true; +} + +void Server::sendUpdate(Transmission::map_t map, bool firstPacket) +{ + m_updatepacket.Clear(); + + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + m_updatepacket.add_field(map[x][y]); + } + + for(unsigned int i = 0; i < m_maxplayers; ++i) + { + m_updatepacket.add_round_points(m_actors.value(Color::order[i])->getRoundPoints()); + m_updatepacket.add_game_points(m_actors.value(Color::order[i])->getGamePoints()); + } + + /* we send the eating_order inside the first packet */ + if (firstPacket) + { + foreach(Color::Color color, m_eatingorder) + m_updatepacket.add_eating_order(color); + } + + QSharedPointer data = Util::createPacket(m_updatepacket); + QMutableMapIterator i(m_clientConnections); + while(i.hasNext()) + { + i.next(); + ENetPeer *peer = i.key(); + + if (!Util::sendPacket(data.data(), peer, m_host)) + { + qWarning() << "[Connect] Error while sending data to client" << i.value() << "-> Disconnecting..."; + std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(i.value())) + << " is now a bot" << std::endl; + m_bots.append(i.value()); + ++m_numbots; + i.remove(); + } + } + + if (m_maxplayers == m_numbots) + { + std::cout << "[Connect] No more real players left. Exiting..." << std::endl; + qApp->quit(); + } +} + +void Server::botCalculate(Actor *actor) +{ + QPoint actorpos = CoordToMapPosition(actor->pos().toPoint()); + + /* first make list of possible directions based on current actor position */ + QHash directions; + if (actorpos.x() > 0) + directions.insert(Actor::Left, 0); + if (actorpos.x() < visualMap.size() - 1) + directions.insert(Actor::Right, 0); + if (actorpos.y() > 0) + directions.insert(Actor::Up, 0); + if (actorpos.y() < visualMap[actorpos.x()].size() - 1) + directions.insert(Actor::Down, 0); + + /* check neighbours for blocks first */ + QMutableHashIterator i(directions); + while(i.hasNext()) + { + i.next(); + QPoint newpos = actorpos + Actor::movementToPoint(i.key()); + if (newpos.x() < 0 || newpos.x() >= visualMap.size()) + continue; + if (newpos.y() < 0 || newpos.y() >= visualMap[newpos.x()].size()) + continue; + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + + /* check if neighbour is a block */ + Block *block = qgraphicsitem_cast(item); + if (block != NULL && block->color() != actor->color()) + i.remove(); + } + + /* we're enclosed by blocks */ + if (directions.empty()) + return; + + /* determine if other actors are in range to be afraid/to hunt */ + int mindistance = Constants::AI::player_minimum_distance; + QList pos_afraid; + QList pos_hunt; + foreach (Actor *other, m_actors) + { + if (actor == other) + continue; + + QList *ptr = NULL; + if (other->canEat(actor, m_eatingorder)) + ptr = &pos_afraid; + else if (actor->canEat(other, m_eatingorder)) + ptr = &pos_hunt; + if (ptr == NULL) + continue; + + QPoint otherpos = CoordToMapPosition(other->pos().toPoint()); + QPoint distance = actorpos - otherpos; + if (distance.manhattanLength() < mindistance) + ptr->append(otherpos); + } + + /* check new directions and change direction-weight */ + i = directions; + while(i.hasNext()) + { + i.next(); + QPoint newpos = actorpos + Actor::movementToPoint(i.key()); + + /* check for new positions in afraid list */ + foreach(QPoint otherpos, pos_afraid) + { + int olddistance = (actorpos - otherpos).manhattanLength(); + int newdistance = (newpos - otherpos).manhattanLength(); + if (newdistance >= olddistance) + i.setValue(i.value() + Constants::AI::weight_afraid); + + /* check for blocks of own color: other pacman can't follow their */ + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + Block *block = qgraphicsitem_cast(item); + if (block != NULL && block->color() == actor->color()) + i.setValue(i.value() + Constants::AI::weight_colorblock); + } + + /* check for new positions in hunt list */ + foreach(QPoint otherpos, pos_hunt) + { + int olddistance = (actorpos - otherpos).manhattanLength(); + int newdistance = (newpos - otherpos).manhattanLength(); + if (newdistance <= olddistance) + i.setValue(i.value() + Constants::AI::weight_hunt); + } + + /* check for bonuspoint */ + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + BonusPoint *bpoint = qgraphicsitem_cast(item); + if (bpoint != NULL) + i.setValue(i.value() + Constants::AI::weight_bonus_point); + + /* check for normal point */ + Point *point = qgraphicsitem_cast(item); + if (point != NULL) + i.setValue(i.value() + Constants::AI::weight_point); + } + + /* sort directions */ + QList weightlist = directions.values(); + qSort(weightlist.begin(), weightlist.end(), qGreater()); + + /* remove directions with lesser weight */ + unsigned int max = weightlist.at(0); + i = directions; + while(i.hasNext()) + { + i.next(); + if (i.value() < max) + i.remove(); + } + + QList list = directions.keys(); + + /* default is no direction change */ + if (list.contains(actor->direction())) + return; + + /* random direction */ + int rand = (int) (list.size() * (qrand() / (RAND_MAX + 1.0))); + m_actorMovements[actor->color()] = list.at(rand); +} + +void Server::keyPressUpdate() +{ + ProtoBuf::KeyPressUpdate packet; + ENetEvent event; + while (enet_host_service(m_host, &event, 1) > 0) + keyPressUpdate(&event); +} + +void Server::keyPressUpdate(ENetEvent *event) +{ + ProtoBuf::KeyPressUpdate packet; + switch(event->type) + { + case ENET_EVENT_TYPE_RECEIVE: + { + QSharedPointer data = Util::receivePacket(event->packet); + enet_packet_destroy(event->packet); + bool worked = packet.ParseFromArray(data->data(), data->size()); + Q_ASSERT(worked); + Q_UNUSED(worked); + Transmission::field_t direction = packet.newkey(); + Color::Color color = m_clientConnections[event->peer]; + qDebug() << "[KeyPress] actor=" << color << "direction=" << direction; + m_actorMovements[color] = Util::transmissionMovementToActor(direction); + } + break; + case ENET_EVENT_TYPE_DISCONNECT: + { + Color::Color color = m_clientConnections[event->peer]; + std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(color)) + << " is now a bot" << std::endl; + m_bots.append(color); + ++m_numbots; + m_clientConnections.remove(event->peer); + } + break; + default: + break; + } +} + +void Server::initRoundMap() +{ + std::cout << "[Game] New round starts..." << std::endl; + m_tickTimer->stop(); + + /* reset scene and clean up items */ + reset(); + + /* randomize color eating order */ + m_eatingorder.removeLast(); + random_shuffle(m_eatingorder.begin(), m_eatingorder.end()); + m_eatingorder.append(m_eatingorder.at(0)); + + /* create new map */ + Transmission::map_t map = Util::createDemoMap(); + Util::placeActors(map, m_maxplayers, Color::order); + Util::fillPoints(map); + +#if 0 // actor eating actor tests - TODO: remove + m_actorMovements.clear(); +#if 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][1] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][3] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][4] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][5] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[1] | Transmission::pacman; + map[0][5] = Color::order[0] | Transmission::pacman; + m_actorMovements.insert(Color::order[1], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][5] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][6] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][7] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 1 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][1] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Down); +#endif +#endif + + /* save positions of blocks for later usage */ + m_blocks.clear(); + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + Transmission::field_t &cur = map[x][y]; + if (cur & Transmission::block) + m_blocks.append(QPoint(x, y)); + } + } + + updateMap(map); + sendUpdate(map, true); + Util::deleteMap(map); + map = NULL; + + m_actorMovements.clear(); + + disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL); + connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(startGame())); + AudioManager::self()->play(Sound::Intro, true); + m_tickTimer->start(); +} + +void Server::startGame() +{ + m_running = true; +} + +void Server::stopGame(bool delay) +{ + /* first finish previous round */ + foreach(Actor *actor, m_actors) + actor->finishRound(); + m_finishRound = false; + m_running = false; + + /* add delay if requested */ + if (delay) + { + disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL); + connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(stopGame())); + AudioManager::self()->play(Sound::Die, true); + return; + } + + std::cout << "[Game] Round finished..." << std::endl; + + /* do next-round work */ + ++m_curRound; + if(m_rounds == 0 || m_curRound < m_rounds) + initRoundMap(); + else + { + /* end of game */ + std::cout << "[Game] All round finished. Exiting..." << std::endl; + qApp->quit(); + } +} + +void Server::setFinishRound() +{ + m_finishRound = true; +} + +bool Server::parseCommandline() +{ + AnyOption opt; + opt.setVerbose(); + + /* usage strings must remain valid until parsing is done */ + QString exec = QFileInfo(qApp->applicationFilePath()).fileName(); + QByteArray usage; + QTextStream out(&usage, QIODevice::ReadWrite | QIODevice::Text); + out << "Usage: " << exec << " [OPTION]" << endl + << "Usage: " << exec << " -h" << endl + << endl; + out << " -b, --bind " << endl + << " Specifies the ip address on which the server listens for connections" << endl + << " Default: " << m_bindaddress.toString() << endl + << endl; + opt.setOption("bind", 'b'); + out << " -p, --port " << endl + << " Specifies the port on which the server listens for connections" << endl + << " Default: " << m_port << endl + << endl; + opt.setOption("port", 'p'); + out << " -m, --maxplayers [1.." << m_maxplayers << "]" << endl + << " Specifies the maximum number of players/pacmans" << endl + << " Default: " << m_maxplayers << endl + << endl; + opt.setOption("maxplayers", 'm'); + out << " --bots [0..maxplayers-1]" << endl + << " Specifies the number of AI pacmans/bots" << endl + << " Default: " << m_numbots << endl + << endl; + opt.setOption("bots"); + out << " --nocolorblocks" << endl + << " Disable random colorized blocks" << endl + << endl; + opt.setOption("rounds", 'r'); + out << " -r, --rounds [0 | 1..n]" << endl + << " Number of rounds to play" << endl + << " Default: " << m_rounds << endl + << endl; + opt.setFlag("nocolorblocks"); + out << " -h, --help" << endl + << " Prints this help message" << endl; + opt.setFlag("help", 'h'); + out.flush(); + opt.addUsage(usage.constData()); + opt.processCommandArgs(qApp->argc(), qApp->argv()); + + if (opt.getFlag("help") || opt.getFlag('h')) + { + opt.printUsage(); + return false; + } + + if (opt.getValue("port") != NULL) + { + bool ok; + m_port = QString(opt.getValue("port")).toUInt(&ok); + if (!ok || m_port < 1 || m_port > 65535) + { + qCritical() << "Invalid port-option:" << opt.getValue("port") << endl + << "Port must be between 1 and 65535"; + return false; + } + } + + if (opt.getValue("bind") != NULL) + { + m_bindaddress = opt.getValue("bind"); + if (m_bindaddress.isNull()) + { + qCritical() << "Invalid bind-option:" << opt.getValue("bind") << endl + << "Bind address must be an ip address"; + return false; + } + } + + if (opt.getValue("maxplayers") != NULL) + { + bool ok; + unsigned int maxplayers = QString(opt.getValue("maxplayers")).toUInt(&ok); + if (!ok || maxplayers < 1 || maxplayers > m_maxplayers) + { + qCritical() << "Invalid maxplayers-option:" << opt.getValue("maxplayers") << endl + << "Maxplayers must be between 1 and" << m_maxplayers; + return false; + } + m_maxplayers = maxplayers; + if (m_maxplayers == 2) + { + qCritical() << "2 player game is not supported (who wins if a player gets eaten?)"; + return false; + } + } + + if (opt.getValue("bots") != NULL) + { + bool ok; + unsigned int numbots = QString(opt.getValue("bots")).toUInt(&ok); + if (!ok || numbots >= m_maxplayers) + { + qCritical() << "Invalid numbots-options:" << opt.getValue("bots") << endl + << "AI pacmans/bots must be between 0 and" << m_maxplayers - 1; + return false; + } + m_numbots = numbots; + } + + if (opt.getValue("rounds") != NULL) + { + bool ok; + unsigned int rounds = QString(opt.getValue("rounds")).toUInt(&ok); + if (!ok) + { + qCritical() << "Invalid number of rounds: " << opt.getValue("rounds") << endl; + return false; + } + m_rounds = rounds; + } + + this->setProperty("coloredblocks", !opt.getFlag("nocolorblocks")); + + return true; +} + +bool operator<(const QPoint& lhs, const QPoint& rhs) +{ + if (lhs.x() < rhs.x()) + return true; + else if (lhs.x() == rhs.x()) + return lhs.y() < rhs.y(); + else + return false; +} + +bool Constants::server = true; + +int main(int argc, char **argv) +{ + /* Verify that the version of the library that we linked against is + * compatible with the version of the headers we compiled against. + */ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + QApplication app(argc, argv, false); + app.setApplicationName("Pacman Server"); + app.setWindowIcon(QIcon(":/appicon")); + + qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); + + int ret = 0; + Server server; + if (!ret && !server.parseCommandline()) + ret = 1; + if (!ret && !server.run()) + ret = 1; + if (!ret) + ret = app.exec(); + + /* Delete all global objects allocated by libprotobuf */ + google::protobuf::ShutdownProtobufLibrary(); + + return ret; +} -- cgit v1.2.3