File indexing completed on 2025-02-02 07:28:24
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"