#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kcomboboxdialog.h" #include "kvolumecontrol.h" #include "vector.h" #include "game.h" inline QString makeGroup(int id, int hole, QString name, int x, int y) { return QString("%1-%2@%3,%4|%5").arg(hole).arg(name).arg(x).arg(y).arg(id); } inline QString makeStateGroup(int id, const QString &name) { return QString("%1|%2").arg(name).arg(id); } ///////////////////////// RectPoint::RectPoint(QColor color, RectItem *rect, QCanvas *canvas) : QCanvasEllipse(canvas) { setZ(9999); setSize(10, 10); this->rect = rect; setBrush(QBrush(color)); setSizeFactor(1.0); dontmove = false; } void RectPoint::moveBy(double dx, double dy) { QCanvasEllipse::moveBy(dx, dy); if (dontmove) { dontmove = false; return; } QCanvasItem *qitem = dynamic_cast(rect); if (!qitem) return; double nw = m_sizeFactor * fabs(x() - qitem->x()); double nh = m_sizeFactor * fabs(y() - qitem->y()); if (nw <= 0 || nh <= 0) return; rect->newSize(nw, nh); } Config *RectPoint::config(QWidget *parent) { CanvasItem *citem = dynamic_cast(rect); if (citem) return citem->config(parent); else return CanvasItem::config(parent); } ///////////////////////// Arrow::Arrow(QCanvas *canvas) : QCanvasLine(canvas) { line1 = new QCanvasLine(canvas); line2 = new QCanvasLine(canvas); m_angle = 0; m_length = 20; m_reversed = false; setPen(black); updateSelf(); setVisible(false); } void Arrow::setPen(QPen p) { QCanvasLine::setPen(p); line1->setPen(p); line2->setPen(p); } void Arrow::setZ(double newz) { QCanvasLine::setZ(newz); line1->setZ(newz); line2->setZ(newz); } void Arrow::setVisible(bool yes) { QCanvasLine::setVisible(yes); line1->setVisible(yes); line2->setVisible(yes); } void Arrow::moveBy(double dx, double dy) { QCanvasLine::moveBy(dx, dy); line1->moveBy(dx, dy); line2->moveBy(dx, dy); } void Arrow::aboutToDie() { delete line1; delete line2; } void Arrow::updateSelf() { QPoint start = startPoint(); QPoint end(m_length * cos(m_angle), m_length * sin(m_angle)); if (m_reversed) { QPoint tmp(start); start = end; end = tmp; } setPoints(start.x(), start.y(), end.x(), end.y()); const double lineLen = m_length / 2; const double angle1 = m_angle - M_PI / 2 - 1; line1->move(end.x() + x(), end.y() + y()); start = end; end = QPoint(lineLen * cos(angle1), lineLen * sin(angle1)); line1->setPoints(0, 0, end.x(), end.y()); const double angle2 = m_angle + M_PI / 2 + 1; line2->move(start.x() + x(), start.y() + y()); end = QPoint(lineLen * cos(angle2), lineLen * sin(angle2)); line2->setPoints(0, 0, end.x(), end.y()); } ///////////////////////// BridgeConfig::BridgeConfig(Bridge *bridge, QWidget *parent) : Config(parent) { this->bridge = bridge; m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint()); QGridLayout *layout = new QGridLayout(m_vlayout, 2, 3, spacingHint()); layout->addWidget(new QLabel(i18n("Walls on:"), this), 0, 0); top = new QCheckBox(i18n("&Top"), this); layout->addWidget(top, 0, 1); connect(top, SIGNAL(toggled(bool)), this, SLOT(topWallChanged(bool))); top->setChecked(bridge->topWallVisible()); bot = new QCheckBox(i18n("&Bottom"), this); layout->addWidget(bot, 1, 1); connect(bot, SIGNAL(toggled(bool)), this, SLOT(botWallChanged(bool))); bot->setChecked(bridge->botWallVisible()); left = new QCheckBox(i18n("&Left"), this); layout->addWidget(left, 1, 0); connect(left, SIGNAL(toggled(bool)), this, SLOT(leftWallChanged(bool))); left->setChecked(bridge->leftWallVisible()); right = new QCheckBox(i18n("&Right"), this); layout->addWidget(right, 1, 2); connect(right, SIGNAL(toggled(bool)), this, SLOT(rightWallChanged(bool))); right->setChecked(bridge->rightWallVisible()); } void BridgeConfig::topWallChanged(bool yes) { bridge->setTopWallVisible(yes); changed(); } void BridgeConfig::botWallChanged(bool yes) { bridge->setBotWallVisible(yes); changed(); } void BridgeConfig::leftWallChanged(bool yes) { bridge->setLeftWallVisible(yes); changed(); } void BridgeConfig::rightWallChanged(bool yes) { bridge->setRightWallVisible(yes); changed(); } ///////////////////////// Bridge::Bridge(QRect rect, QCanvas *canvas) : QCanvasRectangle(rect, canvas) { QColor color("#92772D"); setBrush(QBrush(color)); setPen(NoPen); setZ(998); topWall = new Wall(canvas); topWall->setAlwaysShow(true); botWall = new Wall(canvas); botWall->setAlwaysShow(true); leftWall = new Wall(canvas); leftWall->setAlwaysShow(true); rightWall = new Wall(canvas); rightWall->setAlwaysShow(true); setWallZ(z() + 0.01); setWallColor(color); topWall->setVisible(false); botWall->setVisible(false); leftWall->setVisible(false); rightWall->setVisible(false); point = new RectPoint(color, this, canvas); editModeChanged(false); newSize(width(), height()); } bool Bridge::collision(Ball *ball, long int /*id*/) { ball->setFrictionMultiplier(.63); return false; } void Bridge::setWallZ(double newz) { topWall->setZ(newz); botWall->setZ(newz); leftWall->setZ(newz); rightWall->setZ(newz); } void Bridge::setGame(KolfGame *game) { CanvasItem::setGame(game); topWall->setGame(game); botWall->setGame(game); leftWall->setGame(game); rightWall->setGame(game); } void Bridge::setWallColor(QColor color) { topWall->setPen(QPen(color.dark(), 3)); botWall->setPen(topWall->pen()); leftWall->setPen(topWall->pen()); rightWall->setPen(topWall->pen()); } void Bridge::aboutToDie() { delete point; topWall->aboutToDie(); delete topWall; botWall->aboutToDie(); delete botWall; leftWall->aboutToDie(); delete leftWall; rightWall->aboutToDie(); delete rightWall; } void Bridge::editModeChanged(bool changed) { point->setVisible(changed); moveBy(0, 0); } void Bridge::moveBy(double dx, double dy) { QCanvasRectangle::moveBy(dx, dy); point->dontMove(); point->move(x() + width(), y() + height()); topWall->move(x(), y()); botWall->move(x(), y() - 1); leftWall->move(x(), y()); rightWall->move(x(), y()); QCanvasItemList list = collisions(true); for (QCanvasItemList::Iterator it = list.begin(); it != list.end(); ++it) { CanvasItem *citem = dynamic_cast(*it); if (citem) citem->updateZ(); } } void Bridge::load(KConfig *cfg) { doLoad(cfg); } void Bridge::doLoad(KConfig *cfg) { newSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height())); setTopWallVisible(cfg->readBoolEntry("topWallVisible", topWallVisible())); setBotWallVisible(cfg->readBoolEntry("botWallVisible", botWallVisible())); setLeftWallVisible(cfg->readBoolEntry("leftWallVisible", leftWallVisible())); setRightWallVisible(cfg->readBoolEntry("rightWallVisible", rightWallVisible())); } void Bridge::save(KConfig *cfg) { doSave(cfg); } void Bridge::doSave(KConfig *cfg) { cfg->writeEntry("width", width()); cfg->writeEntry("height", height()); cfg->writeEntry("topWallVisible", topWallVisible()); cfg->writeEntry("botWallVisible", botWallVisible()); cfg->writeEntry("leftWallVisible", leftWallVisible()); cfg->writeEntry("rightWallVisible", rightWallVisible()); } QPtrList Bridge::moveableItems() const { QPtrList ret; ret.append(point); return ret; } void Bridge::newSize(int width, int height) { setSize(width, height); } void Bridge::setSize(int width, int height) { QCanvasRectangle::setSize(width, height); topWall->setPoints(0, 0, width, 0); botWall->setPoints(0, height, width, height); leftWall->setPoints(0, 0, 0, height); rightWall->setPoints(width, 0, width, height); moveBy(0, 0); } ///////////////////////// WindmillConfig::WindmillConfig(Windmill *windmill, QWidget *parent) : BridgeConfig(windmill, parent) { this->windmill = windmill; m_vlayout->addStretch(); QCheckBox *check = new QCheckBox(i18n("Windmill on bottom"), this); check->setChecked(windmill->bottom()); connect(check, SIGNAL(toggled(bool)), this, SLOT(endChanged(bool))); m_vlayout->addWidget(check); QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Slow"), this)); QSlider *slider = new QSlider(1, 10, 1, windmill->curSpeed(), Qt::Horizontal, this); hlayout->addWidget(slider); hlayout->addWidget(new QLabel(i18n("Fast"), this)); connect(slider, SIGNAL(valueChanged(int)), this, SLOT(speedChanged(int))); endChanged(check->isChecked()); } void WindmillConfig::speedChanged(int news) { windmill->setSpeed(news); changed(); } void WindmillConfig::endChanged(bool bottom) { windmill->setBottom(bottom); changed(); bot->setEnabled(!bottom); if (startedUp) { bot->setChecked(!bottom); botWallChanged(bot->isChecked()); } top->setEnabled(bottom); if (startedUp) { top->setChecked(bottom); topWallChanged(top->isChecked()); } } ///////////////////////// Windmill::Windmill(QRect rect, QCanvas *canvas) : Bridge(rect, canvas), speedfactor(16), m_bottom(true) { guard = new WindmillGuard(canvas); guard->setPen(QPen(black, 5)); guard->setVisible(true); guard->setAlwaysShow(true); setSpeed(5); guard->setZ(wallZ() + .1); left = new Wall(canvas); left->setPen(wallPen()); left->setAlwaysShow(true); right = new Wall(canvas); right->setPen(wallPen()); right->setAlwaysShow(true); left->setZ(wallZ()); right->setZ(wallZ()); left->setVisible(true); right->setVisible(true); setTopWallVisible(false); setBotWallVisible(false); setLeftWallVisible(true); setRightWallVisible(true); newSize(width(), height()); moveBy(0, 0); } void Windmill::aboutToDie() { Bridge::aboutToDie(); guard->aboutToDie(); delete guard; left->aboutToDie(); delete left; right->aboutToDie(); delete right; } void Windmill::setSpeed(int news) { if (news < 0) return; speed = news; guard->setXVelocity(((double)news / (double)3) * (guard->xVelocity() > 0? 1 : -1)); } void Windmill::setGame(KolfGame *game) { Bridge::setGame(game); guard->setGame(game); left->setGame(game); right->setGame(game); } void Windmill::save(KConfig *cfg) { cfg->writeEntry("speed", speed); cfg->writeEntry("bottom", m_bottom); doSave(cfg); } void Windmill::load(KConfig *cfg) { setSpeed(cfg->readNumEntry("speed", -1)); doLoad(cfg); left->editModeChanged(false); right->editModeChanged(false); guard->editModeChanged(false); setBottom(cfg->readBoolEntry("bottom", true)); } void Windmill::moveBy(double dx, double dy) { Bridge::moveBy(dx, dy); left->move(x(), y()); right->move(x(), y()); guard->moveBy(dx, dy); guard->setBetween(x(), x() + width()); update(); } void Windmill::setSize(int width, int height) { newSize(width, height); } void Windmill::setBottom(bool yes) { m_bottom = yes; newSize(width(), height()); } void Windmill::newSize(int width, int height) { Bridge::newSize(width, height); const int indent = width / 4; double indentY = m_bottom? height : 0; left->setPoints(0, indentY, indent, indentY); right->setPoints(width - indent, indentY, width, indentY); guard->setBetween(x(), x() + width); double guardY = m_bottom? height + 4 : -4; guard->setPoints(0, guardY, (double)indent / (double)1.07 - 2, guardY); } ///////////////////////// void WindmillGuard::advance(int phase) { Wall::advance(phase); if (phase == 1) { if (x() + startPoint().x() <= min) setXVelocity(fabs(xVelocity())); else if (x() + endPoint().x() >= max) setXVelocity(-fabs(xVelocity())); } } ///////////////////////// Sign::Sign(QCanvas *canvas) : Bridge(QRect(0, 0, 110, 40), canvas) { setZ(998.8); m_text = m_untranslatedText = i18n("New Text"); setBrush(QBrush(white)); setWallColor(black); setWallZ(z() + .01); setTopWallVisible(true); setBotWallVisible(true); setLeftWallVisible(true); setRightWallVisible(true); } void Sign::load(KConfig *cfg) { m_text = cfg->readEntry("Comment", m_text); m_untranslatedText = cfg->readEntryUntranslated("Comment", m_untranslatedText); doLoad(cfg); } void Sign::save(KConfig *cfg) { cfg->writeEntry("Comment", m_untranslatedText); doSave(cfg); } void Sign::setText(const QString &text) { m_text = text; m_untranslatedText = text; update(); } void Sign::draw(QPainter &painter) { Bridge::draw(painter); painter.setPen(QPen(black, 1)); QSimpleRichText txt(m_text, kapp->font()); const int indent = wallPen().width() + 3; txt.setWidth(width() - 2*indent); QColorGroup colorGroup; colorGroup.setColor(QColorGroup::Foreground, black); colorGroup.setColor(QColorGroup::Text, black); colorGroup.setColor(QColorGroup::Background, black); colorGroup.setColor(QColorGroup::Base, black); txt.draw(&painter, x() + indent, y(), QRect(x() + indent, y(), width() - indent, height() - indent), colorGroup); } ///////////////////////// SignConfig::SignConfig(Sign *sign, QWidget *parent) : BridgeConfig(sign, parent) { this->sign = sign; m_vlayout->addStretch(); m_vlayout->addWidget(new QLabel(i18n("Sign HTML:"), this)); KLineEdit *name = new KLineEdit(sign->text(), this); m_vlayout->addWidget(name); connect(name, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &))); } void SignConfig::textChanged(const QString &text) { sign->setText(text); changed(); } ///////////////////////// EllipseConfig::EllipseConfig(Ellipse *ellipse, QWidget *parent) : Config(parent), slow1(0), fast1(0), slow2(0), fast2(0), slider1(0), slider2(0) { this->ellipse = ellipse; m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint()); QCheckBox *check = new QCheckBox(i18n("Enable show/hide"), this); m_vlayout->addWidget(check); connect(check, SIGNAL(toggled(bool)), this, SLOT(check1Changed(bool))); check->setChecked(ellipse->changeEnabled()); QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint()); slow1 = new QLabel(i18n("Slow"), this); hlayout->addWidget(slow1); slider1 = new QSlider(1, 100, 5, 100 - ellipse->changeEvery(), Qt::Horizontal, this); hlayout->addWidget(slider1); fast1 = new QLabel(i18n("Fast"), this); hlayout->addWidget(fast1); connect(slider1, SIGNAL(valueChanged(int)), this, SLOT(value1Changed(int))); check1Changed(ellipse->changeEnabled()); // TODO add slider2 and friends and make it possible for ellipses to grow and contract m_vlayout->addStretch(); } void EllipseConfig::value1Changed(int news) { ellipse->setChangeEvery(100 - news); changed(); } void EllipseConfig::value2Changed(int /*news*/) { changed(); } void EllipseConfig::check1Changed(bool on) { ellipse->setChangeEnabled(on); if (slider1) slider1->setEnabled(on); if (slow1) slow1->setEnabled(on); if (fast1) fast1->setEnabled(on); changed(); } void EllipseConfig::check2Changed(bool on) { //ellipse->setChangeEnabled(on); if (slider2) slider2->setEnabled(on); if (slow2) slow2->setEnabled(on); if (fast2) fast2->setEnabled(on); changed(); } ///////////////////////// Ellipse::Ellipse(QCanvas *canvas) : QCanvasEllipse(canvas) { savingDone(); setChangeEnabled(false); setChangeEvery(50); count = 0; setVisible(true); point = new RectPoint(black, this, canvas); point->setSizeFactor(2.0); } void Ellipse::aboutToDie() { delete point; } void Ellipse::setChangeEnabled(bool changeEnabled) { m_changeEnabled = changeEnabled; setAnimated(m_changeEnabled); if (!m_changeEnabled) setVisible(true); } QPtrList Ellipse::moveableItems() const { QPtrList ret; ret.append(point); return ret; } void Ellipse::newSize(int width, int height) { QCanvasEllipse::setSize(width, height); } void Ellipse::moveBy(double dx, double dy) { QCanvasEllipse::moveBy(dx, dy); point->dontMove(); point->move(x() + width() / 2, y() + height() / 2); } void Ellipse::editModeChanged(bool changed) { point->setVisible(changed); moveBy(0, 0); } void Ellipse::advance(int phase) { QCanvasEllipse::advance(phase); if (phase == 1 && m_changeEnabled && !dontHide) { if (count > (m_changeEvery + 10) * 1.8) count = 0; if (count == 0) setVisible(!isVisible()); count++; } } void Ellipse::load(KConfig *cfg) { setChangeEnabled(cfg->readBoolEntry("changeEnabled", changeEnabled())); setChangeEvery(cfg->readNumEntry("changeEvery", changeEvery())); double newWidth = width(), newHeight = height(); newWidth = cfg->readNumEntry("width", newWidth); newHeight = cfg->readNumEntry("height", newHeight); newSize(newWidth, newHeight); } void Ellipse::save(KConfig *cfg) { cfg->writeEntry("changeEvery", changeEvery()); cfg->writeEntry("changeEnabled", changeEnabled()); cfg->writeEntry("width", width()); cfg->writeEntry("height", height()); } Config *Ellipse::config(QWidget *parent) { return new EllipseConfig(this, parent); } void Ellipse::aboutToSave() { setVisible(true); dontHide = true; } void Ellipse::savingDone() { dontHide = false; } ///////////////////////// Puddle::Puddle(QCanvas *canvas) : Ellipse(canvas) { setSize(45, 30); QBrush brush; QPixmap pic; if (!QPixmapCache::find("puddle", pic)) { pic.load(locate("appdata", "pics/puddle.png")); QPixmapCache::insert("puddle", pic); } brush.setPixmap(pic); setBrush(brush); KPixmap pointPic(pic); KPixmapEffect::intensity(pointPic, .45); brush.setPixmap(pointPic); point->setBrush(brush); setZ(-25); } bool Puddle::collision(Ball *ball, long int /*id*/) { if (ball->isVisible()) { QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas()); i.setVisible(true); // is center of ball in? if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/) { playSound("puddle"); ball->setAddStroke(ball->addStroke() + 1); ball->setPlaceOnGround(true); ball->setVisible(false); ball->setState(Stopped); ball->setVelocity(0, 0); if (game && game->curBall() == ball) game->stoppedBall(); } else return true; } return false; } ///////////////////////// Sand::Sand(QCanvas *canvas) : Ellipse(canvas) { setSize(45, 40); QBrush brush; QPixmap pic; if (!QPixmapCache::find("sand", pic)) { pic.load(locate("appdata", "pics/sand.png")); QPixmapCache::insert("sand", pic); } brush.setPixmap(pic); setBrush(brush); KPixmap pointPic(pic); KPixmapEffect::intensity(pointPic, .45); brush.setPixmap(pointPic); point->setBrush(brush); setZ(-26); } bool Sand::collision(Ball *ball, long int /*id*/) { QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas()); i.setVisible(true); // is center of ball in? if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/) { if (ball->curVector().magnitude() > 0) ball->setFrictionMultiplier(7); else { ball->setVelocity(0, 0); ball->setState(Stopped); } } return true; } ///////////////////////// Putter::Putter(QCanvas *canvas) : QCanvasLine(canvas) { m_showGuideLine = true; oneDegree = M_PI / 180; len = 9; angle = 0; guideLine = new QCanvasLine(canvas); guideLine->setPen(QPen(white, 1, QPen::DotLine)); guideLine->setZ(998.8); setPen(QPen(black, 4)); putterWidth = 11; maxAngle = 2 * M_PI; hideInfo(); // this also sets Z resetAngles(); } void Putter::showInfo() { guideLine->setVisible(isVisible()); } void Putter::hideInfo() { guideLine->setVisible(m_showGuideLine? isVisible() : false); } void Putter::moveBy(double dx, double dy) { QCanvasLine::moveBy(dx, dy); guideLine->move(x(), y()); } void Putter::setShowGuideLine(bool yes) { m_showGuideLine = yes; setVisible(isVisible()); } void Putter::setVisible(bool yes) { QCanvasLine::setVisible(yes); guideLine->setVisible(m_showGuideLine? yes : false); } void Putter::setOrigin(int _x, int _y) { setVisible(true); move(_x, _y); len = 9; finishMe(); } void Putter::setAngle(Ball *ball) { angle = angleMap.contains(ball)? angleMap[ball] : 0; finishMe(); } void Putter::go(Direction d, Amount amount) { double addition = (amount == Amount_More? 6 * oneDegree : amount == Amount_Less? .5 * oneDegree : 2 * oneDegree); switch (d) { case Forwards: len -= 1; guideLine->setVisible(false); break; case Backwards: len += 1; guideLine->setVisible(false); break; case D_Left: angle += addition; if (angle > maxAngle) angle -= maxAngle; break; case D_Right: angle -= addition; if (angle < 0) angle = maxAngle - fabs(angle); break; } finishMe(); } void Putter::finishMe() { midPoint.setX(cos(angle) * len); midPoint.setY(-sin(angle) * len); QPoint start; QPoint end; if (midPoint.y() || !midPoint.x()) { start.setX(midPoint.x() - putterWidth * sin(angle)); start.setY(midPoint.y() - putterWidth * cos(angle)); end.setX(midPoint.x() + putterWidth * sin(angle)); end.setY(midPoint.y() + putterWidth * cos(angle)); } else { start.setX(midPoint.x()); start.setY(midPoint.y() + putterWidth); end.setY(midPoint.y() - putterWidth); end.setX(midPoint.x()); } guideLine->setPoints(midPoint.x(), midPoint.y(), -cos(angle) * len * 4, sin(angle) * len * 4); setPoints(start.x(), start.y(), end.x(), end.y()); } ///////////////////////// Bumper::Bumper(QCanvas *canvas) : QCanvasEllipse(20, 20, canvas) { setZ(-25); firstColor = QColor("#E74804"); secondColor = firstColor.light(); count = 0; setBrush(firstColor); setAnimated(false); inside = new Inside(this, canvas); inside->setBrush(firstColor.light(109)); inside->setSize(width() / 2.6, height() / 2.6); inside->show(); } void Bumper::aboutToDie() { delete inside; } void Bumper::moveBy(double dx, double dy) { QCanvasEllipse::moveBy(dx, dy); //const double insideLen = (double)(width() - inside->width()) / 2.0; inside->move(x(), y()); } void Bumper::editModeChanged(bool changed) { inside->setVisible(!changed); } void Bumper::advance(int phase) { QCanvasEllipse::advance(phase); if (phase == 1) { count++; if (count > 2) { count = 0; setBrush(firstColor); update(); setAnimated(false); } } } bool Bumper::collision(Ball *ball, long int /*id*/) { setBrush(secondColor); double speed = 1.8 + ball->curVector().magnitude() * .9; if (speed > 8) speed = 8; const QPoint start(x(), y()); const QPoint end(ball->x(), ball->y()); Vector betweenVector(start, end); betweenVector.setMagnitude(speed); // add some randomness so we don't go indefinetely betweenVector.setDirection(betweenVector.direction() + deg2rad((kapp->random() % 3) - 1)); ball->setVector(betweenVector); // for some reason, x is always switched... ball->setXVelocity(-ball->xVelocity()); ball->setState(Rolling); setAnimated(true); return true; } ///////////////////////// Hole::Hole(QColor color, QCanvas *canvas) : QCanvasEllipse(15, 15, canvas) { setZ(998.1); setPen(black); setBrush(color); } bool Hole::collision(Ball *ball, long int /*id*/) { bool wasCenter = false; switch (result(QPoint(ball->x(), ball->y()), ball->curVector().magnitude(), &wasCenter)) { case Result_Holed: place(ball, wasCenter); return false; default: break; } return true; } HoleResult Hole::result(QPoint p, double s, bool * /*wasCenter*/) { const double longestRadius = width() > height()? width() : height(); if (s > longestRadius / 5.0) return Result_Miss; QCanvasRectangle i(QRect(p, QSize(1, 1)), canvas()); i.setVisible(true); // is center of ball in cup? if (i.collidesWith(this)) { return Result_Holed; } else return Result_Miss; } ///////////////////////// Cup::Cup(QCanvas *canvas) : Hole(QColor("#808080"), canvas) { if (!QPixmapCache::find("cup", pixmap)) { pixmap.load(locate("appdata", "pics/cup.png")); QPixmapCache::insert("cup", pixmap); } } void Cup::draw(QPainter &p) { p.drawPixmap(QPoint(x() - width() / 2, y() - height() / 2), pixmap); } bool Cup::place(Ball *ball, bool /*wasCenter*/) { ball->setState(Holed); playSound("holed"); // the picture's center is a little different ball->move(x() - 1, y()); ball->setVelocity(0, 0); if (game && game->curBall() == ball) game->stoppedBall(); return true; } void Cup::save(KConfig *cfg) { cfg->writeEntry("dummykey", true); } ///////////////////////// BlackHole::BlackHole(QCanvas *canvas) : Hole(black, canvas), exitDeg(0) { infoLine = 0; m_minSpeed = 3.0; m_maxSpeed = 5.0; runs = 0; const QColor myColor((QRgb)(kapp->random() % 0x01000000)); outside = new QCanvasEllipse(canvas); outside->setZ(z() - .001); outside->setBrush(QBrush(myColor)); setBrush(black); exitItem = new BlackHoleExit(this, canvas); exitItem->setPen(QPen(myColor, 6)); exitItem->setX(300); exitItem->setY(100); setSize(width(), width() / .8); const float factor = 1.3; outside->setSize(width() * factor, height() * factor); outside->setVisible(true); moveBy(0, 0); finishMe(); } void BlackHole::showInfo() { delete infoLine; infoLine = new QCanvasLine(canvas()); infoLine->setVisible(true); infoLine->setPen(QPen(exitItem->pen().color(), 2)); infoLine->setZ(10000); infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y()); exitItem->showInfo(); } void BlackHole::hideInfo() { delete infoLine; infoLine = 0; exitItem->hideInfo(); } void BlackHole::aboutToDie() { Hole::aboutToDie(); delete outside; exitItem->aboutToDie(); delete exitItem; } void BlackHole::updateInfo() { if (infoLine) { infoLine->setVisible(true); infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y()); exitItem->showInfo(); } } void BlackHole::moveBy(double dx, double dy) { QCanvasEllipse::moveBy(dx, dy); outside->move(x(), y()); updateInfo(); } void BlackHole::setExitDeg(int newdeg) { exitDeg = newdeg; if (game && game->isEditing() && game->curSelectedItem() == exitItem) game->updateHighlighter(); exitItem->updateArrowAngle(); finishMe(); } QPtrList BlackHole::moveableItems() const { QPtrList ret; ret.append(exitItem); return ret; } BlackHoleTimer::BlackHoleTimer(Ball *ball, double speed, int msec) : m_speed(speed), m_ball(ball) { QTimer::singleShot(msec, this, SLOT(mySlot())); QTimer::singleShot(msec / 2, this, SLOT(myMidSlot())); } void BlackHoleTimer::mySlot() { emit eject(m_ball, m_speed); delete this; } void BlackHoleTimer::myMidSlot() { emit halfway(); } bool BlackHole::place(Ball *ball, bool /*wasCenter*/) { // most number is 10 if (runs > 10 && game && game->isInPlay()) return false; playSound("blackholeputin"); const double diff = (m_maxSpeed - m_minSpeed); const double speed = m_minSpeed + ball->curVector().magnitude() * (diff / 3.75); ball->setVelocity(0, 0); ball->setState(Stopped); ball->setVisible(false); ball->setForceStillGoing(true); double magnitude = Vector(QPoint(x(), y()), QPoint(exitItem->x(), exitItem->y())).magnitude(); BlackHoleTimer *timer = new BlackHoleTimer(ball, speed, magnitude * 2.5 - speed * 35 + 500); connect(timer, SIGNAL(eject(Ball *, double)), this, SLOT(eject(Ball *, double))); connect(timer, SIGNAL(halfway()), this, SLOT(halfway())); playSound("blackhole"); return false; } void BlackHole::eject(Ball *ball, double speed) { ball->move(exitItem->x(), exitItem->y()); Vector v; v.setMagnitude(10); v.setDirection(deg2rad(exitDeg)); ball->setVector(v); // advance ball 10 ball->doAdvance(); v.setMagnitude(speed); ball->setVector(v); ball->setForceStillGoing(false); ball->setVisible(true); ball->setState(Rolling); runs++; playSound("blackholeeject"); } void BlackHole::halfway() { playSound("blackhole"); } void BlackHole::load(KConfig *cfg) { QPoint exit = cfg->readPointEntry("exit", &exit); exitItem->setX(exit.x()); exitItem->setY(exit.y()); exitDeg = cfg->readNumEntry("exitDeg", exitDeg); m_minSpeed = cfg->readDoubleNumEntry("minspeed", m_minSpeed); m_maxSpeed = cfg->readDoubleNumEntry("maxspeed", m_maxSpeed); exitItem->updateArrowAngle(); exitItem->updateArrowLength(); finishMe(); } void BlackHole::finishMe() { double radians = deg2rad(exitDeg); QPoint midPoint(0, 0); QPoint start; QPoint end; const int width = 15; if (midPoint.y() || !midPoint.x()) { start.setX(midPoint.x() - width*sin(radians)); start.setY(midPoint.y() - width*cos(radians)); end.setX(midPoint.x() + width*sin(radians)); end.setY(midPoint.y() + width*cos(radians)); } else { start.setX(midPoint.x()); start.setY(midPoint.y() + width); end.setY(midPoint.y() - width); end.setX(midPoint.x()); } exitItem->setPoints(start.x(), start.y(), end.x(), end.y()); exitItem->setVisible(true); } void BlackHole::save(KConfig *cfg) { cfg->writeEntry("exit", QPoint(exitItem->x(), exitItem->y())); cfg->writeEntry("exitDeg", exitDeg); cfg->writeEntry("minspeed", m_minSpeed); cfg->writeEntry("maxspeed", m_maxSpeed); } ///////////////////////// BlackHoleExit::BlackHoleExit(BlackHole *blackHole, QCanvas *canvas) : QCanvasLine(canvas) { this->blackHole = blackHole; arrow = new Arrow(canvas); setZ(blackHole->z()); arrow->setZ(z() - .00001); updateArrowLength(); arrow->setVisible(false); } void BlackHoleExit::aboutToDie() { arrow->aboutToDie(); delete arrow; } void BlackHoleExit::moveBy(double dx, double dy) { QCanvasLine::moveBy(dx, dy); arrow->move(x(), y()); blackHole->updateInfo(); } void BlackHoleExit::setPen(QPen p) { QCanvasLine::setPen(p); arrow->setPen(QPen(p.color(), 1)); } void BlackHoleExit::updateArrowAngle() { // arrows work in a different angle system arrow->setAngle(-deg2rad(blackHole->curExitDeg())); arrow->updateSelf(); } void BlackHoleExit::updateArrowLength() { arrow->setLength(10.0 + 5.0 * (double)(blackHole->minSpeed() + blackHole->maxSpeed()) / 2.0); arrow->updateSelf(); } void BlackHoleExit::editModeChanged(bool editing) { if (editing) showInfo(); else hideInfo(); } void BlackHoleExit::showInfo() { arrow->setVisible(true); } void BlackHoleExit::hideInfo() { arrow->setVisible(false); } Config *BlackHoleExit::config(QWidget *parent) { return blackHole->config(parent); } ///////////////////////// BlackHoleConfig::BlackHoleConfig(BlackHole *blackHole, QWidget *parent) : Config(parent) { this->blackHole = blackHole; QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint()); layout->addWidget(new QLabel(i18n("Exiting ball angle:"), this)); QSpinBox *deg = new QSpinBox(0, 359, 10, this); deg->setSuffix(QString(" ") + i18n("degrees")); deg->setValue(blackHole->curExitDeg()); deg->setWrapping(true); layout->addWidget(deg); connect(deg, SIGNAL(valueChanged(int)), this, SLOT(degChanged(int))); layout->addStretch(); QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Minimum exit speed:"), this)); KDoubleNumInput *min = new KDoubleNumInput(this); min->setRange(0, 8, 1, true); hlayout->addWidget(min); connect(min, SIGNAL(valueChanged(double)), this, SLOT(minChanged(double))); min->setValue(blackHole->minSpeed()); hlayout = new QHBoxLayout(layout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Maximum:"), this)); KDoubleNumInput *max = new KDoubleNumInput(this); max->setRange(1, 10, 1, true); hlayout->addWidget(max); connect(max, SIGNAL(valueChanged(double)), this, SLOT(maxChanged(double))); max->setValue(blackHole->maxSpeed()); } void BlackHoleConfig::degChanged(int newdeg) { blackHole->setExitDeg(newdeg); changed(); } void BlackHoleConfig::minChanged(double news) { blackHole->setMinSpeed(news); changed(); } void BlackHoleConfig::maxChanged(double news) { blackHole->setMaxSpeed(news); changed(); } ///////////////////////// WallPoint::WallPoint(bool start, Wall *wall, QCanvas *canvas) : QCanvasEllipse(canvas) { this->wall = wall; this->start = start; alwaysShow = false; editing = false; visible = true; lastId = INT_MAX - 10; dontmove = false; move(0, 0); QPoint p; if (start) p = wall->startPoint(); else p = wall->endPoint(); setX(p.x()); setY(p.y()); } void WallPoint::clean() { int oldWidth = width(); setSize(7, 7); update(); QCanvasItem *onPoint = 0; QCanvasItemList l = collisions(true); for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) if ((*it)->rtti() == rtti()) onPoint = (*it); if (onPoint) move(onPoint->x(), onPoint->y()); setSize(oldWidth, oldWidth); } void WallPoint::moveBy(double dx, double dy) { QCanvasEllipse::moveBy(dx, dy); if (!editing) updateVisible(); if (dontmove) { dontmove = false; return; } if (!wall) return; if (start) { wall->setPoints(x(), y(), wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y()); } else { wall->setPoints(wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y(), x(), y()); } wall->move(0, 0); } void WallPoint::updateVisible() { if (!wall->isVisible()) { visible = false; return; } if (alwaysShow) visible = true; else { visible = true; QCanvasItemList l = collisions(true); for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) if ((*it)->rtti() == rtti()) visible = false; } } void WallPoint::editModeChanged(bool changed) { editing = changed; setVisible(true); if (!editing) updateVisible(); } bool WallPoint::collision(Ball *ball, long int id) { if (ball->curVector().magnitude() <= 0) return false; long int tempLastId = lastId; lastId = id; QCanvasItemList l = collisions(true); for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) { if ((*it)->rtti() == rtti()) { WallPoint *point = (WallPoint *)(*it); point->lastId = id; } } //kdDebug(12007) << "WallPoint::collision id: " << id << ", tempLastId: " << tempLastId << endl; Vector ballVector(ball->curVector()); //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl; int allowableDifference = 1; if (ballVector.magnitude() < .30) allowableDifference = 8; else if (ballVector.magnitude() < .50) allowableDifference = 6; else if (ballVector.magnitude() < .65) allowableDifference = 4; else if (ballVector.magnitude() < .95) allowableDifference = 2; if (abs(id - tempLastId) <= allowableDifference) { //kdDebug(12007) << "WallPoint::collision - SKIP\n"; } else { bool weirdBounce = visible; QPoint relStart(start? wall->startPoint() : wall->endPoint()); QPoint relEnd(start? wall->endPoint() : wall->startPoint()); Vector wallVector(relStart, relEnd); wallVector.setDirection(-wallVector.direction()); // find the angle between vectors, between 0 and PI { double difference = fabs(wallVector.direction() - ballVector.direction()); while (difference > 2 * M_PI) difference -= 2 * M_PI; if (difference < M_PI / 2 || difference > 3 * M_PI / 2) weirdBounce = false; } playSound("wall", ball->curVector().magnitude() / 10.0); ballVector /= wall->dampening; const double ballAngle = ballVector.direction(); double wallAngle = wallVector.direction(); // opposite bounce, because we're the endpoint if (weirdBounce) wallAngle += M_PI / 2; const double collisionAngle = ballAngle - wallAngle; const double leavingAngle = wallAngle - collisionAngle; ballVector.setDirection(leavingAngle); ball->setVector(ballVector); wall->lastId = id; //kdDebug(12007) << "WallPoint::collision - NOT skip, weirdBounce is " << weirdBounce << endl; } // end if that skips wall->lastId = id; return false; } ///////////////////////// Wall::Wall(QCanvas *canvas) : QCanvasLine(canvas) { editing = false; lastId = INT_MAX - 10; dampening = 1.2; startItem = 0; endItem = 0; moveBy(0, 0); setZ(50); startItem = new WallPoint(true, this, canvas); endItem = new WallPoint(false, this, canvas); startItem->setVisible(true); endItem->setVisible(true); setPen(QPen(darkRed, 3)); setPoints(-15, 10, 15, -5); moveBy(0, 0); editModeChanged(false); } void Wall::selectedItem(QCanvasItem *item) { if (item->rtti() == Rtti_WallPoint) { WallPoint *wallPoint = dynamic_cast(item); if (wallPoint) { setPoints(startPoint().x(), startPoint().y(), wallPoint->x() - x(), wallPoint->y() - y()); } } } void Wall::clean() { startItem->clean(); endItem->clean(); } void Wall::setAlwaysShow(bool yes) { startItem->setAlwaysShow(yes); endItem->setAlwaysShow(yes); } void Wall::setVisible(bool yes) { QCanvasLine::setVisible(yes); startItem->setVisible(yes); endItem->setVisible(yes); startItem->updateVisible(); endItem->updateVisible(); } void Wall::setZ(double newz) { QCanvasLine::setZ(newz); if (startItem) startItem->setZ(newz + .002); if (endItem) endItem->setZ(newz + .001); } void Wall::setPen(QPen p) { QCanvasLine::setPen(p); if (startItem) startItem->setBrush(QBrush(p.color())); if (endItem) endItem->setBrush(QBrush(p.color())); } void Wall::aboutToDie() { delete startItem; delete endItem; } void Wall::setGame(KolfGame *game) { CanvasItem::setGame(game); startItem->setGame(game); endItem->setGame(game); } QPtrList Wall::moveableItems() const { QPtrList ret; ret.append(startItem); ret.append(endItem); return ret; } void Wall::moveBy(double dx, double dy) { QCanvasLine::moveBy(dx, dy); if (!startItem || !endItem) return; startItem->dontMove(); endItem->dontMove(); startItem->move(startPoint().x() + x(), startPoint().y() + y()); endItem->move(endPoint().x() + x(), endPoint().y() + y()); } void Wall::setVelocity(double vx, double vy) { QCanvasLine::setVelocity(vx, vy); /* startItem->setVelocity(vx, vy); endItem->setVelocity(vx, vy); */ } QPointArray Wall::areaPoints() const { // editing we want full width for easy moving if (editing) return QCanvasLine::areaPoints(); // lessen width, for QCanvasLine::areaPoints() likes // to make lines _very_ fat :( // from qcanvas.cpp, only the stuff for a line width of 1 taken // it's all squished because I don't want my // line counts to count code I didn't write! QPointArray p(4); const int xi = int(x()); const int yi = int(y()); const QPoint start = startPoint(); const QPoint end = endPoint(); const int x1 = start.x(); const int x2 = end.x(); const int y1 = start.y(); const int y2 = end.y(); const int dx = QABS(x1-x2); const int dy = QABS(y1-y2); if ( dx > dy ) { p[0] = QPoint(x1+xi,y1+yi-1); p[1] = QPoint(x2+xi,y2+yi-1); p[2] = QPoint(x2+xi,y2+yi+1); p[3] = QPoint(x1+xi,y1+yi+1); } else { p[0] = QPoint(x1+xi-1,y1+yi); p[1] = QPoint(x2+xi-1,y2+yi); p[2] = QPoint(x2+xi+1,y2+yi); p[3] = QPoint(x1+xi+1,y1+yi); } return p; } void Wall::editModeChanged(bool changed) { // make big for debugging? const bool debugPoints = false; editing = changed; startItem->setZ(z() + .002); endItem->setZ(z() + .001); startItem->editModeChanged(editing); endItem->editModeChanged(editing); int neww = 0; if (changed || debugPoints) neww = 10; else neww = pen().width(); startItem->setSize(neww, neww); endItem->setSize(neww, neww); moveBy(0, 0); } bool Wall::collision(Ball *ball, long int id) { if (ball->curVector().magnitude() <= 0) return false; long int tempLastId = lastId; lastId = id; startItem->lastId = id; endItem->lastId = id; //kdDebug(12007) << "Wall::collision id: " << id << ", tempLastId: " << tempLastId << endl; Vector ballVector(ball->curVector()); //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl; int allowableDifference = 1; if (ballVector.magnitude() < .30) allowableDifference = 8; else if (ballVector.magnitude() < .50) allowableDifference = 6; else if (ballVector.magnitude() < .75) allowableDifference = 4; else if (ballVector.magnitude() < .95) allowableDifference = 2; //kdDebug(12007) << "Wall::collision allowableDifference is " << allowableDifference << endl; if (abs(id - tempLastId) <= allowableDifference) { //kdDebug(12007) << "Wall::collision - SKIP\n"; return false; } playSound("wall", ball->curVector().magnitude() / 10.0); ballVector /= dampening; const double ballAngle = ballVector.direction(); const double wallAngle = -Vector(startPoint(), endPoint()).direction(); const double collisionAngle = ballAngle - wallAngle; const double leavingAngle = wallAngle - collisionAngle; ballVector.setDirection(leavingAngle); ball->setVector(ballVector); //kdDebug(12007) << "Wall::collision - NOT skip\n"; return false; } void Wall::load(KConfig *cfg) { QPoint start(startPoint()); start = cfg->readPointEntry("startPoint", &start); QPoint end(endPoint()); end = cfg->readPointEntry("endPoint", &end); setPoints(start.x(), start.y(), end.x(), end.y()); moveBy(0, 0); startItem->move(start.x(), start.y()); endItem->move(end.x(), end.y()); } void Wall::save(KConfig *cfg) { cfg->writeEntry("startPoint", QPoint(startItem->x(), startItem->y())); cfg->writeEntry("endPoint", QPoint(endItem->x(), endItem->y())); } ///////////////////////// HoleConfig::HoleConfig(HoleInfo *holeInfo, QWidget *parent) : Config(parent) { this->holeInfo = holeInfo; QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint()); QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Course name: "), this)); KLineEdit *nameEdit = new KLineEdit(holeInfo->untranslatedName(), this); hlayout->addWidget(nameEdit); connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(nameChanged(const QString &))); hlayout = new QHBoxLayout(layout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Course author: "), this)); KLineEdit *authorEdit = new KLineEdit(holeInfo->author(), this); hlayout->addWidget(authorEdit); connect(authorEdit, SIGNAL(textChanged(const QString &)), this, SLOT(authorChanged(const QString &))); layout->addStretch(); hlayout = new QHBoxLayout(layout, spacingHint()); hlayout->addWidget(new QLabel(i18n("Par:"), this)); QSpinBox *par = new QSpinBox(1, 15, 1, this); par->setValue(holeInfo->par()); hlayout->addWidget(par); connect(par, SIGNAL(valueChanged(int)), this, SLOT(parChanged(int))); hlayout->addStretch(); hlayout->addWidget(new QLabel(i18n("Maximum:"), this)); QSpinBox *maxstrokes = new QSpinBox(holeInfo->lowestMaxStrokes(), 30, 1, this); QWhatsThis::add(maxstrokes, i18n("Maximum number of strokes player can take on this hole.")); QToolTip::add(maxstrokes, i18n("Maximum number of strokes")); maxstrokes->setSpecialValueText(i18n("Unlimited")); maxstrokes->setValue(holeInfo->maxStrokes()); hlayout->addWidget(maxstrokes); connect(maxstrokes, SIGNAL(valueChanged(int)), this, SLOT(maxStrokesChanged(int))); QCheckBox *check = new QCheckBox(i18n("Show border walls"), this); check->setChecked(holeInfo->borderWalls()); layout->addWidget(check); connect(check, SIGNAL(toggled(bool)), this, SLOT(borderWallsChanged(bool))); } void HoleConfig::authorChanged(const QString &newauthor) { holeInfo->setAuthor(newauthor); changed(); } void HoleConfig::nameChanged(const QString &newname) { holeInfo->setName(newname); holeInfo->setUntranslatedName(newname); changed(); } void HoleConfig::parChanged(int newpar) { holeInfo->setPar(newpar); changed(); } void HoleConfig::maxStrokesChanged(int newms) { holeInfo->setMaxStrokes(newms); changed(); } void HoleConfig::borderWallsChanged(bool yes) { holeInfo->borderWallsChanged(yes); changed(); } ///////////////////////// StrokeCircle::StrokeCircle(QCanvas *canvas) : QCanvasItem(canvas) { dvalue = 0; dmax = 360; iwidth = 100; iheight = 100; ithickness = 8; setZ(10000); } void StrokeCircle::setValue(double v) { dvalue = v; if (dvalue > dmax) dvalue = dmax; update(); } double StrokeCircle::value() { return dvalue; } bool StrokeCircle::collidesWith(const QCanvasItem*) const { return false; } bool StrokeCircle::collidesWith(const QCanvasSprite*, const QCanvasPolygonalItem*, const QCanvasRectangle*, const QCanvasEllipse*, const QCanvasText*) const { return false; } QRect StrokeCircle::boundingRect() const { return QRect(x(), y(), iwidth, iheight); } void StrokeCircle::setMaxValue(double m) { dmax = m; if (dvalue > dmax) dvalue = dmax; update(); } void StrokeCircle::setSize(int w, int h) { if (w > 0) iwidth = w; if (h > 0) iheight = h; update(); } void StrokeCircle::setThickness(int t) { if (t > 0) ithickness = t; update(); } int StrokeCircle::thickness() const { return ithickness; } int StrokeCircle::width() const { return iwidth; } int StrokeCircle::height() const { return iheight; } void StrokeCircle::draw(QPainter &p) { int al = (int)((dvalue * 360 * 16) / dmax); int length, deg; if (al < 0) { deg = 270 * 16; length = -al; } else if (al <= (270 * 16)) { deg = 270 * 16 - al; length = al; } else { deg = (360 * 16) - (al - (270 * 16)); length = al; } p.setBrush(QBrush(black, Qt::NoBrush)); p.setPen(QPen(white, ithickness / 2)); p.drawEllipse(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness); p.setPen(QPen(QColor((int)(0xff * dvalue) / dmax, 0, 0xff - (int)(0xff * dvalue) / dmax), ithickness)); p.drawArc(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness, deg, length); p.setPen(QPen(white, 1)); p.drawEllipse(x(), y(), iwidth, iheight); p.drawEllipse(x() + ithickness, y() + ithickness, iwidth - ithickness * 2, iheight - ithickness * 2); p.setPen(QPen(white, 3)); p.drawLine(x() + iwidth / 2, y() + iheight - ithickness * 1.5, x() + iwidth / 2, y() + iheight); p.drawLine(x() + iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 - iheight / 20); p.drawLine(x() + iwidth - iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth - iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 - iheight / 20); } ///////////////////////////////////////// KolfGame::KolfGame(ObjectList *obj, PlayerList *players, QString filename, QWidget *parent, const char *name ) : QCanvasView(parent, name) { // for mouse control setMouseTracking(true); viewport()->setMouseTracking(true); setFrameShape(NoFrame); regAdv = false; curHole = 0; // will get ++'d cfg = 0; setFilename(filename); this->players = players; this->obj = obj; curPlayer = players->end(); curPlayer--; // will get ++'d to end and sent back // to beginning paused = false; modified = false; inPlay = false; putting = false; stroking = false; editing = false; strict = false; lastDelId = -1; m_showInfo = false; ballStateList.canUndo = false; fastAdvancedExist = false; soundDir = locate("appdata", "sounds/"); dontAddStroke = false; addingNewHole = false; scoreboardHoles = 0; infoShown = false; m_useMouse = true; m_useAdvancedPutting = false; m_useAdvancedPutting = true; m_sound = true; m_ignoreEvents = false; soundedOnce = false; oldPlayObjects.setAutoDelete(true); highestHole = 0; recalcHighestHole = false; holeInfo.setGame(this); holeInfo.setAuthor(i18n("Course Author")); holeInfo.setName(i18n("Course Name")); holeInfo.setUntranslatedName(i18n("Course Name")); holeInfo.setMaxStrokes(10); holeInfo.borderWallsChanged(true); // width and height are the width and height of the canvas // in easy storage width = 400; height = 400; grass = QColor("#35760D"); margin = 10; setFocusPolicy(QWidget::StrongFocus); setFixedSize(width + 2 * margin, height + 2 * margin); setMargins(margin, margin, margin, margin); course = new QCanvas(this); course->setBackgroundColor(white); course->resize(width, height); QPixmap pic; if (!QPixmapCache::find("grass", pic)) { pic.load(locate("appdata", "pics/grass.png")); QPixmapCache::insert("grass", pic); } course->setBackgroundPixmap(pic); setCanvas(course); move(0, 0); adjustSize(); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->setCanvas(course); // highlighter shows current item highlighter = new QCanvasRectangle(course); highlighter->setPen(QPen(yellow, 1)); highlighter->setBrush(QBrush(NoBrush)); highlighter->setVisible(false); highlighter->setZ(10000); // shows some info about hole infoText = new QCanvasText(course); infoText->setText(""); infoText->setColor(white); QFont font = kapp->font(); font.setPixelSize(12); infoText->move(15, width/2); infoText->setZ(10001); infoText->setFont(font); infoText->setVisible(false); // create the advanced putting indicator strokeCircle = new StrokeCircle(course); strokeCircle->move(width - 90, height - 90); strokeCircle->setSize(80, 80); strokeCircle->setThickness(8); strokeCircle->setVisible(false); strokeCircle->setValue(0); strokeCircle->setMaxValue(360); // whiteBall marks the spot of the whole whilst editing whiteBall = new Ball(course); whiteBall->setGame(this); whiteBall->setColor(white); whiteBall->setVisible(false); whiteBall->setDoDetect(false); int highestLog = 0; // if players have scores from loaded game, move to last hole for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { if ((int)(*it).scores().count() > highestLog) highestLog = (*it).scores().count(); (*it).ball()->setGame(this); (*it).ball()->setAnimated(true); } // here only for saved games if (highestLog) curHole = highestLog; putter = new Putter(course); // border walls: // horiz addBorderWall(QPoint(margin, margin), QPoint(width - margin, margin)); addBorderWall(QPoint(margin, height - margin - 1), QPoint(width - margin, height - margin - 1)); // vert addBorderWall(QPoint(margin, margin), QPoint(margin, height - margin)); addBorderWall(QPoint(width - margin - 1, margin), QPoint(width - margin - 1, height - margin)); timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); timerMsec = 300; fastTimer = new QTimer(this); connect(fastTimer, SIGNAL(timeout()), this, SLOT(fastTimeout())); fastTimerMsec = 11; autoSaveTimer = new QTimer(this); connect(autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveTimeout())); autoSaveMsec = 5 * 1000 * 60; // 5 min autosave // setUseAdvancedPutting() sets maxStrength! setUseAdvancedPutting(false); putting = false; putterTimer = new QTimer(this); connect(putterTimer, SIGNAL(timeout()), this, SLOT(putterTimeout())); putterTimerMsec = 20; } void KolfGame::startFirstHole(int hole) { if (curHole > 0) // if there was saved game, sync scoreboard // with number of holes { for (; scoreboardHoles < curHole; ++scoreboardHoles) { cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)); emit newHole(cfg->readNumEntry("par", 3)); } // lets load all of the scores from saved game if there are any for (int hole = 1; hole <= curHole; ++hole) for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) emit scoreChanged((*it).id(), hole, (*it).score(hole)); } curHole = hole - 1; // this increments curHole, etc recalcHighestHole = true; startNextHole(); paused = true; unPause(); } void KolfGame::setFilename(const QString &filename) { this->filename = filename; delete cfg; cfg = new KConfig(filename, false, false); } KolfGame::~KolfGame() { oldPlayObjects.clear(); delete cfg; } void KolfGame::setModified(bool mod) { modified = mod; emit modifiedChanged(mod); } void KolfGame::pause() { if (paused) { // play along with people who call pause() again, instead of unPause() unPause(); return; } paused = true; timer->stop(); fastTimer->stop(); putterTimer->stop(); } void KolfGame::unPause() { if (!paused) return; paused = false; timer->start(timerMsec); fastTimer->start(fastTimerMsec); if (putting || stroking) putterTimer->start(putterTimerMsec); } void KolfGame::addBorderWall(QPoint start, QPoint end) { Wall *wall = new Wall(course); wall->setPoints(start.x(), start.y(), end.x(), end.y()); wall->setVisible(true); wall->setGame(this); wall->setZ(998.7); borderWalls.append(wall); } void KolfGame::updateHighlighter() { if (!selectedItem) return; QRect rect = selectedItem->boundingRect(); highlighter->move(rect.x() + 1, rect.y() + 1); highlighter->setSize(rect.width(), rect.height()); } void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e) { // allow two fast single clicks handleMousePressEvent(e); } void KolfGame::handleMousePressEvent(QMouseEvent *e) { if (m_ignoreEvents) return; if (editing) { if (inPlay) return; storedMousePos = e->pos(); QCanvasItemList list = course->collisions(e->pos()); if (list.first() == highlighter) list.pop_front(); moving = false; highlighter->setVisible(false); selectedItem = 0; movingItem = 0; if (list.count() < 1) { emit newSelectedItem(&holeInfo); return; } // only items we keep track of if ((!(items.containsRef(list.first()) || list.first() == whiteBall || extraMoveable.containsRef(list.first())))) { emit newSelectedItem(&holeInfo); return; } CanvasItem *citem = dynamic_cast(list.first()); if (!citem || !citem->moveable()) { emit newSelectedItem(&holeInfo); return; } switch (e->button()) { // select AND move now :) case LeftButton: { selectedItem = list.first(); movingItem = selectedItem; moving = true; if (citem->cornerResize()) setCursor(KCursor::sizeFDiagCursor()); else setCursor(KCursor::sizeAllCursor()); emit newSelectedItem(citem); highlighter->setVisible(true); QRect rect = selectedItem->boundingRect(); highlighter->move(rect.x() + 1, rect.y() + 1); highlighter->setSize(rect.width(), rect.height()); } break; default: break; } } else { if (m_useMouse) { if (!inPlay && e->button() == LeftButton) puttPress(); else if (e->button() == RightButton) toggleShowInfo(); } } setFocus(); } QPoint KolfGame::viewportToViewport(const QPoint &p) { // for some reason viewportToContents doesn't work right return p - QPoint(margin, margin); } // the following four functions are needed to handle both // border presses and regular in-course presses void KolfGame::mouseReleaseEvent(QMouseEvent * e) { QMouseEvent fixedEvent (QEvent::MouseButtonRelease, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); handleMouseReleaseEvent(&fixedEvent); } void KolfGame::mousePressEvent(QMouseEvent * e) { QMouseEvent fixedEvent (QEvent::MouseButtonPress, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); handleMousePressEvent(&fixedEvent); } void KolfGame::mouseDoubleClickEvent(QMouseEvent * e) { QMouseEvent fixedEvent (QEvent::MouseButtonDblClick, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); handleMouseDoubleClickEvent(&fixedEvent); } void KolfGame::mouseMoveEvent(QMouseEvent * e) { QMouseEvent fixedEvent (QEvent::MouseMove, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state()); handleMouseMoveEvent(&fixedEvent); } void KolfGame::handleMouseMoveEvent(QMouseEvent *e) { if (inPlay || !putter || m_ignoreEvents) return; QPoint mouse = e->pos(); // mouse moving of putter if (!editing) { updateMouse(); return; } if (!moving) { // lets change the cursor to a hand // if we're hovering over something QCanvasItemList list = course->collisions(e->pos()); if (list.count() > 0) setCursor(KCursor::handCursor()); else setCursor(KCursor::arrowCursor()); return; } int moveX = storedMousePos.x() - mouse.x(); int moveY = storedMousePos.y() - mouse.y(); // moving counts as modifying if (moveX || moveY) setModified(true); highlighter->moveBy(-(double)moveX, -(double)moveY); movingItem->moveBy(-(double)moveX, -(double)moveY); QRect brect = movingItem->boundingRect(); emit newStatusText(QString("%1x%2").arg(brect.x()).arg(brect.y())); storedMousePos = mouse; } void KolfGame::updateMouse() { // don't move putter if in advanced putting sequence if (!m_useMouse || ((stroking || putting) && m_useAdvancedPutting)) return; const QPoint cursor = viewportToViewport(viewportToContents(mapFromGlobal(QCursor::pos()))); const QPoint ball((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); putter->setAngle(-Vector(cursor, ball).direction()); } void KolfGame::handleMouseReleaseEvent(QMouseEvent *e) { setCursor(KCursor::arrowCursor()); if (editing) { emit newStatusText(QString::null); moving = false; } if (m_ignoreEvents) return; if (!editing && m_useMouse) { if (!inPlay && e->button() == LeftButton) puttRelease(); else if (e->button() == RightButton) toggleShowInfo(); } setFocus(); } void KolfGame::keyPressEvent(QKeyEvent *e) { if (inPlay || editing || m_ignoreEvents) return; switch (e->key()) { case Key_Up: if (!e->isAutoRepeat()) toggleShowInfo(); break; case Key_Escape: putting = false; stroking = false; finishStroking = false; strokeCircle->setVisible(false); putterTimer->stop(); putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); break; case Key_Left: case Key_Right: // don't move putter if in advanced putting sequence if ((!stroking && !putting) || !m_useAdvancedPutting) putter->go(e->key() == Key_Left? D_Left : D_Right, e->state() & ShiftButton? Amount_More : e->state() & ControlButton? Amount_Less : Amount_Normal); break; case Key_Space: case Key_Down: puttPress(); break; default: break; } } void KolfGame::toggleShowInfo() { setShowInfo(!m_showInfo); } void KolfGame::updateShowInfo() { setShowInfo(m_showInfo); } void KolfGame::setShowInfo(bool yes) { m_showInfo = yes; if (m_showInfo) { QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) citem->showInfo(); } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->showInfo(); showInfo(); } else { QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) citem->hideInfo(); } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->hideInfo(); hideInfo(); } } void KolfGame::puttPress() { // Advanced putting: 1st click start putting sequence, 2nd determine strength, 3rd determine precision if (!putting && !stroking && !inPlay) { puttCount = 0; puttReverse = false; putting = true; stroking = false; strength = 0; if (m_useAdvancedPutting) { strokeCircle->setValue(0); int pw = putter->endPoint().x() - putter->startPoint().x(); if (pw < 0) pw = -pw; int px = (int)putter->x() + pw / 2; int py = (int)putter->y(); if (px > width / 2 && py < height / 2) strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py + 10); else if (px > width / 2) strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py - 10 - strokeCircle->height()); else if (py < height / 2) strokeCircle->move(px + pw / 2 + 10, py + 10); else strokeCircle->move(px + pw / 2 + 10, py - 10 - strokeCircle->height()); strokeCircle->setVisible(true); } putterTimer->start(putterTimerMsec); } else if (m_useAdvancedPutting && putting && !editing) { putting = false; stroking = true; puttReverse = false; finishStroking = false; } else if (m_useAdvancedPutting && stroking) { finishStroking = true; putterTimeout(); } } void KolfGame::keyReleaseEvent(QKeyEvent *e) { if (e->isAutoRepeat() || m_ignoreEvents) return; if (e->key() == Key_Space || e->key() == Key_Down) puttRelease(); else if ((e->key() == Key_Backspace || e->key() == Key_Delete) && !(e->state() & ControlButton)) { if (editing && !moving && selectedItem) { CanvasItem *citem = dynamic_cast(selectedItem); if (!citem) return; citem = citem->itemToDelete(); if (!citem) return; QCanvasItem *item = dynamic_cast(citem); if (citem && citem->deleteable()) { lastDelId = citem->curId(); highlighter->setVisible(false); items.removeRef(item); citem->hideInfo(); citem->aboutToDelete(); citem->aboutToDie(); delete citem; selectedItem = 0; emit newSelectedItem(&holeInfo); setModified(true); } } } else if (e->key() == Key_I || e->key() == Key_Up) toggleShowInfo(); } void KolfGame::puttRelease() { if (!m_useAdvancedPutting && putting && !editing) { putting = false; stroking = true; } } void KolfGame::stoppedBall() { if (!inPlay) { inPlay = true; dontAddStroke = true; } } void KolfGame::timeout() { Ball *curBall = (*curPlayer).ball(); // test if the ball is gone // in this case we want to stop the ball and // later undo the shot for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { if (!course->rect().contains(QPoint((*it).ball()->x(), (*it).ball()->y()))) { (*it).ball()->setState(Stopped); // don't do it if he's past maxStrokes if ((*it).score(curHole) < holeInfo.maxStrokes() - 1 || !holeInfo.hasMaxStrokes()) { loadStateList(); } shotDone(); return; } } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) if ((*it).ball()->forceStillGoing() || ((*it).ball()->curState() == Rolling && (*it).ball()->curVector().magnitude() > 0 && (*it).ball()->isVisible())) return; int curState = curBall->curState(); if (curState == Stopped && inPlay) { inPlay = false; QTimer::singleShot(0, this, SLOT(shotDone())); } if (curState == Holed && inPlay) { emit inPlayEnd(); emit playerHoled(&(*curPlayer)); int curScore = (*curPlayer).score(curHole); if (!dontAddStroke) curScore++; if (curScore == 1) { playSound("holeinone"); } else if (curScore <= holeInfo.par()) { // I don't have a sound!! // *sob* // playSound("woohoo"); } (*curPlayer).ball()->setZ((*curPlayer).ball()->z() + .1 - (.1)/(curScore)); if (allPlayersDone()) { inPlay = false; if (curHole > 0 && !dontAddStroke) { (*curPlayer).addStrokeToHole(curHole); emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); } QTimer::singleShot(600, this, SLOT(holeDone())); } else { inPlay = false; QTimer::singleShot(0, this, SLOT(shotDone())); } } } void KolfGame::fastTimeout() { // do regular advance every other time if (regAdv) course->advance(); regAdv = !regAdv; if (!editing) { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->doAdvance(); if (fastAdvancedExist) { CanvasItem *citem = 0; for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next()) citem->doAdvance(); } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->fastAdvanceDone(); if (fastAdvancedExist) { CanvasItem *citem = 0; for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next()) citem->fastAdvanceDone(); } } } void KolfGame::ballMoved() { if (putter->isVisible()) { putter->move((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); updateMouse(); } } void KolfGame::putterTimeout() { if (inPlay || editing) return; if (m_useAdvancedPutting) { if (putting) { const float base = 2.0; if (puttReverse && strength <= 0) { // aborted putting = false; strokeCircle->setVisible(false); } else if (strength > maxStrength || puttReverse) { // decreasing strength as we've reached the top puttReverse = true; strength -= pow(base, strength / maxStrength) - 1.8; if ((int)strength < puttCount * 2) { puttCount--; if (puttCount >= 0) putter->go(Forwards); } } else { // make the increase at high strength faster strength += pow(base, strength / maxStrength) - .3; if ((int)strength > puttCount * 2) { putter->go(Backwards); puttCount++; } } // make the visible steps at high strength smaller strokeCircle->setValue(pow(strength / maxStrength, 0.8) * 360); } else if (stroking) { double al = strokeCircle->value(); if (al >= 45) al -= 0.2 + strength / 50 + al / 100; else al -= 0.2 + strength / 50; if (puttReverse) { // show the stroke puttCount--; if (puttCount >= 0) putter->go(Forwards); else { strokeCircle->setVisible(false); finishStroking = false; putterTimer->stop(); putting = false; stroking = false; shotStart(); } } else if (al < -45 || finishStroking) { strokeCircle->setValue(al); int deg; // if > 45 or < -45 then bad stroke if (al > 45) { deg = putter->curDeg() - 45 + rand() % 90; strength -= rand() % (int)strength; } else if (!finishStroking) { deg = putter->curDeg() - 45 + rand() % 90; strength -= rand() % (int)strength; } else deg = putter->curDeg() + (int)(strokeCircle->value() / 3); if (deg < 0) deg += 360; else if (deg > 360) deg -= 360; putter->setDeg(deg); puttReverse = true; } else { strokeCircle->setValue(al); putterTimer->changeInterval(putterTimerMsec/10); } } } else { if (putting) { putter->go(Backwards); puttCount++; strength += 1.5; if (strength > maxStrength) { putting = false; stroking = true; } } else if (stroking) { if (putter->curLen() < (*curPlayer).ball()->height() + 2) { stroking = false; putterTimer->stop(); putting = false; stroking = false; shotStart(); } putter->go(Forwards); putterTimer->changeInterval(putterTimerMsec/10); } } } void KolfGame::autoSaveTimeout() { // this should be a config option // until it is i'll disable it if (editing) { //save(); } } void KolfGame::recreateStateList() { stateDB.clear(); QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { stateDB.setName(makeStateGroup(citem->curId(), citem->name())); citem->saveState(&stateDB); } } ballStateList.clear(); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) ballStateList.append((*it).stateInfo(curHole)); ballStateList.canUndo = true; } void KolfGame::undoShot() { if (ballStateList.canUndo) loadStateList(); } void KolfGame::loadStateList() { QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { stateDB.setName(makeStateGroup(citem->curId(), citem->name())); citem->loadState(&stateDB); } } for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) { BallStateInfo info = (*it); Player &player = (*players->at(info.id - 1)); player.ball()->move(info.spot.x(), info.spot.y()); player.ball()->setBeginningOfHole(info.beginningOfHole); if ((*curPlayer).id() == info.id) ballMoved(); else player.ball()->setVisible(!info.beginningOfHole); player.setScoreForHole(info.score, curHole); player.ball()->setState(info.state); emit scoreChanged(info.id, curHole, info.score); } } void KolfGame::shotDone() { inPlay = false; emit inPlayEnd(); setFocus(); Ball *ball = (*curPlayer).ball(); double oldx = ball->x(), oldy = ball->y(); if (!dontAddStroke && (*curPlayer).numHoles()) (*curPlayer).addStrokeToHole(curHole); dontAddStroke = false; // do hack stuff, shouldn't be done here for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { if ((*it).ball()->addStroke()) { for (int i = 1; i <= (*it).ball()->addStroke(); ++i) (*it).addStrokeToHole(curHole); // emit that we have a new stroke count emit scoreChanged((*it).id(), curHole, (*it).score(curHole)); } (*it).ball()->setAddStroke(0); } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { Ball *ball = (*it).ball(); if (ball->curState() == Holed) continue; Vector v; if (ball->placeOnGround(v)) { ball->setPlaceOnGround(false); QStringList options; const QString placeOutside = i18n("Drop Outside of Hazard"); const QString rehit = i18n("Rehit From Last Location"); options << placeOutside << rehit; const QString choice = KComboBoxDialog::getItem(i18n("What would you like to do for your next shot?"), i18n("%1 is in a Hazard").arg((*it).name()), options, placeOutside, "hazardOptions"); if (choice == placeOutside) { (*it).ball()->setDoDetect(false); double x = ball->x(), y = ball->y(); while (1) { QCanvasItemList list = ball->collisions(true); bool keepMoving = false; while (!list.isEmpty()) { QCanvasItem *item = list.first(); if (item->rtti() == Rtti_DontPlaceOn) keepMoving = true; list.pop_front(); } if (!keepMoving) break; const float movePixel = 3.0; x -= cos(v.direction()) * movePixel; y += sin(v.direction()) * movePixel; ball->move(x, y); } // move another two pixels away x -= cos(v.direction()) * 2; y += sin(v.direction()) * 2; } else if (choice == rehit) { for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it) { if ((*it).id == (*curPlayer).id()) { if ((*it).beginningOfHole) ball->move(whiteBall->x(), whiteBall->y()); else ball->move((*it).spot.x(), (*it).spot.y()); break; } } } ball->setVisible(true); ball->setState(Stopped); (*it).ball()->setDoDetect(true); ball->collisionDetect(oldx, oldy); } } // emit again emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole)); ball->setVelocity(0, 0); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { Ball *ball = (*it).ball(); int curStrokes = (*it).score(curHole); if (curStrokes >= holeInfo.maxStrokes() && holeInfo.hasMaxStrokes()) { ball->setState(Holed); ball->setVisible(false); // move to center in case he/she hit out ball->move(width / 2, height / 2); playerWhoMaxed = (*it).name(); if (allPlayersDone()) { startNextHole(); QTimer::singleShot(100, this, SLOT(emitMax())); return; } QTimer::singleShot(100, this, SLOT(emitMax())); } } // change player to next player // skip player if he's Holed do { curPlayer++; if (curPlayer == players->end()) curPlayer = players->begin(); } while ((*curPlayer).ball()->curState() == Holed); emit newPlayersTurn(&(*curPlayer)); (*curPlayer).ball()->setVisible(true); putter->setAngle((*curPlayer).ball()); putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); updateMouse(); inPlay = false; (*curPlayer).ball()->collisionDetect(oldx, oldy); } void KolfGame::emitMax() { emit maxStrokesReached(playerWhoMaxed); } void KolfGame::startBall(const Vector &vector) { playSound("hit"); emit inPlayStart(); putter->setVisible(false); (*curPlayer).ball()->setState(Rolling); (*curPlayer).ball()->setVector(vector); QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) citem->shotStarted(); } inPlay = true; } void KolfGame::shotStart() { // ensure we never hit the ball back into the hole which // can cause hole skippage if ((*curPlayer).ball()->curState() == Holed) return; // save state recreateStateList(); putter->saveAngle((*curPlayer).ball()); strength /= 8; if (!strength) strength = 1; startBall(Vector(strength, putter->curAngle() + M_PI)); addHoleInfo(ballStateList); } void KolfGame::addHoleInfo(BallStateList &list) { list.player = (*curPlayer).id(); list.vector = (*curPlayer).ball()->curVector(); list.hole = curHole; } void KolfGame::sayWhosGoing() { if (players->count() >= 2) { KMessageBox::information(this, i18n("%1 will start off.").arg((*curPlayer).name()), i18n("New Hole"), "newHole"); } } void KolfGame::holeDone() { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->setVisible(false); startNextHole(); sayWhosGoing(); } // this function is WAY too smart for it's own good // ie, bad design :-( void KolfGame::startNextHole() { setFocus(); bool reset = true; if (askSave(true)) { if (allPlayersDone()) { // we'll reload this hole, but not reset curHole--; reset = false; } else return; } else setModified(false); pause(); dontAddStroke = false; inPlay = false; timer->stop(); putter->resetAngles(); int oldCurHole = curHole; curHole++; emit currentHole(curHole); if (reset) { whiteBall->move(width/2, height/2); holeInfo.borderWallsChanged(true); } int leastScore = INT_MAX; // to get the first player to go first on every hole, // don't do the score stuff below curPlayer = players->begin(); double oldx=(*curPlayer).ball()->x(), oldy=(*curPlayer).ball()->y(); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { if (curHole > 1) { bool ahead = false; if ((*it).lastScore() != 0) { if ((*it).lastScore() < leastScore) ahead = true; else if ((*it).lastScore() == leastScore) { for (int i = curHole - 1; i > 0; --i) { const int thisScore = (*it).score(i); const int thatScore = (*curPlayer).score(i); if (thisScore < thatScore) { ahead = true; break; } else if (thisScore > thatScore) break; } } } if (ahead) { curPlayer = it; leastScore = (*it).lastScore(); } } if (reset) (*it).ball()->move(width / 2, height / 2); else (*it).ball()->move(whiteBall->x(), whiteBall->y()); (*it).ball()->setState(Stopped); // this gets set to false when the ball starts // to move by the Mr. Ball himself. (*it).ball()->setBeginningOfHole(true); if ((int)(*it).scores().count() < curHole) (*it).addHole(); (*it).ball()->setVelocity(0, 0); (*it).ball()->setVisible(false); } emit newPlayersTurn(&(*curPlayer)); if (reset) openFile(); inPlay = false; timer->start(timerMsec); // if (false) { we're done with the round! } if (oldCurHole != curHole) { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->setPlaceOnGround(false); // here we have to make sure the scoreboard shows // all of the holes up until now; for (; scoreboardHoles < curHole; ++scoreboardHoles) { cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1)); emit newHole(cfg->readNumEntry("par", 3)); } resetHoleScores(); updateShowInfo(); // this is from shotDone() (*curPlayer).ball()->setVisible(true); putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y()); updateMouse(); ballStateList.canUndo = false; (*curPlayer).ball()->collisionDetect(oldx, oldy); } unPause(); } void KolfGame::showInfo() { QString text = i18n("Hole %1: par %2, maximum %3 strokes").arg(curHole).arg(holeInfo.par()).arg(holeInfo.maxStrokes()); infoText->move((width - QFontMetrics(infoText->font()).width(text)) / 2, infoText->y()); infoText->setText(text); // I hate this text! Let's not show it //infoText->setVisible(true); emit newStatusText(text); } void KolfGame::showInfoDlg(bool addDontShowAgain) { KMessageBox::information(parentWidget(), i18n("Course name: %1").arg(holeInfo.name()) + QString("\n") + i18n("Created by %1").arg(holeInfo.author()) + QString("\n") + i18n("%1 holes").arg(highestHole), i18n("Course Information"), addDontShowAgain? holeInfo.name() + QString(" ") + holeInfo.author() : QString::null); } void KolfGame::hideInfo() { infoText->setText(""); infoText->setVisible(false); emit newStatusText(QString::null); } void KolfGame::openFile() { Object *curObj = 0; QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { // sometimes info is still showing citem->hideInfo(); citem->aboutToDie(); } } items.setAutoDelete(true); items.clear(); items.setAutoDelete(false); extraMoveable.setAutoDelete(false); extraMoveable.clear(); fastAdvancers.setAutoDelete(false); fastAdvancers.clear(); selectedItem = 0; // will tell basic course info // we do this here for the hell of it. // there is no fake id, by the way, // because it's old and when i added ids i forgot to change it. cfg->setGroup("0-course@-50,-50"); holeInfo.setAuthor(cfg->readEntry("author", holeInfo.author())); holeInfo.setName(cfg->readEntry("Name", holeInfo.name())); holeInfo.setUntranslatedName(cfg->readEntryUntranslated("Name", holeInfo.untranslatedName())); emit titleChanged(holeInfo.name()); cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole)); curPar = cfg->readNumEntry("par", 3); holeInfo.setPar(curPar); holeInfo.borderWallsChanged(cfg->readBoolEntry("borderWalls", holeInfo.borderWalls())); holeInfo.setMaxStrokes(cfg->readNumEntry("maxstrokes", 10)); bool hasFinalLoad = cfg->readBoolEntry("hasFinalLoad", true); QStringList missingPlugins; QStringList groups = cfg->groupList(); int numItems = 0; int _highestHole = 0; for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) { // [-@,|] cfg->setGroup(*it); const int len = (*it).length(); const int dashIndex = (*it).find("-"); const int holeNum = (*it).left(dashIndex).toInt(); if (holeNum > _highestHole) _highestHole = holeNum; const int atIndex = (*it).find("@"); const QString name = (*it).mid(dashIndex + 1, atIndex - (dashIndex + 1)); if (holeNum != curHole) { // if we've had one, break, cause list is sorted // erps, no, cause we need to know highest hole! if (numItems && !recalcHighestHole) break; continue; } numItems++; const int commaIndex = (*it).find(","); const int pipeIndex = (*it).find("|"); const int x = (*it).mid(atIndex + 1, commaIndex - (atIndex + 1)).toInt(); const int y = (*it).mid(commaIndex + 1, pipeIndex - (commaIndex + 1)).toInt(); // will tell where ball is if (name == "ball") { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->move(x, y); whiteBall->move(x, y); continue; } const int id = (*it).right(len - (pipeIndex + 1)).toInt(); bool loaded = false; for (curObj = obj->first(); curObj; curObj = obj->next()) { if (name != curObj->_name()) continue; QCanvasItem *newItem = curObj->newObject(course); items.append(newItem); CanvasItem *canvasItem = dynamic_cast(newItem); if (!canvasItem) continue; canvasItem->setId(id); canvasItem->setGame(this); canvasItem->editModeChanged(editing); canvasItem->setName(curObj->_name()); addItemsToMoveableList(canvasItem->moveableItems()); if (canvasItem->fastAdvance()) addItemToFastAdvancersList(canvasItem); newItem->move(x, y); canvasItem->firstMove(x, y); newItem->setVisible(true); // make things actually show if (!hasFinalLoad) { cfg->setGroup(makeGroup(id, curHole, canvasItem->name(), x, y)); canvasItem->load(cfg); course->update(); } // we don't allow multiple items for the same thing in // the file! loaded = true; break; } if (!loaded && name != "hole" && missingPlugins.contains(name) <= 0) missingPlugins.append(name); } if (!missingPlugins.empty()) { KMessageBox::informationList(this, QString("

<http://katzbrown.com/kolf/Plugins/>

") + i18n("This hole uses the following plugins, which you do not have installed:") + QString("

"), missingPlugins, QString::null, QString("%1 warning").arg(holeInfo.untranslatedName() + QString::number(curHole))); } lastDelId = -1; // if it's the first hole let's not if (!numItems && curHole > 1 && !addingNewHole && curHole >= _highestHole) { // we're done, let's quit curHole--; pause(); emit holesDone(); // tidy things up setBorderWalls(false); clearHole(); setModified(false); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->setVisible(false); return; } // do it down here; if !hasFinalLoad, do it up there! QCanvasItem *qcanvasItem = 0; QPtrList todo; QPtrList qtodo; if (hasFinalLoad) { for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) { CanvasItem *item = dynamic_cast(qcanvasItem); if (item) { if (item->loadLast()) { qtodo.append(qcanvasItem); todo.append(item); } else { QString group = makeGroup(item->curId(), curHole, item->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y()); cfg->setGroup(group); item->load(cfg); } } } CanvasItem *citem = 0; qcanvasItem = qtodo.first(); for (citem = todo.first(); citem; citem = todo.next()) { cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y())); citem->load(cfg); qcanvasItem = qtodo.next(); } } for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) { CanvasItem *citem = dynamic_cast(qcanvasItem); if (citem) citem->updateZ(); } if (curHole > _highestHole) _highestHole = curHole; if (recalcHighestHole) { highestHole = _highestHole; recalcHighestHole = false; emit largestHole(highestHole); } if (curHole == 1 && !filename.isNull() && !infoShown) { // let's not now, because they see it when they choose course //showInfoDlg(true); infoShown = true; } setModified(false); } void KolfGame::addItemsToMoveableList(QPtrList list) { QCanvasItem *item = 0; for (item = list.first(); item; item = list.next()) extraMoveable.append(item); } void KolfGame::addItemToFastAdvancersList(CanvasItem *item) { fastAdvancers.append(item); fastAdvancedExist = fastAdvancers.count() > 0; } void KolfGame::addNewObject(Object *newObj) { QCanvasItem *newItem = newObj->newObject(course); items.append(newItem); newItem->setVisible(true); CanvasItem *canvasItem = dynamic_cast(newItem); if (!canvasItem) return; // we need to find a number that isn't taken int i = lastDelId > 0? lastDelId : items.count() - 30; if (i <= 0) i = 0; for (;; ++i) { bool found = false; QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { if (citem->curId() == i) { found = true; break; } } } if (!found) break; } canvasItem->setId(i); canvasItem->setGame(this); if (m_showInfo) canvasItem->showInfo(); else canvasItem->hideInfo(); canvasItem->editModeChanged(editing); canvasItem->setName(newObj->_name()); addItemsToMoveableList(canvasItem->moveableItems()); if (canvasItem->fastAdvance()) addItemToFastAdvancersList(canvasItem); newItem->move(width/2 - 18, height / 2 - 18); if (selectedItem) canvasItem->selectedItem(selectedItem); setModified(true); } bool KolfGame::askSave(bool noMoreChances) { if (!modified) // not cancel, don't save return false; int result = KMessageBox::warningYesNoCancel(this, i18n("There are unsaved changes to current hole. Save them?"), i18n("Unsaved Changes"), KStdGuiItem::save(), noMoreChances? KStdGuiItem::discard() : i18n("Save &Later"), noMoreChances? "DiscardAsk" : "SaveAsk", true); switch (result) { case KMessageBox::Yes: save(); // fallthrough case KMessageBox::No: return false; break; case KMessageBox::Cancel: return true; break; default: break; } return false; } void KolfGame::addNewHole() { if (askSave(true)) return; // either it's already false // because it was saved by askSave(), // or the user pressed the 'discard' button setModified(false); // find highest hole num, and create new hole // now openFile makes highest hole for us addingNewHole = true; curHole = highestHole; recalcHighestHole = true; startNextHole(); addingNewHole = false; emit currentHole(curHole); // make sure even the current player isn't showing for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) (*it).ball()->setVisible(false); whiteBall->setVisible(editing); highlighter->setVisible(false); putter->setVisible(!editing); inPlay = false; // add default objects Object *curObj = 0; for (curObj = obj->first(); curObj; curObj = obj->next()) if (curObj->addOnNewHole()) addNewObject(curObj); save(); } // kantan deshou ;-) void KolfGame::resetHole() { if (askSave(true)) return; setModified(false); curHole--; startNextHole(); resetHoleScores(); } void KolfGame::resetHoleScores() { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { (*it).resetScore(curHole); emit scoreChanged((*it).id(), curHole, 0); } } void KolfGame::clearHole() { QCanvasItem *qcanvasItem = 0; for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next()) { CanvasItem *citem = dynamic_cast(qcanvasItem); if (citem) citem->aboutToDie(); } items.setAutoDelete(true); items.clear(); items.setAutoDelete(false); emit newSelectedItem(&holeInfo); // add default objects Object *curObj = 0; for (curObj = obj->first(); curObj; curObj = obj->next()) if (curObj->addOnNewHole()) addNewObject(curObj); setModified(true); } void KolfGame::switchHole(int hole) { if (inPlay) return; if (hole < 1 || hole > highestHole) return; bool wasEditing = editing; if (editing) toggleEditMode(); if (askSave(true)) return; setModified(false); curHole = hole; resetHole(); if (wasEditing) toggleEditMode(); } void KolfGame::switchHole(const QString &holestring) { bool ok; int hole = holestring.toInt(&ok); if (!ok) return; switchHole(hole); } void KolfGame::nextHole() { switchHole(curHole + 1); } void KolfGame::prevHole() { switchHole(curHole - 1); } void KolfGame::firstHole() { switchHole(1); } void KolfGame::lastHole() { switchHole(highestHole); } void KolfGame::randHole() { int newHole = 1 + (int)((double)kapp->random() * ((double)(highestHole - 1) / (double)RAND_MAX)); switchHole(newHole); } void KolfGame::save() { if (filename.isNull()) { QString newfilename = KFileDialog::getSaveFileName(":kourses", "application/x-kourse", this, i18n("Pick Kolf Course to Save To")); if (newfilename.isNull()) return; setFilename(newfilename); } emit parChanged(curHole, holeInfo.par()); emit titleChanged(holeInfo.name()); // we use this bool for optimization // in openFile(). bool hasFinalLoad = false; fastAdvancedExist = false; QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { citem->aboutToSave(); if (citem->loadLast()) hasFinalLoad = true; } } QStringList groups = cfg->groupList(); // wipe out all groups from this hole for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) { int holeNum = (*it).left((*it).find("-")).toInt(); if (holeNum == curHole) cfg->deleteGroup(*it); } for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) { citem->clean(); cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)item->x(), (int)item->y())); citem->save(cfg); } } // save where ball starts (whiteBall tells all) cfg->setGroup(QString("%1-ball@%2,%3").arg(curHole).arg((int)whiteBall->x()).arg((int)whiteBall->y())); cfg->writeEntry("dummykey", true); cfg->setGroup("0-course@-50,-50"); cfg->writeEntry("author", holeInfo.author()); cfg->writeEntry("Name", holeInfo.untranslatedName()); // save hole info cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole)); cfg->writeEntry("par", holeInfo.par()); cfg->writeEntry("maxstrokes", holeInfo.maxStrokes()); cfg->writeEntry("borderWalls", holeInfo.borderWalls()); cfg->writeEntry("hasFinalLoad", hasFinalLoad); cfg->sync(); for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) citem->savingDone(); } setModified(false); } void KolfGame::toggleEditMode() { // won't be editing anymore, and user wants to cancel, we return // this is pretty useless. when the person leaves the hole, // he gets asked again /* if (editing && modified) { if (askSave(false)) { emit checkEditing(); return; } } */ moving = false; selectedItem = 0; editing = !editing; if (editing) { emit editingStarted(); emit newSelectedItem(&holeInfo); } else { emit editingEnded(); setCursor(KCursor::arrowCursor()); } // alert our items QCanvasItem *item = 0; for (item = items.first(); item; item = items.next()) { CanvasItem *citem = dynamic_cast(item); if (citem) citem->editModeChanged(editing); } for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { // curplayer shouldn't be hidden no matter what if ((*it).ball()->beginningOfHole() && it != curPlayer) (*it).ball()->setVisible(false); else (*it).ball()->setVisible(!editing); } whiteBall->setVisible(editing); highlighter->setVisible(false); // shouldn't see putter whilst editing putter->setVisible(!editing); if (editing) autoSaveTimer->start(autoSaveMsec); else autoSaveTimer->stop(); inPlay = false; } void KolfGame::playSound(QString file, double vol) { if (m_sound) { KPlayObject *oldPlayObject = 0; for (oldPlayObject = oldPlayObjects.first(); oldPlayObject; oldPlayObject = oldPlayObjects.next()) { if (oldPlayObject && oldPlayObject->state() != Arts::posPlaying) { oldPlayObjects.remove(); // because we will go to next() next time // and after remove current item is one after // removed item (void) oldPlayObjects.prev(); } } file = soundDir + file + QString::fromLatin1(".wav"); // not needed when all of the files are in the distribution //if (!QFile::exists(file)) //return; KPlayObjectFactory factory(artsServer.server()); KPlayObject *playObject = factory.createPlayObject(KURL(file), true); if (playObject && !playObject->isNull()) { if (vol > 1) vol = 1; else if (vol <= .01) { delete playObject; return; } if (vol < .99) { //new KVolumeControl(vol, artsServer.server(), playObject); } playObject->play(); oldPlayObjects.append(playObject); } } } void HoleInfo::borderWallsChanged(bool yes) { m_borderWalls = yes; game->setBorderWalls(yes); } void KolfGame::print(KPrinter &pr) { QPainter p(&pr); QPaintDeviceMetrics metrics(&pr); // translate to center p.translate(metrics.width() / 2 - course->rect().width() / 2, metrics.height() / 2 - course->rect().height() / 2); QPixmap pix(width, height); QPainter pixp(&pix); course->drawArea(course->rect(), &pixp); p.drawPixmap(0, 0, pix); p.setPen(QPen(black, 2)); p.drawRect(course->rect()); p.resetXForm(); if (pr.option("kde-kolf-title") == "true") { QString text = i18n("%1 - Hole %2; by %3").arg(holeInfo.name()).arg(curHole).arg(holeInfo.author()); QFont font(kapp->font()); font.setPointSize(18); QRect rect = QFontMetrics(font).boundingRect(text); p.setFont(font); p.drawText(metrics.width() / 2 - rect.width() / 2, metrics.height() / 2 - course->rect().height() / 2 -20 - rect.height(), text); } } bool KolfGame::allPlayersDone() { for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) if ((*it).ball()->curState() != Holed) return false; return true; } void KolfGame::setBorderWalls(bool showing) { Wall *wall = 0; for (wall = borderWalls.first(); wall; wall = borderWalls.next()) wall->setVisible(showing); } void KolfGame::setUseAdvancedPutting(bool yes) { m_useAdvancedPutting = yes; // increase maxStrength in advanced putting mode if (yes) maxStrength = 65; else maxStrength = 55; } void KolfGame::setShowGuideLine(bool yes) { putter->setShowGuideLine(yes); } void KolfGame::setSound(bool yes) { m_sound = yes; } void KolfGame::courseInfo(CourseInfo &info, const QString& filename) { KConfig cfg(filename); cfg.setGroup("0-course@-50,-50"); info.author = cfg.readEntry("author", info.author); info.name = cfg.readEntry("Name", cfg.readEntry("name", info.name)); info.untranslatedName = cfg.readEntryUntranslated("Name", cfg.readEntryUntranslated("name", info.name)); unsigned int hole = 1; unsigned int par= 0; while (1) { QString group = QString("%1-hole@-50,-50|0").arg(hole); if (!cfg.hasGroup(group)) { hole--; break; } cfg.setGroup(group); par += cfg.readNumEntry("par", 3); hole++; } info.par = par; info.holes = hole; } void KolfGame::scoresFromSaved(KConfig *config, PlayerList &players) { config->setGroup("0 Saved Game"); int numPlayers = config->readNumEntry("Players", 0); if (numPlayers <= 0) return; for (int i = 1; i <= numPlayers; ++i) { // this is same as in kolf.cpp, but we use saved game values config->setGroup(QString::number(i)); players.append(Player()); players.last().ball()->setColor(config->readEntry("Color", "#ffffff")); players.last().setName(config->readEntry("Name")); players.last().setId(i); QStringList scores(config->readListEntry("Scores")); QValueList intscores; for (QStringList::Iterator it = scores.begin(); it != scores.end(); ++it) intscores.append((*it).toInt()); players.last().setScores(intscores); } } void KolfGame::saveScores(KConfig *config) { // wipe out old player info QStringList groups = config->groupList(); for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it) { // this deletes all int groups, ie, the player info groups bool ok = false; (*it).toInt(&ok); if (ok) config->deleteGroup(*it); } config->setGroup("0 Saved Game"); config->writeEntry("Players", players->count()); config->writeEntry("Course", filename); config->writeEntry("Current Hole", curHole); for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it) { config->setGroup(QString::number((*it).id())); config->writeEntry("Name", (*it).name()); config->writeEntry("Color", (*it).ball()->color().name()); QStringList scores; QValueList intscores = (*it).scores(); for (QValueList::Iterator it = intscores.begin(); it != intscores.end(); ++it) scores.append(QString::number(*it)); config->writeEntry("Scores", scores); } } CourseInfo::CourseInfo() : name(i18n("Course Name")), author(i18n("Course Author")), holes(0), par(0) { } #include "game.moc"