feat(command-zone): add game creation UI

Implement UI components for configuring command zones when creating
new games.

New components:
- GameZoneOptionsWidget: reusable widget for zone configuration
- DlgLocalGameOptions: local game setup with command zone options

Integration points:
- DlgCreateGame: add command zone options to game creation dialog
- WindowMain: wire up command zone configuration in game flow

This allows players to:
- Enable/disable command zones for a game
- Select zone type (Commander, Companion, etc.)
- Configure zone-specific options
This commit is contained in:
DawnFire42 2026-02-25 13:38:21 -05:00
parent 437d2e4ed4
commit 4bd6ab56c5
9 changed files with 367 additions and 13 deletions

View file

@ -27,6 +27,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
src/interface/widgets/dialogs/dlg_create_game.cpp
src/interface/widgets/dialogs/dlg_default_tags_editor.cpp
src/interface/widgets/dialogs/game_zone_options_widget.cpp
src/interface/widgets/dialogs/dlg_edit_avatar.cpp
src/interface/widgets/dialogs/dlg_edit_password.cpp
src/interface/widgets/dialogs/dlg_edit_tokens.cpp
@ -39,6 +40,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
src/interface/widgets/dialogs/dlg_register.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
@ -82,8 +84,8 @@ set(cockatrice_SOURCES
src/game/phase.cpp
src/game/phases_toolbar.cpp
src/game/player/menu/card_menu.cpp
src/game/player/menu/custom_zone_menu.cpp
src/game/player/menu/command_zone_menu.cpp
src/game/player/menu/custom_zone_menu.cpp
src/game/player/menu/grave_menu.cpp
src/game/player/menu/hand_menu.cpp
src/game/player/menu/library_menu.cpp
@ -105,18 +107,18 @@ set(cockatrice_SOURCES
src/game/player/player_target.cpp
src/game/replay.cpp
src/game/zones/card_zone.cpp
src/game/zones/command_zone.cpp
src/game/zones/command_zone_state.cpp
src/game/zones/hand_zone.cpp
src/game/zones/command_zone.cpp
src/game/zones/logic/command_zone_logic.cpp
src/game/zones/logic/card_zone_logic.cpp
src/game/zones/logic/command_zone_logic.cpp
src/game/zones/logic/hand_zone_logic.cpp
src/game/zones/logic/pile_zone_logic.cpp
src/game/zones/logic/stack_zone_logic.cpp
src/game/zones/logic/table_zone_logic.cpp
src/game/zones/logic/view_zone_logic.cpp
src/game/zones/zone_toggle_button.cpp
src/game/zones/pile_zone.cpp
src/game/zones/zone_toggle_button.cpp
src/game/zones/select_zone.cpp
src/game/zones/stack_zone.cpp
src/game/zones/table_zone.cpp

View file

@ -2,6 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../interface/widgets/tabs/tab_room.h"
#include "game_zone_options_widget.h"
#include <QApplication>
#include <QCheckBox>
@ -102,6 +103,7 @@ void DlgCreateGame::sharedCtor()
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
zoneOptionsWidget = new GameZoneOptionsWidget(this);
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
@ -109,8 +111,9 @@ void DlgCreateGame::sharedCtor()
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
gameSetupOptionsLayout->addWidget(zoneOptionsWidget, 2, 0, 1, 2);
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0);
} else {
createGameAsJudgeCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setHidden(true);
@ -171,6 +174,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().getShareDecklistsOnLoad());
zoneOptionsWidget->loadFromSettings();
if (!rememberGameSettings->isChecked()) {
actReset();
@ -204,6 +208,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
createGameAsSpectatorCheckBox->setEnabled(false);
startingLifeTotalEdit->setEnabled(false);
shareDecklistsOnLoadCheckBox->setEnabled(false);
zoneOptionsWidget->setReadOnly(true);
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
maxPlayersEdit->setValue(gameInfo.max_players());
@ -249,6 +254,7 @@ void DlgCreateGame::actReset()
startingLifeTotalEdit->setValue(20);
shareDecklistsOnLoadCheckBox->setChecked(false);
zoneOptionsWidget->resetToDefaults();
createGameAsJudgeCheckBox->setChecked(false);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@ -279,6 +285,9 @@ void DlgCreateGame::actOK()
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
cmd.set_starting_life_total(startingLifeTotalEdit->value());
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
cmd.set_enable_command_zone(zoneOptionsWidget->enableCommandZone());
cmd.set_enable_companion_zone(zoneOptionsWidget->enableCompanionZone());
cmd.set_enable_background_zone(zoneOptionsWidget->enableBackgroundZone());
auto _gameTypes = QString();
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@ -303,6 +312,7 @@ void DlgCreateGame::actOK()
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
SettingsCache::instance().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
zoneOptionsWidget->saveToSettings();
SettingsCache::instance().setGameTypes(_gameTypes);
}
PendingCommand *pend = room->prepareRoomCommand(cmd);

View file

@ -11,6 +11,7 @@
#include <QMap>
#include <libcockatrice/utility/macros.h>
class GameZoneOptionsWidget;
class QCheckBox;
class QDialogButtonBox;
class QGroupBox;
@ -48,6 +49,7 @@ private:
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
QCheckBox *shareDecklistsOnLoadCheckBox;
GameZoneOptionsWidget *zoneOptionsWidget;
QDialogButtonBox *buttonBox;
QPushButton *clearButton;
QCheckBox *rememberGameSettings;

View file

@ -0,0 +1,124 @@
#include "dlg_local_game_options.h"
#include "../../../client/settings/cache_settings.h"
#include "game_zone_options_widget.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QSpinBox>
#include <QVBoxLayout>
DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
{
// General settings group
numberPlayersLabel = new QLabel(tr("P&layers:"));
numberPlayersEdit = new QSpinBox();
numberPlayersEdit->setMinimum(1);
numberPlayersEdit->setMaximum(8);
numberPlayersEdit->setValue(1);
numberPlayersLabel->setBuddy(numberPlayersEdit);
auto *generalGrid = new QGridLayout;
generalGrid->addWidget(numberPlayersLabel, 0, 0);
generalGrid->addWidget(numberPlayersEdit, 0, 1);
generalGroupBox = new QGroupBox(tr("General"));
generalGroupBox->setLayout(generalGrid);
// Game setup options group
startingLifeTotalLabel = new QLabel(tr("Starting life total:"));
startingLifeTotalEdit = new QSpinBox();
startingLifeTotalEdit->setMinimum(1);
startingLifeTotalEdit->setMaximum(99999);
startingLifeTotalEdit->setValue(20);
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
zoneOptionsWidget = new GameZoneOptionsWidget(this);
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));
auto *gameSetupGrid = new QGridLayout;
gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupGrid->addWidget(zoneOptionsWidget, 1, 0, 1, 2);
gameSetupGrid->addWidget(spectatorsSeeEverythingCheckBox, 2, 0, 1, 2);
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"));
gameSetupOptionsGroupBox->setLayout(gameSetupGrid);
// Remember settings checkbox
rememberSettingsCheckBox = new QCheckBox(tr("Re&member settings"));
// Button box
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject);
// Main layout
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(generalGroupBox);
mainLayout->addWidget(gameSetupOptionsGroupBox);
mainLayout->addWidget(rememberSettingsCheckBox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
// Load settings
rememberSettingsCheckBox->setChecked(SettingsCache::instance().getRememberGameSettings());
if (rememberSettingsCheckBox->isChecked()) {
numberPlayersEdit->setValue(SettingsCache::instance().getMaxPlayers());
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
zoneOptionsWidget->loadFromSettings();
spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything());
} else {
zoneOptionsWidget->resetToDefaults();
}
setWindowTitle(tr("Local game options"));
setFixedHeight(sizeHint().height());
numberPlayersEdit->setFocus();
}
void DlgLocalGameOptions::actOK()
{
// Save settings if remember is checked
SettingsCache::instance().setRememberGameSettings(rememberSettingsCheckBox->isChecked());
if (rememberSettingsCheckBox->isChecked()) {
SettingsCache::instance().setMaxPlayers(numberPlayersEdit->value());
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
zoneOptionsWidget->saveToSettings();
SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked());
}
accept();
}
int DlgLocalGameOptions::getNumberPlayers() const
{
return numberPlayersEdit->value();
}
int DlgLocalGameOptions::getStartingLifeTotal() const
{
return startingLifeTotalEdit->value();
}
bool DlgLocalGameOptions::getEnableCommandZone() const
{
return zoneOptionsWidget->enableCommandZone();
}
bool DlgLocalGameOptions::getEnableCompanionZone() const
{
return zoneOptionsWidget->enableCompanionZone();
}
bool DlgLocalGameOptions::getEnableBackgroundZone() const
{
return zoneOptionsWidget->enableBackgroundZone();
}
bool DlgLocalGameOptions::getSpectatorsSeeEverything() const
{
return spectatorsSeeEverythingCheckBox->isChecked();
}

View file

@ -0,0 +1,52 @@
/**
* @file dlg_local_game_options.h
* @ingroup RoomDialogs
* @brief Dialog for configuring local game options.
*/
#ifndef DLG_LOCAL_GAME_OPTIONS_H
#define DLG_LOCAL_GAME_OPTIONS_H
#include <QDialog>
class GameZoneOptionsWidget;
class QCheckBox;
class QDialogButtonBox;
class QGroupBox;
class QLabel;
class QSpinBox;
class DlgLocalGameOptions : public QDialog
{
Q_OBJECT
public:
explicit DlgLocalGameOptions(QWidget *parent = nullptr);
int getNumberPlayers() const;
int getStartingLifeTotal() const;
bool getEnableCommandZone() const;
bool getEnableCompanionZone() const;
bool getEnableBackgroundZone() const;
bool getSpectatorsSeeEverything() const;
private slots:
void actOK();
private:
QGroupBox *generalGroupBox;
QGroupBox *gameSetupOptionsGroupBox;
QLabel *numberPlayersLabel;
QSpinBox *numberPlayersEdit;
QLabel *startingLifeTotalLabel;
QSpinBox *startingLifeTotalEdit;
GameZoneOptionsWidget *zoneOptionsWidget;
QCheckBox *spectatorsSeeEverythingCheckBox;
QCheckBox *rememberSettingsCheckBox;
QDialogButtonBox *buttonBox;
};
#endif // DLG_LOCAL_GAME_OPTIONS_H

View file

@ -0,0 +1,78 @@
#include "game_zone_options_widget.h"
#include "../../../client/settings/cache_settings.h"
#include <QCheckBox>
#include <QVBoxLayout>
GameZoneOptionsWidget::GameZoneOptionsWidget(QWidget *parent) : QWidget(parent)
{
enableCommandZoneCheckBox = new QCheckBox(tr("Enable command zone (Commander)"), this);
enableCompanionZoneCheckBox = new QCheckBox(tr("Enable companion zone"), this);
enableBackgroundZoneCheckBox = new QCheckBox(tr("Enable background zone"), this);
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(enableCommandZoneCheckBox);
layout->addWidget(enableCompanionZoneCheckBox);
layout->addWidget(enableBackgroundZoneCheckBox);
setLayout(layout);
}
bool GameZoneOptionsWidget::enableCommandZone() const
{
return enableCommandZoneCheckBox->isChecked();
}
bool GameZoneOptionsWidget::enableCompanionZone() const
{
return enableCompanionZoneCheckBox->isChecked();
}
bool GameZoneOptionsWidget::enableBackgroundZone() const
{
return enableBackgroundZoneCheckBox->isChecked();
}
void GameZoneOptionsWidget::setCommandZoneEnabled(bool enabled)
{
enableCommandZoneCheckBox->setChecked(enabled);
}
void GameZoneOptionsWidget::setCompanionZoneEnabled(bool enabled)
{
enableCompanionZoneCheckBox->setChecked(enabled);
}
void GameZoneOptionsWidget::setBackgroundZoneEnabled(bool enabled)
{
enableBackgroundZoneCheckBox->setChecked(enabled);
}
void GameZoneOptionsWidget::loadFromSettings()
{
enableCommandZoneCheckBox->setChecked(SettingsCache::instance().getEnableCommandZone());
enableCompanionZoneCheckBox->setChecked(SettingsCache::instance().getEnableCompanionZone());
enableBackgroundZoneCheckBox->setChecked(SettingsCache::instance().getEnableBackgroundZone());
}
void GameZoneOptionsWidget::saveToSettings()
{
SettingsCache::instance().setEnableCommandZone(enableCommandZoneCheckBox->isChecked());
SettingsCache::instance().setEnableCompanionZone(enableCompanionZoneCheckBox->isChecked());
SettingsCache::instance().setEnableBackgroundZone(enableBackgroundZoneCheckBox->isChecked());
}
void GameZoneOptionsWidget::resetToDefaults()
{
enableCommandZoneCheckBox->setChecked(false);
enableCompanionZoneCheckBox->setChecked(true);
enableBackgroundZoneCheckBox->setChecked(true);
}
void GameZoneOptionsWidget::setReadOnly(bool readOnly)
{
enableCommandZoneCheckBox->setEnabled(!readOnly);
enableCompanionZoneCheckBox->setEnabled(!readOnly);
enableBackgroundZoneCheckBox->setEnabled(!readOnly);
}

View file

@ -0,0 +1,67 @@
/**
* @file game_zone_options_widget.h
* @ingroup RoomDialogs
* @brief Reusable widget for Commander format zone options.
*
* This widget encapsulates the zone enable/disable checkboxes used in both
* DlgCreateGame and DlgLocalGameOptions. It provides a simple interface
* while hiding settings persistence details.
*/
#ifndef GAME_ZONE_OPTIONS_WIDGET_H
#define GAME_ZONE_OPTIONS_WIDGET_H
#include <QWidget>
class QCheckBox;
class QVBoxLayout;
/**
* @class GameZoneOptionsWidget
* @brief Widget for configuring Commander format zone options.
*
* Provides checkboxes for enabling command zone, companion zone, and
* background zone. Handles settings persistence internally, so callers
* don't need to know about SettingsCache keys or default values.
*/
class GameZoneOptionsWidget : public QWidget
{
Q_OBJECT
public:
explicit GameZoneOptionsWidget(QWidget *parent = nullptr);
[[nodiscard]] bool enableCommandZone() const;
[[nodiscard]] bool enableCompanionZone() const;
[[nodiscard]] bool enableBackgroundZone() const;
void setCommandZoneEnabled(bool enabled);
void setCompanionZoneEnabled(bool enabled);
void setBackgroundZoneEnabled(bool enabled);
void loadFromSettings();
void saveToSettings();
void resetToDefaults();
void setReadOnly(bool readOnly);
QCheckBox *getCommandZoneCheckBox() const
{
return enableCommandZoneCheckBox;
}
QCheckBox *getCompanionZoneCheckBox() const
{
return enableCompanionZoneCheckBox;
}
QCheckBox *getBackgroundZoneCheckBox() const
{
return enableBackgroundZoneCheckBox;
}
private:
QCheckBox *enableCommandZoneCheckBox;
QCheckBox *enableCompanionZoneCheckBox;
QCheckBox *enableBackgroundZoneCheckBox;
};
#endif // GAME_ZONE_OPTIONS_WIDGET_H

View file

@ -39,6 +39,7 @@
#include "../main.h"
#include "logger.h"
#include "version_string.h"
#include "widgets/dialogs/dlg_local_game_options.h"
#include "widgets/utility/get_text_with_max.h"
#include <QAction>
@ -225,16 +226,20 @@ void MainWindow::actDisconnect()
void MainWindow::actSinglePlayer()
{
bool ok;
int numberPlayers =
QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 1, 1, 8, 1, &ok);
if (!ok)
DlgLocalGameOptions dlg(this);
if (!dlg.exec())
return;
startLocalGame(numberPlayers);
startLocalGame(dlg.getNumberPlayers(), dlg.getStartingLifeTotal(), dlg.getEnableCommandZone(),
dlg.getEnableCompanionZone(), dlg.getEnableBackgroundZone(), dlg.getSpectatorsSeeEverything());
}
void MainWindow::startLocalGame(int numberPlayers)
void MainWindow::startLocalGame(int numberPlayers,
int startingLifeTotal,
bool enableCommandZone,
bool enableCompanionZone,
bool enableBackgroundZone,
bool spectatorsSeeEverything)
{
aConnect->setEnabled(false);
aRegister->setEnabled(false);
@ -258,6 +263,11 @@ void MainWindow::startLocalGame(int numberPlayers)
Command_CreateGame createCommand;
createCommand.set_max_players(static_cast<google::protobuf::uint32>(numberPlayers));
createCommand.set_starting_life_total(startingLifeTotal);
createCommand.set_enable_command_zone(enableCommandZone);
createCommand.set_enable_companion_zone(enableCompanionZone);
createCommand.set_enable_background_zone(enableBackgroundZone);
createCommand.set_spectators_see_everything(spectatorsSeeEverything);
mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0));
}
@ -913,7 +923,11 @@ MainWindow::MainWindow(QWidget *parent)
void MainWindow::startupConfigCheck()
{
if (SettingsCache::instance().debug().getLocalGameOnStartup()) {
startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount());
startLocalGame(
SettingsCache::instance().debug().getLocalGamePlayerCount(),
SettingsCache::instance().getDefaultStartingLifeTotal(), SettingsCache::instance().getEnableCommandZone(),
SettingsCache::instance().getEnableCompanionZone(), SettingsCache::instance().getEnableBackgroundZone(),
SettingsCache::instance().getSpectatorsCanSeeEverything());
}
if (SettingsCache::instance().getCheckUpdatesOnStartup()) {

View file

@ -137,7 +137,12 @@ private:
void createCardUpdateProcess(bool background = false);
void exitCardDatabaseUpdate();
void startLocalGame(int numberPlayers);
void startLocalGame(int numberPlayers,
int startingLifeTotal,
bool enableCommandZone,
bool enableCompanionZone,
bool enableBackgroundZone,
bool spectatorsSeeEverything);
QList<QMenu *> tabMenus;
QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu;