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 | |
28 | const int KReversiGame::DX[KReversiGame::DIRECTIONS_COUNT] = {0, 0, 1, 1, 1, -1, -1, -1}; |
29 | const int KReversiGame::DY[KReversiGame::DIRECTIONS_COUNT] = {1, -1, 1, 0, -1, 1, 0, -1}; |
30 | |
31 | KReversiGame::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 | |
67 | KReversiGame::~KReversiGame() |
68 | { |
69 | delete m_engine; |
70 | } |
71 | |
72 | bool KReversiGame::canUndo() const |
73 | { |
74 | if (m_curPlayer == NoColor) |
75 | return false; |
76 | return (m_player[m_curPlayer]->isUndoAllowed() && !m_undoStack.isEmpty()); |
77 | } |
78 | |
79 | void 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 | |
103 | void 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 | |
129 | int 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 | |
178 | void 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 | |
202 | bool 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 | |
215 | bool 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 | |
249 | bool 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 | |
258 | bool 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 | |
271 | void KReversiGame::setDelay(int delay) |
272 | { |
273 | m_delay = delay; |
274 | } |
275 | |
276 | int 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 | |
286 | MoveList 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 | |
294 | bool KReversiGame::isHintAllowed() const |
295 | { |
296 | if (m_curPlayer == NoColor) |
297 | return false; |
298 | return m_player[m_curPlayer]->isHintAllowed(); |
299 | } |
300 | |
301 | void KReversiGame::blackPlayerMove(KReversiMove move) |
302 | { |
303 | if (move.color == White) |
304 | return; // Black can't do White moves |
305 | makeMove(move); |
306 | } |
307 | |
308 | void KReversiGame::whitePlayerMove(KReversiMove move) |
309 | { |
310 | if (move.color == Black) |
311 | return; // White can't do Black moves |
312 | makeMove(move); |
313 | } |
314 | |
315 | void KReversiGame::onDelayTimer() |
316 | { |
317 | startNextTurn(); |
318 | } |
319 | |
320 | void KReversiGame::blackReady() |
321 | { |
322 | m_isReady[Black] = true; |
323 | if (m_isReady[White]) |
324 | m_player[Black]->takeTurn(); |
325 | } |
326 | |
327 | void KReversiGame::whiteReady() |
328 | { |
329 | m_isReady[White] = true; |
330 | if (m_isReady[Black]) |
331 | m_player[Black]->takeTurn(); |
332 | } |
333 | |
334 | KReversiMove 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 | |
341 | KReversiMove 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 | |
351 | MoveList 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 | |
366 | int KReversiGame::playerScore(ChipColor player) const |
367 | { |
368 | return m_score[player]; |
369 | } |
370 | |
371 | void 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 | |
384 | ChipColor KReversiGame::chipColorAt(KReversiPos pos) const |
385 | { |
386 | return m_cells[pos.row][pos.col]; |
387 | } |
388 | |
389 | |
390 | void KReversiGame::kickCurrentPlayer() |
391 | { |
392 | if (m_curPlayer == White) |
393 | emit whitePlayerTurn(); |
394 | else |
395 | emit blackPlayerTurn(); |
396 | } |
397 | |
398 | #include "kreversigame.moc" |
399 | |