1/*******************************************************************
2 *
3 * Copyright 2006-2007 Dmitry Suzdalev <dimsuz@gmail.com>
4 * Copyright 2013 Denis Kuplyakov <dener.kup@gmail.com>
5 *
6 * This file is part of the KDE project "KReversi"
7 *
8 * KReversi 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, or (at your option)
11 * any later version.
12 *
13 * KReversi 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 KReversi; see the file COPYING. If not, write to
20 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 ********************************************************************/
24#include <kreversigame.h>
25
26#include <KDebug>
27
28const int KReversiGame::DX[KReversiGame::DIRECTIONS_COUNT] = {0, 0, 1, 1, 1, -1, -1, -1};
29const int KReversiGame::DY[KReversiGame::DIRECTIONS_COUNT] = {1, -1, 1, 0, -1, 1, 0, -1};
30
31KReversiGame::KReversiGame(KReversiPlayer *blackPlayer, KReversiPlayer *whitePlayer)
32 : m_curPlayer(Black), m_delay(300)
33{
34 m_isReady[White] = m_isReady[Black] = false;
35
36 // reset board
37 for (int r = 0; r < 8; ++r)
38 for (int c = 0; c < 8; ++c)
39 m_cells[r][c] = NoColor;
40 // initial pos
41 m_cells[3][3] = m_cells[4][4] = White;
42 m_cells[3][4] = m_cells[4][3] = Black;
43
44 m_score[White] = m_score[Black] = 2;
45
46 m_player[White] = whitePlayer;
47 m_player[Black] = blackPlayer;
48
49 connect(this, SIGNAL(blackPlayerCantMove()), blackPlayer, SLOT(skipTurn()));
50 connect(this, SIGNAL(blackPlayerTurn()), blackPlayer, SLOT(takeTurn()));
51 connect(this, SIGNAL(gameOver()), blackPlayer, SLOT(gameOver()));
52 connect(blackPlayer, SIGNAL(makeMove(KReversiMove)), this, SLOT(blackPlayerMove(KReversiMove)));
53 connect(blackPlayer, SIGNAL(ready()), this, SLOT(blackReady()));
54
55 connect(this, SIGNAL(whitePlayerCantMove()), whitePlayer, SLOT(skipTurn()));
56 connect(this, SIGNAL(whitePlayerTurn()), whitePlayer, SLOT(takeTurn()));
57 connect(this, SIGNAL(gameOver()), whitePlayer, SLOT(gameOver()));
58 connect(whitePlayer, SIGNAL(makeMove(KReversiMove)), this, SLOT(whitePlayerMove(KReversiMove)));
59 connect(whitePlayer, SIGNAL(ready()), this, SLOT(whiteReady()));
60
61 m_engine = new Engine(1);
62
63 whitePlayer->prepare(this);
64 blackPlayer->prepare(this);
65}
66
67KReversiGame::~KReversiGame()
68{
69 delete m_engine;
70}
71
72bool KReversiGame::canUndo() const
73{
74 if (m_curPlayer == NoColor)
75 return false;
76 return (m_player[m_curPlayer]->isUndoAllowed() && !m_undoStack.isEmpty());
77}
78
79void KReversiGame::makeMove(const KReversiMove &move)
80{
81 if (!move.isValid()) {
82 kickCurrentPlayer();
83 return; // Move is invalid!
84 }
85
86 if (move.color != m_curPlayer)
87 return; // It's not your turn now
88
89 if (!isMovePossible(move)) {
90 kickCurrentPlayer();
91 return; // Unpossible move
92 }
93
94 m_lastPlayer = m_curPlayer;
95 m_curPlayer = NoColor; // both players wait for animations
96
97 turnChips(move);
98
99 m_delayTimer.singleShot(m_delay * (qMax(1, m_changedChips.count() - 1)), this, SLOT(onDelayTimer()));
100 emit boardChanged();
101}
102
103void KReversiGame::startNextTurn()
104{
105 m_curPlayer = Utils::opponentColorFor(m_lastPlayer);
106
107 emit moveFinished(); // previous move has just finished
108
109 if (!isGameOver()) {
110 if (isAnyPlayerMovePossible(m_curPlayer)) {
111 if (m_curPlayer == White)
112 emit whitePlayerTurn();
113 else
114 emit blackPlayerTurn();
115 } else {
116 if (m_curPlayer == White)
117 emit whitePlayerCantMove();
118 else
119 emit blackPlayerCantMove();
120
121 m_lastPlayer = m_curPlayer;
122 startNextTurn();
123 }
124 } else { //Game is over
125 emit gameOver();
126 }
127}
128
129int KReversiGame::undo()
130{
131 m_player[m_curPlayer]->undoUsed();
132 // we're undoing all moves (if any) until we meet move done by a player.
133 // We undo that player move too and we're done.
134 // Simply put: we're undoing all_moves_of_computer + one_move_of_player
135
136 int movesUndone = 0;
137
138 while (!m_undoStack.isEmpty()) {
139 MoveList lastUndo = m_undoStack.pop();
140 // One thing that matters here is that we take the
141 // chip color directly from board, rather than from move.color
142 // That allows to take into account a previously made undo, while
143 // undoing changes which are in the current list
144 // Sounds not very understandable?
145 // Then try to use move.color instead of m_cells[row][col]
146 // and it will mess things when undoing such moves as
147 // "Player captures computer-owned chip,
148 // Computer makes move and captures this chip back"
149 // Yes, I like long descriptions in comments ;).
150
151 KReversiMove move = lastUndo.takeFirst();
152 setChipColor(KReversiMove(NoColor, move.row, move.col));
153
154 // and change back the color of the rest chips
155 foreach(const KReversiMove & pos, lastUndo) {
156 ChipColor opponentColor = Utils::opponentColorFor(m_cells[pos.row][pos.col]);
157 setChipColor(KReversiMove(opponentColor, pos.row, pos.col));
158 }
159
160 lastUndo.clear();
161
162 movesUndone++;
163 if (move.color == m_curPlayer)
164 break; //we've undone all opponent's + one current player's moves
165 }
166
167 if (!m_undoStack.empty())
168 m_changedChips = m_undoStack.top();
169 else
170 m_changedChips.clear();
171
172 emit boardChanged();
173 kickCurrentPlayer();
174
175 return movesUndone;
176}
177
178void KReversiGame::turnChips(const KReversiMove &move)
179{
180 m_changedChips.clear();
181
182 // the first one is the move itself
183 setChipColor(move);
184 m_changedChips.append(move);
185 // now turn color of all chips that were won
186 for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++) {
187 if (hasChunk(dirNum, move)) {
188 for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
189 r >= 0 && c >= 0 && r < 8 && c < 8;
190 r += DX[dirNum], c += DY[dirNum]) {
191 if (m_cells[r][c] == move.color)
192 break;
193 setChipColor(KReversiMove(move.color, r, c));
194 m_changedChips.append(KReversiMove(move.color, r, c));
195 }
196 }
197 }
198
199 m_undoStack.push(m_changedChips);
200}
201
202bool KReversiGame::isMovePossible(const KReversiMove& move) const
203{
204 // first - the trivial case:
205 if (m_cells[move.row][move.col] != NoColor || move.color == NoColor)
206 return false;
207
208 for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++)
209 if (hasChunk(dirNum, move))
210 return true;
211
212 return false;
213}
214
215bool KReversiGame::hasChunk(int dirNum, const KReversiMove& move) const
216{
217 // On each step (as we proceed) we must ensure that current chip is of the
218 // opponent color.
219 // We'll do our steps until we reach the chip of player color or we reach
220 // the end of the board in this direction.
221 // If we found player-colored chip and number of opponent chips between it and
222 // the starting one is greater than zero, then Hurray! we found a chunk
223 //
224 // Well, I wrote this description from my head, now lets produce some code for that ;)
225
226 ChipColor opColor = Utils::opponentColorFor(move.color);
227 int opponentChipsNum = 0;
228 bool foundPlayerColor = false;
229
230 for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
231 r >= 0 && c >= 0 && r < 8 && c < 8;
232 r += DX[dirNum], c += DY[dirNum]) {
233 ChipColor color = m_cells[r][c];
234 if (color == opColor) {
235 opponentChipsNum++;
236 } else if (color == move.color) {
237 foundPlayerColor = true;
238 break;
239 } else
240 break;
241 }
242
243 if (foundPlayerColor && opponentChipsNum != 0)
244 return true;
245
246 return false;
247}
248
249bool KReversiGame::isGameOver() const
250{
251 // trivial fast-check
252 if (m_score[White] + m_score[Black] == 64)
253 return true; // the board is full
254 else
255 return !(isAnyPlayerMovePossible(White) || isAnyPlayerMovePossible(Black));
256}
257
258bool KReversiGame::isAnyPlayerMovePossible(ChipColor player) const
259{
260 for (int r = 0; r < 8; ++r)
261 for (int c = 0; c < 8; ++c) {
262 if (m_cells[r][c] == NoColor) {
263 // let's see if we can put chip here
264 if (isMovePossible(KReversiMove(player, r, c)))
265 return true;
266 }
267 }
268 return false;
269}
270
271void KReversiGame::setDelay(int delay)
272{
273 m_delay = delay;
274}
275
276int KReversiGame::getPreAnimationDelay(KReversiPos pos) const
277{
278 for (int i = 1; i < m_changedChips.size(); i++) {
279 if (m_changedChips[i].row == pos.row && m_changedChips[i].col == pos.col) {
280 return (i - 1) * m_delay;
281 }
282 }
283 return 0;
284}
285
286MoveList KReversiGame::getHistory() const
287{
288 MoveList l;
289 for (int i = 0; i < m_undoStack.size(); i++)
290 l.push_back(m_undoStack.at(i).at(0));
291 return l;
292}
293
294bool KReversiGame::isHintAllowed() const
295{
296 if (m_curPlayer == NoColor)
297 return false;
298 return m_player[m_curPlayer]->isHintAllowed();
299}
300
301void KReversiGame::blackPlayerMove(KReversiMove move)
302{
303 if (move.color == White)
304 return; // Black can't do White moves
305 makeMove(move);
306}
307
308void KReversiGame::whitePlayerMove(KReversiMove move)
309{
310 if (move.color == Black)
311 return; // White can't do Black moves
312 makeMove(move);
313}
314
315void KReversiGame::onDelayTimer()
316{
317 startNextTurn();
318}
319
320void KReversiGame::blackReady()
321{
322 m_isReady[Black] = true;
323 if (m_isReady[White])
324 m_player[Black]->takeTurn();
325}
326
327void KReversiGame::whiteReady()
328{
329 m_isReady[White] = true;
330 if (m_isReady[Black])
331 m_player[Black]->takeTurn();
332}
333
334KReversiMove KReversiGame::getHint() const
335{
336 /// FIXME: dimsuz: don't use true, use m_competitive
337 m_player[m_curPlayer]->hintUsed();
338 return m_engine->computeMove(*this, true);
339}
340
341KReversiMove KReversiGame::getLastMove() const
342{
343 // we'll take this move from changed list
344 if (m_changedChips.isEmpty())
345 return KReversiMove(); // invalid one
346
347 // first item in this list is the actual move, rest is turned chips
348 return m_changedChips.first();
349}
350
351MoveList KReversiGame::possibleMoves() const
352{
353 MoveList l;
354 if (m_curPlayer == NoColor) // we are at animation period: no move is possible
355 return l;
356
357 for (int r = 0; r < 8; ++r)
358 for (int c = 0; c < 8; ++c) {
359 KReversiMove move(m_curPlayer, r, c);
360 if (isMovePossible(move))
361 l.append(move);
362 }
363 return l;
364}
365
366int KReversiGame::playerScore(ChipColor player) const
367{
368 return m_score[player];
369}
370
371void KReversiGame::setChipColor(const KReversiMove &move)
372{
373 // first: if the current cell already contains a chip we remove it
374 if (m_cells[move.row][move.col] != NoColor)
375 m_score[m_cells[move.row][move.col]]--;
376
377 // and now replacing with chip of 'color'
378 m_cells[move.row][move.col] = move.color;
379
380 if (move.color != NoColor)
381 m_score[move.color]++;
382}
383
384ChipColor KReversiGame::chipColorAt(KReversiPos pos) const
385{
386 return m_cells[pos.row][pos.col];
387}
388
389
390void KReversiGame::kickCurrentPlayer()
391{
392 if (m_curPlayer == White)
393 emit whitePlayerTurn();
394 else
395 emit blackPlayerTurn();
396}
397
398#include "kreversigame.moc"
399