[Refactor] Split CreateGameFormWidget out of DlgCreateGame (#7801)

* Extract CreateGameFormWidget from DlgCreateGame

* Address review findings
This commit is contained in:
BruebachL 2026-11-08 16:22:31 +01:00 • committed by GitHub
parent 554368967d
commit 39220a2ea9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 864 additions and 325 deletions

View file

@ -216,6 +216,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/deck_editor/deck_zone_dialog.cpp
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
src/interface/widgets/game_hosting/create_game_form_widget.cpp
src/interface/widgets/game_hosting/game_creation_options.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp

View file

@ -1,341 +1,109 @@
#include "dlg_create_game.h"
#include "../../../client/settings/cache_settings.h"
#include "../interface/widgets/game_hosting/game_creation_options.h"
#include "../interface/widgets/tabs/tab_room.h"
#include "../game_hosting/create_game_form_widget.h"
#include "../game_hosting/game_creation_options.h"
#include "../tabs/tab_room.h"
#include "libcockatrice/protocol/pb/response.pb.h"
#include "libcockatrice/protocol/pb/room_commands.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_game.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_user.pb.h"
#include "libcockatrice/utility/macros.h"
#include "libcockatrice/protocol/pending_command.h"
#include "libcockatrice/settings/game_settings.h"
#include <QCheckBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QMapIterator>
#include <QMessageBox>
#include <QPushButton>
#include <QRadioButton>
#include <QSet>
#include <QSharedPointer>
#include <QSize>
#include <QSpinBox>
#include <QVBoxLayout>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/game_settings.h>
#include <libcockatrice/utility/string_limits.h>
#include <qnamespace.h>
#include <string>
class QWidget;
void DlgCreateGame::sharedCtor()
{
rememberGameSettings = new QCheckBox(tr("Re&member settings"));
descriptionLabel = new QLabel(tr("&Description:"));
descriptionEdit = new QLineEdit;
descriptionEdit->setMaxLength(MAX_NAME_LENGTH);
descriptionLabel->setBuddy(descriptionEdit);
maxPlayersLabel = new QLabel(tr("P&layers:"));
maxPlayersEdit = new QSpinBox();
maxPlayersEdit->setMinimum(1);
maxPlayersEdit->setMaximum(100);
maxPlayersEdit->setValue(2);
maxPlayersLabel->setBuddy(maxPlayersEdit);
auto *generalGrid = new QGridLayout;
generalGrid->addWidget(descriptionLabel, 0, 0);
generalGrid->addWidget(descriptionEdit, 0, 1);
generalGrid->addWidget(maxPlayersLabel, 1, 0);
generalGrid->addWidget(maxPlayersEdit, 1, 1);
generalGroupBox = new QGroupBox(tr("General"));
generalGroupBox->setLayout(generalGrid);
auto *gameTypeLayout = new QVBoxLayout;
QMapIterator<int, QString> gameTypeIterator(gameTypes);
while (gameTypeIterator.hasNext()) {
gameTypeIterator.next();
auto *gameTypeRadioButton = new QRadioButton(gameTypeIterator.value(), this);
gameTypeLayout->addWidget(gameTypeRadioButton);
gameTypeCheckBoxes.insert(gameTypeIterator.key(), gameTypeRadioButton);
bool isChecked = SettingsCache::instance().game().getGameTypes().contains(gameTypeIterator.value() + ", ");
gameTypeCheckBoxes[gameTypeIterator.key()]->setChecked(isChecked);
}
auto *gameTypeGroupBox = new QGroupBox(tr("Game type"));
gameTypeGroupBox->setLayout(gameTypeLayout);
passwordLabel = new QLabel(tr("&Password:"));
passwordEdit = new QLineEdit;
passwordEdit->setMaxLength(MAX_NAME_LENGTH);
passwordLabel->setBuddy(passwordEdit);
onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join"));
onlyRegisteredCheckBox = new QCheckBox(tr("Only &registered users can join"));
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) {
onlyRegisteredCheckBox->setChecked(true);
} else {
onlyBuddiesCheckBox->setEnabled(false);
onlyRegisteredCheckBox->setEnabled(false);
}
auto *joinRestrictionsLayout = new QGridLayout;
joinRestrictionsLayout->addWidget(passwordLabel, 0, 0);
joinRestrictionsLayout->addWidget(passwordEdit, 0, 1);
joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2);
joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2);
auto *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions"));
joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout);
spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch"));
spectatorsAllowedCheckBox->setChecked(true);
connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgCreateGame::spectatorsAllowedChanged);
spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch"));
spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat"));
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));
createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator"));
auto *spectatorsLayout = new QVBoxLayout;
spectatorsLayout->addWidget(spectatorsAllowedCheckBox);
spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox);
spectatorsLayout->addWidget(spectatorsCanTalkCheckBox);
spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox);
spectatorsLayout->addWidget(createGameAsSpectatorCheckBox);
spectatorsGroupBox = new QGroupBox(tr("Spectators"));
spectatorsGroupBox->setLayout(spectatorsLayout);
startingLifeTotalLabel = new QLabel(tr("Starting life total:"));
startingLifeTotalEdit = new QSpinBox();
startingLifeTotalEdit->setMinimum(1);
startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining.
startingLifeTotalEdit->setValue(20);
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
shufflePlayersCheckBox = new QCheckBox(tr("Randomize turn order (seats) on each game start"));
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
auto *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0, 1, 2);
gameSetupOptionsLayout->addWidget(shufflePlayersCheckBox, 2, 0, 1, 2);
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0, 1, 2);
} else {
createGameAsJudgeCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setHidden(true);
}
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"));
gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout);
auto *grid = new QGridLayout;
// Top row
grid->addWidget(generalGroupBox, 0, 0);
grid->addWidget(joinRestrictionsGroupBox, 0, 1);
// Middle row: left column
grid->addWidget(gameTypeGroupBox, 1, 0);
// Middle row: right column (game setup + spectators)
auto *rightLayout = new QVBoxLayout;
rightLayout->addWidget(spectatorsGroupBox, Qt::AlignTop); // top
rightLayout->addWidget(gameSetupOptionsGroupBox); // bottom
grid->addLayout(rightLayout, 1, 1);
// Bottom row
grid->addWidget(rememberGameSettings, 3, 0, 1, 2); // span both columns if needed
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setFixedHeight(sizeHint().height());
}
DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameTypes, QWidget *parent)
: QDialog(parent), room(_room), gameTypes(_gameTypes)
: QDialog(parent), room(_room)
{
sharedCtor();
const bool registered = room && (room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered);
const bool judge = room && (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge);
rememberGameSettings->setChecked(SettingsCache::instance().game().getRememberGameSettings());
descriptionEdit->setText(SettingsCache::instance().game().getGameDescription());
maxPlayersEdit->setValue(SettingsCache::instance().game().getMaxPlayers());
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) {
onlyBuddiesCheckBox->setChecked(SettingsCache::instance().game().getOnlyBuddies());
onlyRegisteredCheckBox->setChecked(SettingsCache::instance().game().getOnlyRegistered());
form = new CreateGameFormWidget(_gameTypes, registered, judge, this);
const bool rememberSettings = SettingsCache::instance().game().getRememberGameSettings();
form->setRememberSettingsChecked(rememberSettings);
if (rememberSettings) {
form->setOptions(GameCreationOptions::fromSettings(SettingsCache::instance().game(), _gameTypes, registered));
} else {
onlyBuddiesCheckBox->setEnabled(false);
onlyRegisteredCheckBox->setEnabled(false);
form->resetToDefaults();
}
spectatorsAllowedCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsAllowed());
spectatorsNeedPasswordCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsNeedPassword());
spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsCanTalk());
spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsCanSeeEverything());
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().game().getCreateGameAsSpectator());
startingLifeTotalEdit->setValue(SettingsCache::instance().game().getDefaultStartingLifeTotal());
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().game().getShareDecklistsOnLoad());
shufflePlayersCheckBox->setChecked(SettingsCache::instance().game().getShufflePlayers());
form->focusDescription();
if (!rememberGameSettings->isChecked()) {
actReset();
}
errorLabel = new QLabel(this);
errorLabel->setObjectName("createGameErrorLabel");
errorLabel->setWordWrap(true);
errorLabel->setVisible(false);
descriptionEdit->setFocus();
clearButton = new QPushButton(tr("&Clear"));
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject);
clearButton = new QPushButton(tr("&Clear"), this);
buttonBox->addButton(QDialogButtonBox::Cancel);
buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::actOK);
connect(clearButton, &QPushButton::clicked, this, &DlgCreateGame::actReset);
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(form);
mainLayout->addWidget(errorLabel);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Create game"));
}
DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QString> &_gameTypes, QWidget *parent)
: QDialog(parent), room(0), gameTypes(_gameTypes)
: QDialog(parent), room(nullptr)
{
sharedCtor();
rememberGameSettings->setEnabled(false);
descriptionEdit->setEnabled(false);
maxPlayersEdit->setEnabled(false);
passwordEdit->setEnabled(false);
onlyBuddiesCheckBox->setEnabled(false);
onlyRegisteredCheckBox->setEnabled(false);
spectatorsAllowedCheckBox->setEnabled(false);
spectatorsNeedPasswordCheckBox->setEnabled(false);
spectatorsCanTalkCheckBox->setEnabled(false);
spectatorsSeeEverythingCheckBox->setEnabled(false);
createGameAsSpectatorCheckBox->setEnabled(false);
startingLifeTotalEdit->setEnabled(false);
shareDecklistsOnLoadCheckBox->setEnabled(false);
shufflePlayersCheckBox->setEnabled(false);
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
maxPlayersEdit->setValue(gameInfo.max_players());
onlyBuddiesCheckBox->setChecked(gameInfo.only_buddies());
onlyRegisteredCheckBox->setChecked(gameInfo.only_registered());
spectatorsAllowedCheckBox->setChecked(gameInfo.spectators_allowed());
spectatorsNeedPasswordCheckBox->setChecked(gameInfo.spectators_need_password());
spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat());
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load());
shufflePlayersCheckBox->setChecked(gameInfo.shuffle_players());
QSet<int> types;
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
types.insert(gameInfo.game_types(i));
}
QMapIterator<int, QString> gameTypeIterator(gameTypes);
while (gameTypeIterator.hasNext()) {
gameTypeIterator.next();
QRadioButton *gameTypeCheckBox = gameTypeCheckBoxes.value(gameTypeIterator.key());
gameTypeCheckBox->setEnabled(false);
gameTypeCheckBox->setChecked(types.contains(gameTypeIterator.key()));
}
form = new CreateGameFormWidget(_gameTypes, false, false, this);
form->setOptions(GameCreationOptions::fromGameInfo(gameInfo, _gameTypes));
form->setReadOnly(true);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::accept);
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(form);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Game information"));
}
void DlgCreateGame::actReset()
{
descriptionEdit->setText("");
maxPlayersEdit->setValue(2);
passwordEdit->setText("");
onlyBuddiesCheckBox->setChecked(false);
onlyRegisteredCheckBox->setChecked(room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered);
spectatorsAllowedCheckBox->setChecked(true);
spectatorsNeedPasswordCheckBox->setChecked(false);
spectatorsCanTalkCheckBox->setChecked(false);
spectatorsSeeEverythingCheckBox->setChecked(false);
createGameAsSpectatorCheckBox->setChecked(false);
startingLifeTotalEdit->setValue(20);
shareDecklistsOnLoadCheckBox->setChecked(false);
shufflePlayersCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setChecked(false);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) {
gameTypeCheckBoxIterator.next();
// must set auto enclusive to false to be able to set the check to false
gameTypeCheckBoxIterator.value()->setAutoExclusive(false);
gameTypeCheckBoxIterator.value()->setChecked(false);
gameTypeCheckBoxIterator.value()->setAutoExclusive(true);
}
descriptionEdit->setFocus();
form->resetToDefaults();
clearError();
}
void DlgCreateGame::actOK()
{
auto _gameTypes = QString();
QList<int> gameTypeIds;
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) {
gameTypeCheckBoxIterator.next();
if (gameTypeCheckBoxIterator.value()->isChecked()) {
gameTypeIds.append(gameTypeCheckBoxIterator.key());
_gameTypes += gameTypeCheckBoxIterator.value()->text() + ", ";
}
}
GameCreationOptions options{
.description = descriptionEdit->text(),
.password = passwordEdit->text(),
.maxPlayers = maxPlayersEdit->value(),
.onlyBuddies = onlyBuddiesCheckBox->isChecked(),
.onlyRegistered = onlyRegisteredCheckBox->isChecked(),
.spectatorsAllowed = spectatorsAllowedCheckBox->isChecked(),
.spectatorsNeedPassword = spectatorsNeedPasswordCheckBox->isChecked(),
.spectatorsCanTalk = spectatorsCanTalkCheckBox->isChecked(),
.spectatorsSeeEverything = spectatorsSeeEverythingCheckBox->isChecked(),
.joinAsJudge = createGameAsJudgeCheckBox->isChecked(),
.joinAsSpectator = createGameAsSpectatorCheckBox->isChecked(),
.startingLifeTotal = startingLifeTotalEdit->value(),
.shareDecklistsOnLoad = shareDecklistsOnLoadCheckBox->isChecked(),
.shufflePlayers = shufflePlayersCheckBox->isChecked(),
.gameTypeIds = gameTypeIds,
};
clearError();
const GameCreationOptions options = form->options();
Command_CreateGame cmd = options.toCreateGameCommand();
SettingsCache::instance().game().setRememberGameSettings(rememberGameSettings->isChecked());
if (rememberGameSettings->isChecked()) {
SettingsCache::instance().game().setGameDescription(descriptionEdit->text());
SettingsCache::instance().game().setMaxPlayers(maxPlayersEdit->value());
SettingsCache::instance().game().setOnlyBuddies(onlyBuddiesCheckBox->isChecked());
SettingsCache::instance().game().setOnlyRegistered(onlyRegisteredCheckBox->isChecked());
SettingsCache::instance().game().setSpectatorsAllowed(spectatorsAllowedCheckBox->isChecked());
SettingsCache::instance().game().setSpectatorsNeedPassword(spectatorsNeedPasswordCheckBox->isChecked());
SettingsCache::instance().game().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked());
SettingsCache::instance().game().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked());
SettingsCache::instance().game().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
SettingsCache::instance().game().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
SettingsCache::instance().game().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
SettingsCache::instance().game().setShufflePlayers(shufflePlayersCheckBox->isChecked());
SettingsCache::instance().game().setGameTypes(_gameTypes);
const bool rememberSettings = form->isRememberSettingsChecked();
auto &gameSettings = SettingsCache::instance().game();
gameSettings.setRememberGameSettings(rememberSettings);
if (rememberSettings) {
gameSettings.setGameDescription(options.description);
gameSettings.setMaxPlayers(options.maxPlayers);
gameSettings.setOnlyBuddies(options.onlyBuddies);
gameSettings.setOnlyRegistered(options.onlyRegistered);
gameSettings.setSpectatorsAllowed(options.spectatorsAllowed);
gameSettings.setSpectatorsNeedPassword(options.spectatorsNeedPassword);
gameSettings.setSpectatorsCanTalk(options.spectatorsCanTalk);
gameSettings.setSpectatorsCanSeeEverything(options.spectatorsSeeEverything);
gameSettings.setCreateGameAsSpectator(options.joinAsSpectator);
gameSettings.setDefaultStartingLifeTotal(options.startingLifeTotal);
gameSettings.setShareDecklistsOnLoad(options.shareDecklistsOnLoad);
gameSettings.setShufflePlayers(options.shufflePlayers);
gameSettings.setGameTypes(form->selectedGameTypesSettingString());
}
PendingCommand *pend = room->prepareRoomCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DlgCreateGame::checkResponse);
room->sendRoomCommand(pend);
@ -350,14 +118,26 @@ void DlgCreateGame::checkResponse(const Response &response)
if (response.response_code() == Response::RespOk) {
accept();
} else {
QMessageBox::critical(this, tr("Error"), tr("Server error."));
return;
showError(tr("Server error."));
}
}
void DlgCreateGame::spectatorsAllowedChanged(QT_STATE_CHANGED_T state)
void DlgCreateGame::showError(const QString &message)
{
spectatorsNeedPasswordCheckBox->setEnabled(state);
spectatorsCanTalkCheckBox->setEnabled(state);
spectatorsSeeEverythingCheckBox->setEnabled(state);
if (!errorLabel) {
return;
}
errorLabel->setText(message);
errorLabel->setVisible(true);
adjustSize();
}
void DlgCreateGame::clearError()
{
if (!errorLabel) {
return;
}
errorLabel->clear();
errorLabel->setVisible(false);
adjustSize();
}

View file

@ -2,7 +2,6 @@
* @file dlg_create_game.h
* @ingroup RoomDialogs
*/
//! \todo Document this file.
#ifndef DLG_CREATEGAME_H
#define DLG_CREATEGAME_H
@ -10,21 +9,15 @@
#include <QDialog>
#include <QMap>
#include <QString>
#include <libcockatrice/utility/macros.h>
#include <qtmetamacros.h>
class QCheckBox;
class CreateGameFormWidget;
class QDialogButtonBox;
class QGroupBox;
class QLabel;
class QLineEdit;
class QPushButton;
class QRadioButton;
class QSpinBox;
class Response;
class ServerInfo_Game;
class TabRoom;
class QWidget;
class DlgCreateGame : public QDialog
{
@ -36,27 +29,16 @@ private slots:
void actOK();
void actReset();
void checkResponse(const Response &response);
void spectatorsAllowedChanged(QT_STATE_CHANGED_T state);
private:
TabRoom *room;
QMap<int, QString> gameTypes;
QMap<int, QRadioButton *> gameTypeCheckBoxes;
CreateGameFormWidget *form = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QPushButton *clearButton = nullptr;
QLabel *errorLabel = nullptr;
QGroupBox *generalGroupBox, *spectatorsGroupBox, *gameSetupOptionsGroupBox;
QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel, *shareDecklistsOnLoadLabel;
QLineEdit *descriptionEdit, *passwordEdit;
QSpinBox *maxPlayersEdit, *startingLifeTotalEdit;
QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox;
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
QCheckBox *shareDecklistsOnLoadCheckBox;
QCheckBox *shufflePlayersCheckBox;
QDialogButtonBox *buttonBox;
QPushButton *clearButton;
QCheckBox *rememberGameSettings;
void sharedCtor();
void showError(const QString &message);
void clearError();
};
#endif
#endif // DLG_CREATEGAME_H

View file

@ -0,0 +1,260 @@
#include "create_game_form_widget.h"
#include <QCheckBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QRadioButton>
#include <QSpinBox>
#include <QVBoxLayout>
#include <libcockatrice/utility/string_limits.h>
#include <qnamespace.h>
CreateGameFormWidget::CreateGameFormWidget(const QMap<int, QString> &gameTypes,
bool _registeredUser,
bool judge,
QWidget *parent)
: QWidget(parent), registeredUser(_registeredUser)
{
setObjectName("createGameForm");
rememberGameSettings = new QCheckBox(tr("Re&member settings"));
auto *descriptionLabel = new QLabel(tr("&Description:"));
descriptionEdit = new QLineEdit;
descriptionEdit->setMaxLength(MAX_NAME_LENGTH);
descriptionLabel->setBuddy(descriptionEdit);
auto *maxPlayersLabel = new QLabel(tr("P&layers:"));
maxPlayersEdit = new QSpinBox();
maxPlayersEdit->setMinimum(1);
maxPlayersEdit->setMaximum(100);
maxPlayersEdit->setValue(2);
maxPlayersLabel->setBuddy(maxPlayersEdit);
auto *generalGrid = new QGridLayout;
generalGrid->addWidget(descriptionLabel, 0, 0);
generalGrid->addWidget(descriptionEdit, 0, 1);
generalGrid->addWidget(maxPlayersLabel, 1, 0);
generalGrid->addWidget(maxPlayersEdit, 1, 1);
auto *generalGroupBox = new QGroupBox(tr("General"));
generalGroupBox->setLayout(generalGrid);
auto *gameTypeLayout = new QVBoxLayout;
for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) {
auto *gameTypeRadioButton = new QRadioButton(it.value(), this);
gameTypeLayout->addWidget(gameTypeRadioButton);
gameTypeCheckBoxes.insert(it.key(), gameTypeRadioButton);
}
auto *gameTypeGroupBox = new QGroupBox(tr("Game type"));
gameTypeGroupBox->setLayout(gameTypeLayout);
auto *passwordLabel = new QLabel(tr("&Password:"));
passwordEdit = new QLineEdit;
passwordEdit->setMaxLength(MAX_NAME_LENGTH);
passwordLabel->setBuddy(passwordEdit);
onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join"));
onlyRegisteredCheckBox = new QCheckBox(tr("Only &registered users can join"));
if (registeredUser) {
onlyRegisteredCheckBox->setChecked(true);
} else {
onlyBuddiesCheckBox->setEnabled(false);
onlyRegisteredCheckBox->setEnabled(false);
}
auto *joinRestrictionsLayout = new QGridLayout;
joinRestrictionsLayout->addWidget(passwordLabel, 0, 0);
joinRestrictionsLayout->addWidget(passwordEdit, 0, 1);
joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2);
joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2);
auto *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions"));
joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout);
spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch"));
spectatorsAllowedCheckBox->setChecked(true);
connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&CreateGameFormWidget::spectatorsAllowedChanged);
spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch"));
spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat"));
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));
createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator"));
auto *spectatorsLayout = new QVBoxLayout;
spectatorsLayout->addWidget(spectatorsAllowedCheckBox);
spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox);
spectatorsLayout->addWidget(spectatorsCanTalkCheckBox);
spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox);
spectatorsLayout->addWidget(createGameAsSpectatorCheckBox);
auto *spectatorsGroupBox = new QGroupBox(tr("Spectators"));
spectatorsGroupBox->setLayout(spectatorsLayout);
auto *startingLifeTotalLabel = new QLabel(tr("Starting life total:"));
startingLifeTotalEdit = new QSpinBox();
startingLifeTotalEdit->setMinimum(1);
startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining.
startingLifeTotalEdit->setValue(20);
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
shufflePlayersCheckBox = new QCheckBox(tr("Randomize turn order (seats) on each game start"));
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
auto *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0, 1, 2);
gameSetupOptionsLayout->addWidget(shufflePlayersCheckBox, 2, 0, 1, 2);
if (judge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0, 1, 2);
} else {
createGameAsJudgeCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setHidden(true);
}
auto *gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"));
gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout);
auto *grid = new QGridLayout;
// Top row
grid->addWidget(generalGroupBox, 0, 0);
grid->addWidget(joinRestrictionsGroupBox, 0, 1);
// Middle row: left column
grid->addWidget(gameTypeGroupBox, 1, 0);
// Middle row: right column (game setup + spectators)
auto *rightLayout = new QVBoxLayout;
rightLayout->addWidget(spectatorsGroupBox, Qt::AlignTop); // top
rightLayout->addWidget(gameSetupOptionsGroupBox); // bottom
grid->addLayout(rightLayout, 1, 1);
// Bottom row
grid->addWidget(rememberGameSettings, 3, 0, 1, 2); // span both columns if needed
setLayout(grid);
}
void CreateGameFormWidget::setOptions(const GameCreationOptions &options)
{
descriptionEdit->setText(options.description);
maxPlayersEdit->setValue(options.maxPlayers);
passwordEdit->setText(options.password);
onlyBuddiesCheckBox->setChecked(options.onlyBuddies);
onlyRegisteredCheckBox->setChecked(options.onlyRegistered);
spectatorsAllowedCheckBox->setChecked(options.spectatorsAllowed);
spectatorsNeedPasswordCheckBox->setChecked(options.spectatorsNeedPassword);
spectatorsCanTalkCheckBox->setChecked(options.spectatorsCanTalk);
spectatorsSeeEverythingCheckBox->setChecked(options.spectatorsSeeEverything);
createGameAsJudgeCheckBox->setChecked(options.joinAsJudge);
createGameAsSpectatorCheckBox->setChecked(options.joinAsSpectator);
startingLifeTotalEdit->setValue(options.startingLifeTotal);
shareDecklistsOnLoadCheckBox->setChecked(options.shareDecklistsOnLoad);
shufflePlayersCheckBox->setChecked(options.shufflePlayers);
for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) {
it.value()->setChecked(options.gameTypeIds.contains(it.key()));
}
}
GameCreationOptions CreateGameFormWidget::options() const
{
GameCreationOptions options;
options.description = descriptionEdit->text();
options.password = passwordEdit->text();
options.maxPlayers = maxPlayersEdit->value();
options.onlyBuddies = onlyBuddiesCheckBox->isChecked();
options.onlyRegistered = onlyRegisteredCheckBox->isChecked();
options.spectatorsAllowed = spectatorsAllowedCheckBox->isChecked();
options.spectatorsNeedPassword = spectatorsNeedPasswordCheckBox->isChecked();
options.spectatorsCanTalk = spectatorsCanTalkCheckBox->isChecked();
options.spectatorsSeeEverything = spectatorsSeeEverythingCheckBox->isChecked();
options.joinAsJudge = createGameAsJudgeCheckBox->isChecked();
options.joinAsSpectator = createGameAsSpectatorCheckBox->isChecked();
options.startingLifeTotal = startingLifeTotalEdit->value();
options.shareDecklistsOnLoad = shareDecklistsOnLoadCheckBox->isChecked();
options.shufflePlayers = shufflePlayersCheckBox->isChecked();
for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) {
if (it.value()->isChecked()) {
options.gameTypeIds.append(it.key());
}
}
return options;
}
QString CreateGameFormWidget::selectedGameTypesSettingString() const
{
QString gameTypes;
for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) {
if (it.value()->isChecked()) {
gameTypes += it.value()->text() + ", ";
}
}
return gameTypes;
}
void CreateGameFormWidget::resetToDefaults()
{
GameCreationOptions defaults;
defaults.onlyRegistered = registeredUser;
setOptions(defaults);
// An exclusive button group refuses to uncheck its selected button, which setOptions
// cannot do, so clear the exclusive flag while resetting the game type selection.
for (auto *gameTypeCheckBox : gameTypeCheckBoxes) {
gameTypeCheckBox->setAutoExclusive(false);
gameTypeCheckBox->setChecked(false);
gameTypeCheckBox->setAutoExclusive(true);
}
descriptionEdit->setFocus();
}
void CreateGameFormWidget::setReadOnly(bool readOnly)
{
const bool editable = !readOnly;
rememberGameSettings->setEnabled(editable);
descriptionEdit->setEnabled(editable);
maxPlayersEdit->setEnabled(editable);
passwordEdit->setEnabled(editable);
onlyBuddiesCheckBox->setEnabled(editable && registeredUser);
onlyRegisteredCheckBox->setEnabled(editable && registeredUser);
spectatorsAllowedCheckBox->setEnabled(editable);
spectatorsNeedPasswordCheckBox->setEnabled(editable);
spectatorsCanTalkCheckBox->setEnabled(editable);
spectatorsSeeEverythingCheckBox->setEnabled(editable);
createGameAsSpectatorCheckBox->setEnabled(editable);
startingLifeTotalEdit->setEnabled(editable);
shareDecklistsOnLoadCheckBox->setEnabled(editable);
shufflePlayersCheckBox->setEnabled(editable);
for (auto *gameTypeCheckBox : gameTypeCheckBoxes) {
gameTypeCheckBox->setEnabled(editable);
}
}
void CreateGameFormWidget::focusDescription()
{
descriptionEdit->setFocus();
}
bool CreateGameFormWidget::isRememberSettingsChecked() const
{
return rememberGameSettings->isChecked();
}
void CreateGameFormWidget::setRememberSettingsChecked(bool checked)
{
rememberGameSettings->setChecked(checked);
}
void CreateGameFormWidget::spectatorsAllowedChanged(QT_STATE_CHANGED_T state)
{
spectatorsNeedPasswordCheckBox->setEnabled(state);
spectatorsCanTalkCheckBox->setEnabled(state);
spectatorsSeeEverythingCheckBox->setEnabled(state);
}

View file

@ -0,0 +1,69 @@
/**
* @file create_game_form_widget.h
* @ingroup RoomWidgets
*/
#ifndef CREATE_GAME_FORM_WIDGET_H
#define CREATE_GAME_FORM_WIDGET_H
#include "game_creation_options.h"
#include <QMap>
#include <QWidget>
#include <libcockatrice/utility/macros.h>
#include <qtmetamacros.h>
class QCheckBox;
class QLineEdit;
class QRadioButton;
class QSpinBox;
/**
* @brief The editable create-game form, independent of settings, the network, and the room.
*
* The dialog loads @ref GameCreationOptions into it and reads them back; a future host
* activity can do the same without duplicating the widget layout.
*/
class CreateGameFormWidget : public QWidget
{
Q_OBJECT
public:
CreateGameFormWidget(const QMap<int, QString> &gameTypes,
bool registeredUser,
bool judge,
QWidget *parent = nullptr);
void setOptions(const GameCreationOptions &options);
[[nodiscard]] GameCreationOptions options() const;
[[nodiscard]] QString selectedGameTypesSettingString() const;
void resetToDefaults();
void setReadOnly(bool readOnly);
void focusDescription();
[[nodiscard]] bool isRememberSettingsChecked() const;
void setRememberSettingsChecked(bool checked);
private slots:
void spectatorsAllowedChanged(QT_STATE_CHANGED_T state);
private:
bool registeredUser;
QMap<int, QRadioButton *> gameTypeCheckBoxes;
QLineEdit *descriptionEdit;
QLineEdit *passwordEdit;
QSpinBox *maxPlayersEdit;
QSpinBox *startingLifeTotalEdit;
QCheckBox *onlyBuddiesCheckBox;
QCheckBox *onlyRegisteredCheckBox;
QCheckBox *spectatorsAllowedCheckBox;
QCheckBox *spectatorsNeedPasswordCheckBox;
QCheckBox *spectatorsCanTalkCheckBox;
QCheckBox *spectatorsSeeEverythingCheckBox;
QCheckBox *createGameAsJudgeCheckBox;
QCheckBox *createGameAsSpectatorCheckBox;
QCheckBox *shareDecklistsOnLoadCheckBox;
QCheckBox *shufflePlayersCheckBox;
QCheckBox *rememberGameSettings;
};
#endif // CREATE_GAME_FORM_WIDGET_H

View file

@ -1,5 +1,69 @@
#include "game_creation_options.h"
#include <QSet>
#include <libcockatrice/interfaces/interface_game_settings_provider.h>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
GameCreationOptions GameCreationOptions::fromSettings(const IGameSettingsProvider &settings,
const QMap<int, QString> &gameTypes,
bool registeredUser)
{
GameCreationOptions options;
options.description = settings.getGameDescription();
options.maxPlayers = settings.getMaxPlayers();
if (registeredUser) {
options.onlyBuddies = settings.getOnlyBuddies();
options.onlyRegistered = settings.getOnlyRegistered();
}
options.spectatorsAllowed = settings.getSpectatorsAllowed();
options.spectatorsNeedPassword = settings.getSpectatorsNeedPassword();
options.spectatorsCanTalk = settings.getSpectatorsCanTalk();
options.spectatorsSeeEverything = settings.getSpectatorsCanSeeEverything();
options.joinAsSpectator = settings.getCreateGameAsSpectator();
options.startingLifeTotal = settings.getDefaultStartingLifeTotal();
options.shareDecklistsOnLoad = settings.getShareDecklistsOnLoad();
options.shufflePlayers = settings.getShufflePlayers();
// The old dialog stored game type names as "Name, " in one string; keep the exact
// substring test so an existing game.ini selects the same types after the split.
const QString savedGameTypes = settings.getGameTypes();
for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) {
if (savedGameTypes.contains(it.value() + ", ")) {
options.gameTypeIds.append(it.key());
}
}
return options;
}
GameCreationOptions GameCreationOptions::fromGameInfo(const ServerInfo_Game &gameInfo,
const QMap<int, QString> &gameTypes)
{
GameCreationOptions options;
options.description = QString::fromStdString(gameInfo.description());
options.maxPlayers = gameInfo.max_players();
options.onlyBuddies = gameInfo.only_buddies();
options.onlyRegistered = gameInfo.only_registered();
options.spectatorsAllowed = gameInfo.spectators_allowed();
options.spectatorsNeedPassword = gameInfo.spectators_need_password();
options.spectatorsCanTalk = gameInfo.spectators_can_chat();
options.spectatorsSeeEverything = gameInfo.spectators_omniscient();
options.shareDecklistsOnLoad = gameInfo.share_decklists_on_load();
options.shufflePlayers = gameInfo.shuffle_players();
QSet<int> selectedTypes;
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
selectedTypes.insert(gameInfo.game_types(i));
}
for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) {
if (selectedTypes.contains(it.key())) {
options.gameTypeIds.append(it.key());
}
}
return options;
}
Command_CreateGame GameCreationOptions::toCreateGameCommand() const
{
Command_CreateGame cmd;

View file

@ -9,8 +9,12 @@
#include "libcockatrice/protocol/pb/room_commands.pb.h"
#include <QList>
#include <QMap>
#include <QString>
class IGameSettingsProvider;
class ServerInfo_Game;
/**
* @brief The values needed to create a game, independent of the widgets that edit them.
*
@ -35,6 +39,22 @@ struct GameCreationOptions
bool shufflePlayers = false;
QList<int> gameTypeIds;
/**
* @brief Reads the remembered create-game settings.
*
* Join restrictions are only meaningful for registered users, so they are left
* unset for an unregistered host. Game type ids are resolved from the room's map
* by matching the remembered names.
*/
[[nodiscard]] static GameCreationOptions
fromSettings(const IGameSettingsProvider &settings, const QMap<int, QString> &gameTypes, bool registeredUser);
/**
* @brief Reads the read-only fields shown in the game information view.
*/
[[nodiscard]] static GameCreationOptions fromGameInfo(const ServerInfo_Game &gameInfo,
const QMap<int, QString> &gameTypes);
/**
* @brief Builds the room command, simplifying the description's whitespace.
*/

View file

@ -469,6 +469,20 @@ cockatrice_add_test(
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp
game_creation_options_test.cpp
LIBS
libcockatrice_interfaces
libcockatrice_protocol
INCLUDE_DIRS
${CMAKE_SOURCE_DIR}/cockatrice/src
)
cockatrice_add_test(
NAME
create_game_form_widget_test
SOURCES
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp
create_game_form_widget_test.cpp
LIBS
libcockatrice_interfaces
libcockatrice_protocol
INCLUDE_DIRS
${CMAKE_SOURCE_DIR}/cockatrice/src

View file

@ -0,0 +1,132 @@
#include "interface/widgets/game_hosting/create_game_form_widget.h"
#include <QApplication>
#include <QMap>
#include <gtest/gtest.h>
namespace
{
const QMap<int, QString> gameTypes{{1, "Draft"}, {2, "Commander"}};
void expectSameOptions(const GameCreationOptions &actual, const GameCreationOptions &expected)
{
EXPECT_EQ(actual.description, expected.description);
EXPECT_EQ(actual.password, expected.password);
EXPECT_EQ(actual.maxPlayers, expected.maxPlayers);
EXPECT_EQ(actual.onlyBuddies, expected.onlyBuddies);
EXPECT_EQ(actual.onlyRegistered, expected.onlyRegistered);
EXPECT_EQ(actual.spectatorsAllowed, expected.spectatorsAllowed);
EXPECT_EQ(actual.spectatorsNeedPassword, expected.spectatorsNeedPassword);
EXPECT_EQ(actual.spectatorsCanTalk, expected.spectatorsCanTalk);
EXPECT_EQ(actual.spectatorsSeeEverything, expected.spectatorsSeeEverything);
EXPECT_EQ(actual.joinAsJudge, expected.joinAsJudge);
EXPECT_EQ(actual.joinAsSpectator, expected.joinAsSpectator);
EXPECT_EQ(actual.startingLifeTotal, expected.startingLifeTotal);
EXPECT_EQ(actual.shareDecklistsOnLoad, expected.shareDecklistsOnLoad);
EXPECT_EQ(actual.shufflePlayers, expected.shufflePlayers);
EXPECT_EQ(actual.gameTypeIds, expected.gameTypeIds);
}
} // namespace
TEST(CreateGameFormWidget, RegisteredHostDefaultsToRegisteredOnly)
{
const CreateGameFormWidget form(gameTypes, true, false);
const GameCreationOptions options = form.options();
EXPECT_TRUE(options.onlyRegistered);
EXPECT_FALSE(options.onlyBuddies);
EXPECT_EQ(options.maxPlayers, 2);
EXPECT_TRUE(options.spectatorsAllowed);
EXPECT_EQ(options.startingLifeTotal, 20);
EXPECT_TRUE(options.gameTypeIds.isEmpty());
}
TEST(CreateGameFormWidget, UnregisteredHostDefaultsToOpenJoin)
{
const CreateGameFormWidget form(gameTypes, false, false);
const GameCreationOptions options = form.options();
EXPECT_FALSE(options.onlyRegistered);
EXPECT_FALSE(options.onlyBuddies);
}
TEST(CreateGameFormWidget, SetOptionsRoundTripsEveryField)
{
CreateGameFormWidget form(gameTypes, true, true);
GameCreationOptions expected;
expected.description = "Some game";
expected.password = "hunter2";
expected.maxPlayers = 5;
expected.onlyBuddies = true;
expected.onlyRegistered = false;
expected.spectatorsAllowed = false;
expected.spectatorsNeedPassword = true;
expected.spectatorsCanTalk = true;
expected.spectatorsSeeEverything = true;
expected.joinAsJudge = true;
expected.joinAsSpectator = true;
expected.startingLifeTotal = 40;
expected.shareDecklistsOnLoad = true;
expected.shufflePlayers = true;
expected.gameTypeIds = {2};
form.setOptions(expected);
expectSameOptions(form.options(), expected);
}
TEST(CreateGameFormWidget, ResetRestoresDefaultsAndClearsTheGameType)
{
CreateGameFormWidget form(gameTypes, true, false);
GameCreationOptions configured;
configured.description = "Configured";
configured.maxPlayers = 8;
configured.onlyBuddies = true;
configured.joinAsJudge = true;
configured.gameTypeIds = {2};
form.setOptions(configured);
form.resetToDefaults();
const GameCreationOptions options = form.options();
EXPECT_TRUE(options.description.isEmpty());
EXPECT_EQ(options.maxPlayers, 2);
EXPECT_FALSE(options.onlyBuddies);
EXPECT_TRUE(options.onlyRegistered);
EXPECT_TRUE(options.spectatorsAllowed);
EXPECT_EQ(options.startingLifeTotal, 20);
EXPECT_FALSE(options.joinAsJudge);
EXPECT_TRUE(options.gameTypeIds.isEmpty());
}
TEST(CreateGameFormWidget, RememberSettingsFlagRoundTrips)
{
CreateGameFormWidget form(gameTypes, true, false);
EXPECT_FALSE(form.isRememberSettingsChecked());
form.setRememberSettingsChecked(true);
EXPECT_TRUE(form.isRememberSettingsChecked());
}
TEST(CreateGameFormWidget, SelectedGameTypesSettingStringUsesTheLegacyEncoding)
{
CreateGameFormWidget form(gameTypes, true, false);
GameCreationOptions configured;
configured.gameTypeIds = {1};
form.setOptions(configured);
EXPECT_EQ(form.selectedGameTypesSettingString(), "Draft, ");
}
int main(int argc, char **argv)
{
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -1,7 +1,100 @@
#include "interface/widgets/game_hosting/game_creation_options.h"
#include <gtest/gtest.h>
#include <libcockatrice/interfaces/interface_game_settings_provider.h>
#include <libcockatrice/protocol/pb/room_commands.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
namespace
{
/**
* @brief The remembered settings a test hands to fromSettings.
*
* The fake cannot itself be an aggregate (it derives from a polymorphic interface), so the
* remembered values live in a GameCreationOptions member and the getters read them from
* there. That keeps the test data in one designated initializer.
*/
class FakeGameSettings : public IGameSettingsProvider
{
public:
explicit FakeGameSettings(const GameCreationOptions &_options = {}) : options(_options)
{
}
[[nodiscard]] QString getGameDescription() const override
{
return options.description;
}
[[nodiscard]] int getMaxPlayers() const override
{
return options.maxPlayers;
}
[[nodiscard]] QString getGameTypes() const override
{
return gameTypes;
}
[[nodiscard]] bool getOnlyBuddies() const override
{
return options.onlyBuddies;
}
[[nodiscard]] bool getOnlyRegistered() const override
{
return options.onlyRegistered;
}
[[nodiscard]] bool getSpectatorsAllowed() const override
{
return options.spectatorsAllowed;
}
[[nodiscard]] bool getSpectatorsNeedPassword() const override
{
return options.spectatorsNeedPassword;
}
[[nodiscard]] bool getSpectatorsCanTalk() const override
{
return options.spectatorsCanTalk;
}
[[nodiscard]] bool getSpectatorsCanSeeEverything() const override
{
return options.spectatorsSeeEverything;
}
[[nodiscard]] bool getCreateGameAsSpectator() const override
{
return options.joinAsSpectator;
}
[[nodiscard]] int getDefaultStartingLifeTotal() const override
{
return options.startingLifeTotal;
}
[[nodiscard]] bool getShareDecklistsOnLoad() const override
{
return options.shareDecklistsOnLoad;
}
[[nodiscard]] bool getShufflePlayers() const override
{
return options.shufflePlayers;
}
[[nodiscard]] bool getRememberGameSettings() const override
{
return false;
}
[[nodiscard]] bool getLocalGameRememberSettings() const override
{
return false;
}
[[nodiscard]] int getLocalGameMaxPlayers() const override
{
return 0;
}
[[nodiscard]] int getLocalGameStartingLifeTotal() const override
{
return 0;
}
GameCreationOptions options;
/// The legacy "Name, " list; it has no GameCreationOptions counterpart.
QString gameTypes;
};
} // namespace
TEST(GameCreationOptions, DefaultsMatchTheDialog)
{
@ -91,3 +184,127 @@ TEST(GameCreationOptions, DescriptionWhitespaceIsSimplified)
EXPECT_EQ(cmd.description(), "a b");
}
TEST(GameCreationOptions, FromSettingsCopiesEveryRememberedField)
{
const FakeGameSettings settings{GameCreationOptions{.description = "Remembered game",
.password = {},
.maxPlayers = 6,
.onlyBuddies = true,
.onlyRegistered = true,
.spectatorsAllowed = false,
.spectatorsNeedPassword = true,
.spectatorsCanTalk = true,
.spectatorsSeeEverything = true,
.joinAsJudge = false,
.joinAsSpectator = true,
.startingLifeTotal = 40,
.shareDecklistsOnLoad = true,
.shufflePlayers = true,
.gameTypeIds = {}}};
const GameCreationOptions options = GameCreationOptions::fromSettings(settings, {}, true);
EXPECT_EQ(options.description, "Remembered game");
EXPECT_EQ(options.maxPlayers, 6);
EXPECT_TRUE(options.onlyBuddies);
EXPECT_TRUE(options.onlyRegistered);
EXPECT_FALSE(options.spectatorsAllowed);
EXPECT_TRUE(options.spectatorsNeedPassword);
EXPECT_TRUE(options.spectatorsCanTalk);
EXPECT_TRUE(options.spectatorsSeeEverything);
EXPECT_TRUE(options.joinAsSpectator);
EXPECT_EQ(options.startingLifeTotal, 40);
EXPECT_TRUE(options.shareDecklistsOnLoad);
EXPECT_TRUE(options.shufflePlayers);
}
TEST(GameCreationOptions, FromSettingsDropsJoinRestrictionsForUnregisteredHosts)
{
FakeGameSettings settings;
settings.options.onlyBuddies = true;
settings.options.onlyRegistered = true;
const GameCreationOptions options = GameCreationOptions::fromSettings(settings, {}, false);
EXPECT_FALSE(options.onlyBuddies);
EXPECT_FALSE(options.onlyRegistered);
}
TEST(GameCreationOptions, FromSettingsMatchesGameTypesByTheLegacyNameList)
{
FakeGameSettings settings;
settings.gameTypes = "Draft, Commander, ";
const QMap<int, QString> gameTypes{{1, "Draft"}, {2, "Commander"}, {3, "Standard"}};
const GameCreationOptions options = GameCreationOptions::fromSettings(settings, gameTypes, true);
EXPECT_EQ(options.gameTypeIds, QList<int>({1, 2}));
}
TEST(GameCreationOptions, FromSettingsDoesNotMatchGameTypesWithoutTheLegacySeparator)
{
FakeGameSettings settings;
settings.gameTypes = "Draft Commander";
const QMap<int, QString> gameTypes{{1, "Draft"}, {2, "Commander"}};
const GameCreationOptions options = GameCreationOptions::fromSettings(settings, gameTypes, true);
EXPECT_TRUE(options.gameTypeIds.isEmpty());
}
TEST(GameCreationOptions, FromGameInfoCopiesTheVisibleFields)
{
ServerInfo_Game gameInfo;
gameInfo.set_description("Some game");
gameInfo.set_max_players(4);
gameInfo.set_only_buddies(true);
gameInfo.set_only_registered(true);
gameInfo.set_spectators_allowed(false);
gameInfo.set_spectators_need_password(true);
gameInfo.set_spectators_can_chat(true);
gameInfo.set_spectators_omniscient(true);
gameInfo.set_share_decklists_on_load(true);
gameInfo.set_shuffle_players(true);
gameInfo.add_game_types(2);
const QMap<int, QString> gameTypes{{1, "Draft"}, {2, "Commander"}};
const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, gameTypes);
EXPECT_EQ(options.description, "Some game");
EXPECT_EQ(options.maxPlayers, 4);
EXPECT_TRUE(options.onlyBuddies);
EXPECT_TRUE(options.onlyRegistered);
EXPECT_FALSE(options.spectatorsAllowed);
EXPECT_TRUE(options.spectatorsNeedPassword);
EXPECT_TRUE(options.spectatorsCanTalk);
EXPECT_TRUE(options.spectatorsSeeEverything);
EXPECT_TRUE(options.shareDecklistsOnLoad);
EXPECT_TRUE(options.shufflePlayers);
EXPECT_EQ(options.gameTypeIds, QList<int>({2}));
}
TEST(GameCreationOptions, FromGameInfoLeavesUnshownFieldsAtDefaults)
{
ServerInfo_Game gameInfo;
gameInfo.set_max_players(3);
const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, {});
EXPECT_TRUE(options.password.isEmpty());
EXPECT_EQ(options.startingLifeTotal, 20);
EXPECT_FALSE(options.joinAsJudge);
EXPECT_FALSE(options.joinAsSpectator);
}
TEST(GameCreationOptions, FromGameInfoIgnoresUnknownGameTypeIds)
{
ServerInfo_Game gameInfo;
gameInfo.add_game_types(7);
gameInfo.add_game_types(9);
const QMap<int, QString> gameTypes{{1, "Draft"}, {7, "Commander"}};
const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, gameTypes);
EXPECT_EQ(options.gameTypeIds, QList<int>({7}));
}