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 | |
41 | static const int BLACK_STATUSBAR_ID = 1; |
42 | static const int WHITE_STATUSBAR_ID = 2; |
43 | static const int COMMON_STATUSBAR_ID = 0; |
44 | |
45 | KReversiMainWindow::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 | |
99 | KReversiMainWindow::~KReversiMainWindow() |
100 | { |
101 | delete m_provider; |
102 | } |
103 | |
104 | void 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 | |
150 | void 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 | |
164 | void 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 | |
177 | void KReversiMainWindow::slotAnimSpeedChanged(int speed) |
178 | { |
179 | m_view->setAnimationSpeed(speed); |
180 | Preferences::setAnimationSpeed(speed); |
181 | Preferences::self()->writeConfig(); |
182 | } |
183 | |
184 | void 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 | |
195 | void KReversiMainWindow::slotShowMovesHistory(bool toggled) |
196 | { |
197 | m_historyDock->setVisible(toggled); |
198 | m_view->setShowBoardLabels(toggled); |
199 | } |
200 | |
201 | void KReversiMainWindow::slotNewGame() |
202 | { |
203 | m_startDialog->exec(); |
204 | } |
205 | |
206 | void 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 | |
283 | void KReversiMainWindow::slotMoveFinished() |
284 | { |
285 | updateHistory(); |
286 | updateStatusBar(); |
287 | |
288 | m_hintAct->setEnabled(m_game->isHintAllowed()); |
289 | m_undoAct->setEnabled(m_game->canUndo()); |
290 | } |
291 | |
292 | void 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 | |
307 | void 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 | |
319 | void KReversiMainWindow::slotHighscores() |
320 | { |
321 | KExtHighscore::show(this); |
322 | } |
323 | |
324 | void KReversiMainWindow::slotDialogReady() |
325 | { |
326 | GameStartInformation info = m_startDialog->createGameStartInformation(); |
327 | receivedGameStartInformation(info); |
328 | } |
329 | |
330 | void 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 | |
341 | void 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!!! |
376 | void 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 | |
386 | void 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 | |
396 | void 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 | |