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