mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-13 05:20:00 -08:00
[Server] Shuffle and reorder player seats before a game starts
Auto-shuffles the seats at game start when enabled (now for games with two or more players instead of three or more) and handles the new SET_PLAYER_ORDER command so the game host or a moderator can reorder or randomize the seats before the game starts. Re-seating re-keys the player ids while keeping the per-user game/seat routing and persistent player records in sync, then broadcasts the new order to everyone.
This commit is contained in:
parent
a2657847ad
commit
d2d65836ab
8 changed files with 457 additions and 6 deletions
|
|
@ -21,6 +21,7 @@ struct GameConfig
|
|||
bool spectatorsSeeEverything = true;
|
||||
int startingLifeTotal = 20;
|
||||
bool shareDecklistsOnLoad = true;
|
||||
bool shufflePlayers = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_card_counter.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_player_order.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||
|
|
@ -434,6 +435,33 @@ Response::ResponseCode Server_AbstractParticipant::cmdReverseTurn(const Command_
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractParticipant::cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd,
|
||||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage & /*ges*/)
|
||||
{
|
||||
if ((game->getHostId() != playerId) && !(userInfo->user_level() & ServerInfo_User::IsModerator)) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
if (game->getGameStarted()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (cmd.randomize()) {
|
||||
game->shufflePlayerSeats();
|
||||
} else {
|
||||
QList<QString> playerNames;
|
||||
for (int i = 0; i < cmd.player_names_size(); ++i) {
|
||||
playerNames.append(QString::fromStdString(cmd.player_names(i)));
|
||||
}
|
||||
game->reorderPlayerSeats(playerNames);
|
||||
}
|
||||
|
||||
game->sendGameStateToPlayers();
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
Server_AbstractParticipant::processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges)
|
||||
{
|
||||
|
|
@ -543,6 +571,9 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
|||
case GameCommand::SET_PLAYMAT:
|
||||
return cmdSetPlaymat(command.GetExtension(Command_SetPlaymat::ext), rc, ges);
|
||||
break;
|
||||
case GameCommand::SET_PLAYER_ORDER:
|
||||
return cmdSetPlayerOrder(command.GetExtension(Command_SetPlayerOrder::ext), rc, ges);
|
||||
break;
|
||||
default:
|
||||
return Response::RespInvalidCommand;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class Command_DeckSelect;
|
|||
class Command_SetSideboardLock;
|
||||
class Command_ChangeZoneProperties;
|
||||
class Command_SetPlaymat;
|
||||
class Command_SetPlayerOrder;
|
||||
|
||||
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
||||
{
|
||||
|
|
@ -89,6 +90,10 @@ public:
|
|||
{
|
||||
return playerId;
|
||||
}
|
||||
void setPlayerId(int _playerId)
|
||||
{
|
||||
playerId = _playerId;
|
||||
}
|
||||
bool isSpectator() const
|
||||
{
|
||||
return roleInfo.isSpectator;
|
||||
|
|
@ -173,6 +178,8 @@ public:
|
|||
virtual Response::ResponseCode
|
||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
|
||||
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
||||
|
|
|
|||
|
|
@ -73,11 +73,13 @@
|
|||
#include <libcockatrice/protocol/pb/event_join.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_kicked.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_leave.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_player_order_changed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_player_properties_changed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_replay.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
|
|
@ -91,8 +93,8 @@ Server_Game::Server_Game(const GameConfig &config, Server_Room *_room)
|
|||
spectatorsAllowed(config.spectatorsAllowed), spectatorsNeedPassword(config.spectatorsNeedPassword),
|
||||
spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything),
|
||||
startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad),
|
||||
inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false),
|
||||
turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
||||
shufflePlayers(config.shufflePlayers), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0),
|
||||
firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
||||
deckValidationStrategy(new Server_DefaultDeckValidationStrategy),
|
||||
lifecycleStrategy(new Server_DefaultLifecycleStrategy), matchResultStrategy(new Server_NullMatchResultStrategy),
|
||||
gameMutex()
|
||||
|
|
@ -309,13 +311,17 @@ void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event,
|
|||
for (Server_AbstractParticipant *participant : participants.values()) {
|
||||
participant->getInfo(event->add_player_list(), recipient, omniscient, withUserInfo);
|
||||
}
|
||||
|
||||
if (recipient != nullptr) {
|
||||
event->set_local_player_id(recipient->getPlayerId());
|
||||
}
|
||||
}
|
||||
|
||||
void Server_Game::sendGameStateToPlayers()
|
||||
{
|
||||
// game state information for replay and omniscient spectators
|
||||
Event_GameStateChanged omniscientEvent;
|
||||
createGameStateChangedEvent(&omniscientEvent, nullptr, true, false);
|
||||
createGameStateChangedEvent(&omniscientEvent, nullptr, true, true);
|
||||
|
||||
GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1);
|
||||
replayCont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame);
|
||||
|
|
@ -327,7 +333,7 @@ void Server_Game::sendGameStateToPlayers()
|
|||
// the data we used for the replay. All spectators are equal, so we don't need to make a createGameStateChangedEvent
|
||||
// call for each one.
|
||||
Event_GameStateChanged spectatorNormalEvent;
|
||||
createGameStateChangedEvent(&spectatorNormalEvent, nullptr, false, false);
|
||||
createGameStateChangedEvent(&spectatorNormalEvent, nullptr, false, true);
|
||||
|
||||
// send game state info to clients according to their role in the game
|
||||
for (auto *participant : participants.values()) {
|
||||
|
|
@ -340,7 +346,7 @@ void Server_Game::sendGameStateToPlayers()
|
|||
}
|
||||
} else {
|
||||
Event_GameStateChanged event;
|
||||
createGameStateChangedEvent(&event, participant, participant->isJudge(), false);
|
||||
createGameStateChangedEvent(&event, participant, participant->isJudge(), true);
|
||||
|
||||
gec = prepareGameEvent(event, -1);
|
||||
}
|
||||
|
|
@ -380,6 +386,11 @@ void Server_Game::doStartGameIfReady(bool forceStartGame)
|
|||
return;
|
||||
}
|
||||
|
||||
if (shufflePlayers && players.size() > 1) {
|
||||
shufflePlayerSeats();
|
||||
players = getPlayers();
|
||||
}
|
||||
|
||||
for (Server_AbstractPlayer *player : players.values()) {
|
||||
player->setupZones();
|
||||
}
|
||||
|
|
@ -433,6 +444,115 @@ void Server_Game::startGameIfReady(bool forceStartGame)
|
|||
emit sigStartGameIfReady(forceStartGame);
|
||||
}
|
||||
|
||||
void Server_Game::shufflePlayerSeats()
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Split participants into players and spectators. Spectators keep their seats.
|
||||
QList<Server_AbstractParticipant *> players;
|
||||
QList<Server_AbstractParticipant *> spectators;
|
||||
for (auto *participant : participants.values()) {
|
||||
if (participant->isSpectator()) {
|
||||
spectators.append(participant);
|
||||
} else {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
// Fisher-Yates shuffle of the players.
|
||||
QList<Server_AbstractParticipant *> shuffledPlayers;
|
||||
while (!players.isEmpty()) {
|
||||
const int index = rng->rand(0, players.size() - 1);
|
||||
shuffledPlayers.append(players.takeAt(index));
|
||||
}
|
||||
|
||||
reseatPlayers(shuffledPlayers + spectators, true);
|
||||
}
|
||||
|
||||
void Server_Game::reorderPlayerSeats(const QList<QString> &orderedNames)
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Split participants into players and spectators. Spectators keep their seats.
|
||||
QList<Server_AbstractParticipant *> players;
|
||||
QList<Server_AbstractParticipant *> spectators;
|
||||
for (auto *participant : participants.values()) {
|
||||
if (participant->isSpectator()) {
|
||||
spectators.append(participant);
|
||||
} else {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
// Order the players according to the given names, appending any player that
|
||||
// was not listed (should not happen) in their current seat order.
|
||||
QList<Server_AbstractParticipant *> orderedPlayers;
|
||||
QSet<Server_AbstractParticipant *> placed;
|
||||
for (const QString &name : orderedNames) {
|
||||
for (auto *participant : players) {
|
||||
if (!placed.contains(participant) && (QString::fromStdString(participant->getUserInfo()->name()) == name)) {
|
||||
orderedPlayers.append(participant);
|
||||
placed.insert(participant);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto *participant : players) {
|
||||
if (!placed.contains(participant)) {
|
||||
orderedPlayers.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
reseatPlayers(orderedPlayers + spectators, false);
|
||||
}
|
||||
|
||||
void Server_Game::reseatPlayers(const QList<Server_AbstractParticipant *> &orderedParticipants, bool randomized)
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Reassign fresh, unique player ids following the given order.
|
||||
QMap<int, Server_AbstractParticipant *> newParticipants;
|
||||
int newHostId = -1;
|
||||
for (auto *participant : orderedParticipants) {
|
||||
const int newPlayerId = nextPlayerId++;
|
||||
const int oldPlayerId = participant->getPlayerId();
|
||||
participant->setPlayerId(newPlayerId);
|
||||
newParticipants.insert(newPlayerId, participant);
|
||||
|
||||
// Keep the user interface's per-user game/seat mapping in sync so that
|
||||
// incoming game commands are routed to the participant under its new id.
|
||||
if (participant->getUserInterface()) {
|
||||
participant->getUserInterface()->playerAddedToGame(gameId, room->getId(), newPlayerId);
|
||||
}
|
||||
|
||||
if (!participant->isSpectator() && (participant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered)) {
|
||||
const QString playerName = QString::fromStdString(participant->getUserInfo()->name());
|
||||
room->getServer()->removePersistentPlayer(playerName, room->getId(), gameId, oldPlayerId);
|
||||
room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newPlayerId);
|
||||
}
|
||||
|
||||
if (participant->getUserInfo()->name() == creatorInfo->name()) {
|
||||
newHostId = newPlayerId;
|
||||
}
|
||||
}
|
||||
participants = newParticipants;
|
||||
|
||||
if (newHostId != -1) {
|
||||
hostId = newHostId;
|
||||
sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId));
|
||||
}
|
||||
|
||||
// Broadcast the new seating order so that every client can display it.
|
||||
Event_PlayerOrderChanged event;
|
||||
for (auto *participant : orderedParticipants) {
|
||||
if (!participant->isSpectator()) {
|
||||
event.add_player_names(QString::fromStdString(participant->getUserInfo()->name()).toStdString());
|
||||
}
|
||||
}
|
||||
event.set_randomized(randomized);
|
||||
sendGameEventContainer(prepareGameEvent(event, -1));
|
||||
}
|
||||
|
||||
void Server_Game::stopGameIfFinished()
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
|
@ -950,6 +1070,7 @@ void Server_Game::getInfo(ServerInfo_Game &result) const
|
|||
result.set_spectators_can_chat(spectatorsCanTalk);
|
||||
result.set_spectators_omniscient(spectatorsSeeEverything);
|
||||
result.set_share_decklists_on_load(shareDecklistsOnLoad);
|
||||
result.set_shuffle_players(shufflePlayers);
|
||||
result.set_spectators_count(getSpectatorCount());
|
||||
result.set_start_time(startTime.toSecsSinceEpoch());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ private:
|
|||
bool spectatorsSeeEverything;
|
||||
int startingLifeTotal;
|
||||
bool shareDecklistsOnLoad;
|
||||
bool shufflePlayers;
|
||||
int inactivityCounter;
|
||||
int startTimeOfThisGame, secondsElapsed;
|
||||
bool firstGameStarted;
|
||||
|
|
@ -109,6 +110,7 @@ private:
|
|||
Server_AbstractParticipant *recipient,
|
||||
bool omniscient,
|
||||
bool withUserInfo);
|
||||
void reseatPlayers(const QList<Server_AbstractParticipant *> &orderedParticipants, bool randomized);
|
||||
void storeGameInformation();
|
||||
bool canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const;
|
||||
signals:
|
||||
|
|
@ -189,6 +191,10 @@ public:
|
|||
{
|
||||
return shareDecklistsOnLoad;
|
||||
}
|
||||
bool getShufflePlayers() const
|
||||
{
|
||||
return shufflePlayers;
|
||||
}
|
||||
Response::ResponseCode
|
||||
checkJoin(ServerInfo_User *user, const QString &_password, const RoleInfo &roleInfo, bool overrideRestrictions);
|
||||
bool containsUser(const QString &userName) const;
|
||||
|
|
@ -202,6 +208,8 @@ public:
|
|||
bool kickParticipant(int playerId);
|
||||
void startGameIfReady(bool forceStartGame);
|
||||
void stopGameIfFinished();
|
||||
void shufflePlayerSeats();
|
||||
void reorderPlayerSeats(const QList<QString> &orderedNames);
|
||||
int getActivePlayer() const
|
||||
{
|
||||
return activePlayer;
|
||||
|
|
|
|||
|
|
@ -1116,6 +1116,7 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
|||
int startingLifeTotal = cmd.has_starting_life_total() ? cmd.starting_life_total() : 20;
|
||||
|
||||
bool shareDecklistsOnLoad = cmd.has_share_decklists_on_load() ? cmd.share_decklists_on_load() : false;
|
||||
bool shufflePlayers = cmd.has_shuffle_players() ? cmd.shuffle_players() : false;
|
||||
|
||||
const int gameId = databaseInterface->getNextGameId();
|
||||
if (gameId == -1) {
|
||||
|
|
@ -1137,7 +1138,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
|||
.spectatorsCanTalk = cmd.spectators_can_talk(),
|
||||
.spectatorsSeeEverything = cmd.spectators_see_everything(),
|
||||
.startingLifeTotal = startingLifeTotal,
|
||||
.shareDecklistsOnLoad = shareDecklistsOnLoad};
|
||||
.shareDecklistsOnLoad = shareDecklistsOnLoad,
|
||||
.shufflePlayers = shufflePlayers};
|
||||
|
||||
auto *game = new Server_Game(config, room);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,3 +9,15 @@ cockatrice_add_test(
|
|||
LABELS
|
||||
server
|
||||
)
|
||||
|
||||
cockatrice_add_test(
|
||||
NAME
|
||||
player_order_test
|
||||
SOURCES
|
||||
player_order_test.cpp
|
||||
LIBS
|
||||
libcockatrice_network_server_remote
|
||||
libcockatrice_rng
|
||||
LABELS
|
||||
server
|
||||
)
|
||||
|
|
|
|||
269
tests/movecard_tests/player_order_test.cpp
Normal file
269
tests/movecard_tests/player_order_test.cpp
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
#include "game/game_config.h"
|
||||
#include "game/server_abstract_participant.h"
|
||||
#include "game/server_game.h"
|
||||
#include "server_abstractuserinterface.h"
|
||||
#include "server_response_containers.h"
|
||||
#include "server_room.h"
|
||||
#include "server_test_helpers.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||
#include <libcockatrice/protocol/pb/room_event.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_event.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
|
||||
RNG_Abstract *rng = nullptr; // this needs to be defined due to other functions in server
|
||||
|
||||
class ScriptedRng : public RNG_Abstract
|
||||
{
|
||||
public:
|
||||
QList<unsigned int> values;
|
||||
|
||||
unsigned int rand(int min, int max) override
|
||||
{
|
||||
const unsigned int value = values.takeFirst();
|
||||
EXPECT_GE(value, static_cast<unsigned int>(min));
|
||||
EXPECT_LE(value, static_cast<unsigned int>(max));
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
class FakeUserInterface : public Server_AbstractUserInterface
|
||||
{
|
||||
public:
|
||||
FakeUserInterface(Server *_server, const ServerInfo_User &_userInfo) : Server_AbstractUserInterface(_server)
|
||||
{
|
||||
setUserInfo(_userInfo);
|
||||
}
|
||||
|
||||
int getLastCommandTime() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool addSaidMessageSize(int) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendProtocolItem(const Response &) override
|
||||
{
|
||||
}
|
||||
|
||||
void sendProtocolItem(const SessionEvent &) override
|
||||
{
|
||||
}
|
||||
|
||||
void sendProtocolItem(const GameEventContainer &) override
|
||||
{
|
||||
}
|
||||
|
||||
void sendProtocolItem(const RoomEvent &) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
QStringList participantNamesInOrder(const Server_Game &game)
|
||||
{
|
||||
QStringList names;
|
||||
for (const auto *participant : game.getParticipants()) {
|
||||
names.append(QString::fromStdString(participant->getUserInfo()->name()));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
QList<int> participantIdsInOrder(const Server_Game &game)
|
||||
{
|
||||
QList<int> ids;
|
||||
for (int id : game.getParticipants().keys()) {
|
||||
ids.append(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class PlayerOrderTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
ServerInfo_User creator;
|
||||
ServerInfo_User userB;
|
||||
ServerInfo_User userC;
|
||||
|
||||
PlayerOrderTest()
|
||||
{
|
||||
creator.set_name("A");
|
||||
creator.set_user_level(ServerInfo_User::IsRegistered);
|
||||
userB.set_name("B");
|
||||
userB.set_user_level(ServerInfo_User::IsRegistered);
|
||||
userC.set_name("C");
|
||||
userC.set_user_level(ServerInfo_User::IsRegistered);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PlayerOrderTest, JoinOrderIsSeatOrder)
|
||||
{
|
||||
FakeServer server;
|
||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||
FakeUserInterface uiA(&server, creator);
|
||||
FakeUserInterface uiB(&server, userB);
|
||||
FakeUserInterface uiC(&server, userC);
|
||||
GameConfig config{.creatorInfo = creator,
|
||||
.gameId = 1,
|
||||
.description = QString(),
|
||||
.password = QString(),
|
||||
.maxPlayers = 3,
|
||||
.gameTypes = QList<int>(),
|
||||
.onlyBuddies = false,
|
||||
.onlyRegistered = false,
|
||||
.spectatorsAllowed = false,
|
||||
.spectatorsNeedPassword = false,
|
||||
.spectatorsCanTalk = false,
|
||||
.spectatorsSeeEverything = false,
|
||||
.startingLifeTotal = 20,
|
||||
.shareDecklistsOnLoad = false,
|
||||
.shufflePlayers = false};
|
||||
Server_Game game(config, &room);
|
||||
ResponseContainer rc(0);
|
||||
|
||||
game.addPlayer(&uiA, rc, false, false);
|
||||
game.addPlayer(&uiB, rc, false, false);
|
||||
game.addPlayer(&uiC, rc, false, false);
|
||||
|
||||
EXPECT_EQ(participantNamesInOrder(game), QStringList({"A", "B", "C"}));
|
||||
EXPECT_EQ(participantIdsInOrder(game), QList<int>({0, 1, 2}));
|
||||
EXPECT_EQ(game.getHostId(), 0);
|
||||
EXPECT_EQ(game.getPlayerCount(), 3);
|
||||
}
|
||||
|
||||
TEST_F(PlayerOrderTest, ReorderPlayerSeats)
|
||||
{
|
||||
FakeServer server;
|
||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||
FakeUserInterface uiA(&server, creator);
|
||||
FakeUserInterface uiB(&server, userB);
|
||||
FakeUserInterface uiC(&server, userC);
|
||||
GameConfig config{.creatorInfo = creator,
|
||||
.gameId = 1,
|
||||
.description = QString(),
|
||||
.password = QString(),
|
||||
.maxPlayers = 3,
|
||||
.gameTypes = QList<int>(),
|
||||
.onlyBuddies = false,
|
||||
.onlyRegistered = false,
|
||||
.spectatorsAllowed = false,
|
||||
.spectatorsNeedPassword = false,
|
||||
.spectatorsCanTalk = false,
|
||||
.spectatorsSeeEverything = false,
|
||||
.startingLifeTotal = 20,
|
||||
.shareDecklistsOnLoad = false,
|
||||
.shufflePlayers = false};
|
||||
Server_Game game(config, &room);
|
||||
ResponseContainer rc(0);
|
||||
|
||||
game.addPlayer(&uiA, rc, false, false);
|
||||
game.addPlayer(&uiB, rc, false, false);
|
||||
game.addPlayer(&uiC, rc, false, false);
|
||||
|
||||
game.reorderPlayerSeats({"C", "A", "B"});
|
||||
|
||||
const QList<int> ids = participantIdsInOrder(game);
|
||||
EXPECT_EQ(participantNamesInOrder(game), QStringList({"C", "A", "B"}));
|
||||
EXPECT_EQ(ids.size(), 3);
|
||||
EXPECT_GE(ids.at(0), 3); // re-seated players get fresh ids
|
||||
EXPECT_LT(ids.at(0), ids.at(1));
|
||||
EXPECT_LT(ids.at(1), ids.at(2));
|
||||
|
||||
// the host seat follows the creator
|
||||
EXPECT_EQ(game.getHostId(), ids.at(1));
|
||||
|
||||
// every user interface's game list is updated to the new player id
|
||||
const int gameId = game.getGameId();
|
||||
EXPECT_EQ(uiA.getGames().value(gameId).second, ids.at(1));
|
||||
EXPECT_EQ(uiB.getGames().value(gameId).second, ids.at(2));
|
||||
EXPECT_EQ(uiC.getGames().value(gameId).second, ids.at(0));
|
||||
}
|
||||
|
||||
TEST_F(PlayerOrderTest, ReorderPlayerSeatsKeepsPlayerCount)
|
||||
{
|
||||
FakeServer server;
|
||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||
FakeUserInterface uiA(&server, creator);
|
||||
FakeUserInterface uiB(&server, userB);
|
||||
FakeUserInterface uiC(&server, userC);
|
||||
GameConfig config{.creatorInfo = creator,
|
||||
.gameId = 1,
|
||||
.description = QString(),
|
||||
.password = QString(),
|
||||
.maxPlayers = 3,
|
||||
.gameTypes = QList<int>(),
|
||||
.onlyBuddies = false,
|
||||
.onlyRegistered = false,
|
||||
.spectatorsAllowed = false,
|
||||
.spectatorsNeedPassword = false,
|
||||
.spectatorsCanTalk = false,
|
||||
.spectatorsSeeEverything = false,
|
||||
.startingLifeTotal = 20,
|
||||
.shareDecklistsOnLoad = false,
|
||||
.shufflePlayers = false};
|
||||
Server_Game game(config, &room);
|
||||
ResponseContainer rc(0);
|
||||
|
||||
game.addPlayer(&uiA, rc, false, false);
|
||||
game.addPlayer(&uiB, rc, false, false);
|
||||
game.addPlayer(&uiC, rc, false, false);
|
||||
|
||||
// listing only a subset appends the remaining players in their current order
|
||||
game.reorderPlayerSeats({"C"});
|
||||
|
||||
EXPECT_EQ(participantNamesInOrder(game), QStringList({"C", "A", "B"}));
|
||||
EXPECT_EQ(game.getPlayerCount(), 3);
|
||||
EXPECT_EQ(game.getHostId(), participantIdsInOrder(game).at(1));
|
||||
}
|
||||
|
||||
TEST_F(PlayerOrderTest, ShufflePlayerSeatsIsAPermutation)
|
||||
{
|
||||
FakeServer server;
|
||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||
FakeUserInterface uiA(&server, creator);
|
||||
FakeUserInterface uiB(&server, userB);
|
||||
FakeUserInterface uiC(&server, userC);
|
||||
GameConfig config{.creatorInfo = creator,
|
||||
.gameId = 1,
|
||||
.description = QString(),
|
||||
.password = QString(),
|
||||
.maxPlayers = 3,
|
||||
.gameTypes = QList<int>(),
|
||||
.onlyBuddies = false,
|
||||
.onlyRegistered = false,
|
||||
.spectatorsAllowed = false,
|
||||
.spectatorsNeedPassword = false,
|
||||
.spectatorsCanTalk = false,
|
||||
.spectatorsSeeEverything = false,
|
||||
.startingLifeTotal = 20,
|
||||
.shareDecklistsOnLoad = false,
|
||||
.shufflePlayers = false};
|
||||
Server_Game game(config, &room);
|
||||
ResponseContainer rc(0);
|
||||
|
||||
game.addPlayer(&uiA, rc, false, false);
|
||||
game.addPlayer(&uiB, rc, false, false);
|
||||
game.addPlayer(&uiC, rc, false, false);
|
||||
|
||||
// fisher-yates on [A, B, C]: rand(0, 2) -> 1 (take B), rand(0, 1) -> 0 (take A), rand(0, 0) -> 0 (take C)
|
||||
auto *scriptedRng = new ScriptedRng;
|
||||
scriptedRng->values = {1, 0, 0};
|
||||
rng = scriptedRng;
|
||||
|
||||
game.shufflePlayerSeats();
|
||||
|
||||
EXPECT_EQ(participantNamesInOrder(game), QStringList({"B", "A", "C"}));
|
||||
const QList<int> ids = participantIdsInOrder(game);
|
||||
EXPECT_EQ(ids.size(), 3);
|
||||
QSet<int> idSet(ids.cbegin(), ids.cend());
|
||||
EXPECT_EQ(idSet.size(), 3); // ids are unique
|
||||
EXPECT_EQ(game.getHostId(), ids.at(1));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue