~neon/kreversi/master

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();
}