1/*******************************************************************
2 *
3 * Copyright 2006 Dmitry Suzdalev <dimsuz@gmail.com>
4 * Copyright 2010 Brian Croom <brian.s.croom@gmail.com>
5 * Copyright 2013 Denis Kuplyakov <dener.kup@gmail.com>
6 *
7 * This file is part of the KDE project "KReversi"
8 *
9 * KReversi 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, or (at your option)
12 * any later version.
13 *
14 * KReversi 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 KReversi; see the file COPYING. If not, write to
21 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 ********************************************************************/
25#include <mainwindow.h>
26
27#include <KDebug>
28#include <KIcon>
29#include <KLocale>
30#include <KMessageBox>
31#include <KStandardDirs>
32#include <KStatusBar>
33#include <KActionCollection>
34#include <KStandardGameAction>
35#include <KExtHighscore>
36
37#include <commondefs.h>
38#include <kreversihumanplayer.h>
39#include <kreversicomputerplayer.h>
40
41static const int BLACK_STATUSBAR_ID = 1;
42static const int WHITE_STATUSBAR_ID = 2;
43static const int COMMON_STATUSBAR_ID = 0;
44
45KReversiMainWindow::KReversiMainWindow(QWidget* parent, bool startDemo)
46 : KXmlGuiWindow(parent), m_view(0), m_game(0),
47 m_historyDock(0), m_historyView(0),
48 m_firstShow(true), m_startInDemoMode(startDemo),
49 m_undoAct(0), m_hintAct(0), m_startDialog(0)
50{
51 memset(m_player, 0, sizeof(m_player));
52
53 m_provider = new KgThemeProvider();
54 m_provider->discoverThemes("appdata", QLatin1String("pics"));
55
56 statusBar()->insertItem(i18n("Press start game!"), COMMON_STATUSBAR_ID);
57 statusBar()->insertItem("", BLACK_STATUSBAR_ID);
58 statusBar()->insertItem("", WHITE_STATUSBAR_ID);
59
60 // initialize difficulty stuff
61 Kg::difficulty()->addStandardLevelRange(
62 KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Impossible,
63 KgDifficultyLevel::Easy //default
64 );
65
66 KgDifficultyGUI::init(this);
67 connect(Kg::difficulty(), SIGNAL(currentLevelChanged(const KgDifficultyLevel*)), SLOT(levelChanged()));
68 Kg::difficulty()->setEditable(false);
69
70 // initialize history dock
71 m_historyView = new QListWidget(this);
72 m_historyView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
73 m_historyDock = new QDockWidget(i18n("Move History"));
74 m_historyDock->setWidget(m_historyView);
75 m_historyDock->setObjectName("history_dock");
76
77 m_historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
78 addDockWidget(Qt::RightDockWidgetArea, m_historyDock);
79
80 // create main game view
81 m_view = new KReversiView(m_game, this, m_provider);
82 setCentralWidget(m_view);
83
84 // initialise dialog handler
85 m_startDialog = new StartGameDialog(this, m_provider);
86 connect(m_startDialog, SIGNAL(startGame()), this, SLOT(slotDialogReady()));
87
88 // initialise actions
89 setupActionsInit();
90
91 // load saved settings
92 loadSettings();
93
94 setupGUI(qApp->desktop()->availableGeometry().size() * 0.7);
95
96 m_historyDock->hide();
97}
98
99KReversiMainWindow::~KReversiMainWindow()
100{
101 delete m_provider;
102}
103
104void KReversiMainWindow::setupActionsInit()
105{
106 // Common actions
107 KStandardGameAction::gameNew(this, SLOT(slotNewGame()), actionCollection());
108 KStandardGameAction::highscores(this, SLOT(slotHighscores()), actionCollection());
109 KStandardGameAction::quit(this, SLOT(close()), actionCollection());
110
111 // Undo
112 m_undoAct = KStandardGameAction::undo(this, SLOT(slotUndo()), actionCollection());
113 m_undoAct->setEnabled(false); // nothing to undo at the start of the game
114
115 // Hint
116 m_hintAct = KStandardGameAction::hint(m_view, SLOT(slotHint()), actionCollection());
117 m_hintAct->setEnabled(false);
118
119 // Last move
120 m_showLast = new KToggleAction(KIcon(QLatin1String("lastmoves")), i18n("Show Last Move"), this);
121 actionCollection()->addAction(QLatin1String("show_last_move"), m_showLast);
122 connect(m_showLast, SIGNAL(triggered(bool)), m_view, SLOT(setShowLastMove(bool)));
123
124 // Legal moves
125 m_showLegal = new KToggleAction(KIcon(QLatin1String("legalmoves")), i18n("Show Legal Moves"), this);
126 actionCollection()->addAction(QLatin1String("show_legal_moves"), m_showLegal);
127 connect(m_showLegal, SIGNAL(triggered(bool)), m_view, SLOT(setShowLegalMoves(bool)));
128
129 // Animation speed
130 m_animSpeedAct = new KSelectAction(i18n("Animation Speed"), this);
131 actionCollection()->addAction(QLatin1String("anim_speed"), m_animSpeedAct);
132
133 QStringList acts;
134 acts << i18n("Slow") << i18n("Normal") << i18n("Fast");
135 m_animSpeedAct->setItems(acts);
136 connect(m_animSpeedAct, SIGNAL(triggered(int)), SLOT(slotAnimSpeedChanged(int)));
137
138 // Chip's color
139 m_coloredChipsAct = new KToggleAction(i18n("Use Colored Chips"), this);
140 actionCollection()->addAction(QLatin1String("use_colored_chips"), m_coloredChipsAct);
141 connect(m_coloredChipsAct, SIGNAL(triggered(bool)), SLOT(slotUseColoredChips(bool)));
142
143 // Move history
144 // NOTE: read/write this from/to config file? Or not necessary?
145 m_showMovesAct = new KToggleAction(KIcon(QLatin1String("view-history")), i18n("Show Move History"), this);
146 actionCollection()->addAction(QLatin1String("show_moves"), m_showMovesAct);
147 connect(m_showMovesAct, SIGNAL(triggered(bool)), SLOT(slotShowMovesHistory(bool)));
148}
149
150void KReversiMainWindow::loadSettings()
151{
152 // Animation speed
153 m_animSpeedAct->setCurrentItem(Preferences::animationSpeed());
154 m_view->setAnimationSpeed(Preferences::animationSpeed());
155
156 // Chip's color
157 m_coloredChipsAct->setChecked(Preferences::useColoredChips());
158 m_view->setChipsPrefix(Preferences::useColoredChips() ?
159 Colored : BlackWhite);
160 m_startDialog->setChipsPrefix(Preferences::useColoredChips() ?
161 Colored : BlackWhite);
162}
163
164void KReversiMainWindow::levelChanged()
165{
166 // we are assuming that level can be changed here only when it is
167 // USER-AI or AI-USER match
168
169 int skill = Utils::difficultyLevelToInt();
170
171 if (m_nowPlayingInfo.type[White] == GameStartInformation::AI)
172 ((KReversiComputerPlayer *)(m_player[White]))->setSkill(skill);
173 else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human)
174 ((KReversiComputerPlayer *)(m_player[Black]))->setSkill(skill);
175}
176
177void KReversiMainWindow::slotAnimSpeedChanged(int speed)
178{
179 m_view->setAnimationSpeed(speed);
180 Preferences::setAnimationSpeed(speed);
181 Preferences::self()->writeConfig();
182}
183
184void KReversiMainWindow::slotUseColoredChips(bool toggled)
185{
186 ChipsPrefix chipsPrefix = m_coloredChipsAct->isChecked() ?
187 Colored :
188 BlackWhite;
189 m_view->setChipsPrefix(chipsPrefix);
190 m_startDialog->setChipsPrefix(chipsPrefix);
191 Preferences::setUseColoredChips(toggled);
192 Preferences::self()->writeConfig();
193}
194
195void KReversiMainWindow::slotShowMovesHistory(bool toggled)
196{
197 m_historyDock->setVisible(toggled);
198 m_view->setShowBoardLabels(toggled);
199}
200
201void KReversiMainWindow::slotNewGame()
202{
203 m_startDialog->exec();
204}
205
206void KReversiMainWindow::slotGameOver()
207{
208 m_hintAct->setEnabled(false);
209 m_undoAct->setEnabled(m_game->canUndo());
210
211 int blackScore = m_game->playerScore(Black);
212 int whiteScore = m_game->playerScore(White);
213
214 bool storeScore = false;
215 KExtHighscore::Score score;
216
217 QString res;
218 if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human
219 && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // we are playing black
220 storeScore = true;
221 KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[White])->lowestSkill());
222 score.setScore(blackScore);
223 if (blackScore == whiteScore) {
224 res = i18n("Game is drawn!");
225 score.setType(KExtHighscore::Draw);
226 } else if (blackScore > whiteScore) {
227 res = i18n("You win!");
228 score.setType(KExtHighscore::Won);
229 } else {
230 res = i18n("You have lost!");
231 score.setType(KExtHighscore::Lost);
232 }
233 } else if (m_nowPlayingInfo.type[White] == GameStartInformation::Human
234 && m_nowPlayingInfo.type[Black] == GameStartInformation::AI) { // we are playing white
235 storeScore = true;
236 KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[Black])->lowestSkill());
237 score.setScore(whiteScore);
238 if (blackScore == whiteScore) {
239 res = i18n("Game is drawn!");
240 score.setType(KExtHighscore::Draw);
241 } else if (blackScore < whiteScore) {
242 res = i18n("You win!");
243 score.setType(KExtHighscore::Won);
244 } else {
245 res = i18n("You have lost!");
246 score.setType(KExtHighscore::Lost);
247 }
248
249 } else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human
250 && m_nowPlayingInfo.type[White] == GameStartInformation::Human) { // friends match
251 if (blackScore == whiteScore) {
252 res = i18n("Game is drawn!");
253 } else if (blackScore > whiteScore) {
254 res = i18n("%1 has won!", m_nowPlayingInfo.name[Black]);
255 } else {
256 res = i18n("%1 has won!", m_nowPlayingInfo.name[White]);
257 }
258 } else { // using Black White names in other cases
259 if (blackScore == whiteScore) {
260 res = i18n("Game is drawn!");
261 } else if (blackScore > whiteScore) {
262 res = i18n("%1 has won!", Utils::colorToString(Black));
263 } else {
264 res = i18n("%1 has won!", Utils::colorToString(White));
265 }
266 }
267
268 if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI
269 && m_nowPlayingInfo.type[White] == GameStartInformation::AI) {
270 res += i18n("\n%1: %2", Utils::colorToString(Black), blackScore);
271 res += i18n("\n%1: %2", Utils::colorToString(White), whiteScore);
272 } else {
273 res += i18n("\n%1: %2", m_nowPlayingInfo.name[Black], blackScore);
274 res += i18n("\n%1: %2", m_nowPlayingInfo.name[White], whiteScore);
275 }
276
277 KMessageBox::information(this, res, i18n("Game over"));
278
279 if (storeScore)
280 KExtHighscore::submitScore(score, this);
281}
282
283void KReversiMainWindow::slotMoveFinished()
284{
285 updateHistory();
286 updateStatusBar();
287
288 m_hintAct->setEnabled(m_game->isHintAllowed());
289 m_undoAct->setEnabled(m_game->canUndo());
290}
291
292void KReversiMainWindow::updateHistory()
293{
294 MoveList history = m_game->getHistory();
295 m_historyView->clear();
296
297 for (int i = 0; i < history.size(); i++) {
298 QString numStr = QString::number(i + 1) + QLatin1String(". ");
299 m_historyView->addItem(numStr + Utils::moveToString(history.at(i)));
300 }
301
302 QListWidgetItem *last = m_historyView->item(m_historyView->count() - 1);
303 m_historyView->setCurrentItem(last);
304 m_historyView->scrollToItem(last);
305}
306
307void KReversiMainWindow::slotUndo()
308{
309 // scene will automatically notice that it needs to update
310 m_game->undo();
311
312 updateHistory();
313 updateStatusBar();
314
315 m_undoAct->setEnabled(m_game->canUndo());
316 m_hintAct->setEnabled(m_game->isHintAllowed());
317}
318
319void KReversiMainWindow::slotHighscores()
320{
321 KExtHighscore::show(this);
322}
323
324void KReversiMainWindow::slotDialogReady()
325{
326 GameStartInformation info = m_startDialog->createGameStartInformation();
327 receivedGameStartInformation(info);
328}
329
330void KReversiMainWindow::showEvent(QShowEvent*)
331{
332 if (m_firstShow && m_startInDemoMode) {
333 kDebug() << "starting demo...";
334 startDemo();
335 } else if (m_firstShow) {
336 QTimer::singleShot(0, this, SLOT(slotNewGame()));
337 }
338 m_firstShow = false;
339}
340
341void KReversiMainWindow::updateStatusBar()
342{
343 if (m_game->isGameOver()) {
344 statusBar()->changeItem(i18n("GAME OVER"), COMMON_STATUSBAR_ID);
345 }
346
347 if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI
348 && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // using Black White names
349 statusBar()->changeItem(i18n("%1: %2",
350 Utils::colorToString(Black),
351 m_game->playerScore(Black)), BLACK_STATUSBAR_ID);
352 statusBar()->changeItem(i18n("%1: %2",
353 Utils::colorToString(White),
354 m_game->playerScore(White)), WHITE_STATUSBAR_ID);
355
356 if (!m_game->isGameOver()) {
357 statusBar()->changeItem(i18n("%1 turn",
358 Utils::colorToString(m_game->currentPlayer())),
359 COMMON_STATUSBAR_ID);
360 }
361 } else { // using player's names
362 statusBar()->changeItem(i18n("%1: %2", m_nowPlayingInfo.name[Black],
363 m_game->playerScore(Black)), BLACK_STATUSBAR_ID);
364 statusBar()->changeItem(i18n("%1: %2", m_nowPlayingInfo.name[White],
365 m_game->playerScore(White)), WHITE_STATUSBAR_ID);
366
367 if (!m_game->isGameOver() && m_game->currentPlayer() != NoColor) {
368 statusBar()->changeItem(i18n("%1's turn",
369 m_nowPlayingInfo.name[m_game->currentPlayer()]), COMMON_STATUSBAR_ID);
370 }
371 }
372}
373
374
375// TODO: test it!!!
376void KReversiMainWindow::startDemo()
377{
378 GameStartInformation info;
379 info.name[0] = info.name[1] = i18n("Computer");
380 info.type[0] = info.type[1] = GameStartInformation::AI;
381 info.skill[0] = info.skill[1] = Utils::difficultyLevelToInt();
382
383 receivedGameStartInformation(info);
384}
385
386void KReversiMainWindow::clearPlayers()
387{
388 for (int i = 0; i < 2; i++) // iterating through white to black
389 if (m_player[i]) {
390 m_player[i]->disconnect();
391 delete m_player[i];
392 m_player[i] = 0;
393 }
394}
395
396void KReversiMainWindow::receivedGameStartInformation(GameStartInformation info)
397{
398 clearPlayers();
399 m_nowPlayingInfo = info;
400
401 for (int i = 0; i < 2; i++) // iterating through black and white
402 if (info.type[i] == GameStartInformation::AI) {
403 m_player[i] = new KReversiComputerPlayer(ChipColor(i), info.name[i]);
404 ((KReversiComputerPlayer *)(m_player[i]))->setSkill(info.skill[i]);
405 levelChanged();
406 } else {
407 m_player[i] = new KReversiHumanPlayer(ChipColor(i), info.name[i]);
408 }
409
410 m_game = new KReversiGame(m_player[Black], m_player[White]);
411
412 m_view->setGame(m_game);
413
414 connect(m_game, SIGNAL(gameOver()), SLOT(slotGameOver()));
415 connect(m_game, SIGNAL(moveFinished()), SLOT(slotMoveFinished()));
416
417 for (int i = 0; i < 2; i++) // iterating white to black
418 if (info.type[i] == GameStartInformation::Human)
419 connect(m_view, SIGNAL(userMove(KReversiPos)),
420 (KReversiHumanPlayer *)(m_player[i]), SLOT(onUICellClick(KReversiPos)));
421
422 updateStatusBar();
423 updateHistory();
424
425 if (info.type[White] == GameStartInformation::AI
426 && info.type[Black] == GameStartInformation::Human) {
427 Kg::difficulty()->setEditable(true);
428 Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[White]));
429 } else if (info.type[White] == GameStartInformation::Human
430 && info.type[Black] == GameStartInformation::AI) {
431 Kg::difficulty()->setEditable(true);
432 Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[Black]));
433 } else
434 Kg::difficulty()->setEditable(false);
435
436 m_hintAct->setEnabled(m_game->isHintAllowed());
437 m_undoAct->setEnabled(m_game->canUndo());
438}
439