File indexing completed on 2024-04-28 04:04:39

0001 /*
0002     This file is part of the game 'KTron'
0003 
0004     SPDX-FileCopyrightText: 1998-2000 Matthias Kiefer <matthias.kiefer@gmx.de>
0005     SPDX-FileCopyrightText: 2005 Benjamin C. Meyer <ben at meyerhome dot net>
0006     SPDX-FileCopyrightText: 2008-2009 Stas Verberkt <legolas at legolasweb dot nl>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 
0010 */
0011 
0012 #include "tron.h"
0013 
0014 // Normal class
0015 #include <QTimer>
0016 #include <QPainter>
0017 #include <QPoint>
0018 
0019 #include "ksnakeduel_debug.h"
0020 #include <KLocalizedString>
0021 // KDEGames
0022 #include <KGameDifficulty>
0023 #include <KGameThemeProvider>
0024 
0025 #include "settings.h"
0026 #include "renderer.h"
0027 #include "object.h"
0028 #include "obstacle.h"
0029 
0030 /**
0031  * init-functions
0032  **/
0033 
0034 Tron::Tron(QWidget *parent) : QWidget(parent)
0035 {
0036     players[0] = new Player(pf, 0);
0037     players[1] = new Player(pf, 1);
0038 
0039     connect(players[0], &Player::fetchedItem, this, &Tron::itemHit);
0040     connect(players[1], &Player::fetchedItem, this, &Tron::itemHit);
0041 
0042     intelligence.referenceTron(this);
0043 
0044     setFocusPolicy(Qt::StrongFocus);
0045 
0046     gameBlocked = false;
0047     gameEnded = true;
0048 
0049     timer = new QTimer(this);
0050     //loadSettings();
0051     connect(timer, &QTimer::timeout, this, &Tron::doMove);
0052     connect(Renderer::self()->themeProvider(), &KGameThemeProvider::currentThemeChanged,
0053         this, &Tron::resetOnThemeChange);
0054 }
0055 
0056 void Tron::loadSettings(){
0057     createNewPlayfield();
0058     reset();
0059 
0060     // Velocity
0061     setVelocity( lineSpeed() );
0062 
0063     // Style
0064     updatePixmap();
0065     update();
0066 
0067     // Player 0 is always human
0068     if (Settings::gameType() == Settings::EnumGameType::PlayerVSPlayer)
0069     {
0070         players[1]->setComputer(false);
0071     }
0072     else
0073     {
0074         players[1]->setComputer(true);
0075     }
0076 }
0077 
0078 Tron::~Tron()
0079 {
0080     delete timer;
0081     delete players[0];
0082     delete players[1];
0083 }
0084 
0085 void Tron::resizeRenderer()
0086 {
0087     // Block size
0088     blockWidth = width() / (pf.getWidth() + 2);
0089     blockHeight = height() / (pf.getHeight() + 2);
0090     if (blockWidth > blockHeight)
0091     {
0092         blockWidth = blockHeight;
0093     }
0094     else
0095     {
0096         blockHeight = blockWidth;
0097     }
0098 
0099     Renderer::self()->boardResized(width(), height(), blockWidth, blockHeight);
0100 }
0101 
0102 void Tron::createNewPlayfield()
0103 {
0104     resizeRenderer();
0105 
0106     pf.initialize();
0107 }
0108 
0109 void Tron::newGame()
0110 {
0111     players[0]->resetScore();
0112     players[1]->resetScore();
0113     //Q_EMIT gameEnds(KTronEnum::Nobody);
0114     Q_EMIT updatedScore();
0115     reset();
0116 }
0117 
0118 void Tron::reset()
0119 {
0120     gamePaused = false;
0121     stopGame();
0122 
0123     players[0]->reset();
0124     players[1]->reset();
0125 
0126     if (Settings::gameType() == Settings::EnumGameType::Snake)
0127     {
0128         players[0]->resetScore();
0129         players[1]->resetScore();
0130     }
0131 
0132     setVelocity( lineSpeed() );
0133 
0134     modMoves = 0;
0135 
0136     pf.initialize();
0137 
0138     // set start coordinates
0139     players[0]->setStartPosition();
0140 
0141     if (Settings::gameType() != Settings::EnumGameType::Snake)
0142     {
0143         players[1]->setStartPosition();
0144     }
0145 
0146     updatePixmap();
0147     update();
0148 
0149     setFocus();
0150 
0151     Q_EMIT gameReset();
0152     Q_EMIT updatedScore();
0153 }
0154 
0155 //
0156 // Getters / Setters
0157 //
0158 
0159 PlayField *Tron::getPlayField()
0160 {
0161     return &pf;
0162 }
0163 
0164 Player *Tron::getPlayer(int playerNr)
0165 {
0166     if (playerNr != 0 && playerNr != 1)
0167     {
0168         qCDebug(KSNAKEDUEL_LOG) << "Inexistent player requested: " << playerNr;
0169         return nullptr;
0170     }
0171 
0172     return players[playerNr];
0173 }
0174 
0175 
0176 /* *************************************************************** **
0177 **                      ??? functions                                        **
0178 ** *************************************************************** */
0179 
0180 void Tron::startGame()
0181 {
0182     gameEnded = false;
0183     Q_EMIT pauseBlocked(false);
0184 
0185     if (Settings::gameType() == Settings::EnumGameType::Snake)
0186     {
0187         newApple();
0188     }
0189 
0190     timer->start(velocity);
0191 }
0192 
0193 void Tron::itemHit(int playerNumber, int, int)
0194 {
0195     //qCDebug(KSNAKEDUEL_LOG) << "Got Item Hit for " << playerNumber;
0196 
0197     newApple();
0198     players[playerNumber]->setEnlargement(3);
0199     players[playerNumber]->addScore(5);
0200     if (velocity > 15)
0201     {
0202         velocity--;
0203         timer->stop();
0204         timer->start(velocity);
0205     }
0206 
0207     Q_EMIT updatedScore();
0208 }
0209 
0210 void Tron::newApple()
0211 {
0212     int x = rand() % pf.getWidth();
0213     int y = rand() % pf.getHeight();
0214 
0215     while (pf.getObjectAt(x, y)->getObjectType() != ObjectType::Object)
0216     {
0217         x = rand() % pf.getWidth();
0218         y = rand() % pf.getHeight();
0219     }
0220 
0221     //qCDebug(KSNAKEDUEL_LOG) << "Drawn apple at (" << x << ", " << y << ")";
0222 
0223     apple.setType((int)(rand() % 3));
0224 
0225     pf.setObjectAt(x, y, apple);
0226 }
0227 
0228 void Tron::newObstacle()
0229 {
0230     // KSnake only
0231     if (Settings::gameType() != Settings::EnumGameType::Snake)
0232         return;
0233 
0234     int x = rand() % pf.getWidth();
0235     int y = rand() % pf.getHeight();
0236 
0237     // Don't render if it's at an unwanted place
0238     if (pf.getObjectAt(x, y)->getObjectType() != ObjectType::Object)
0239         return;
0240     else if (x == players[0]->getX() || y == players[0]->getY())
0241         return;
0242 
0243     Obstacle obst;
0244     pf.setObjectAt(x, y, obst);
0245 
0246     // Score +2
0247     players[0]->addScore(2);
0248     Q_EMIT updatedScore();
0249 }
0250 
0251 void Tron::stopGame()
0252 {
0253     timer->stop();
0254     gameEnded = true;
0255 }
0256 
0257 void Tron::togglePause() // pause or continue game
0258 {
0259     if (!gameEnded)
0260     {
0261         if (gamePaused)
0262         {
0263             gamePaused = false;
0264             update();
0265             timer->start(velocity);
0266 
0267             Q_EMIT updatedScore();
0268         }
0269         else
0270         {
0271             gamePaused = true;
0272             timer->stop();
0273             update();
0274 
0275             Q_EMIT updatedScore();
0276         }
0277     }
0278 }
0279 
0280 void Tron::showWinner()
0281 {
0282     update();
0283 
0284     Q_EMIT gameEnds();
0285     Q_EMIT pauseBlocked(true);
0286 }
0287 
0288 /* *************************************************************** **
0289 **                    paint functions                                        **
0290 ** *************************************************************** */
0291 
0292 void Tron::updatePixmap()
0293 {
0294     Renderer::self()->updatePlayField(pf);
0295 }
0296 
0297 /* *************************************************************** **
0298 **                    config functions                                       **
0299 ** *************************************************************** */
0300 
0301 void Tron::setVelocity(int newVel)            // set new velocity
0302 {
0303     velocity = (10 - newVel) * 15;
0304 
0305     if (!gameEnded && !gamePaused)
0306     {
0307         timer->start(velocity);
0308     }
0309 }
0310 
0311 /* *************************************************************** **
0312 **                              Events                                       **
0313 ** *************************************************************** */
0314 
0315 void Tron::paintEvent(QPaintEvent *e)
0316 {
0317     QPainter p(this);
0318 
0319     p.drawPixmap(e->rect().topLeft(), *Renderer::self()->getPlayField(), e->rect());
0320 
0321     if (gamePaused) // if game is paused, print message
0322     {
0323         QString message = i18n("Game paused");
0324         QPixmap messageBox = Renderer::self()->messageBox(message);
0325         QPoint point(width() / 2 - messageBox.width() / 2, height() / 2 - messageBox.height() / 2);
0326         p.drawPixmap(point, messageBox, e->rect());
0327     }
0328     else if (gameEnded) // If game ended, print "Crash!"
0329     {
0330         QString message = QString();
0331 
0332         if (Settings::gameType() != Settings::EnumGameType::Snake) {
0333             if (hasWinner())
0334             {
0335                 int winner = getWinner();
0336                 int loser = 1 - winner;
0337 
0338                 QString winnerName = players[winner]->getName();
0339                 QString loserName = players[loser]->getName();
0340                 int winnerScore = players[winner]->getScore();
0341                 int loserScore = players[loser]->getScore();
0342 
0343                 message += i18np("%1 has won versus %2 with %4 versus %3 point!", "%1 has won versus %2 with %4 versus %3 points!", winnerName, loserName, loserScore, winnerScore);
0344                 message += QLatin1Char( '\n' );
0345             }
0346             else
0347             {
0348                 QString name1 = players[0]->getName();
0349                 QString name2 = players[1]->getName();
0350                 int points1 = players[0]->getScore();
0351                 int points2 = players[1]->getScore();
0352 
0353                 message += i18nc("%2 = 'x points' [player %1], %4 = 'x points' [player %3]",
0354                     "%1 (%2) versus %3 (%4)",
0355                     name2, i18np("%1 point", "%1 points", points2),
0356                     name1, i18np("%1 point", "%1 points", points1));
0357                 message += QLatin1Char( '\n' );
0358             }
0359         }
0360         else
0361         {
0362             int points = players[0]->getScore();
0363 
0364             message += i18np("KSnake game ended with 1 point", "KSnake game ended with %1 points", points);
0365             message += QLatin1Char( '\n' );
0366         }
0367 
0368         if (Settings::gameType() == Settings::EnumGameType::PlayerVSPlayer) {
0369             message += i18n("The game starts when each player has pressed one of their direction keys!");
0370         }
0371         else {
0372             message += i18n("Press any of your direction keys to start!");
0373         }
0374 
0375         QPixmap messageBox = Renderer::self()->messageBox(message);
0376         QPoint point(width() / 2 - messageBox.width() / 2, height() / 2 - messageBox.height() / 2);
0377         p.drawPixmap(point, messageBox, e->rect());
0378     }
0379 }
0380 
0381 void Tron::resizeEvent(QResizeEvent *)
0382 {
0383     resizeRenderer();
0384     updatePixmap();
0385     update();
0386 }
0387 
0388 void Tron::resetOnThemeChange()
0389 {
0390     Renderer::self()->clearPixmapCache();
0391     updatePixmap();
0392     update();
0393 }
0394 
0395 void Tron::triggerKey(int player, KBAction::Action action, bool trigger)
0396 {
0397     if (action == KBAction::ACCELERATE && !trigger)
0398     {
0399         switchKeyOff(player, action);
0400     }
0401     else
0402     {
0403         switchKeyOn(player, action);
0404     }
0405 }
0406 
0407 void Tron::switchKeyOn(int player, KBAction::Action action)
0408 {
0409     // Set key pressed
0410     if (!players[player]->isComputer())
0411     {
0412         switch (action)
0413         {
0414             case KBAction::UP:
0415             case KBAction::DOWN:
0416             case KBAction::LEFT:
0417             case KBAction::RIGHT:
0418                 players[player]->setKeyPressed(true);
0419                 break;
0420             case KBAction::ACCELERATE:
0421                 break;
0422             default:
0423                 break;
0424         }
0425     }
0426 
0427     // if both players press keys at the same time, start game...
0428     if (players[0]->hasKeyPressed() && players[1]->hasKeyPressed())
0429     {
0430         // Start game
0431         if (gameEnded && !gameBlocked)
0432         {
0433             if (hasWinner())
0434             {
0435                 newGame();
0436             }
0437 
0438             reset();
0439             startGame();
0440         }
0441         // ...or continue
0442         else if (gamePaused)
0443         {
0444             togglePause();
0445         }
0446     }
0447 
0448     // Key handling for movement
0449     if (!players[player]->isComputer())
0450     {
0451         switch (action)
0452         {
0453             case KBAction::UP:
0454                 players[player]->setDirection(PlayerDirections::Up);
0455                 break;
0456             case KBAction::DOWN:
0457                 players[player]->setDirection(PlayerDirections::Down);
0458                 break;
0459             case KBAction::LEFT:
0460                 players[player]->setDirection(PlayerDirections::Left);
0461                 break;
0462             case KBAction::RIGHT:
0463                 players[player]->setDirection(PlayerDirections::Right);
0464                 break;
0465             case KBAction::ACCELERATE:
0466                 if (!Settings::acceleratorBlocked())
0467                 {
0468                     players[player]->setAccelerated(true);
0469                 }
0470                 break;
0471             default:
0472                 break;
0473         }
0474     }
0475 }
0476 
0477 void Tron::switchKeyOff(int player, KBAction::Action action)
0478 {
0479     if (!players[player]->isComputer())
0480     {
0481         switch (action)
0482         {
0483             case KBAction::UP:
0484             case KBAction::DOWN:
0485             case KBAction::LEFT:
0486             case KBAction::RIGHT:
0487                 players[player]->setKeyPressed(false);
0488                 break;
0489             case KBAction::ACCELERATE:
0490                 players[player]->setAccelerated(false);
0491                 break;
0492             default:
0493                 break;
0494         }
0495     }
0496 }
0497 
0498 // if playingfield loses keyboard focus, pause game
0499 void Tron::focusOutEvent(QFocusEvent *)
0500 {
0501    if(!gameEnded && !gamePaused)
0502    {
0503       togglePause();
0504    }
0505 }
0506 
0507 /* *************************************************************** **
0508 **                  slots                                        **
0509 ** *************************************************************** */
0510 
0511 void Tron::unblockGame()
0512 {
0513     gameBlocked = false;
0514 }
0515 
0516 // doMove() is called from QTimer
0517 void Tron::doMove()
0518 {
0519     if (Settings::gameType() == Settings::EnumGameType::Snake)
0520     {
0521         players[0]->movePlayer();
0522 
0523         modMoves++;
0524 
0525         if (modMoves == 20)
0526         {
0527             modMoves = 0;
0528             newObstacle();
0529         }
0530 
0531         updatePixmap();
0532         update();
0533 
0534         if (!players[0]->isAlive())
0535         {
0536             stopGame();
0537             showWinner();
0538         }
0539     }
0540     else
0541     {
0542         if (players[0]->isAccelerated() || players[1]->isAccelerated())
0543         {
0544             movementHelper(true);
0545         }
0546 
0547         if (!gameEnded)
0548         {
0549             // Player 0 is never a computer nowadays...
0550             if (players[1]->isComputer())
0551             {
0552                 intelligence.think(1);
0553             }
0554 
0555             movementHelper(false);
0556         }
0557     }
0558 
0559     if (gameEnded)
0560     {
0561         //this is for waiting 1s before starting next game
0562         gameBlocked = true;
0563         QTimer::singleShot(1000, this, &Tron::unblockGame);
0564     }
0565 }
0566 
0567 void Tron::movementHelper(bool onlyAcceleratedPlayers)
0568 {
0569     if (!onlyAcceleratedPlayers || players[0]->isAccelerated())
0570     {
0571         players[0]->movePlayer();
0572     }
0573 
0574     if (!onlyAcceleratedPlayers || players[1]->isAccelerated())
0575     {
0576         players[1]->movePlayer();
0577     }
0578 
0579     /* player collision check */
0580     if (!players[1]->isAlive())
0581     {
0582         checkHeadToHeadCollision();
0583     }
0584 
0585     updatePixmap();
0586     update();
0587 
0588     // crashtest
0589     if (!players[0]->isAlive() || !players[1]->isAlive())
0590     {
0591         stopGame();
0592 
0593         if (!players[0]->isAlive() && !players[1]->isAlive())
0594         {
0595             // Don't award points when both players die
0596             //players[0]->addScore(1);
0597             //players[1]->addScore(1);
0598         }
0599         else if (!players[0]->isAlive())
0600         {
0601             players[1]->addScore(1);
0602         }
0603         else if (!players[1]->isAlive())
0604         {
0605             players[0]->addScore(1);
0606         }
0607 
0608         showWinner();
0609     }
0610 }
0611 
0612 void Tron::checkHeadToHeadCollision()
0613 {
0614     // As player 1 and player 2 move at the same time
0615     // a head to head collision is possible
0616     // but tough movement actually is done sequential
0617     // we have to check back if player 1 should die when player 2 did so
0618     // that's where this function comes in :)
0619 
0620     int xInc = 0;
0621     int yInc = 0;
0622 
0623     switch (players[1]->getDirection())
0624     {
0625         case PlayerDirections::Left:
0626             xInc = -1;
0627             break;
0628         case PlayerDirections::Right:
0629             xInc = 1;
0630             break;
0631         case PlayerDirections::Up:
0632             yInc = -1;
0633             break;
0634         case PlayerDirections::Down:
0635             yInc = 1;
0636             break;
0637         default:
0638             break;
0639     }
0640 
0641     if ((players[1]->getX() + xInc) == players[0]->getX())
0642     {
0643         if ((players[1]->getY() + yInc) == players[0]->getY())
0644         {
0645             players[0]->die();
0646         }
0647     }
0648 }
0649 
0650 /**
0651  * Skill settings
0652  */
0653 
0654 /** retrieves the line speed */
0655 int Tron::lineSpeed() {
0656     switch (KGameDifficulty::globalLevel()) {
0657         case KGameDifficultyLevel::VeryEasy:
0658             return 2;
0659         default:
0660         case KGameDifficultyLevel::Easy:
0661             return 3;
0662         case KGameDifficultyLevel::Medium:
0663             return 5;
0664         case KGameDifficultyLevel::Hard:
0665             return 7;
0666         case KGameDifficultyLevel::VeryHard:
0667             return 8;
0668     }
0669 }
0670 
0671 bool Tron::running() {
0672     return !gameEnded;
0673 }
0674 
0675 bool Tron::paused() {
0676     return !gameEnded && gamePaused;
0677 }
0678 
0679 bool Tron::hasWinner()
0680 {
0681     return getWinner() == 0 || getWinner() == 1;
0682 }
0683 
0684 int Tron::getWinner()
0685 {
0686     const unsigned short int WINNING_DIFF = Settings::rounds();
0687     if (Settings::gameType() != Settings::EnumGameType::Snake)
0688     {
0689         if (players[0]->getScore() >= WINNING_DIFF && players[1]->getScore() < players[0]->getScore() - 1) {
0690             return 0;
0691         }
0692         else if (players[1]->getScore() >= WINNING_DIFF && players[0]->getScore() < players[1]->getScore() - 1) {
0693             return 1;
0694         }
0695 
0696     }
0697 
0698     return -1;
0699 }
0700 
0701 #include "moc_tron.cpp"