1/*
2 Copyright 2003 Russell Steffen <rsteffen@bayarea.net>
3 Copyright 2003 Stephan Zehetner <s.zehetner@nevox.org>
4 Copyright 2006 Dmitry Suzdalev <dimsuz@gmail.com>
5 Copyright 2006 Inge Wallin <inge@lysator.liu.se>
6 Copyright 2006 Pierre Ducroquet <pinaraf@gmail.com>
7 Copyright 2011 Jeffrey Kelling <overlordapophis@gmail.com>
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24#include "gameview.h"
25
26#include <QCheckBox>
27#include <QDockWidget>
28#include <QLabel>
29#include <QPushButton>
30#include <QLineEdit>
31#include <QTextEdit>
32#include <QPixmap>
33#include <QKeyEvent>
34#include <QIntValidator>
35#include <KDebug>
36
37#include <kmessagebox.h>
38
39#include "players/player.h"
40#include "players/localplayer.h"
41#include "images.h"
42#include "map/mapview.h"
43#include "map/mapscene.h"
44
45#include "dialogs/newgamedlg.h"
46#include "dialogs/scoredlg.h"
47#include "dialogs/fleetdlg.h"
48
49#include "view/standingswidget.h"
50
51#include <cmath>
52
53/*********************************************************************
54 Game Board
55*********************************************************************/
56
57GameView::GameView(QWidget *parent, Game *game, QDockWidget *messagesDock, QDockWidget *standingsDock)
58 : QWidget( parent ),
59 m_msgWidgetLastTurn(0),
60 m_messagesDock(messagesDock),
61 m_standingsDock(standingsDock),
62 m_game( game ),
63 m_queueMessages(false),
64 m_messageQueue(),
65 m_showInformations(false),
66 m_initCompleted( false ),
67 m_cleanupNeeded( false ),
68 m_guiState( NONE )
69{
70 QPalette blackPal;
71 blackPal.setColor( backgroundRole(), Qt::black );
72 setPalette( blackPal );
73 setAutoFillBackground( true );
74
75 QColor col(Qt::green);
76 QPalette palette;
77 palette.setColorGroup( QPalette::Active, Qt::white, Qt::black,
78 col.lighter(), col.darker(), col,
79 col.lighter(125), col.lighter(125), col.darker(),
80 Qt::black );
81 palette.setColorGroup( QPalette::Inactive, Qt::white, Qt::black,
82 col.lighter(), col.darker(), col,
83 col.lighter(125), col.lighter(125), col.darker(),
84 Qt::black );
85 palette.setColorGroup( QPalette::Disabled, Qt::white, QColor(Qt::darkGray).darker(),
86 col.lighter(), col.darker(), col,
87 col.darker(150), col.lighter(125), Qt::black,
88 Qt::black );
89
90 blackPal.setColor( QPalette::Base, Qt::black );
91 blackPal.setColor( QPalette::Window, Qt::black );
92 blackPal.setColor( QPalette::Button, QColor(Qt::darkGray).darker() );
93 blackPal.setColor( QPalette::Text, Qt::white );
94 blackPal.setColor( QPalette::ButtonText, Qt::white );
95 blackPal.setColor( QPalette::WindowText, Qt::white );
96
97 //********************************************************************
98 // Create the widgets in the main window
99 //********************************************************************
100 m_mapScene = new MapScene(m_game);
101 m_mapWidget = new MapView( m_mapScene );
102 m_mapWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
103 m_mapWidget->setFrameShape(QFrame::NoFrame);
104
105 m_msgWidget = new QTextEdit();
106 m_msgWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
107 m_msgWidget->setReadOnly(true);
108 m_msgWidget->setPalette( blackPal );
109 m_msgWidget->setAutoFillBackground( true );
110
111 m_messagesDock->setWidget(m_msgWidget);
112
113 m_standingsWidget = new StandingsWidget(0);
114 m_standingsDock->setWidget(m_standingsWidget);
115
116 m_gameMessage = new QLabel( this );
117 m_gameMessage->setPalette( palette );
118
119 m_endTurnBtn = new QPushButton( i18n("End Turn"), this );
120 m_endTurnBtn->setFixedSize( m_endTurnBtn->sizeHint() );
121 m_endTurnBtn->setPalette( palette );
122
123 m_shipCountEdit = new QLineEdit( this );
124 m_shipValidator = new QIntValidator(1, 32767, this );
125 m_shipCountEdit->setValidator( m_shipValidator );
126 m_shipCountEdit->setMinimumSize( 40, 0 );
127 m_shipCountEdit->setMaximumSize( 32767, 40 );
128 m_shipCountEdit->setEnabled(false);
129 m_shipCountEdit->setPalette( palette );
130 m_shipCountEdit->setEchoMode( QLineEdit::Password );
131
132 m_standingOrder = new QCheckBox(i18n("Standing order"), this);
133 m_standingOrder->setEnabled(false);
134 m_standingOrder->setPalette( palette );
135 m_standingOrder->setCheckState(Qt::Unchecked);
136
137 m_splashScreen = new QLabel( this );
138 m_splashScreen->setPixmap(QPixmap(IMAGE_SPLASH));
139 m_splashScreen->setScaledContents(true);
140
141 setMouseTracking( true );
142 setFocusPolicy( Qt::StrongFocus );
143 setFocus();
144
145 //********************************************************************
146 // Layout the main window
147 //********************************************************************
148 QVBoxLayout *mainLayout = new QVBoxLayout( this );
149 QHBoxLayout *topLineLayout = new QHBoxLayout;
150
151 topLineLayout->addSpacing( 5 );
152 topLineLayout->addWidget( m_gameMessage, 10 );
153 topLineLayout->addWidget( m_standingOrder, 1 );
154 topLineLayout->addWidget( m_shipCountEdit, 1 );
155 topLineLayout->addWidget( m_endTurnBtn, 1 );
156
157 mainLayout->addLayout( topLineLayout );
158 mainLayout->addWidget( m_mapWidget );
159
160 //**********************************************************************
161 // Set up signal/slot connections
162 //**********************************************************************
163 connect( m_mapScene, SIGNAL( planetSelected(Planet *) ),
164 this, SLOT(planetSelected(Planet *)) );
165 connect( m_shipCountEdit, SIGNAL(returnPressed()),
166 this, SLOT(newShipCount()) );
167 connect( m_standingOrder, SIGNAL(clicked()),
168 this, SLOT(standingOrdersClicked()) );
169 connect( m_endTurnBtn, SIGNAL( clicked() ),
170 this, SLOT( nextPlayer() ) );
171
172 changeGameView();
173}
174
175//**********************************************************************
176// Destructor
177//**********************************************************************
178GameView::~GameView()
179{
180 // Nothing much to do yet
181}
182
183//************************************************************************
184// Event handlers
185//************************************************************************
186void GameView::standingOrdersClicked() {
187 m_shipCountEdit->setFocus();
188 if(m_standingOrder->checkState() == Qt::Checked)
189 m_shipValidator->setTop(INT_MAX);
190 else
191 m_shipValidator->setTop(sourcePlanet->fleet().shipCount());
192}
193
194void
195GameView::resizeEvent ( QResizeEvent * ) {
196 m_splashScreen->setGeometry( 0, 0, width(), height() );
197}
198
199void
200GameView::keyPressEvent( QKeyEvent *e )
201{
202 // Check for the escape key
203 if( e->key() == Qt::Key_Escape ) {
204 switch( m_guiState ) {
205 case DEST_PLANET:
206 case SHIP_COUNT:
207 case RULER_SOURCE:
208 case RULER_DEST:
209 m_guiState = SOURCE_PLANET;
210 haveSourcePlanet = false;
211 haveDestPlanet = false;
212 turn();
213 break;
214 default:
215 break;
216 }
217 m_mapScene->displayPlanetInfo( NULL );
218 m_mapScene->unselectPlanet();
219 m_showInformations = false;
220 return;
221 }
222
223 if( e->text().isEmpty() || e->text().at(0).isSpace() ) {
224 e->ignore();
225 return;
226 }
227
228 if ( e->text() == "?" ) {
229 // Switch the information key info...
230 m_showInformations = !m_showInformations;
231 return;
232 }
233
234 QString planetName;
235
236 planetName += e->text().toUpper();
237
238 foreach (Planet *p, m_game->planets()) {
239 if( p->name() == planetName ) {
240 if ( m_showInformations ) {
241 m_mapScene->selectPlanet(p);
242 m_mapScene->displayPlanetInfo( p );
243 m_showInformations = false;
244 } else {
245 m_mapScene->displayPlanetInfo( NULL );
246 m_mapScene->selectPlanet(p);
247 planetSelected( p );
248 }
249 break;
250 }
251 }
252
253}
254
255
256//************************************************************************
257// Game engine/state machine
258//************************************************************************
259
260/**
261 * Prepare the turn for a local player by updating the informational widgets.
262 */
263
264void
265GameView::turnPreparation()
266{
267 m_standingsWidget->update(m_game->players());
268
269 turn();
270}
271
272
273void
274GameView::turn()
275{
276 switch( m_guiState ) {
277 case NONE :
278 // The standby state, waiting for clicking on a planet or starting
279 // to measure a distance..
280 m_guiState = SOURCE_PLANET;
281 haveSourcePlanet = false;
282 haveDestPlanet = false;
283 haveShipCount = false;
284 standingOrder = false;
285 shipCount = 0;
286 m_mapScene->unselectPlanet();
287
288 turn();
289 setFocus();
290 break;
291
292 case SOURCE_PLANET :
293 // The user has clicked on a source planet for moving some fleets.
294 if( haveSourcePlanet ) {
295 m_guiState = DEST_PLANET;
296
297 m_mapScene->selectPlanet(sourcePlanet);
298 turn();
299
300 } else {
301 m_shipCountEdit->setEnabled(false);
302 m_shipCountEdit->setText( QString() );
303 m_standingOrder->setEnabled(false);
304 m_standingOrder->setCheckState(Qt::Unchecked);
305 m_mapScene->unselectPlanet();
306 m_gameMessage->setText( i18n("%1: Select source planet...", m_game->currentPlayer()->coloredName()) );
307 setFocus();
308 }
309
310 break;
311
312 case DEST_PLANET :
313 // The user has chosen a destination planet and should now
314 // specify a number of ships.
315 if( haveDestPlanet ) {
316 m_mapScene->unselectPlanet();
317 m_guiState = SHIP_COUNT;
318 turn();
319 } else {
320 m_shipCountEdit->setEnabled(false);
321 m_standingOrder->setEnabled(false);
322 m_mapScene->selectPlanet(sourcePlanet);
323 m_gameMessage->setText( i18n("%1: Select destination planet...", m_game->currentPlayer()->coloredName()) );
324 setFocus();
325 }
326
327 break;
328
329 case SHIP_COUNT:
330 // The user has selected, source, distance, ship count.
331 if( haveShipCount ) {
332 // We now have a complete fleet to send, so send it
333 if( !m_game->attack(sourcePlanet, destPlanet, shipCount, standingOrder) ) {
334 KMessageBox::error( this, i18n("Not enough ships to send.") );
335 }
336
337 m_shipCountEdit->setEnabled(false);
338 m_standingOrder->setEnabled(false);
339
340 m_guiState = NONE;
341 turn();
342
343 m_endTurnBtn->setFocus();
344
345 } else {
346 m_gameMessage->setText( i18n("%1: How many ships?", m_game->currentPlayer()->coloredName()) );
347
348 m_shipCountEdit->setEnabled(true);
349 m_standingOrder->setEnabled(true);
350 m_shipCountEdit->setFocus();
351
352 m_mapScene->unselectPlanet();
353 }
354
355 break;
356
357 case RULER_SOURCE:
358 // The user has selected to measure a distance with the ruler.
359 if( haveSourcePlanet ) {
360 m_guiState = RULER_DEST;
361 m_mapScene->selectPlanet(sourcePlanet);
362 turn();
363 } else {
364 m_shipCountEdit->setEnabled(false);
365 m_mapScene->unselectPlanet();
366
367 m_gameMessage->setText( i18n("Ruler: Select starting planet.") );
368 setFocus();
369 }
370 break;
371
372 case RULER_DEST:
373 if( haveDestPlanet ) {
374 m_mapScene->unselectPlanet();
375
376 // Display the distance between the two planets
377 double dist = m_game->map()->distance( sourcePlanet,
378 destPlanet );
379
380 QString msg;
381 msg = i18n("The distance from Planet %1 to Planet %2 is %3 light years.\n"
382 "A ship leaving this turn will arrive on turn %4",
383 sourcePlanet->name(),
384 destPlanet->name(),
385 QString::number(dist, 'f', 1),
386 m_game->turnCounter() + (int)std::ceil(dist));
387 KMessageBox::information( this, msg, i18n("Distance"));
388
389 m_guiState = NONE;
390 turn();
391 } else {
392 m_gameMessage->setText( i18n("Ruler: Select ending planet.") );
393 m_shipCountEdit->setEnabled(false);
394 m_mapScene->selectPlanet(sourcePlanet);
395
396 setFocus();
397 }
398 break;
399
400 default:
401 break;
402 }
403
404 m_endTurnBtn->setEnabled(m_guiState == SOURCE_PLANET);
405
406 emit newGUIState( m_guiState );
407}
408
409void
410GameView::gameMsg(const KLocalizedString &msg, Player *player, Planet *planet,
411 Player *planetPlayer)
412{
413 bool isHumanInvolved = false;
414
415 QString color = "white";
416 KLocalizedString colorMsg = msg;
417 KLocalizedString plainMsg = msg;
418
419 if (player) {
420 if (!player->isAiPlayer())
421 isHumanInvolved = true;
422 colorMsg = colorMsg.subs(player->coloredName());
423 plainMsg = plainMsg.subs(player->name());
424 }
425
426 if (planet) {
427 if (!planetPlayer)
428 planetPlayer = planet->player();
429 if (!planetPlayer->isAiPlayer() && !planetPlayer->isNeutral())
430 isHumanInvolved = true;
431
432 QString color = planetPlayer->color().name();
433 colorMsg = colorMsg.subs(QString("<font color=\"%1\">%2</font>")
434 .arg(color, planet->name()));
435 plainMsg = plainMsg.subs(planet->name());
436 }
437
438 if (m_msgWidgetLastTurn < m_game->turnCounter()) {
439 m_msgWidgetLastTurn = m_game->turnCounter();
440
441 m_msgWidget->append("<font color=\"gray\">"
442 + i18n("Turn %1:", m_game->turnCounter())
443 + "</font>");
444 }
445
446 m_msgWidget->append("- <font color=\"" + color + "\">"
447 + colorMsg.toString()+"</font>");
448 m_msgWidget->moveCursor(QTextCursor::End);
449
450 if (isHumanInvolved) {
451 if ( m_queueMessages ) {
452 GameMessage msg;
453
454 msg.text = colorMsg.toString();
455 msg.sender = player;
456 msg.receiver = planetPlayer;
457 m_messageQueue.append(msg);
458 } else {
459 KMessageBox::information(this, plainMsg.toString());
460 }
461 }
462}
463
464//************************************************************************
465// Confirm aborting existing game
466//************************************************************************
467bool GameView::confirmNewGame()
468{
469 if( m_game->isRunning() )
470 return shutdownGame();
471
472 return true;
473}
474
475//************************************************************************
476// Set up the game board for a new game
477//************************************************************************
478void
479GameView::startNewGame()
480{
481 NewGameDlg *newGame = new NewGameDlg( this, m_game );
482
483 if( !newGame->exec() ) {
484 delete newGame;
485 return;
486 }
487
488 newGame->save(); // Save settings for next time
489
490 delete newGame;
491
492 foreach (Player *player, m_game->players()) {
493 if (player->isSpectator()) {
494
495 // All planets a spectator player has assigned are turned into
496 // neutral planets so that a spectator player starts without
497 // any planet.
498
499 m_game->map()->turnOverPlayerPlanets(player, m_game->neutral());
500 }
501
502 LocalPlayer *local = qobject_cast<LocalPlayer*>(player);
503 if (local)
504 connect(local, SIGNAL(canPlay()), this, SLOT(turnPreparation()));
505 }
506
507 connect(m_game, SIGNAL(finished()), this, SLOT(gameOver()));
508 m_game->start();
509
510 // Fix all the widgets to run a new game.
511 changeGameView();
512
513 // setup the map scene
514 m_mapScene->mapUpdate();
515
516 // Set up the base GUI for a new game.
517 m_msgWidget->clear();
518 m_msgWidgetLastTurn = 0;
519 m_shipCountEdit->setEnabled(false);
520 m_initCompleted = true;
521
522 //call GameView::gameOver now if needed happens if the game ends immediately after starting.
523 if(m_cleanupNeeded)
524 gameOver();
525}
526
527
528//************************************************************************
529// Shut down the current game
530//************************************************************************
531bool
532GameView::shutdownGame()
533{
534 if (!m_game->isRunning())
535 return true;
536
537 int choice = KMessageBox::warningContinueCancel
538 ( this,
539 i18n("Do you wish to retire this game?"),
540 i18n("End Game"),
541 KStandardGuiItem::ok() );
542
543 if( choice == KMessageBox::Cancel )
544 return false;
545
546 gameOver();
547 return true;
548}
549
550
551void
552GameView::gameOver()
553{
554 if (m_initCompleted) {
555 kDebug() << "Game over";
556
557 /**
558 * @todo This is an attempt to remove duplicate information from screen.
559 * It is not a final solution, but only the best we came up with. The
560 * problem is that the messages cannot be seen anymore, so the player
561 * cannot check what happened last. Furthermore, this sudden change of
562 * the GUI setup can be confusing for players.
563 */
564
565 m_messagesDock->hide();
566 m_standingsDock->hide();
567
568 ScoreDlg *scoreDlg = new ScoreDlg(this, i18n("Final Standings"), m_game->players());
569 scoreDlg->exec();
570 scoreDlg->deleteLater();
571
572 cleanupGame();
573 }
574 else
575 m_cleanupNeeded = true;
576}
577
578
579void
580GameView::cleanupGame()
581{
582 m_mapScene->clearMap();
583 m_game->stop();
584
585 m_endTurnBtn->setEnabled( false );
586
587 changeGameView();
588 m_guiState = NONE;
589
590 //m_game->cleanupGame();
591
592 m_initCompleted = false;
593 m_cleanupNeeded = false;
594 emit newGUIState(m_guiState);
595}
596
597
598//************************************************************************
599// Player selected a planet
600//************************************************************************
601void
602GameView::planetSelected( Planet *planet )
603{
604 kDebug() << "planetSelected with " << m_guiState;
605 switch( m_guiState ) {
606 case SOURCE_PLANET:
607 if( planet->player() == m_game->currentPlayer() ) {
608 // got a match
609 m_shipValidator->setRange(1, planet->fleet().shipCount());
610 haveSourcePlanet = true;
611 sourcePlanet = planet;
612
613 turn();
614 return;
615 }
616 break;
617 case RULER_SOURCE:
618 haveSourcePlanet = true;
619 sourcePlanet = planet;
620 turn();
621 return;
622 case DEST_PLANET:
623 case RULER_DEST:
624 if( planet != sourcePlanet ) {
625 // got a match
626 haveDestPlanet = true;
627 destPlanet = planet;
628
629 turn();
630 }
631 return;
632 default:
633 break;
634 }
635
636 // The selected planet just cannot be selected, cancel it.
637 m_mapScene->unselectPlanet();
638}
639
640
641//************************************************************************
642// Player hit return in the ship count edit box
643//************************************************************************
644void
645GameView::newShipCount()
646{
647 bool ok;
648
649 switch (m_guiState) {
650 case SHIP_COUNT:
651 shipCount = m_shipCountEdit->text().toInt(&ok);
652 standingOrder = m_standingOrder->checkState() == Qt::Checked;
653 if (ok)
654 haveShipCount = true;
655 m_shipCountEdit->setText( QString() );
656 m_standingOrder->setCheckState(Qt::Unchecked);
657 turn();
658 break;
659
660 default:
661 break;
662 };
663}
664
665//**********************************************************************
666// transition board from play to non-play
667//**********************************************************************
668void
669GameView::changeGameView()
670{
671 bool isRunning = m_game->isRunning();
672
673 kDebug() << "Calling GameView::changeGameView" << isRunning;
674
675 m_messagesDock->setVisible(isRunning);
676
677 if (!isRunning) {
678
679 // Only hide the standings dock if the game is not running, but do not
680 // automatically show it as soon as the game is running.
681
682 m_standingsDock->hide();
683 }
684
685 m_mapWidget->setVisible(isRunning);
686 m_gameMessage->setVisible(isRunning);
687 m_standingOrder->setVisible(isRunning);
688 m_shipCountEdit->setVisible(isRunning);
689 m_endTurnBtn->setVisible(isRunning);
690 m_splashScreen->setVisible(!isRunning); // negation
691}
692
693
694//************************************************************************
695// Player clicked the 'End Turn' button
696//************************************************************************
697void
698GameView::nextPlayer()
699{
700 // Hum, this should be straightforward
701 Player *currentPlayer = m_game->currentPlayer();
702 LocalPlayer *humanPlayer = qobject_cast<LocalPlayer*>(currentPlayer);
703 if (humanPlayer)
704 humanPlayer->done();
705 /*
706 if(m_game->options().BlindMap && m_game->humanPlayerCount() > 1) {
707 QString name = m_gameLogic->currentPlayer()->name();
708 m_gameLogic->setBlindBreak(true);
709 m_mapScene->update();
710 KMessageBox::information(this, "Blind Map " + name + " Turn done");
711 m_gameLogic->setBlindBreak(false);
712 }
713 */
714}
715
716//************************************************************************
717// Toolbar items
718//************************************************************************
719void
720GameView::measureDistance()
721{
722 switch( m_guiState ) {
723 case SOURCE_PLANET:
724 m_guiState = RULER_SOURCE;
725 turn();
726 default:
727 break;
728 }
729}
730
731
732void
733GameView::showFleets()
734{
735 Player *current = m_game->currentPlayer();
736 FleetDlg *fleetDlg = new FleetDlg( this, current->attackList(),
737 current->newAttacks(), current->standingOrders());
738 if (fleetDlg->exec()) {
739 AttackFleetList *deleteAttacks = fleetDlg->uncheckedFleets();
740 foreach(AttackFleet *curFleet, *deleteAttacks) {
741 current->cancelNewAttack(curFleet);
742 }
743 delete deleteAttacks;
744 m_mapScene->update();
745 }
746 fleetDlg->deleteLater();
747}
748