File indexing completed on 2024-04-21 15:08:01

0001 // Copyright (c) 2002-2004 Rob Kaper <cap@capsi.com>
0002 //
0003 // This library is free software; you can redistribute it and/or
0004 // modify it under the terms of the GNU Lesser General Public
0005 // License version 2.1 as published by the Free Software Foundation.
0006 //
0007 // This library is distributed in the hope that it will be useful,
0008 // but WITHOUT ANY WARRANTY; without even the implied warranty of
0009 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0010 // Lesser General Public License for more details.
0011 //
0012 // You should have received a copy of the GNU Lesser General Public License
0013 // along with this library; see the file COPYING.LIB.  If not, write to
0014 // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0015 // Boston, MA 02110-1301, USA.
0016 
0017 #include <iostream>
0018 
0019 #include <QGridLayout>
0020 #include <QResizeEvent>
0021 
0022 #include <klocalizedstring.h>
0023 
0024 #include <atlantic_core.h>
0025 #include <player.h>
0026 #include <estate.h>
0027 #include <auction.h>
0028 #include <game.h>
0029 
0030 #include "auction_widget.h"
0031 #include "configoption.h"
0032 #include "estatedetails.h"
0033 #include "estateview.h"
0034 #include "token.h"
0035 
0036 #include "board.h"
0037 
0038 #include <libatlantikui_debug.h>
0039 
0040 AtlantikBoard::AtlantikBoard(AtlanticCore *atlanticCore, int maxEstates, DisplayMode mode, QWidget *parent)
0041     : QWidget(parent)
0042     , m_atlanticCore(atlanticCore)
0043     , m_mode(mode)
0044     , m_lastServerDisplay(nullptr)
0045     , m_lastServerDisplayBeforeAuction(nullptr)
0046     , m_movingToken(nullptr)
0047     , m_resumeTimer(false)
0048     , m_animateTokens(false)
0049     , m_maxEstates(maxEstates)
0050 {
0051     setMinimumSize(QSize(500, 500));
0052 
0053     int sideLen = maxEstates/4;
0054 
0055     // Animated token movement
0056     m_timer = new QTimer(this);
0057     connect(m_timer, SIGNAL(timeout()), this, SLOT(slotMoveToken()));
0058 
0059     m_gridLayout = new QGridLayout( this );
0060     m_gridLayout->setSpacing(0);
0061     m_gridLayout->setContentsMargins(0, 0, 0, 0);
0062     for(int i=0;i<=sideLen;i++)
0063     {
0064         if (i==0 || i==sideLen)
0065         {
0066             m_gridLayout->setRowStretch(i, 3);
0067             m_gridLayout->setColumnStretch(i, 3);
0068         }
0069         else
0070         {
0071             m_gridLayout->setRowStretch(i, 2);
0072             m_gridLayout->setColumnStretch(i, 2);
0073         }
0074     }
0075 
0076 //  spacer = new QWidget(this);
0077 //  m_gridLayout->addWidget(spacer, sideLen, sideLen); // SE
0078 
0079     displayDefault();
0080 }
0081 
0082 AtlantikBoard::~AtlantikBoard()
0083 {
0084     reset();
0085 }
0086 
0087 void AtlantikBoard::reset()
0088 {
0089     qCDebug(LIBATLANTIKUI_LOG);
0090 
0091     qDeleteAll(m_tokens);
0092     m_tokens.clear();
0093     qDeleteAll(m_estateViews);
0094     m_estateViews.clear();
0095     qDeleteAll(m_displayQueue);
0096     m_displayQueue.clear();
0097     m_lastServerDisplay = nullptr;
0098     m_movingToken = nullptr;
0099 }
0100 
0101 void AtlantikBoard::setViewProperties(bool indicateUnowned, bool highlightUnowned, bool darkenMortgaged, bool quartzEffects, bool animateTokens)
0102 {
0103     if (m_animateTokens != animateTokens)
0104         m_animateTokens = animateTokens;
0105 
0106     // Update EstateViews
0107     foreach (EstateView *estateView, m_estateViews)
0108             estateView->setViewProperties(indicateUnowned, highlightUnowned, darkenMortgaged, quartzEffects);
0109 }
0110 
0111 int AtlantikBoard::heightForWidth(int width) const
0112 {
0113     return width;
0114 }
0115 
0116 EstateView *AtlantikBoard::findEstateView(Estate *estate) const
0117 {
0118     return m_estateViews.value(estate, nullptr);
0119 }
0120 
0121 void AtlantikBoard::addEstateView(Estate *estate, bool indicateUnowned, bool highlightUnowned, bool darkenMortgaged, bool quartzEffects)
0122 {
0123     QString icon = estate->icon();
0124     int estateId = estate->id();
0125     EstateOrientation orientation = North;
0126     int sideLen = m_gridLayout->rowCount() - 1;
0127 
0128     if (estateId < sideLen)
0129         orientation = North;
0130     else if (estateId < 2*sideLen)
0131         orientation = East;
0132     else if (estateId < 3*sideLen)
0133         orientation = South;
0134     else //if (estateId < 4*sideLen)
0135         orientation = West;
0136 
0137     EstateView *estateView = new EstateView(estate, orientation, icon, indicateUnowned, highlightUnowned, darkenMortgaged, quartzEffects, this);
0138         estateView->setObjectName(QStringLiteral("estateview"));
0139     estateView->setAllowEstateSales(true); // XXX should use the allowestatesales config option
0140     m_estateViews.insert(estate, estateView);
0141 
0142     connect(estate, SIGNAL(changed()), estateView, SLOT(estateChanged()));
0143     connect(estateView, SIGNAL(estateToggleMortgage(Estate *)), estate, SIGNAL(estateToggleMortgage(Estate *)));
0144     connect(estateView, SIGNAL(LMBClicked(Estate *)), estate, SIGNAL(LMBClicked(Estate *)));
0145     connect(estateView, SIGNAL(estateHouseBuy(Estate *)), estate, SIGNAL(estateHouseBuy(Estate *)));
0146     connect(estateView, SIGNAL(estateHouseSell(Estate *)), estate, SIGNAL(estateHouseSell(Estate *)));
0147     connect(estateView, SIGNAL(estateSell(Estate *)), estate, SIGNAL(estateSell(Estate *)));
0148     connect(estateView, SIGNAL(newTrade(Player *)), estate, SIGNAL(newTrade(Player *)));
0149 
0150     // Designer has its own LMBClicked slot
0151     if (m_mode == Play)
0152         connect(estateView, SIGNAL(LMBClicked(Estate *)), this, SLOT(prependEstateDetails(Estate *)));
0153 
0154     if (estateId<sideLen)
0155         m_gridLayout->addWidget(estateView, sideLen, sideLen-estateId);
0156     else if (estateId<2*sideLen)
0157         m_gridLayout->addWidget(estateView, 2*sideLen-estateId, 0);
0158     else if (estateId<3*sideLen)
0159         m_gridLayout->addWidget(estateView, 0, estateId-2*sideLen);
0160     else
0161         m_gridLayout->addWidget(estateView, estateId-3*sideLen, sideLen);
0162 
0163     estateView->show();
0164 
0165     if (m_atlanticCore)
0166     {
0167         foreach (Player *player, m_atlanticCore->players())
0168             if (player->location() == estate)
0169                 addToken(player);
0170     }
0171 }
0172 
0173 void AtlantikBoard::addAuctionWidget(Auction *auction)
0174 {
0175     AuctionWidget *auctionW = new AuctionWidget(m_atlanticCore, auction, this);
0176     m_lastServerDisplayBeforeAuction = m_lastServerDisplay;
0177     m_lastServerDisplay = auctionW;
0178     m_displayQueue.prepend(auctionW);
0179     updateCenter();
0180 
0181     connect(auction, SIGNAL(completed()), this, SLOT(displayDefault()));
0182 }
0183 
0184 Token *AtlantikBoard::findToken(Player *player) const
0185 {
0186     return m_tokens.value(player, nullptr);
0187 }
0188 
0189 void AtlantikBoard::addToken(Player *player)
0190 {
0191     if (!player->location())
0192     {
0193         qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - estateView null";
0194         return;
0195     }
0196 
0197     if (player->isSpectator())
0198     {
0199         qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - is a spectator";
0200         return;
0201     }
0202 
0203     Player *playerSelf = nullptr;
0204     if (m_atlanticCore)
0205         playerSelf = m_atlanticCore->playerSelf();
0206 
0207     if (playerSelf && playerSelf->game() != player->game() )
0208     {
0209         qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - not in same game as playerSelf";
0210         return;
0211     }
0212 
0213     qCDebug(LIBATLANTIKUI_LOG) << "addToken";
0214 
0215     Token *token = new Token(player, this);
0216         token->setObjectName(QStringLiteral("token"));
0217     token->setTokenTheme(m_tokenTheme);
0218     m_tokens.insert(player, token);
0219     connect(player, SIGNAL(changed(Player *)), token, SLOT(playerChanged()));
0220 
0221     jumpToken(token);
0222 
0223     // Timer to reinit the gameboard _after_ event loop
0224     QTimer::singleShot(100, this, SLOT(slotResizeAftermath()));
0225 }
0226 
0227 void AtlantikBoard::playerChanged(Player *player)
0228 {
0229     qCDebug(LIBATLANTIKUI_LOG) << "playerLoc" << (player->location() ? player->location()->name() : QStringLiteral("none"));
0230 
0231     Player *playerSelf = nullptr;
0232     if (m_atlanticCore)
0233         playerSelf = m_atlanticCore->playerSelf();
0234 
0235     // Update token
0236     Token *token = findToken(player);
0237     if (token)
0238     {
0239         qCDebug(LIBATLANTIKUI_LOG) << "tokenLoc" << (token->location() ? token->location()->name() : QStringLiteral("none"));
0240         if (player->isBankrupt() || player->isSpectator() || (playerSelf && playerSelf->game() != player->game()) )
0241             token->hide();
0242         if (!player->isSpectator())
0243             token->show();
0244         if (player->hasTurn())
0245             token->raise();
0246 
0247         bool jump = false, move = false;
0248 
0249         if (token->inJail() != player->inJail())
0250         {
0251             token->setInJail(player->inJail());
0252 
0253             // If any status such as jail is ever allowed to
0254             // change in the future, during movement, this needs
0255             // to be addressed in moveToken and subsequent steps.
0256             if (token != m_movingToken)
0257                 jump = true;
0258         }
0259 
0260         if (token->location() != player->location())
0261         {
0262             token->setLocation(player->location());
0263             jump = true;
0264         }
0265 
0266         if (player->destination() && token->destination() != player->destination())
0267         {
0268             if (m_animateTokens)
0269             {
0270                 token->setDestination(player->destination());
0271                 move = true;
0272             }
0273             else
0274             {
0275                 token->setLocation(player->destination());
0276                 jump = true;
0277             }
0278         }
0279 
0280         if (move)
0281             moveToken(token);
0282         else if (jump)
0283             jumpToken(token);
0284     }
0285     else
0286         addToken(player);
0287 }
0288 
0289 void AtlantikBoard::removeToken(Player *player)
0290 {
0291     Token *token = m_tokens.take(player);
0292     if (!token)
0293         return;
0294 
0295     if (token == m_movingToken)
0296     {
0297         m_timer->stop();
0298         m_movingToken = nullptr;
0299     }
0300 
0301     delete token;
0302 }
0303 
0304 void AtlantikBoard::jumpToken(Token *token)
0305 {
0306     if (!token || !token->location())
0307         return;
0308 
0309     qCDebug(LIBATLANTIKUI_LOG) << "to" << token->location()->name();
0310 
0311     QPoint tGeom = calculateTokenDestination(token);
0312     token->move(tGeom);
0313 
0314     Player *player = token->player();
0315     if (player)
0316     {
0317         player->setLocation(token->location());
0318         player->setDestination(nullptr);
0319 
0320         if (token->isHidden() && !player->isBankrupt() && !player->isSpectator())
0321             token->show();
0322     }
0323 
0324     if (token == m_movingToken)
0325     {
0326         m_timer->stop();
0327 
0328         if (!m_resumeTimer)
0329             m_movingToken = nullptr;
0330     }
0331 
0332     Q_EMIT tokenConfirmation(token->location());
0333 }
0334 
0335 void AtlantikBoard::moveToken(Token *token)
0336 {
0337     qCDebug(LIBATLANTIKUI_LOG) << "to" << token->destination()->name();
0338 
0339     m_movingToken = token;
0340 
0341     // Start timer
0342     m_timer->start(15);
0343 }
0344 
0345 QPoint AtlantikBoard::calculateTokenDestination(Token *token, Estate *eDest)
0346 {
0347         if (!eDest)
0348             eDest = token->location();
0349 
0350         EstateView *evDest = findEstateView(eDest);
0351         if (!evDest)
0352             return QPoint(0, 0);
0353 
0354         int x = 0, y = 0;
0355         const QRect evDestGeom = evDest->geometry();
0356         if (token->player()->inJail())
0357         {
0358             x = evDestGeom.right() - token->width() - 2;
0359             y = evDestGeom.top();
0360         }
0361         else
0362         {
0363             const QPoint c = evDestGeom.center();
0364             x = c.x() - (token->width()/2);
0365             y = c.y() - (token->height()/2);
0366 
0367 /*
0368             // Re-center because of EstateView headers
0369             switch(evDest->orientation())
0370             {
0371                 case North:
0372                     y += evDest->height()/8; break;
0373                 case East:
0374                     x -= evDest->width()/8; break;
0375                 case South:
0376                     y -= evDest->height()/8; break;
0377                 case West:
0378                     x += evDest->width()/8; break;
0379             }
0380 */
0381         }
0382     return QPoint(x, y);
0383 }
0384 
0385 void AtlantikBoard::slotMoveToken()
0386 {
0387     // Requires a core with estates to operate on
0388     if (!m_atlanticCore)
0389     {
0390         qCDebug(LIBATLANTIKUI_LOG) << "ignored - no atlanticCore";
0391         return;
0392     }
0393 
0394     // Do we actually have a token to move?
0395     if (!m_movingToken)
0396     {
0397         m_timer->stop();
0398         return;
0399     }
0400 
0401     // Where are we?
0402     QPoint tokenPos = m_movingToken->pos();
0403     int xCurrent = tokenPos.x();
0404     int yCurrent = tokenPos.y();
0405 
0406     // Where do we want to go today?
0407     Estate *eDest = m_atlanticCore->estateAfter(m_movingToken->location());
0408     QPoint tGeom = calculateTokenDestination(m_movingToken, eDest);
0409 
0410     int xDest = tGeom.x();
0411     int yDest = tGeom.y();
0412 
0413     if (xDest - xCurrent > 1)
0414         xDest = xCurrent + 2;
0415     else if (xCurrent - xDest > 1)
0416         xDest = xCurrent - 2;
0417     else
0418         xDest = xCurrent;
0419 
0420     if (yDest - yCurrent > 1)
0421         yDest = yCurrent + 2;
0422     else if (yCurrent - yDest > 1)
0423         yDest = yCurrent - 2;
0424     else
0425         yDest = yCurrent;
0426 
0427 //  qCDebug(LIBATLANTIKUI_LOG) << "TOKEN: at " << xCurrent << "," << yCurrent << " and going to " << xDest << "," << yDest;
0428 
0429     if (xCurrent != xDest || yCurrent != yDest)
0430     {
0431         tokenPos.setX(xDest);
0432         tokenPos.setY(yDest);
0433         m_movingToken->move(tokenPos);
0434         return;
0435     }
0436 
0437     // We have arrived at our destination!
0438     m_movingToken->setLocation(eDest);
0439     m_movingToken->player()->setLocation(eDest);
0440     Q_EMIT tokenConfirmation(eDest);
0441 
0442     // We have arrived at our _final_ destination!
0443     if (eDest == m_movingToken->destination())
0444     {
0445         m_movingToken->setDestination(nullptr);
0446         m_movingToken->player()->setDestination(nullptr);
0447 
0448         m_timer->stop();
0449         m_movingToken = nullptr;
0450     }
0451 }
0452 
0453 void AtlantikBoard::resizeEvent(QResizeEvent *)
0454 {
0455     // Stop moving tokens, slotResizeAftermath will re-enable this
0456     if (m_timer!=nullptr && m_timer->isActive())
0457     {
0458         m_timer->stop();
0459         m_resumeTimer=true;
0460     }
0461 
0462 /*
0463     // Adjust spacer to make sure board stays a square
0464     int q = e->size().width() - e->size().height();
0465     if (q > 0)
0466     {
0467         QSize s(q, 0);
0468         spacer->setFixedSize(s);
0469     }
0470     else
0471     {
0472         QSize s(0, -q);
0473         spacer->setFixedSize(s);
0474     }
0475 */
0476     // Timer to reinit the gameboard _after_ resizeEvent
0477     QTimer::singleShot(0, this, SLOT(slotResizeAftermath()));
0478 }
0479 
0480 void AtlantikBoard::slotResizeAftermath()
0481 {
0482     qCDebug(LIBATLANTIKUI_LOG);
0483     // Move tokens back to their last known location (this has to be done
0484     // _after_ resizeEvent has returned to make sure we have the correct
0485     // adjusted estate geometries.
0486 
0487     foreach (Token *token, m_tokens)
0488         jumpToken(token);
0489 
0490     // Restart the timer that was stopped in resizeEvent
0491     if (m_resumeTimer && m_timer!=nullptr && !m_timer->isActive())
0492     {
0493         m_timer->start(15);
0494         m_resumeTimer=false;
0495     }
0496 }
0497 
0498 void AtlantikBoard::displayDefault()
0499 {
0500     switch(m_displayQueue.count())
0501     {
0502     case 0:
0503         m_displayQueue.prepend(new QWidget(this));
0504         break;
0505     case 1:
0506         if (EstateDetails *display = dynamic_cast<EstateDetails*>(m_lastServerDisplay))
0507             display->setEstate(nullptr);
0508         break;
0509     default:
0510         if (m_displayQueue.first() == m_lastServerDisplay)
0511             m_lastServerDisplay = nullptr;
0512         delete m_displayQueue.takeFirst();
0513         if (m_lastServerDisplayBeforeAuction)
0514             m_lastServerDisplay = m_lastServerDisplayBeforeAuction;
0515         break;
0516     }
0517     updateCenter();
0518 }
0519 
0520 void AtlantikBoard::displayButton(const QString &command, const QString &caption, bool enabled)
0521 {
0522     if (EstateDetails *display = dynamic_cast<EstateDetails*>(m_lastServerDisplay))
0523         display->addButton(command, caption, enabled);
0524 }
0525 
0526 void AtlantikBoard::addCloseButton()
0527 {
0528     EstateDetails *eDetails = nullptr;
0529     if ((eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay)) && eDetails != m_displayQueue.last())
0530         eDetails->addCloseButton();
0531 }
0532 
0533 void AtlantikBoard::insertDetails(const QString &text, bool clearText, bool clearButtons, Estate *estate)
0534 {
0535     EstateDetails *eDetails = nullptr;
0536 
0537     if ((eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay)))
0538     {
0539         if (clearText)
0540             eDetails->setText(text);
0541         else
0542             eDetails->appendText(text);
0543 
0544         if (clearButtons)
0545             eDetails->clearButtons();
0546 
0547         eDetails->setEstate(estate);
0548         return;
0549     }
0550 
0551     if (!m_displayQueue.isEmpty() && m_displayQueue.first() != m_lastServerDisplay)
0552         delete m_displayQueue.takeFirst();
0553 
0554     eDetails = new EstateDetails(estate, text, this);
0555     m_lastServerDisplay = eDetails;
0556     connect(eDetails, SIGNAL(buttonCommand(QString)), this, SIGNAL(buttonCommand(QString)));
0557     connect(eDetails, SIGNAL(buttonClose()), this, SLOT(displayDefault()));
0558 
0559     m_displayQueue.insert(0, eDetails);
0560     updateCenter();
0561 }
0562 
0563 void AtlantikBoard::insertText(const QString &text, bool clearText, bool clearButtons)
0564 {
0565     EstateDetails *eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay);
0566     if (!eDetails)
0567         return;
0568 
0569     if (clearText)
0570         eDetails->setText(text);
0571     else
0572         eDetails->appendText(text);
0573 
0574     if (clearButtons)
0575         eDetails->clearButtons();
0576 }
0577 
0578 void AtlantikBoard::prependEstateDetails(Estate *estate)
0579 {
0580     if (!estate)
0581         return;
0582 
0583     EstateDetails *eDetails = nullptr;
0584 
0585     if (m_displayQueue.first() == m_lastServerDisplay)
0586     {
0587         eDetails = new EstateDetails(estate, QString(), this);
0588         m_displayQueue.prepend(eDetails);
0589 
0590         connect(eDetails, SIGNAL(buttonCommand(QString)), this, SIGNAL(buttonCommand(QString)));
0591         connect(eDetails, SIGNAL(buttonClose()), this, SLOT(displayDefault()));
0592     }
0593     else
0594     {
0595         eDetails = dynamic_cast<EstateDetails*>(m_displayQueue.first());
0596         if (eDetails)
0597         {
0598             eDetails->setEstate(estate);
0599             eDetails->setText( QString() );
0600             // eDetails->clearButtons();
0601         }
0602         else
0603         {
0604             qCDebug(LIBATLANTIKUI_LOG) << "manual estatedetails with first in queue neither server nor details";
0605             return;
0606         }
0607     }
0608 
0609     eDetails->addDetails();
0610     eDetails->addCloseButton();
0611 
0612     updateCenter();
0613 }
0614 
0615 void AtlantikBoard::updateCenter()
0616 {
0617     QWidget *center = m_displayQueue.first();
0618     m_gridLayout->addWidget(center, 1, 1, m_gridLayout->rowCount()-2, m_gridLayout->columnCount()-2);
0619     center->show();
0620 }
0621 
0622 QWidget *AtlantikBoard::centerWidget()
0623 {
0624     return m_displayQueue.first();
0625 }
0626 
0627 void AtlantikBoard::setTokenTheme(const TokenTheme &theme)
0628 {
0629     m_tokenTheme = theme;
0630     foreach (Token *token, m_tokens)
0631         token->setTokenTheme(m_tokenTheme);
0632 }
0633 
0634 #include "moc_board.cpp"