mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-08 17:13:57 -07:00
feat(command-zone): integrate into player system
Wire command zones into the player object hierarchy, making them functional game elements. Player class modifications: - player.h/cpp: command zone ownership and lifecycle - player_actions.h/cpp: command zone action handlers - player_graphics_item.h/cpp: command zone visual positioning This commit connects all previous command zone components to the player, enabling: - Zone creation during game setup - Zone positioning relative to other player zones - Action routing for command zone interactions - Proper cleanup on player destruction
This commit is contained in:
parent
1ae1d876b1
commit
437d2e4ed4
6 changed files with 655 additions and 44 deletions
|
|
@ -1,13 +1,21 @@
|
|||
#include "player_graphics_item.h"
|
||||
|
||||
// @see tests/command_zone/counter_visibility_test.cpp - test harness duplicates visibility logic
|
||||
// @see tests/command_zone/command_zone_integration_test.cpp - test harness duplicates counter positioning
|
||||
|
||||
#include "../../interface/widgets/tabs/tab_game.h"
|
||||
#include "../board/abstract_card_item.h"
|
||||
#include "../board/commander_tax_counter.h"
|
||||
#include "../hand_counter.h"
|
||||
#include "../z_values.h"
|
||||
#include "../zones/command_zone.h"
|
||||
#include "../zones/hand_zone.h"
|
||||
#include "../zones/pile_zone.h"
|
||||
#include "../zones/stack_zone.h"
|
||||
#include "../zones/table_zone.h"
|
||||
|
||||
#include <libcockatrice/common/counter_ids.h>
|
||||
|
||||
PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::horizontalHandChanged, this,
|
||||
|
|
@ -83,6 +91,54 @@ void PlayerGraphicsItem::initializeZones()
|
|||
handZoneGraphicsItem =
|
||||
new HandZone(player->getHandZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
|
||||
|
||||
// Command zones - mixed hierarchy:
|
||||
// - Partner is Qt child of Command (tight coupling)
|
||||
// - Companion and Background are Qt siblings (direct children of PlayerGraphicsItem)
|
||||
// with logical parent-child relationships for hierarchy traversal
|
||||
//
|
||||
// OWNERSHIP INVARIANT: All command zones must remain Qt children of either
|
||||
// CommandZone (for Partner) or PlayerGraphicsItem (for Primary, Companion, Background)
|
||||
// for the duration of their lifetime. The logical parent-child relationships
|
||||
// (logicalParentZone, logicalChildZones) are raw pointers that rely on this
|
||||
// ownership guarantee. Do not reparent zones or change destruction order.
|
||||
|
||||
// Primary command zone
|
||||
commandZoneGraphicsItem =
|
||||
new CommandZone(player->getCommandZone(), ZoneSizes::COMMAND_ZONE_HEIGHT, CommandZoneType::Primary, this);
|
||||
commandZoneGraphicsItem->setZValue(ZValues::COMMAND_ZONE);
|
||||
commandZoneGraphicsItem->setVisible(false);
|
||||
connect(commandZoneGraphicsItem, &CommandZone::minimizedChanged, this, &PlayerGraphicsItem::rearrangeZones);
|
||||
|
||||
// Partner zone is Qt child of Command (nested - preserves tight Command-Partner relationship)
|
||||
partnerZoneGraphicsItem = new CommandZone(player->getPartnerZone(), ZoneSizes::COMMAND_ZONE_HEIGHT,
|
||||
CommandZoneType::Partner, commandZoneGraphicsItem);
|
||||
partnerZoneGraphicsItem->setZValue(ZValues::PARTNER_ZONE);
|
||||
connect(partnerZoneGraphicsItem, &CommandZone::expandedChanged, this, &PlayerGraphicsItem::onZoneExpanded);
|
||||
connect(partnerZoneGraphicsItem, &CommandZone::minimizedChanged, this, &PlayerGraphicsItem::rearrangeZones);
|
||||
|
||||
// Companion zone is SIBLING - direct child of PlayerGraphicsItem (fixes spacing bugs)
|
||||
companionZoneGraphicsItem =
|
||||
new CommandZone(player->getCompanionZone(), ZoneSizes::COMMAND_ZONE_HEIGHT, CommandZoneType::Companion, this);
|
||||
companionZoneGraphicsItem->setZValue(ZValues::PARTNER_ZONE + 1);
|
||||
companionZoneGraphicsItem->setVisible(false);
|
||||
connect(companionZoneGraphicsItem, &CommandZone::expandedChanged, this, &PlayerGraphicsItem::onZoneExpanded);
|
||||
connect(companionZoneGraphicsItem, &CommandZone::minimizedChanged, this, &PlayerGraphicsItem::rearrangeZones);
|
||||
|
||||
// Background zone is SIBLING - direct child of PlayerGraphicsItem (fixes spacing bugs)
|
||||
backgroundZoneGraphicsItem =
|
||||
new CommandZone(player->getBackgroundZone(), ZoneSizes::COMMAND_ZONE_HEIGHT, CommandZoneType::Background, this);
|
||||
backgroundZoneGraphicsItem->setZValue(ZValues::PARTNER_ZONE + 2);
|
||||
backgroundZoneGraphicsItem->setVisible(false);
|
||||
connect(backgroundZoneGraphicsItem, &CommandZone::expandedChanged, this, &PlayerGraphicsItem::onZoneExpanded);
|
||||
connect(backgroundZoneGraphicsItem, &CommandZone::minimizedChanged, this, &PlayerGraphicsItem::rearrangeZones);
|
||||
|
||||
// Set up logical parent-child relationships for sibling zones
|
||||
// (used by hierarchy traversal methods like tryMinimizeAbove, shouldPreventCollapse)
|
||||
companionZoneGraphicsItem->setLogicalParentZone(partnerZoneGraphicsItem);
|
||||
backgroundZoneGraphicsItem->setLogicalParentZone(companionZoneGraphicsItem);
|
||||
partnerZoneGraphicsItem->addLogicalChildZone(companionZoneGraphicsItem);
|
||||
companionZoneGraphicsItem->addLogicalChildZone(backgroundZoneGraphicsItem);
|
||||
|
||||
connect(handZoneGraphicsItem->getLogic(), &HandZoneLogic::cardCountChanged, handCounter,
|
||||
&HandCounter::updateNumber);
|
||||
connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu);
|
||||
|
|
@ -111,7 +167,6 @@ void PlayerGraphicsItem::paint(QPainter * /*painter*/,
|
|||
|
||||
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
|
||||
{
|
||||
// Extend table (and hand, if horizontal) to accommodate the new player width.
|
||||
qreal tableWidth =
|
||||
newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stackZoneGraphicsItem->boundingRect().width();
|
||||
if (!SettingsCache::instance().getHorizontalHand()) {
|
||||
|
|
@ -136,10 +191,33 @@ void PlayerGraphicsItem::rearrangeCounters()
|
|||
const qreal padding = 5;
|
||||
qreal ySize = boundingRect().y() + marginTop;
|
||||
|
||||
// Place objects
|
||||
bool commandZonesVisible = commandZoneGraphicsItem->isVisible();
|
||||
|
||||
for (const auto &counter : player->getCounters()) {
|
||||
AbstractCounter *ctr = counter;
|
||||
|
||||
if (ctr->getId() == CounterIds::CommanderTax) {
|
||||
if (commandZonesVisible) {
|
||||
ctr->setPos(2, 2);
|
||||
ctr->setZValue(ZValues::TAX_COUNTERS);
|
||||
ctr->setVisible(true);
|
||||
} else {
|
||||
ctr->setVisible(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctr->getId() == CounterIds::PartnerTax) {
|
||||
if (commandZonesVisible && partnerZoneGraphicsItem->isExpanded()) {
|
||||
ctr->setPos(2, 2);
|
||||
ctr->setZValue(ZValues::TAX_COUNTERS);
|
||||
ctr->setVisible(true);
|
||||
} else {
|
||||
ctr->setVisible(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ctr->getShownInCounterArea()) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -150,9 +228,132 @@ void PlayerGraphicsItem::rearrangeCounters()
|
|||
}
|
||||
}
|
||||
|
||||
qreal PlayerGraphicsItem::positionCommandZones(const QPointF &base, bool commandZonesVisible)
|
||||
{
|
||||
qreal runningY = 0;
|
||||
|
||||
// Position command/partner zones only if command zone is visible
|
||||
if (commandZonesVisible) {
|
||||
commandZoneGraphicsItem->setPos(base);
|
||||
partnerZoneGraphicsItem->setPos(0, commandZoneGraphicsItem->currentHeight());
|
||||
runningY = commandZoneGraphicsItem->currentHeight() + partnerZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
|
||||
// Companion and background are Qt siblings - position based on their own visibility
|
||||
bool companionZoneEnabled = companionZoneGraphicsItem->isVisible();
|
||||
bool backgroundZoneEnabled = backgroundZoneGraphicsItem->isVisible();
|
||||
|
||||
if (companionZoneEnabled) {
|
||||
companionZoneGraphicsItem->setPos(base.x(), base.y() + runningY);
|
||||
runningY += companionZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
|
||||
if (backgroundZoneEnabled) {
|
||||
backgroundZoneGraphicsItem->setPos(base.x(), base.y() + runningY);
|
||||
runningY += backgroundZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
|
||||
// Toggle visibility rules with availability guards:
|
||||
// A zone's toggle is ONLY visible if:
|
||||
// 1. The zone itself is enabled (Qt visible), AND
|
||||
// 2. Either the zone is open OR it's the "next available" to open
|
||||
//
|
||||
// Truth table for entry point (game start, nothing open):
|
||||
// | Cmd Enabled | Comp Enabled | Bg Enabled | Entry Point Toggle |
|
||||
// |-------------|--------------|------------|--------------------|
|
||||
// | Y | Y | Y | Partner |
|
||||
// | Y | N | Y | Partner |
|
||||
// | N | Y | Y | Companion |
|
||||
// | N | Y | N | Companion |
|
||||
// | N | N | Y | Background |
|
||||
if (commandZonesVisible || companionZoneEnabled || backgroundZoneEnabled) {
|
||||
bool commandZoneEnabled = commandZonesVisible;
|
||||
|
||||
bool partnerCollapsed = partnerZoneGraphicsItem->isCollapsed();
|
||||
bool companionCollapsed = companionZoneGraphicsItem->isCollapsed();
|
||||
bool backgroundCollapsed = backgroundZoneGraphicsItem->isCollapsed();
|
||||
|
||||
bool partnerIsOpen = !partnerCollapsed;
|
||||
bool companionIsOpen = !companionCollapsed;
|
||||
bool backgroundIsOpen = !backgroundCollapsed;
|
||||
bool noDeeperZonesOpen = companionCollapsed && backgroundCollapsed;
|
||||
|
||||
// Partner toggle: visible when open OR at game start (no deeper zones open)
|
||||
partnerZoneGraphicsItem->setToggleButtonVisible(commandZoneEnabled && (partnerIsOpen || noDeeperZonesOpen));
|
||||
|
||||
// Companion toggle: visible when open OR when it's next in cascade OR entry point
|
||||
companionZoneGraphicsItem->setToggleButtonVisible(
|
||||
companionZoneEnabled &&
|
||||
(companionIsOpen || (partnerIsOpen && backgroundCollapsed) || (!commandZoneEnabled && noDeeperZonesOpen)));
|
||||
|
||||
// Background toggle: visible when open OR parent open OR skipping disabled companion
|
||||
backgroundZoneGraphicsItem->setToggleButtonVisible(
|
||||
backgroundZoneEnabled && (backgroundIsOpen || companionIsOpen || (partnerIsOpen && !companionZoneEnabled) ||
|
||||
(!commandZoneEnabled && !companionZoneEnabled)));
|
||||
}
|
||||
|
||||
stackZoneGraphicsItem->setPos(base + QPointF(0, runningY));
|
||||
|
||||
return runningY;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::rearrangeZones()
|
||||
{
|
||||
auto base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0);
|
||||
|
||||
bool commandZoneEnabled = commandZoneGraphicsItem->isVisible();
|
||||
bool companionZoneEnabled = companionZoneGraphicsItem->isVisible();
|
||||
bool backgroundZoneEnabled = backgroundZoneGraphicsItem->isVisible();
|
||||
bool anyCommandFamilyZonesVisible = commandZoneEnabled || companionZoneEnabled || backgroundZoneEnabled;
|
||||
|
||||
// Calculate available stack height, accounting for command zones if present
|
||||
qreal tableHeight = tableZoneGraphicsItem->boundingRect().height();
|
||||
qreal stackHeight = tableHeight;
|
||||
if (anyCommandFamilyZonesVisible) {
|
||||
qreal totalCommandHeight = totalCommandZoneHeight();
|
||||
|
||||
// Safety-net auto-minimize for non-expansion triggers (window resize, etc.)
|
||||
// Most overflow is now handled at expansion time by onZoneExpanded()
|
||||
constexpr int MAX_MINIMIZE_ITERATIONS = 3;
|
||||
int iterations = 0;
|
||||
while (totalCommandHeight > tableHeight - ZoneSizes::MINIMUM_STACK_HEIGHT &&
|
||||
iterations < MAX_MINIMIZE_ITERATIONS) {
|
||||
bool minimized = false;
|
||||
|
||||
// Try command zone hierarchy first (includes partner via logical children)
|
||||
if (commandZoneEnabled) {
|
||||
minimized = commandZoneGraphicsItem->tryAutoMinimize();
|
||||
}
|
||||
|
||||
// Fall back to companion/background directly when command zone is disabled.
|
||||
// These zones use toggleMinimized() directly since they don't have the same
|
||||
// hierarchical auto-minimize traversal that command zone provides.
|
||||
if (!minimized && companionZoneEnabled && companionZoneGraphicsItem->isExpanded() &&
|
||||
!companionZoneGraphicsItem->isMinimized()) {
|
||||
companionZoneGraphicsItem->toggleMinimized();
|
||||
minimized = true;
|
||||
}
|
||||
|
||||
if (!minimized && backgroundZoneEnabled && backgroundZoneGraphicsItem->isExpanded() &&
|
||||
!backgroundZoneGraphicsItem->isMinimized()) {
|
||||
backgroundZoneGraphicsItem->toggleMinimized();
|
||||
minimized = true;
|
||||
}
|
||||
|
||||
if (!minimized)
|
||||
break;
|
||||
|
||||
totalCommandHeight = totalCommandZoneHeight();
|
||||
++iterations;
|
||||
}
|
||||
|
||||
stackHeight = tableHeight - totalCommandHeight;
|
||||
if (stackHeight < ZoneSizes::MINIMUM_STACK_HEIGHT) {
|
||||
stackHeight = ZoneSizes::MINIMUM_STACK_HEIGHT;
|
||||
}
|
||||
}
|
||||
stackZoneGraphicsItem->setHeight(stackHeight);
|
||||
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
if (mirrored) {
|
||||
if (player->getHandZone()->contentsKnown()) {
|
||||
|
|
@ -163,12 +364,12 @@ void PlayerGraphicsItem::rearrangeZones()
|
|||
player->getPlayerInfo()->setHandVisible(false);
|
||||
}
|
||||
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandZones(base, commandZoneEnabled);
|
||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base);
|
||||
} else {
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandZones(base, commandZoneEnabled);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base.x() + stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
base += QPointF(0, tableZoneGraphicsItem->boundingRect().height());
|
||||
|
|
@ -188,7 +389,7 @@ void PlayerGraphicsItem::rearrangeZones()
|
|||
handZoneGraphicsItem->setPos(base);
|
||||
base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandZones(base, commandZoneEnabled);
|
||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base);
|
||||
|
|
@ -217,4 +418,93 @@ void PlayerGraphicsItem::updateBoundingRect()
|
|||
playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height());
|
||||
|
||||
emit sizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal PlayerGraphicsItem::totalCommandZoneHeight() const
|
||||
{
|
||||
qreal total = 0;
|
||||
|
||||
// Command zone and partner use Qt parent-child - checking command visibility
|
||||
// implicitly covers partner (Qt hides children when parent is hidden)
|
||||
if (commandZoneGraphicsItem->isVisible()) {
|
||||
total += commandZoneGraphicsItem->currentHeight();
|
||||
if (partnerZoneGraphicsItem->isExpanded()) {
|
||||
total += partnerZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
}
|
||||
|
||||
// Sibling zones have independent visibility - check each one
|
||||
if (companionZoneGraphicsItem->isVisible()) {
|
||||
total += companionZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
|
||||
if (backgroundZoneGraphicsItem->isVisible()) {
|
||||
total += backgroundZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::onZoneExpanded(bool expanded)
|
||||
{
|
||||
if (!expanded) {
|
||||
rearrangeZones();
|
||||
return;
|
||||
}
|
||||
|
||||
auto *expandedZone = qobject_cast<CommandZone *>(sender());
|
||||
if (expandedZone == nullptr) {
|
||||
rearrangeZones();
|
||||
return;
|
||||
}
|
||||
|
||||
qreal tableHeight = tableZoneGraphicsItem->boundingRect().height();
|
||||
qreal totalHeight = totalCommandZoneHeight();
|
||||
|
||||
constexpr int MAX_MINIMIZE_ITERATIONS = 3;
|
||||
int iterations = 0;
|
||||
while (totalHeight > tableHeight - ZoneSizes::MINIMUM_STACK_HEIGHT && iterations < MAX_MINIMIZE_ITERATIONS) {
|
||||
if (!expandedZone->tryMinimizeAbove())
|
||||
break;
|
||||
|
||||
totalHeight = totalCommandZoneHeight();
|
||||
++iterations;
|
||||
}
|
||||
|
||||
rearrangeZones();
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::setCommandZonesVisible(bool visible)
|
||||
{
|
||||
if (commandZoneGraphicsItem) {
|
||||
commandZoneGraphicsItem->setVisible(visible);
|
||||
}
|
||||
|
||||
// Reset partner zone to collapsed state when hiding
|
||||
if (!visible && partnerZoneGraphicsItem) {
|
||||
partnerZoneGraphicsItem->setExpanded(false);
|
||||
}
|
||||
rearrangeZones();
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::setCompanionZoneVisible(bool visible)
|
||||
{
|
||||
if (companionZoneGraphicsItem) {
|
||||
companionZoneGraphicsItem->setVisible(visible);
|
||||
if (!visible) {
|
||||
companionZoneGraphicsItem->setExpanded(false);
|
||||
}
|
||||
}
|
||||
rearrangeZones();
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::setBackgroundZoneVisible(bool visible)
|
||||
{
|
||||
if (backgroundZoneGraphicsItem) {
|
||||
backgroundZoneGraphicsItem->setVisible(visible);
|
||||
if (!visible) {
|
||||
backgroundZoneGraphicsItem->setExpanded(false);
|
||||
}
|
||||
}
|
||||
rearrangeZones();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue