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 | |
51 | Tron::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 | |
70 | void 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 | |
92 | Tron::~Tron() |
93 | { |
94 | delete timer; |
95 | delete players[0]; |
96 | delete players[1]; |
97 | } |
98 | |
99 | void 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 | |
118 | void Tron::createNewPlayfield() |
119 | { |
120 | resizeRenderer(); |
121 | |
122 | pf.initialize(); |
123 | } |
124 | |
125 | void Tron::newGame() |
126 | { |
127 | players[0]->resetScore(); |
128 | players[1]->resetScore(); |
129 | //emit gameEnds(KTronEnum::Nobody); |
130 | emit updatedScore(); |
131 | reset(); |
132 | } |
133 | |
134 | void 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 | |
175 | PlayField *Tron::getPlayField() |
176 | { |
177 | return &pf; |
178 | } |
179 | |
180 | Player *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 | |
196 | void 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 | |
209 | void 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 | |
226 | void 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 | |
244 | void 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 | |
267 | void Tron::stopGame() |
268 | { |
269 | timer->stop(); |
270 | gameEnded = true; |
271 | } |
272 | |
273 | void 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 | |
296 | void Tron::showWinner() |
297 | { |
298 | update(); |
299 | |
300 | emit gameEnds(); |
301 | emit pauseBlocked(true); |
302 | } |
303 | |
304 | /* *************************************************************** ** |
305 | ** paint functions ** |
306 | ** *************************************************************** */ |
307 | |
308 | void Tron::updatePixmap() |
309 | { |
310 | Renderer::self()->updatePlayField(pf); |
311 | } |
312 | |
313 | /* *************************************************************** ** |
314 | ** config functions ** |
315 | ** *************************************************************** */ |
316 | |
317 | void 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 | |
331 | void 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 | |
397 | void Tron::resizeEvent(QResizeEvent *) |
398 | { |
399 | resizeRenderer(); |
400 | updatePixmap(); |
401 | update(); |
402 | } |
403 | |
404 | void 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 | |
416 | void 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 | |
486 | void 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 |
508 | void Tron::focusOutEvent(QFocusEvent *) |
509 | { |
510 | if(!gameEnded && !gamePaused) |
511 | { |
512 | togglePause(); |
513 | } |
514 | } |
515 | |
516 | /* *************************************************************** ** |
517 | ** slots ** |
518 | ** *************************************************************** */ |
519 | |
520 | void Tron::unblockGame() |
521 | { |
522 | gameBlocked = false; |
523 | } |
524 | |
525 | // doMove() is called from QTimer |
526 | void 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 | |
576 | void 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 | |
621 | void 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 */ |
664 | int 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 | |
680 | bool Tron::running() { |
681 | return !gameEnded; |
682 | } |
683 | |
684 | bool Tron::paused() { |
685 | return !gameEnded && gamePaused; |
686 | } |
687 | |
688 | bool Tron::hasWinner() |
689 | { |
690 | return getWinner() == 0 || getWinner() == 1; |
691 | } |
692 | |
693 | int 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 | |