[Server] Keep player ids stable across seat shuffles

This commit is contained in:
Lukas Brübach 2026-10-11 14:58:26 +02:00
parent cc9847c33b
commit 00c0b2c96b
3 changed files with 129 additions and 74 deletions

View file

@ -1,4 +1,5 @@
#include "game/game_config.h"
#include "game/role_info.h"
#include "game/server_abstract_participant.h"
#include "game/server_game.h"
#include "server_abstractuserinterface.h"
@ -70,19 +71,18 @@ namespace
QStringList participantNamesInOrder(const Server_Game &game)
{
QStringList names;
for (const auto *participant : game.getParticipants()) {
names.append(QString::fromStdString(participant->getUserInfo()->name()));
for (int id : game.getSeatOrder()) {
const auto *participant = game.getParticipants().value(id, nullptr);
if (participant) {
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;
return game.getSeatOrder();
}
} // namespace
@ -129,9 +129,9 @@ TEST_F(PlayerOrderTest, JoinOrderIsSeatOrder)
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.addPlayer(&uiA, rc, RoleInfo{}, false);
game.addPlayer(&uiB, rc, RoleInfo{}, false);
game.addPlayer(&uiC, rc, RoleInfo{}, false);
EXPECT_EQ(participantNamesInOrder(game), QStringList({"A", "B", "C"}));
EXPECT_EQ(participantIdsInOrder(game), QList<int>({0, 1, 2}));
@ -164,27 +164,25 @@ TEST_F(PlayerOrderTest, ReorderPlayerSeats)
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.addPlayer(&uiA, rc, RoleInfo{}, false);
game.addPlayer(&uiB, rc, RoleInfo{}, false);
game.addPlayer(&uiC, rc, RoleInfo{}, false);
game.reorderPlayerSeats({"C", "A", "B"});
const QList<int> ids = participantIdsInOrder(game);
// player ids are stable; only the seat order changes
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));
EXPECT_EQ(participantIdsInOrder(game), QList<int>({2, 0, 1}));
EXPECT_EQ(game.getParticipants().size(), 3);
// the host seat follows the creator
EXPECT_EQ(game.getHostId(), ids.at(1));
// the host keeps its seat: creator A is still in the game, so host stays 0
EXPECT_EQ(game.getHostId(), 0);
// every user interface's game list is updated to the new player id
// no re-keying means every mapping keyed by player id stays valid
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));
EXPECT_EQ(uiA.getGames().value(gameId).second, 0);
EXPECT_EQ(uiB.getGames().value(gameId).second, 1);
EXPECT_EQ(uiC.getGames().value(gameId).second, 2);
}
TEST_F(PlayerOrderTest, ReorderPlayerSeatsKeepsPlayerCount)
@ -212,16 +210,17 @@ TEST_F(PlayerOrderTest, ReorderPlayerSeatsKeepsPlayerCount)
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.addPlayer(&uiA, rc, RoleInfo{}, false);
game.addPlayer(&uiB, rc, RoleInfo{}, false);
game.addPlayer(&uiC, rc, RoleInfo{}, 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(participantIdsInOrder(game), QList<int>({2, 0, 1}));
EXPECT_EQ(game.getPlayerCount(), 3);
EXPECT_EQ(game.getHostId(), participantIdsInOrder(game).at(1));
EXPECT_EQ(game.getHostId(), 0);
}
TEST_F(PlayerOrderTest, ShufflePlayerSeatsIsAPermutation)
@ -249,9 +248,9 @@ TEST_F(PlayerOrderTest, ShufflePlayerSeatsIsAPermutation)
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.addPlayer(&uiA, rc, RoleInfo{}, false);
game.addPlayer(&uiB, rc, RoleInfo{}, false);
game.addPlayer(&uiC, rc, RoleInfo{}, 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;
@ -261,9 +260,9 @@ TEST_F(PlayerOrderTest, ShufflePlayerSeatsIsAPermutation)
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(participantIdsInOrder(game), QList<int>({1, 0, 2}));
QSet<int> idSet(participantIdsInOrder(game).cbegin(), participantIdsInOrder(game).cend());
EXPECT_EQ(idSet.size(), 3); // ids are unique
EXPECT_EQ(game.getHostId(), ids.at(1));
// ids are stable, so the host (creator A, id 0) is unchanged
EXPECT_EQ(game.getHostId(), 0);
}