1/**********************************************************************************
2 This file is part of the game 'KTron'
3
4 Copyright (C) 1998-2000 by Matthias Kiefer <matthias.kiefer@gmx.de>
5 Copyright (C) 2005 Benjamin C. Meyer <ben at meyerhome dot net>
6 Copyright (C) 2008-2009 Stas Verberkt <legolas at legolasweb dot nl>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
22 *******************************************************************************/
23
24#include "tron.h"
25
26// Background
27#include <kio/netaccess.h>
28
29// Normal class
30#include <QTimer>
31#include <QPainter>
32#include <QPaintEvent>
33#include <QPoint>
34#include <QResizeEvent>
35#include <QFocusEvent>
36#include <QPixmap>
37
38#include <KDebug>
39#include <KLocale>
40#include <KgDifficulty>
41
42#include "settings.h"
43#include "renderer.h"
44#include "object.h"
45#include "obstacle.h"
46
47/**
48 * init-functions
49 **/
50
51Tron::Tron(QWidget *parent) : QWidget(parent)
52{
53 players[0] = new Player(pf, 0);
54 players[1] = new Player(pf, 1);
55
56 connect(players[0], SIGNAL(fetchedItem(int,int,int)), SLOT(itemHit(int,int,int)));
57 connect(players[1], SIGNAL(fetchedItem(int,int,int)), SLOT(itemHit(int,int,int)));
58
59 intelligence.referenceTron(this);
60
61 setFocusPolicy(Qt::StrongFocus);
62
63 gameBlocked = false;
64
65 timer = new QTimer(this);
66 //loadSettings();
67 connect(timer, SIGNAL(timeout()), SLOT(doMove()));
68}
69
70void Tron::loadSettings(){
71 createNewPlayfield();
72 reset();
73
74 // Velocity
75 setVelocity( lineSpeed() );
76
77 // Style
78 updatePixmap();
79 update();
80
81 // Player 0 is always human
82 if (Settings::gameType() == Settings::EnumGameType::PlayerVSPlayer)
83 {
84 players[1]->setComputer(false);
85 }
86 else
87 {
88 players[1]->setComputer(true);
89 }
90}
91
92Tron::~Tron()
93{
94 delete timer;
95 delete players[0];
96 delete players[1];
97}
98
99void Tron::resizeRenderer()
100{
101 // Block size
102 blockWidth = width() / (pf.getWidth() + 2);
103 blockHeight = height() / (pf.getHeight() + 2);
104 if (blockWidth > blockHeight)
105 {
106 blockWidth = blockHeight;
107 }
108 else
109 {
110 blockHeight = blockWidth;
111 }
112
113 Renderer::self()->boardResized(width(), height(), blockWidth, blockHeight);
114
115 Renderer::self()->resetPlayField();
116}
117
118void Tron::createNewPlayfield()
119{
120 resizeRenderer();
121
122 pf.initialize();
123}
124
125void Tron::newGame()
126{
127 players[0]->resetScore();
128 players[1]->resetScore();
129 //emit gameEnds(KTronEnum::Nobody);
130 emit updatedScore();
131 reset();
132}
133
134void Tron::reset()
135{
136 gamePaused = false;
137 stopGame();
138
139 players[0]->reset();
140 players[1]->reset();
141
142 if (Settings::gameType() == Settings::EnumGameType::Snake)
143 {
144 players[0]->resetScore();
145 players[1]->resetScore();
146 }
147
148 setVelocity( lineSpeed() );
149
150 modMoves = 0;
151
152 pf.initialize();
153
154 // set start coordinates
155 players[0]->setStartPosition();
156
157 if (Settings::gameType() != Settings::EnumGameType::Snake)
158 {
159 players[1]->setStartPosition();
160 }
161
162 updatePixmap();
163 update();
164
165 setFocus();
166
167 emit gameReset();
168 emit updatedScore();
169}
170
171//
172// Getters / Setters
173//
174
175PlayField *Tron::getPlayField()
176{
177 return &pf;
178}
179
180Player *Tron::getPlayer(int playerNr)
181{
182 if (playerNr != 0 && playerNr != 1)
183 {
184 kDebug() << "Inexistent player requested: " << playerNr;
185 return 0;
186 }
187
188 return players[playerNr];
189}
190
191
192/* *************************************************************** **
193** ??? functions **
194** *************************************************************** */
195
196void Tron::startGame()
197{
198 gameEnded = false;
199 emit pauseBlocked(false);
200
201 if (Settings::gameType() == Settings::EnumGameType::Snake)
202 {
203 newApple();
204 }
205
206 timer->start(velocity);
207}
208
209void Tron::itemHit(int playerNumber, int, int)
210{
211 //kDebug() << "Got Item Hit for " << playerNumber;
212
213 newApple();
214 players[playerNumber]->setEnlargement(3);
215 players[playerNumber]->addScore(5);
216 if (velocity > 15)
217 {
218 velocity--;
219 timer->stop();
220 timer->start(velocity);
221 }
222
223 emit updatedScore();
224}
225
226void Tron::newApple()
227{
228 int x = rand() % pf.getWidth();
229 int y = rand() % pf.getHeight();
230
231 while (pf.getObjectAt(x, y)->getObjectType() != ObjectType::Object)
232 {
233 x = rand() % pf.getWidth();
234 y = rand() % pf.getHeight();
235 }
236
237 //kDebug() << "Drawn apple at (" << x << ", " << y << ")";
238
239 apple.setType((int)(rand() % 3));
240
241 pf.setObjectAt(x, y, apple);
242}
243
244void Tron::newObstacle()
245{
246 // KSnake only
247 if (Settings::gameType() != Settings::EnumGameType::Snake)
248 return;
249
250 int x = rand() % pf.getWidth();
251 int y = rand() % pf.getHeight();
252
253 // Don't render if it's at an unwanted place
254 if (pf.getObjectAt(x, y)->getObjectType() != ObjectType::Object)
255 return;
256 else if (x == players[0]->getX() || y == players[0]->getY())
257 return;
258
259 Obstacle obst;
260 pf.setObjectAt(x, y, obst);
261
262 // Score +2
263 players[0]->addScore(2);
264 emit updatedScore();
265}
266
267void Tron::stopGame()
268{
269 timer->stop();
270 gameEnded = true;
271}
272
273void Tron::togglePause() // pause or continue game
274{
275 if (!gameEnded)
276 {
277 if (gamePaused)
278 {
279 gamePaused = false;
280 update();
281 timer->start(velocity);
282
283 emit updatedScore();
284 }
285 else
286 {
287 gamePaused = true;
288 timer->stop();
289 update();
290
291 emit updatedScore();
292 }
293 }
294}
295
296void Tron::showWinner()
297{
298 update();
299
300 emit gameEnds();
301 emit pauseBlocked(true);
302}
303
304/* *************************************************************** **
305** paint functions **
306** *************************************************************** */
307
308void Tron::updatePixmap()
309{
310 Renderer::self()->updatePlayField(pf);
311}
312
313/* *************************************************************** **
314** config functions **
315** *************************************************************** */
316
317void Tron::setVelocity(int newVel) // set new velocity
318{
319 velocity = (10 - newVel) * 15;
320
321 if (!gameEnded && !gamePaused)
322 {
323 timer->start(velocity);
324 }
325}
326
327/* *************************************************************** **
328** Events **
329** *************************************************************** */
330
331void Tron::paintEvent(QPaintEvent *e)
332{
333 QPainter p(this);
334
335 p.drawPixmap(e->rect().topLeft(), *Renderer::self()->getPlayField(), e->rect());
336
337 if (gamePaused) // if game is paused, print message
338 {
339 QString message = i18n("Game paused");
340 QPixmap messageBox = Renderer::self()->messageBox(message);
341 QPoint point(width() / 2 - messageBox.width() / 2, height() / 2 - messageBox.height() / 2);
342 p.drawPixmap(point, messageBox, e->rect());
343 }
344 else if (gameEnded) // If game ended, print "Crash!"
345 {
346 QString message = QString();
347
348 if (Settings::gameType() != Settings::EnumGameType::Snake) {
349 if (hasWinner())
350 {
351 int winner = getWinner();
352 int loser = 1 - winner;
353
354 QString winnerName = players[winner]->getName();
355 QString loserName = players[loser]->getName();
356 int winnerScore = players[winner]->getScore();
357 int loserScore = players[loser]->getScore();
358
359 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);
360 message += QLatin1Char( '\n' );
361 }
362 else
363 {
364 QString name1 = players[0]->getName();
365 QString name2 = players[1]->getName();
366 int points1 = players[0]->getScore();
367 int points2 = players[1]->getScore();
368
369 message += i18nc("%2 = 'x points' [player %1], %4 = 'x points' [player %3]",
370 "%1 (%2) versus %3 (%4)",
371 name2, i18np("%1 point", "%1 points", points2),
372 name1, i18np("%1 point", "%1 points", points1));
373 message += QLatin1Char( '\n' );
374 }
375 }
376 else
377 {
378 int points = players[0]->getScore();
379
380 message += i18np("KSnake game ended with 1 point", "KSnake game ended with %1 points", points);
381 message += QLatin1Char( '\n' );
382 }
383
384 if (Settings::gameType() == Settings::EnumGameType::PlayerVSPlayer) {
385 message += i18n("The game starts when each player has pressed one of their direction keys!");
386 }
387 else {
388 message += i18n("Press any of your direction keys to start!");
389 }
390
391 QPixmap messageBox = Renderer::self()->messageBox(message);
392 QPoint point(width() / 2 - messageBox.width() / 2, height() / 2 - messageBox.height() / 2);
393 p.drawPixmap(point, messageBox, e->rect());
394 }
395}
396
397void Tron::resizeEvent(QResizeEvent *)
398{
399 resizeRenderer();
400 updatePixmap();
401 update();
402}
403
404void Tron::triggerKey(int player, KBAction::Action action, bool trigger)
405{
406 if (action == KBAction::ACCELERATE && !trigger)
407 {
408 switchKeyOff(player, action);
409 }
410 else
411 {
412 switchKeyOn(player, action);
413 }
414}
415
416void Tron::switchKeyOn(int player, KBAction::Action action)
417{
418 // Set key pressed
419 if (!players[player]->isComputer())
420 {
421 switch (action)
422 {
423 case KBAction::UP:
424 case KBAction::DOWN:
425 case KBAction::LEFT:
426 case KBAction::RIGHT:
427 players[player]->setKeyPressed(true);
428 break;
429 case KBAction::ACCELERATE:
430 break;
431 default:
432 break;
433 }
434 }
435
436 // if both players press keys at the same time, start game...
437 if (players[0]->hasKeyPressed() && players[1]->hasKeyPressed())
438 {
439 // Start game
440 if (gameEnded && !gameBlocked)
441 {
442 if (hasWinner())
443 {
444 newGame();
445 }
446
447 reset();
448 startGame();
449 }
450 // ...or continue
451 else if (gamePaused)
452 {
453 togglePause();
454 }
455 }
456
457 // Key handling for movement
458 if (!players[player]->isComputer())
459 {
460 switch (action)
461 {
462 case KBAction::UP:
463 players[player]->setDirection(PlayerDirections::Up);
464 break;
465 case KBAction::DOWN:
466 players[player]->setDirection(PlayerDirections::Down);
467 break;
468 case KBAction::LEFT:
469 players[player]->setDirection(PlayerDirections::Left);
470 break;
471 case KBAction::RIGHT:
472 players[player]->setDirection(PlayerDirections::Right);
473 break;
474 case KBAction::ACCELERATE:
475 if (!Settings::acceleratorBlocked())
476 {
477 players[player]->setAccelerated(true);
478 }
479 break;
480 default:
481 break;
482 }
483 }
484}
485
486void Tron::switchKeyOff(int player, KBAction::Action action)
487{
488 if (!players[player]->isComputer())
489 {
490 switch (action)
491 {
492 case KBAction::UP:
493 case KBAction::DOWN:
494 case KBAction::LEFT:
495 case KBAction::RIGHT:
496 players[player]->setKeyPressed(false);
497 break;
498 case KBAction::ACCELERATE:
499 players[player]->setAccelerated(false);
500 break;
501 default:
502 break;
503 }
504 }
505}
506
507// if playingfield loses keyboard focus, pause game
508void Tron::focusOutEvent(QFocusEvent *)
509{
510 if(!gameEnded && !gamePaused)
511 {
512 togglePause();
513 }
514}
515
516/* *************************************************************** **
517** slots **
518** *************************************************************** */
519
520void Tron::unblockGame()
521{
522 gameBlocked = false;
523}
524
525// doMove() is called from QTimer
526void Tron::doMove()
527{
528 if (Settings::gameType() == Settings::EnumGameType::Snake)
529 {
530 players[0]->movePlayer();
531
532 modMoves++;
533
534 if (modMoves == 20)
535 {
536 modMoves = 0;
537 newObstacle();
538 }
539
540 updatePixmap();
541 update();
542
543 if (!players[0]->isAlive())
544 {
545 stopGame();
546 showWinner();
547 }
548 }
549 else
550 {
551 if (players[0]->isAccelerated() || players[1]->isAccelerated())
552 {
553 movementHelper(true);
554 }
555
556 if (!gameEnded)
557 {
558 // Player 0 is never a computer nowadays...
559 if (players[1]->isComputer())
560 {
561 intelligence.think(1);
562 }
563
564 movementHelper(false);
565 }
566 }
567
568 if (gameEnded)
569 {
570 //this is for waiting 1s before starting next game
571 gameBlocked = true;
572 QTimer::singleShot(1000, this, SLOT(unblockGame()));
573 }
574}
575
576void Tron::movementHelper(bool onlyAcceleratedPlayers)
577{
578 if (!onlyAcceleratedPlayers || players[0]->isAccelerated())
579 {
580 players[0]->movePlayer();
581 }
582
583 if (!onlyAcceleratedPlayers || players[1]->isAccelerated())
584 {
585 players[1]->movePlayer();
586 }
587
588 /* player collision check */
589 if (!players[1]->isAlive())
590 {
591 checkHeadToHeadCollission();
592 }
593
594 updatePixmap();
595 update();
596
597 // crashtest
598 if (!players[0]->isAlive() || !players[1]->isAlive())
599 {
600 stopGame();
601
602 if (!players[0]->isAlive() && !players[1]->isAlive())
603 {
604 // Don't award points when both players die
605 //players[0]->addScore(1);
606 //players[1]->addScore(1);
607 }
608 else if (!players[0]->isAlive())
609 {
610 players[1]->addScore(1);
611 }
612 else if (!players[1]->isAlive())
613 {
614 players[0]->addScore(1);
615 }
616
617 showWinner();
618 }
619}
620
621void Tron::checkHeadToHeadCollission()
622{
623 // As player 1 and player 2 move at the same time
624 // a head to head collission is possible
625 // but tough movement actually is done sequential
626 // we have to check back if player 1 should die when player 2 did so
627 // that's where this function comes in :)
628
629 int xInc = 0;
630 int yInc = 0;
631
632 switch (players[1]->getDirection())
633 {
634 case PlayerDirections::Left:
635 xInc = -1;
636 break;
637 case PlayerDirections::Right:
638 xInc = 1;
639 break;
640 case PlayerDirections::Up:
641 yInc = -1;
642 break;
643 case PlayerDirections::Down:
644 yInc = 1;
645 break;
646 default:
647 break;
648 }
649
650 if ((players[1]->getX() + xInc) == players[0]->getX())
651 {
652 if ((players[1]->getY() + yInc) == players[0]->getY())
653 {
654 players[0]->die();
655 }
656 }
657}
658
659/**
660 * Skill settings
661 */
662
663/** retrieves the line speed */
664int Tron::lineSpeed() {
665 switch (Kg::difficultyLevel()) {
666 case KgDifficultyLevel::VeryEasy:
667 return 2;
668 default:
669 case KgDifficultyLevel::Easy:
670 return 3;
671 case KgDifficultyLevel::Medium:
672 return 5;
673 case KgDifficultyLevel::Hard:
674 return 7;
675 case KgDifficultyLevel::VeryHard:
676 return 8;
677 }
678}
679
680bool Tron::running() {
681 return !gameEnded;
682}
683
684bool Tron::paused() {
685 return !gameEnded && gamePaused;
686}
687
688bool Tron::hasWinner()
689{
690 return getWinner() == 0 || getWinner() == 1;
691}
692
693int Tron::getWinner()
694{
695 if (Settings::gameType() != Settings::EnumGameType::Snake)
696 {
697 if (players[0]->getScore() >= WINNING_DIFF && players[1]->getScore() < players[0]->getScore() - 1) {
698 return 0;
699 }
700 else if (players[1]->getScore() >= WINNING_DIFF && players[0]->getScore() < players[1]->getScore() - 1) {
701 return 1;
702 }
703
704 }
705
706 return -1;
707}
708
709#include "tron.moc"
710
711