File indexing completed on 2024-05-12 04:04:34

0001 /*
0002     Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
0003     Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
0004 
0005     This program is free software; you can redistribute it and/or modify
0006     it under the terms of the GNU General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or
0008     (at your option) any later version.
0009 
0010     This program is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013     GNU General Public License for more details.
0014 
0015     You should have received a copy of the GNU General Public License
0016     along with this program; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 #include "objects.h"
0021 #include "ball.h"
0022 #include "game.h"
0023 #include "tagaro/board.h"
0024 
0025 #include <QFormLayout>
0026 #include <QHBoxLayout>
0027 #include <QRandomGenerator>
0028 #include <QSlider>
0029 #include <QTimer>
0030 #include <KConfigGroup>
0031 #include <KPluralHandlingSpinBox>
0032 #include <KLocalizedString>
0033 //BEGIN Kolf::BlackHole
0034 
0035 Kolf::BlackHole::BlackHole(QGraphicsItem* parent, b2World* world)
0036     : EllipticalCanvasItem(true, QStringLiteral("black_hole"), parent, world)
0037     , m_minSpeed(3.0)
0038     , m_maxSpeed(5.0)
0039     , m_runs(0)
0040     , m_exitDeg(0)
0041     , m_exitItem(new QGraphicsLineItem(0, -15, 0, 15, Kolf::findBoard(this)))
0042     , m_directionItem(new ArrowItem(Kolf::findBoard(this)))
0043     , m_infoLine(new QGraphicsLineItem(this))
0044 {
0045     setSize(QSizeF(16, 18));
0046     setZBehavior(CanvasItem::IsRaisedByStrut, 4);
0047     setSimulationType(CanvasItem::NoSimulation);
0048 
0049     const QColor myColor((QRgb)(QRandomGenerator::global()->bounded(0x01000000)));
0050     ellipseItem()->setBrush(myColor);
0051     m_exitItem->setPen(QPen(myColor, 6));
0052     m_directionItem->setPen(myColor);
0053     m_infoLine->setPen(QPen(myColor, 2));
0054 
0055     setExitPos(QPointF(300, 100));
0056     m_directionItem->setVisible(false);
0057     m_infoLine->setVisible(false);
0058     moveBy(0, 0); //initializes line item
0059 }
0060 
0061 Kolf::BlackHole::~BlackHole()
0062 {
0063     //these items are not direct childs
0064     delete m_directionItem;
0065     delete m_exitItem;
0066 }
0067 
0068 double Kolf::BlackHole::minSpeed() const
0069 {
0070     return m_minSpeed;
0071 }
0072 
0073 void Kolf::BlackHole::setMinSpeed(double news)
0074 {
0075     m_minSpeed = news;
0076     m_directionItem->setLength(10.0 + 2.5 * (m_minSpeed + m_maxSpeed));
0077     propagateUpdate();
0078 }
0079 
0080 double Kolf::BlackHole::maxSpeed() const
0081 {
0082     return m_maxSpeed;
0083 }
0084 
0085 void Kolf::BlackHole::setMaxSpeed(double news)
0086 {
0087     m_maxSpeed = news;
0088     m_directionItem->setLength(10.0 + 2.5 * (m_minSpeed + m_maxSpeed));
0089     propagateUpdate();
0090 }
0091 
0092 QList<QGraphicsItem*> Kolf::BlackHole::infoItems() const
0093 {
0094     return QList<QGraphicsItem*>() << m_infoLine << m_directionItem;
0095 }
0096 
0097 Kolf::Overlay* Kolf::BlackHole::createOverlay()
0098 {
0099     return new Kolf::BlackHoleOverlay(this);
0100 }
0101 
0102 Config* Kolf::BlackHole::config(QWidget* parent)
0103 {
0104     return new Kolf::BlackHoleConfig(this, parent);
0105 }
0106 
0107 void Kolf::BlackHole::moveBy(double dx, double dy)
0108 {
0109     EllipticalCanvasItem::moveBy(dx, dy);
0110     m_infoLine->setLine(QLineF(QPointF(), m_exitItem->pos() - pos()));
0111     propagateUpdate();
0112 }
0113 
0114 int Kolf::BlackHole::curExitDeg() const
0115 {
0116     return m_exitDeg;
0117 }
0118 
0119 void Kolf::BlackHole::setExitDeg(int newdeg)
0120 {
0121     m_exitDeg = newdeg;
0122     m_exitItem->setRotation(-newdeg);
0123     m_directionItem->setAngle(-deg2rad(newdeg));
0124     propagateUpdate();
0125 }
0126 
0127 QPointF Kolf::BlackHole::exitPos() const
0128 {
0129     return m_exitItem->pos();
0130 }
0131 
0132 void Kolf::BlackHole::setExitPos(const QPointF& exitPos)
0133 {
0134     m_exitItem->setPos(exitPos);
0135     m_directionItem->setPos(exitPos);
0136     moveBy(0, 0); //updates line item, and calls propagateUpdate()
0137 }
0138 
0139 Vector Kolf::BlackHole::exitDirection() const
0140 {
0141     return m_directionItem->vector();
0142 }
0143 
0144 void Kolf::BlackHole::shotStarted()
0145 {
0146     m_runs = 0;
0147 }
0148 
0149 bool Kolf::BlackHole::collision(Ball* ball)
0150 {
0151     //miss if speed too high
0152     const double speed = Vector(ball->velocity()).magnitude();
0153     if (speed > 3.75)
0154         return true;
0155     // is center of ball in cup?
0156     if (!contains(ball->pos() - pos()))
0157         return true;
0158     // warp through blackhole at most 10 times per shot
0159     if (m_runs > 10 && game && game->isInPlay())
0160         return true;
0161 
0162     game->playSound(Sound::BlackHolePutIn);
0163 
0164     const double diff = m_maxSpeed - m_minSpeed;
0165     const double newSpeed = m_minSpeed + speed / 3.75 * diff;
0166 
0167     ball->setVelocity(Vector());
0168     ball->setState(Stopped);
0169     ball->setVisible(false);
0170     ball->setForceStillGoing(true);
0171 
0172     const double distance = Vector(pos() - m_exitItem->pos()).magnitude();
0173     BlackHoleTimer* timer = new BlackHoleTimer(ball, newSpeed, distance * 2.5 - newSpeed * 35 + 500);
0174 
0175     connect(timer, &BlackHoleTimer::eject, this, &Kolf::BlackHole::eject);
0176     connect(timer, &BlackHoleTimer::halfway, this, &Kolf::BlackHole::halfway);
0177 
0178     game->playSound(Sound::BlackHole);
0179     return false;
0180 }
0181 
0182 Kolf::BlackHoleTimer::BlackHoleTimer(Ball* ball, double speed, int msec)
0183     : m_speed(speed), m_ball(ball)
0184 {
0185     QTimer::singleShot(msec, this, &Kolf::BlackHoleTimer::emitEject);
0186     QTimer::singleShot(msec / 2, this, &Kolf::BlackHoleTimer::halfway);
0187 }
0188 
0189 void Kolf::BlackHoleTimer::emitEject()
0190 {
0191     Q_EMIT eject(m_ball, m_speed);
0192     deleteLater();
0193 }
0194 
0195 void Kolf::BlackHole::eject(Ball* ball, double speed)
0196 {
0197     ball->setVisible(true);
0198     //place ball 10 units after exit, and set exit velocity
0199     const Vector direction = Vector::fromMagnitudeDirection(1, -deg2rad(m_exitDeg));
0200     ball->setPos(m_exitItem->pos() + 10 * direction);
0201     ball->setVelocity(speed * direction);
0202 
0203     ball->setForceStillGoing(false);
0204     ball->setState(Rolling);
0205 
0206     m_runs++;
0207 
0208     game->playSound(Sound::BlackHoleEject);
0209 }
0210 
0211 void Kolf::BlackHole::halfway()
0212 {
0213     game->playSound(Sound::BlackHole);
0214 }
0215 
0216 void Kolf::BlackHole::load(KConfigGroup* cfgGroup)
0217 {
0218     setExitPos(cfgGroup->readEntry("exit", exitPos().toPoint()));
0219     setExitDeg(cfgGroup->readEntry("exitDeg", m_exitDeg));
0220     setMinSpeed(cfgGroup->readEntry("minspeed", m_minSpeed));
0221     setMaxSpeed(cfgGroup->readEntry("maxspeed", m_maxSpeed));
0222 }
0223 
0224 void Kolf::BlackHole::save(KConfigGroup* cfgGroup)
0225 {
0226     cfgGroup->writeEntry("exit", m_exitItem->pos().toPoint());
0227     cfgGroup->writeEntry("exitDeg", m_exitDeg);
0228     cfgGroup->writeEntry("minspeed", m_minSpeed);
0229     cfgGroup->writeEntry("maxspeed", m_maxSpeed);
0230 }
0231 
0232 //END Kolf::BlackHole
0233 //BEGIN Kolf::BlackHoleConfig
0234 
0235 Kolf::BlackHoleConfig::BlackHoleConfig(BlackHole* blackHole, QWidget* parent)
0236     : Config(parent)
0237     , m_blackHole(blackHole)
0238 {
0239     QFormLayout* layout = new QFormLayout(this);
0240 
0241     KPluralHandlingSpinBox* deg = new KPluralHandlingSpinBox(this);
0242     deg->setRange(0, 359);
0243     deg->setSingleStep(10);
0244     deg->setSuffix(ki18np(" degree", " degrees"));
0245     deg->setValue(m_blackHole->curExitDeg());
0246     deg->setWrapping(true);
0247     layout->addRow(i18n("Exiting ball angle:"), deg);
0248         connect(deg, &KPluralHandlingSpinBox::valueChanged,
0249                 this, &Kolf::BlackHoleConfig::degChanged);
0250 
0251         QSlider* minSlider = new QSlider(Qt::Horizontal, this);
0252     minSlider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
0253     minSlider->setRange(0, 800);
0254     minSlider->setSingleStep(1);
0255     minSlider->setTickPosition(QSlider::TicksBelow);
0256     QDoubleSpinBox* minSpinBox = new QDoubleSpinBox(this);
0257     minSpinBox->setRange(0, 8);
0258     minSpinBox->setSingleStep(0.01);
0259     connect(minSlider, &QSlider::valueChanged, [minSlider, minSpinBox] {minSpinBox->setValue(minSlider->value()/100.0);});
0260         connect(minSpinBox, &QDoubleSpinBox::valueChanged,
0261                 [minSlider, minSpinBox] {
0262                   minSlider->setValue(qRound(minSpinBox->value() * 100));
0263                 });
0264         QHBoxLayout *min = new QHBoxLayout;
0265     min->addWidget(minSlider);
0266     min->addWidget(minSpinBox);
0267     layout->addRow(i18n("Minimum exit speed:"), min);
0268     minSpinBox->setValue(m_blackHole->minSpeed());
0269         connect(minSpinBox, &QDoubleSpinBox::valueChanged,
0270                 this, &Kolf::BlackHoleConfig::minChanged);
0271 
0272         QSlider* maxSlider = new QSlider(Qt::Horizontal, this);
0273     maxSlider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
0274     maxSlider->setRange(0, 800);
0275     maxSlider->setSingleStep(1);
0276     maxSlider->setTickPosition(QSlider::TicksBelow);
0277     QDoubleSpinBox* maxSpinBox = new QDoubleSpinBox(this);
0278     maxSpinBox->setRange(0, 8);
0279     maxSpinBox->setSingleStep(0.01);
0280     connect(maxSlider, &QSlider::valueChanged, [maxSlider, maxSpinBox] {maxSpinBox->setValue(maxSlider->value()/100.0);});
0281         connect(maxSpinBox, &QDoubleSpinBox::valueChanged,
0282                 [maxSlider, maxSpinBox] {
0283                   maxSlider->setValue(qRound(maxSpinBox->value() * 100));
0284                 });
0285         QHBoxLayout *max = new QHBoxLayout;
0286     max->addWidget(maxSlider);
0287     max->addWidget(maxSpinBox);
0288     layout->addRow(i18n("Maximum exit speed:"), max);
0289     maxSpinBox->setValue(m_blackHole->maxSpeed());
0290         connect(maxSpinBox, &QDoubleSpinBox::valueChanged,
0291                 this, &Kolf::BlackHoleConfig::maxChanged);
0292 }
0293 
0294 void Kolf::BlackHoleConfig::degChanged(int newdeg)
0295 {
0296     m_blackHole->setExitDeg(newdeg);
0297     changed();
0298 }
0299 
0300 void Kolf::BlackHoleConfig::minChanged(double news)
0301 {
0302     m_blackHole->setMinSpeed(news);
0303     changed();
0304 }
0305 
0306 void Kolf::BlackHoleConfig::maxChanged(double news)
0307 {
0308     m_blackHole->setMaxSpeed(news);
0309     changed();
0310 }
0311 
0312 //END Kolf::BlackHoleConfig
0313 //BEGIN Kolf::BlackHoleOverlay
0314 
0315 Kolf::BlackHoleOverlay::BlackHoleOverlay(Kolf::BlackHole* blackHole)
0316     : Kolf::Overlay(blackHole, blackHole)
0317     , m_exitIndicator(new QGraphicsLineItem(this))
0318     , m_exitHandle(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0319     , m_speedHandle(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0320 {
0321     addHandle(m_exitIndicator);
0322     addHandle(m_exitHandle);
0323     addHandle(m_speedHandle);
0324     connect(m_exitHandle, &Kolf::OverlayHandle::moveRequest, this, &Kolf::BlackHoleOverlay::moveHandle);
0325     connect(m_speedHandle, &Kolf::OverlayHandle::moveRequest, this, &Kolf::BlackHoleOverlay::moveHandle);
0326 }
0327 
0328 void Kolf::BlackHoleOverlay::update()
0329 {
0330     Kolf::Overlay::update();
0331     Kolf::BlackHole* blackHole = dynamic_cast<Kolf::BlackHole*>(qitem());
0332     m_exitHandle->setPos(blackHole->exitPos() - blackHole->pos());
0333     m_speedHandle->setPos(m_exitHandle->pos() + blackHole->exitDirection());
0334     m_exitIndicator->setLine(QLineF(m_exitHandle->pos(), m_speedHandle->pos()));
0335     const qreal exitAngle = -deg2rad(blackHole->curExitDeg());
0336     m_exitHandle->setRotation(exitAngle);
0337     m_speedHandle->setRotation(exitAngle);
0338 }
0339 
0340 void Kolf::BlackHoleOverlay::moveHandle(const QPointF& handleScenePos)
0341 {
0342     Kolf::BlackHole* blackHole = dynamic_cast<Kolf::BlackHole*>(qitem());
0343     if (sender() == m_exitHandle)
0344         blackHole->setExitPos(handleScenePos);
0345     else if (sender() == m_speedHandle)
0346     {
0347         //this modifies only exit direction, not speed
0348         Vector dir = handleScenePos - blackHole->exitPos();
0349         blackHole->setExitDeg(-rad2deg(dir.direction()));
0350     }
0351 }
0352 
0353 //END Kolf::BlackHoleOverlay
0354 //BEGIN Kolf::Cup
0355 
0356 Kolf::Cup::Cup(QGraphicsItem* parent, b2World* world)
0357     : EllipticalCanvasItem(false, QStringLiteral("cup"), parent, world)
0358 {
0359     const int diameter = 16;
0360     setSize(QSizeF(diameter, diameter));
0361     setZBehavior(CanvasItem::IsRaisedByStrut, 4);
0362     setSimulationType(CanvasItem::NoSimulation);
0363 }
0364 
0365 Kolf::Overlay* Kolf::Cup::createOverlay()
0366 {
0367     return new Kolf::Overlay(this, this);
0368 }
0369 
0370 bool Kolf::Cup::collision(Ball* ball)
0371 {
0372     //miss if speed too high
0373     const double speed = Vector(ball->velocity()).magnitude();
0374     if (speed > 3.75)
0375         return true;
0376     //miss if center of ball not inside cup
0377     if (!contains(ball->pos() - pos()))
0378         return true;
0379     //place ball in hole
0380     ball->setState(Holed);
0381     game->playSound(Sound::Holed);
0382     ball->setPos(pos());
0383     ball->setVelocity(Vector());
0384     return false;
0385 }
0386 
0387 //END Kolf::Cup
0388 
0389 #include "moc_objects.cpp"