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"