mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-11 04:23:29 -08:00
Simplify random reveal to follow existing command patterns
Forward server dispatch through cmdRevealRandomCards and use ordinary PlayerActions command sending. Remove the bespoke server-version error UI and long-lived-object guards. Reduce the test from 246 to 150 lines with two focused cases using the current RoleInfo player API, preserving distinct-card, private-event and rejected-count coverage.
This commit is contained in:
parent
6fab4410d0
commit
4a3cb564ca
10 changed files with 127 additions and 255 deletions
|
|
@ -13,9 +13,7 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <libcockatrice/game/abstract_game.h>
|
#include <libcockatrice/game/abstract_game.h>
|
||||||
|
|
@ -25,7 +23,6 @@
|
||||||
#include <libcockatrice/game/player/player_logic.h>
|
#include <libcockatrice/game/player/player_logic.h>
|
||||||
#include <libcockatrice/game/player/player_manager.h>
|
#include <libcockatrice/game/player/player_manager.h>
|
||||||
#include <libcockatrice/game/zones/hand_zone_logic.h>
|
#include <libcockatrice/game/zones/hand_zone_logic.h>
|
||||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
@ -33,7 +30,6 @@ class QWidget;
|
||||||
HandMenu::HandMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
|
HandMenu::HandMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
|
||||||
{
|
{
|
||||||
auto *actions = player->getLogic()->getPlayerActions();
|
auto *actions = player->getLogic()->getPlayerActions();
|
||||||
connect(actions, &PlayerActions::randomHandRevealFailed, this, &HandMenu::onRandomHandRevealFailed);
|
|
||||||
|
|
||||||
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
|
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
|
||||||
aViewHand = new QAction(this);
|
aViewHand = new QAction(this);
|
||||||
|
|
@ -279,12 +275,7 @@ void HandMenu::onRevealRandomHandCardsTriggered()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int targetId = action->data().toInt();
|
const int targetId = action->data().toInt();
|
||||||
QPointer<PlayerActions> actions = player->getLogic()->getPlayerActions();
|
auto *actions = player->getLogic()->getPlayerActions();
|
||||||
QPointer<AbstractClient> client =
|
|
||||||
player->getLogic()->getGame()->getClientForPlayer(player->getLogic()->getPlayerInfo()->getId());
|
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto *dialog = new QInputDialog(dialogParent());
|
auto *dialog = new QInputDialog(dialogParent());
|
||||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
dialog->setWindowTitle(tr("Reveal Random Hand Cards"));
|
dialog->setWindowTitle(tr("Reveal Random Hand Cards"));
|
||||||
|
|
@ -292,15 +283,8 @@ void HandMenu::onRevealRandomHandCardsTriggered()
|
||||||
dialog->setInputMode(QInputDialog::IntInput);
|
dialog->setInputMode(QInputDialog::IntInput);
|
||||||
dialog->setIntRange(1, handSize);
|
dialog->setIntRange(1, handSize);
|
||||||
dialog->setIntValue(qMin(2, handSize));
|
dialog->setIntValue(qMin(2, handSize));
|
||||||
connect(this, &QObject::destroyed, dialog, &QDialog::reject);
|
connect(dialog, &QDialog::accepted, actions,
|
||||||
connect(client, &QObject::destroyed, dialog, &QDialog::reject);
|
[dialog, actions, targetId] { actions->actRevealRandomHandCards(targetId, dialog->intValue()); });
|
||||||
connect(actions, &QObject::destroyed, dialog, &QDialog::reject);
|
|
||||||
connect(dialog, &QDialog::accepted, this, [dialog, actions, client, targetId] {
|
|
||||||
if (!actions || !client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actions->actRevealRandomHandCards(targetId, dialog->intValue());
|
|
||||||
});
|
|
||||||
dialog->open();
|
dialog->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,20 +295,3 @@ QWidget *HandMenu::dialogParent() const
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandMenu::onRandomHandRevealFailed(bool unsupported)
|
|
||||||
{
|
|
||||||
const QString text =
|
|
||||||
unsupported ? tr("This server does not support revealing multiple random cards. No cards were revealed.")
|
|
||||||
: tr("The random reveal could not be completed. The hand or recipient may have changed.");
|
|
||||||
auto *message =
|
|
||||||
new QMessageBox(QMessageBox::Warning, tr("Reveal Random Hand Cards"), text, QMessageBox::Ok, dialogParent());
|
|
||||||
message->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
connect(this, &QObject::destroyed, message, &QDialog::reject);
|
|
||||||
connect(player->getLogic()->getPlayerActions(), &QObject::destroyed, message, &QDialog::reject);
|
|
||||||
if (auto *client =
|
|
||||||
player->getLogic()->getGame()->getClientForPlayer(player->getLogic()->getPlayerInfo()->getId())) {
|
|
||||||
connect(client, &QObject::destroyed, message, &QDialog::reject);
|
|
||||||
}
|
|
||||||
message->open();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ private slots:
|
||||||
void onRevealRandomHandCardTriggered();
|
void onRevealRandomHandCardTriggered();
|
||||||
void populateRevealRandomHandCardsMenuWithActivePlayers();
|
void populateRevealRandomHandCardsMenuWithActivePlayers();
|
||||||
void onRevealRandomHandCardsTriggered();
|
void onRevealRandomHandCardsTriggered();
|
||||||
void onRandomHandRevealFailed(bool unsupported);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget *dialogParent() const;
|
QWidget *dialogParent() const;
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ When you choose a specific player, that player receives the selected card identi
|
||||||
|
|
||||||
If the hand becomes too small or the recipient leaves before the server processes the request, the request fails without a partial reveal. Cancelling the count dialog sends nothing.
|
If the hand becomes too small or the recipient leaves before the server processes the request, the request fails without a partial reveal. Cancelling the count dialog sends nothing.
|
||||||
|
|
||||||
This operation requires a server that supports multiple random reveals. An older server rejects it without revealing any cards, and the client explains that it is unsupported. The existing **Reveal random card to...** action and its shortcut remain available for single-card reveals.
|
This operation requires a server that supports multiple random reveals. An older server rejects it without revealing any cards. The existing **Reveal random card to...** action and its shortcut remain available for single-card reveals.
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
#include <QPointer>
|
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
@ -48,7 +47,6 @@
|
||||||
#include <libcockatrice/deck_list/loaded_deck.h>
|
#include <libcockatrice/deck_list/loaded_deck.h>
|
||||||
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
|
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
|
||||||
#include <libcockatrice/interfaces/interface_interface_settings_provider.h>
|
#include <libcockatrice/interfaces/interface_interface_settings_provider.h>
|
||||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
|
||||||
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
||||||
|
|
@ -68,8 +66,6 @@
|
||||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
|
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
|
||||||
#include <libcockatrice/utility/clamped_arithmetic.h>
|
#include <libcockatrice/utility/clamped_arithmetic.h>
|
||||||
#include <libcockatrice/utility/color.h>
|
#include <libcockatrice/utility/color.h>
|
||||||
#include <libcockatrice/utility/counter_limits.h>
|
#include <libcockatrice/utility/counter_limits.h>
|
||||||
|
|
@ -1762,23 +1758,13 @@ void PlayerActions::actRevealRandomHandCard(int revealToPlayerId)
|
||||||
|
|
||||||
void PlayerActions::actRevealRandomHandCards(int revealToPlayerId, int amount)
|
void PlayerActions::actRevealRandomHandCards(int revealToPlayerId, int amount)
|
||||||
{
|
{
|
||||||
QPointer<AbstractClient> client = player->getGame()->getClientForPlayer(player->getPlayerInfo()->getId());
|
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Command_RevealRandomCards cmd;
|
Command_RevealRandomCards cmd;
|
||||||
if (revealToPlayerId != -1) {
|
if (revealToPlayerId != -1) {
|
||||||
cmd.set_player_id(revealToPlayerId);
|
cmd.set_player_id(revealToPlayerId);
|
||||||
}
|
}
|
||||||
cmd.set_zone_name(ZoneNames::HAND);
|
cmd.set_zone_name(ZoneNames::HAND);
|
||||||
cmd.set_number_of_cards(amount);
|
cmd.set_number_of_cards(amount);
|
||||||
auto *pending = prepareGameCommand(cmd);
|
sendGameCommand(cmd);
|
||||||
connect(pending, &PendingCommand::finished, this, [this, client](const Response &response) {
|
|
||||||
if (client && response.response_code() != Response::RespOk) {
|
|
||||||
emit randomHandRevealFailed(response.response_code() == Response::RespInvalidCommand);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sendGameCommand(pending);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actRevealLibrary(int revealToPlayerId)
|
void PlayerActions::actRevealLibrary(int revealToPlayerId)
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void randomHandRevealFailed(bool unsupported);
|
|
||||||
void requestViewTopCardsDialog(int defaultNumberTopCards, int deckSize);
|
void requestViewTopCardsDialog(int defaultNumberTopCards, int deckSize);
|
||||||
void requestViewBottomCardsDialog(int defaultNumberBottomCards, int deckSize);
|
void requestViewBottomCardsDialog(int defaultNumberBottomCards, int deckSize);
|
||||||
void requestShuffleTopDialog(int defaultNumberTopCards, int maxCards);
|
void requestShuffleTopDialog(int defaultNumberTopCards, int maxCards);
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,13 @@ Response::ResponseCode Server_AbstractParticipant::cmdRevealCards(const Command_
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdRevealRandomCards(const Command_RevealRandomCards & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_AbstractParticipant::cmdChangeZoneProperties(const Command_ChangeZoneProperties & /*cmd*/,
|
Response::ResponseCode Server_AbstractParticipant::cmdChangeZoneProperties(const Command_ChangeZoneProperties & /*cmd*/,
|
||||||
ResponseContainer & /*rc*/,
|
ResponseContainer & /*rc*/,
|
||||||
GameEventStorage & /*ges*/)
|
GameEventStorage & /*ges*/)
|
||||||
|
|
@ -545,20 +552,9 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
||||||
case GameCommand::REVEAL_CARDS:
|
case GameCommand::REVEAL_CARDS:
|
||||||
return cmdRevealCards(command.GetExtension(Command_RevealCards::ext), rc, ges);
|
return cmdRevealCards(command.GetExtension(Command_RevealCards::ext), rc, ges);
|
||||||
break;
|
break;
|
||||||
case GameCommand::REVEAL_RANDOM_CARDS: {
|
case GameCommand::REVEAL_RANDOM_CARDS:
|
||||||
const auto &random = command.GetExtension(Command_RevealRandomCards::ext);
|
return cmdRevealRandomCards(command.GetExtension(Command_RevealRandomCards::ext), rc, ges);
|
||||||
if (random.number_of_cards() <= 0) {
|
break;
|
||||||
return Response::RespInvalidData;
|
|
||||||
}
|
|
||||||
// Reuse the existing player/judge authorization and private reveal delivery.
|
|
||||||
Command_RevealCards reveal;
|
|
||||||
reveal.set_zone_name(random.zone_name());
|
|
||||||
reveal.add_card_id(-2);
|
|
||||||
if (random.has_player_id()) {
|
|
||||||
reveal.set_player_id(random.player_id());
|
|
||||||
}
|
|
||||||
return cmdRevealCards(reveal, rc, ges, random.number_of_cards());
|
|
||||||
}
|
|
||||||
case GameCommand::MOVE_CARD:
|
case GameCommand::MOVE_CARD:
|
||||||
return cmdMoveCard(command.GetExtension(Command_MoveCard::ext), rc, ges);
|
return cmdMoveCard(command.GetExtension(Command_MoveCard::ext), rc, ges);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class Command_NextTurn;
|
||||||
class Command_SetActivePhase;
|
class Command_SetActivePhase;
|
||||||
class Command_DumpZone;
|
class Command_DumpZone;
|
||||||
class Command_RevealCards;
|
class Command_RevealCards;
|
||||||
|
class Command_RevealRandomCards;
|
||||||
class Command_ReverseTurn;
|
class Command_ReverseTurn;
|
||||||
class Command_MoveCard;
|
class Command_MoveCard;
|
||||||
class Command_SetSideboardPlan;
|
class Command_SetSideboardPlan;
|
||||||
|
|
@ -176,6 +177,8 @@ public:
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges, int randomCount = 1);
|
cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges, int randomCount = 1);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
|
cmdRevealRandomCards(const Command_RevealRandomCards &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
|
|
||||||
|
|
@ -1494,6 +1494,22 @@ Server_AbstractPlayer::cmdDumpZone(const Command_DumpZone &cmd, ResponseContaine
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractPlayer::cmdRevealRandomCards(const Command_RevealRandomCards &cmd,
|
||||||
|
ResponseContainer &rc,
|
||||||
|
GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
if (cmd.number_of_cards() <= 0) {
|
||||||
|
return Response::RespInvalidData;
|
||||||
|
}
|
||||||
|
Command_RevealCards reveal;
|
||||||
|
reveal.set_zone_name(cmd.zone_name());
|
||||||
|
reveal.add_card_id(-2);
|
||||||
|
if (cmd.has_player_id()) {
|
||||||
|
reveal.set_player_id(cmd.player_id());
|
||||||
|
}
|
||||||
|
return cmdRevealCards(reveal, rc, ges, cmd.number_of_cards());
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd,
|
Response::ResponseCode Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd,
|
||||||
ResponseContainer & /*rc*/,
|
ResponseContainer & /*rc*/,
|
||||||
GameEventStorage &ges,
|
GameEventStorage &ges,
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,8 @@ public:
|
||||||
ResponseContainer &rc,
|
ResponseContainer &rc,
|
||||||
GameEventStorage &ges,
|
GameEventStorage &ges,
|
||||||
int randomCount = 1) override;
|
int randomCount = 1) override;
|
||||||
|
Response::ResponseCode
|
||||||
|
cmdRevealRandomCards(const Command_RevealRandomCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
virtual Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd,
|
virtual Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd,
|
||||||
ResponseContainer &rc,
|
ResponseContainer &rc,
|
||||||
GameEventStorage &ges) override;
|
GameEventStorage &ges) override;
|
||||||
|
|
|
||||||
|
|
@ -9,46 +9,34 @@
|
||||||
#include "server_room.h"
|
#include "server_room.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QSet>
|
|
||||||
#include <google/protobuf/descriptor.pb.h>
|
|
||||||
#include <google/protobuf/dynamic_message.h>
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
#include <libcockatrice/protocol/get_pb_extension.h>
|
|
||||||
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
|
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_reveal_cards.pb.h>
|
#include <libcockatrice/protocol/pb/event_reveal_cards.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_zone.pb.h>
|
|
||||||
#include <libcockatrice/rng/rng_abstract.h>
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
RNG_Abstract *rng = nullptr;
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class AlternatingRng : public RNG_Abstract
|
class FirstRng : public RNG_Abstract
|
||||||
{
|
{
|
||||||
int calls = 0;
|
unsigned int rand(int min, int) override
|
||||||
|
|
||||||
public:
|
|
||||||
unsigned int rand(int min, int max) override
|
|
||||||
{
|
{
|
||||||
EXPECT_LE(min, max);
|
return min;
|
||||||
return calls++ % 2 == 0 && min < max ? min + 1 : min;
|
|
||||||
}
|
}
|
||||||
};
|
} firstRng;
|
||||||
|
|
||||||
class Recipient : public Server_AbstractUserInterface
|
class User : public Server_AbstractUserInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QList<Event_RevealCards> reveals;
|
User(Server *server, const char *name) : Server_AbstractUserInterface(server)
|
||||||
Recipient(Server *server, const char *name) : Server_AbstractUserInterface(server)
|
|
||||||
{
|
{
|
||||||
ServerInfo_User user;
|
ServerInfo_User info;
|
||||||
user.set_name(name);
|
info.set_name(name);
|
||||||
setUserInfo(user);
|
setUserInfo(info);
|
||||||
}
|
}
|
||||||
int getLastCommandTime() const override
|
int getLastCommandTime() const override
|
||||||
{
|
{
|
||||||
|
|
@ -67,180 +55,96 @@ public:
|
||||||
void sendProtocolItem(const RoomEvent &) override
|
void sendProtocolItem(const RoomEvent &) override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void sendProtocolItem(const GameEventContainer &container) override
|
void sendProtocolItem(const GameEventContainer &) override
|
||||||
{
|
{
|
||||||
for (const auto &event : container.event_list()) {
|
|
||||||
if (event.HasExtension(Event_RevealCards::ext)) {
|
|
||||||
reveals.append(event.GetExtension(Event_RevealCards::ext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GameCommand randomReveal(int count, int recipient = -1)
|
class RandomReveal : public ::testing::Test
|
||||||
{
|
{
|
||||||
GameCommand command;
|
protected:
|
||||||
auto *request = command.MutableExtension(Command_RevealRandomCards::ext);
|
FakeServer server;
|
||||||
request->set_zone_name(ZoneNames::HAND);
|
User owner{&server, "Owner"}, target{&server, "Target"};
|
||||||
request->set_number_of_cards(count);
|
Server_Room room{0, 0, "", "", "", "", false, "", {}, &server};
|
||||||
if (recipient != -1) {
|
std::unique_ptr<Server_Game> game;
|
||||||
request->set_player_id(recipient);
|
Server_AbstractPlayer *player;
|
||||||
|
Server_CardZone *hand;
|
||||||
|
ResponseContainer response{1};
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
GameConfig config;
|
||||||
|
config.creatorInfo = *owner.getUserInfo();
|
||||||
|
config.gameId = 1;
|
||||||
|
config.maxPlayers = 2;
|
||||||
|
config.shareDecklistsOnLoad = false;
|
||||||
|
game = std::make_unique<Server_Game>(config, &room);
|
||||||
|
game->addPlayer(&owner, response, {});
|
||||||
|
game->addPlayer(&target, response, {});
|
||||||
|
for (auto *participant : game->getPlayers()) {
|
||||||
|
GameCommand command;
|
||||||
|
command.MutableExtension(Command_DeckSelect::ext)
|
||||||
|
->set_deck(DeckList().writeToString_Native().toStdString());
|
||||||
|
GameEventStorage events;
|
||||||
|
ASSERT_EQ(participant->processGameCommand(command, response, events), Response::RespOk);
|
||||||
|
participant->setReadyStart(true);
|
||||||
|
}
|
||||||
|
game->startGameIfReady(false);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
ASSERT_TRUE(game->getGameStarted());
|
||||||
|
player = game->getPlayer(0);
|
||||||
|
hand = player->getZones().value(ZoneNames::HAND);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
hand->insertCard(new Server_Card({"Twin", QString::number(i)}, player->newCardId(), i, 0), -1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode reveal(int count, GameEventStorage &events)
|
||||||
|
{
|
||||||
|
GameCommand command;
|
||||||
|
auto *request = command.MutableExtension(Command_RevealRandomCards::ext);
|
||||||
|
request->set_zone_name(ZoneNames::HAND);
|
||||||
|
request->set_player_id(1);
|
||||||
|
request->set_number_of_cards(count);
|
||||||
|
return player->processGameCommand(command, response, events);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(RandomReveal, DistinctPhysicalCardsArePrivateAndLeaveTheHandUnchanged)
|
||||||
|
{
|
||||||
|
const auto original = hand->getCards();
|
||||||
|
GameEventStorage events;
|
||||||
|
ASSERT_EQ(reveal(2, events), Response::RespOk);
|
||||||
|
ASSERT_EQ(events.getGameEventList().size(), 2);
|
||||||
|
EXPECT_EQ(events.getPrivatePlayerId(), 1);
|
||||||
|
const auto *privateEvent = events.getGameEventList().at(0);
|
||||||
|
EXPECT_EQ(privateEvent->getRecipients(), GameEventStorageItem::SendToPrivate);
|
||||||
|
const auto &cards = privateEvent->getGameEvent().GetExtension(Event_RevealCards::ext);
|
||||||
|
ASSERT_EQ(cards.cards_size(), 2);
|
||||||
|
EXPECT_NE(cards.cards(0).id(), cards.cards(1).id());
|
||||||
|
const auto *publicEvent = events.getGameEventList().at(1);
|
||||||
|
EXPECT_EQ(publicEvent->getRecipients(), GameEventStorageItem::SendToOthers);
|
||||||
|
const auto ¬ice = publicEvent->getGameEvent().GetExtension(Event_RevealCards::ext);
|
||||||
|
EXPECT_EQ(notice.cards_size(), 0);
|
||||||
|
EXPECT_EQ(notice.number_of_cards(), 2);
|
||||||
|
EXPECT_EQ(hand->getCards(), original);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RandomReveal, InvalidCountsDoNotQueueAnyReveal)
|
||||||
|
{
|
||||||
|
for (int count : {0, -1, 5}) {
|
||||||
|
GameEventStorage events;
|
||||||
|
EXPECT_NE(reveal(count, events), Response::RespOk);
|
||||||
|
EXPECT_TRUE(events.getGameEventList().isEmpty());
|
||||||
}
|
}
|
||||||
return command;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(RandomReveal, DistinctSamplesPrivacyAndCompatibility)
|
RNG_Abstract *rng = &firstRng;
|
||||||
{
|
|
||||||
int argc = 1;
|
|
||||||
char name[] = "random-reveal-test";
|
|
||||||
char *argv[] = {name, nullptr};
|
|
||||||
std::unique_ptr<QCoreApplication> application;
|
|
||||||
if (!QCoreApplication::instance()) {
|
|
||||||
application = std::make_unique<QCoreApplication>(argc, argv);
|
|
||||||
}
|
|
||||||
AlternatingRng random;
|
|
||||||
rng = &random;
|
|
||||||
FakeServer server;
|
|
||||||
Recipient owner(&server, "Owner"), target(&server, "Target"), bystander(&server, "Bystander"),
|
|
||||||
judge(&server, "Judge"), spectator(&server, "Spectator");
|
|
||||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
|
||||||
GameConfig config;
|
|
||||||
config.creatorInfo = *owner.getUserInfo();
|
|
||||||
config.gameId = 1;
|
|
||||||
config.maxPlayers = 3;
|
|
||||||
config.spectatorsAllowed = true;
|
|
||||||
config.spectatorsSeeEverything = false;
|
|
||||||
config.shareDecklistsOnLoad = false;
|
|
||||||
Server_Game game(config, &room);
|
|
||||||
for (auto *recipient : {&owner, &target, &bystander}) {
|
|
||||||
ResponseContainer response(1);
|
|
||||||
game.addPlayer(recipient, response, false, false, false);
|
|
||||||
}
|
|
||||||
ResponseContainer judgeResponse(1);
|
|
||||||
game.addPlayer(&judge, judgeResponse, true, true, false);
|
|
||||||
ResponseContainer spectatorResponse(1);
|
|
||||||
game.addPlayer(&spectator, spectatorResponse, true, false, false);
|
|
||||||
auto *player = game.getPlayer(0);
|
|
||||||
ASSERT_NE(player, nullptr);
|
|
||||||
ASSERT_NE(game.getPlayer(1), nullptr);
|
|
||||||
ResponseContainer response(2);
|
|
||||||
GameEventStorage notStarted;
|
|
||||||
EXPECT_EQ(player->processGameCommand(randomReveal(2, 1), response, notStarted), Response::RespGameNotStarted);
|
|
||||||
EXPECT_TRUE(notStarted.getGameEventList().isEmpty());
|
|
||||||
for (auto *participant : game.getPlayers()) {
|
|
||||||
Command_DeckSelect select;
|
|
||||||
select.set_deck(DeckList().writeToString_Native().toStdString());
|
|
||||||
GameCommand command;
|
|
||||||
command.MutableExtension(Command_DeckSelect::ext)->CopyFrom(select);
|
|
||||||
GameEventStorage events;
|
|
||||||
EXPECT_EQ(participant->processGameCommand(command, response, events), Response::RespOk);
|
|
||||||
participant->setReadyStart(true);
|
|
||||||
}
|
|
||||||
game.startGameIfReady(false);
|
|
||||||
QCoreApplication::processEvents();
|
|
||||||
ASSERT_TRUE(game.getGameStarted());
|
|
||||||
auto *hand = player->getZones().value(ZoneNames::HAND);
|
|
||||||
ASSERT_NE(hand, nullptr);
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
auto *card = new Server_Card({i < 2 ? "Twin" : "Other", QString::number(i)}, player->newCardId(), i, 0);
|
|
||||||
hand->insertCard(card, -1, 0);
|
|
||||||
}
|
|
||||||
const auto original = hand->getCards();
|
|
||||||
GameEventStorage privateReveal;
|
|
||||||
EXPECT_EQ(player->processGameCommand(randomReveal(2, 1), response, privateReveal), Response::RespOk);
|
|
||||||
ASSERT_EQ(privateReveal.getGameEventList().size(), 2);
|
|
||||||
EXPECT_EQ(privateReveal.getPrivatePlayerId(), 1);
|
|
||||||
privateReveal.sendToGame(&game);
|
|
||||||
ASSERT_EQ(owner.reveals.size(), 1);
|
|
||||||
ASSERT_EQ(target.reveals.size(), 1);
|
|
||||||
ASSERT_EQ(bystander.reveals.size(), 1);
|
|
||||||
ASSERT_EQ(judge.reveals.size(), 1);
|
|
||||||
ASSERT_EQ(spectator.reveals.size(), 1);
|
|
||||||
EXPECT_EQ(target.reveals.last().cards_size(), 2);
|
|
||||||
EXPECT_EQ(judge.reveals.last().cards_size(), 2); // existing judge visibility is retained
|
|
||||||
EXPECT_NE(target.reveals.last().cards(0).id(), target.reveals.last().cards(1).id());
|
|
||||||
EXPECT_EQ(target.reveals.last().cards(0).id(), original.at(1)->getId());
|
|
||||||
EXPECT_EQ(target.reveals.last().cards(1).id(), original.at(0)->getId());
|
|
||||||
EXPECT_EQ(target.reveals.last().cards(0).name(),
|
|
||||||
target.reveals.last().cards(1).name()); // physical copies are distinct
|
|
||||||
for (auto *recipient : {&owner, &bystander, &spectator}) {
|
|
||||||
EXPECT_EQ(recipient->reveals.last().cards_size(), 0);
|
|
||||||
EXPECT_EQ(recipient->reveals.last().number_of_cards(), 2);
|
|
||||||
ASSERT_EQ(recipient->reveals.last().card_id_size(), 1);
|
|
||||||
EXPECT_EQ(recipient->reveals.last().card_id(0), -2); // no actual card ids leak
|
|
||||||
EXPECT_EQ(recipient->reveals.last().other_player_id(), 1);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(hand->getCards(), original);
|
|
||||||
GameEventStorage publicReveal;
|
|
||||||
EXPECT_EQ(player->processGameCommand(randomReveal(4), response, publicReveal), Response::RespOk);
|
|
||||||
publicReveal.sendToGame(&game);
|
|
||||||
for (auto *recipient : {&owner, &target, &bystander, &judge, &spectator}) {
|
|
||||||
ASSERT_EQ(recipient->reveals.last().cards_size(), 4);
|
|
||||||
QSet<int> ids;
|
|
||||||
for (const auto &card : recipient->reveals.last().cards()) {
|
|
||||||
ids.insert(card.id());
|
|
||||||
}
|
|
||||||
EXPECT_EQ(ids.size(), 4);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(hand->getCards(), original);
|
|
||||||
for (int amount : {0, -1, 5, 2147483647}) {
|
|
||||||
GameEventStorage invalid;
|
|
||||||
EXPECT_NE(player->processGameCommand(randomReveal(amount, 1), response, invalid), Response::RespOk);
|
|
||||||
EXPECT_TRUE(invalid.getGameEventList().isEmpty());
|
|
||||||
}
|
|
||||||
GameEventStorage missingRecipient;
|
|
||||||
EXPECT_EQ(player->processGameCommand(randomReveal(2, 999), response, missingRecipient), Response::RespNameNotFound);
|
|
||||||
EXPECT_TRUE(missingRecipient.getGameEventList().isEmpty());
|
|
||||||
player->setConceded(true);
|
|
||||||
GameEventStorage conceded;
|
|
||||||
EXPECT_EQ(player->processGameCommand(randomReveal(2, 1), response, conceded), Response::RespContextError);
|
|
||||||
EXPECT_TRUE(conceded.getGameEventList().isEmpty());
|
|
||||||
player->setConceded(false);
|
|
||||||
GameCommand legacy;
|
|
||||||
auto *single = legacy.MutableExtension(Command_RevealCards::ext);
|
|
||||||
single->set_zone_name(ZoneNames::HAND);
|
|
||||||
single->add_card_id(-2);
|
|
||||||
single->set_player_id(1);
|
|
||||||
GameEventStorage legacyReveal;
|
|
||||||
EXPECT_EQ(player->processGameCommand(legacy, response, legacyReveal), Response::RespOk);
|
|
||||||
legacyReveal.sendToGame(&game);
|
|
||||||
EXPECT_EQ(target.reveals.last().cards_size(), 1);
|
|
||||||
EXPECT_EQ(owner.reveals.last().cards_size(), 0);
|
|
||||||
|
|
||||||
// A reader with only the old GameCommand schema keeps the new extension unknown.
|
int main(int argc, char **argv)
|
||||||
// Existing dispatch returns RespInvalidCommand; it cannot fall back to reveal-all.
|
{
|
||||||
google::protobuf::FileDescriptorProto file;
|
QCoreApplication application(argc, argv);
|
||||||
GameCommand::descriptor()->file()->CopyTo(&file);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
auto *values = file.mutable_message_type(0)->mutable_enum_type(0)->mutable_value();
|
return RUN_ALL_TESTS();
|
||||||
for (int i = values->size() - 1; i >= 0; --i) {
|
|
||||||
if (values->Get(i).number() == 1036) {
|
|
||||||
values->DeleteSubrange(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
google::protobuf::DescriptorPool pool;
|
|
||||||
ASSERT_NE(pool.BuildFile(file), nullptr);
|
|
||||||
google::protobuf::FileDescriptorProto legacyRevealFile;
|
|
||||||
Command_RevealCards::descriptor()->file()->CopyTo(&legacyRevealFile);
|
|
||||||
auto *messages = legacyRevealFile.mutable_message_type();
|
|
||||||
for (int i = messages->size() - 1; i >= 0; --i) {
|
|
||||||
if (messages->Get(i).name() == "Command_RevealRandomCards") {
|
|
||||||
messages->DeleteSubrange(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT_NE(pool.BuildFile(legacyRevealFile), nullptr);
|
|
||||||
google::protobuf::DynamicMessageFactory factory(&pool);
|
|
||||||
std::unique_ptr<google::protobuf::Message> old(
|
|
||||||
factory.GetPrototype(pool.FindMessageTypeByName("GameCommand"))->New());
|
|
||||||
ASSERT_TRUE(old->ParseFromString(legacy.SerializeAsString()));
|
|
||||||
EXPECT_EQ(getPbExtension(*old), GameCommand::REVEAL_CARDS);
|
|
||||||
old->Clear();
|
|
||||||
ASSERT_TRUE(old->ParseFromString(randomReveal(2, 1).SerializeAsString()));
|
|
||||||
EXPECT_EQ(getPbExtension(*old), -1);
|
|
||||||
GameCommand unknown;
|
|
||||||
unknown.GetReflection()->MutableUnknownFields(&unknown)->MergeFrom(old->GetReflection()->GetUnknownFields(*old));
|
|
||||||
GameEventStorage unsupported;
|
|
||||||
EXPECT_EQ(player->processGameCommand(unknown, response, unsupported), Response::RespInvalidCommand);
|
|
||||||
EXPECT_TRUE(unsupported.getGameEventList().isEmpty());
|
|
||||||
rng = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue