kreversigame.cpp 11.5 KB
Newer Older
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
1 2
/*******************************************************************
 *
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
3
 * Copyright 2006-2007 Dmitry Suzdalev <dimsuz@gmail.com>
Denis Kuplyakov's avatar
Denis Kuplyakov committed
4
 * Copyright 2013 Denis Kuplyakov <dener.kup@gmail.com>
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * 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.
 *
 ********************************************************************/
24
#include "kreversigame.h"
Denis Kuplyakov's avatar
Denis Kuplyakov committed
25

26

Denis Kuplyakov's avatar
Denis Kuplyakov committed
27 28
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};
29

Denis Kuplyakov's avatar
Denis Kuplyakov committed
30
KReversiGame::KReversiGame(KReversiPlayer *blackPlayer, KReversiPlayer *whitePlayer)
Frederik Schwarzer's avatar
Frederik Schwarzer committed
31
    : m_delay(300), m_curPlayer(Black)
32
{
33 34
    m_isReady[White] = m_isReady[Black] = false;

35
    // reset board
Denis Kuplyakov's avatar
Denis Kuplyakov committed
36 37
    for (int r = 0; r < 8; ++r)
        for (int c = 0; c < 8; ++c)
38 39 40 41 42 43 44
            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;

Denis Kuplyakov's avatar
Denis Kuplyakov committed
45 46 47
    m_player[White] = whitePlayer;
    m_player[Black] = blackPlayer;

Laurent Montel's avatar
Laurent Montel committed
48 49 50 51 52 53 54 55 56 57 58
    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);
Denis Kuplyakov's avatar
Denis Kuplyakov committed
59

60
    m_engine = new Engine(1);
61 62 63

    whitePlayer->prepare(this);
    blackPlayer->prepare(this);
64 65 66 67
}

KReversiGame::~KReversiGame()
{
68
    delete m_engine;
69 70
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
71 72 73 74 75 76 77
bool KReversiGame::canUndo() const
{
    if (m_curPlayer == NoColor)
        return false;
    return (m_player[m_curPlayer]->isUndoAllowed() && !m_undoStack.isEmpty());
}

Laurent Montel's avatar
Laurent Montel committed
78
void KReversiGame::makeMove(KReversiMove move)
79
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
80
    if (!move.isValid()) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
81 82
        kickCurrentPlayer();
        return; // Move is invalid!
83
    }
84

Denis Kuplyakov's avatar
Denis Kuplyakov committed
85 86 87
    if (move.color != m_curPlayer)
        return; // It's not your turn now

Denis Kuplyakov's avatar
Denis Kuplyakov committed
88
    if (!isMovePossible(move)) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
89 90
        kickCurrentPlayer();
        return; // Unpossible move
91
    }
Denis Kuplyakov's avatar
Denis Kuplyakov committed
92 93 94 95 96

    m_lastPlayer = m_curPlayer;
    m_curPlayer = NoColor; // both players wait for animations

    turnChips(move);
Denis Kuplyakov's avatar
Denis Kuplyakov committed
97
    
98
    m_delayTimer.singleShot(m_delay * (qMax(1, m_changedChips.count() - 1)), this, &KReversiGame::onDelayTimer);
99
    Q_EMIT boardChanged();
100 101
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
102
void KReversiGame::startNextTurn()
103
{
104
    m_curPlayer = Utils::opponentColorFor(m_lastPlayer);
Denis Kuplyakov's avatar
Denis Kuplyakov committed
105

106
    Q_EMIT moveFinished(); // previous move has just finished
Denis Kuplyakov's avatar
Denis Kuplyakov committed
107

Denis Kuplyakov's avatar
Denis Kuplyakov committed
108
    if (!isGameOver()) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
109 110
        if (isAnyPlayerMovePossible(m_curPlayer)) {
            if (m_curPlayer == White)
111
                Q_EMIT whitePlayerTurn();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
112
            else
113
                Q_EMIT blackPlayerTurn();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
114
        } else {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
115
            if (m_curPlayer == White)
116
                Q_EMIT whitePlayerCantMove();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
117
            else
118
                Q_EMIT blackPlayerCantMove();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
119 120 121

            m_lastPlayer = m_curPlayer;
            startNextTurn();
122
        }
Denis Kuplyakov's avatar
Denis Kuplyakov committed
123
    } else { //Game is over
124
        Q_EMIT gameOver();
125 126 127
    }
}

128
int KReversiGame::undo()
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
129
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
130
    m_player[m_curPlayer]->undoUsed();
131 132 133
    // 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
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
134

135
    int movesUndone = 0;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
136

Denis Kuplyakov's avatar
Denis Kuplyakov committed
137
    while (!m_undoStack.isEmpty()) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
138
        MoveList lastUndo = m_undoStack.pop();
139 140 141 142 143
        // 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?
144
        // Then try to use move.color instead of m_cells[row][col]
145 146 147 148 149
        // 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 ;).

Denis Kuplyakov's avatar
Denis Kuplyakov committed
150 151
        KReversiMove move = lastUndo.takeFirst();
        setChipColor(KReversiMove(NoColor, move.row, move.col));
152 153

        // and change back the color of the rest chips
Andrius Štikonas's avatar
Andrius Štikonas committed
154
        for (const KReversiMove & pos : qAsConst(lastUndo)) {
155
            ChipColor opponentColor = Utils::opponentColorFor(m_cells[pos.row][pos.col]);
Denis Kuplyakov's avatar
Denis Kuplyakov committed
156
            setChipColor(KReversiMove(opponentColor, pos.row, pos.col));
157 158 159
        }

        lastUndo.clear();
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
160

161
        movesUndone++;
Denis Kuplyakov's avatar
Denis Kuplyakov committed
162
        if (move.color == m_curPlayer)
Denis Kuplyakov's avatar
Denis Kuplyakov committed
163
            break; //we've undone all opponent's + one current player's moves
164
    }
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
165

166 167 168 169
    if (!m_undoStack.empty())
        m_changedChips = m_undoStack.top();
    else
        m_changedChips.clear();
170

171
    Q_EMIT boardChanged();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
172
    kickCurrentPlayer();
173 174

    return movesUndone;
175 176
}

Laurent Montel's avatar
Laurent Montel committed
177
void KReversiGame::turnChips(KReversiMove move)
178
{
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
179 180 181
    m_changedChips.clear();

    // the first one is the move itself
Denis Kuplyakov's avatar
Denis Kuplyakov committed
182 183
    setChipColor(move);
    m_changedChips.append(move);
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
184
    // now turn color of all chips that were won
Denis Kuplyakov's avatar
Denis Kuplyakov committed
185 186
    for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++) {
        if (hasChunk(dirNum, move)) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
187
            for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
Denis Kuplyakov's avatar
Denis Kuplyakov committed
188 189 190
                    r >= 0 && c >= 0 && r < 8 && c < 8;
                    r += DX[dirNum], c += DY[dirNum]) {
                if (m_cells[r][c] == move.color)
Denis Kuplyakov's avatar
Denis Kuplyakov committed
191 192 193 194
                    break;
                setChipColor(KReversiMove(move.color, r, c));
                m_changedChips.append(KReversiMove(move.color, r, c));
            }
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
195 196 197
        }
    }

Denis Kuplyakov's avatar
Denis Kuplyakov committed
198
    m_undoStack.push(m_changedChips);
199 200
}

Laurent Montel's avatar
Laurent Montel committed
201
bool KReversiGame::isMovePossible(KReversiMove move) const
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
202 203
{
    // first - the trivial case:
Denis Kuplyakov's avatar
Denis Kuplyakov committed
204
    if (m_cells[move.row][move.col] != NoColor || move.color == NoColor)
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
205 206
        return false;

Denis Kuplyakov's avatar
Denis Kuplyakov committed
207 208 209
    for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++)
        if (hasChunk(dirNum, move))
            return true;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
210 211 212 213

    return false;
}

Laurent Montel's avatar
Laurent Montel committed
214
bool KReversiGame::hasChunk(int dirNum, KReversiMove move) const
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
215
{
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
216
    // On each step (as we proceed) we must ensure that current chip is of the
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
217 218 219 220 221 222 223 224
    // 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 ;)

225
    ChipColor opColor = Utils::opponentColorFor(move.color);
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
226 227 228
    int opponentChipsNum = 0;
    bool foundPlayerColor = false;

Denis Kuplyakov's avatar
Denis Kuplyakov committed
229
    for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
Denis Kuplyakov's avatar
Denis Kuplyakov committed
230 231
            r >= 0 && c >= 0 && r < 8 && c < 8;
            r += DX[dirNum], c += DY[dirNum]) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
232
        ChipColor color = m_cells[r][c];
Denis Kuplyakov's avatar
Denis Kuplyakov committed
233
        if (color == opColor) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
234
            opponentChipsNum++;
Denis Kuplyakov's avatar
Denis Kuplyakov committed
235
        } else if (color == move.color) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
236 237
            foundPlayerColor = true;
            break;
Denis Kuplyakov's avatar
Denis Kuplyakov committed
238
        } else
Denis Kuplyakov's avatar
Denis Kuplyakov committed
239
            break;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
240 241
    }

Denis Kuplyakov's avatar
Denis Kuplyakov committed
242 243
    if (foundPlayerColor && opponentChipsNum != 0)
        return true;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
244 245 246 247

    return false;
}

Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
248 249 250
bool KReversiGame::isGameOver() const
{
    // trivial fast-check
Denis Kuplyakov's avatar
Denis Kuplyakov committed
251
    if (m_score[White] + m_score[Black] == 64)
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
252 253
        return true; // the board is full
    else
Denis Kuplyakov's avatar
Denis Kuplyakov committed
254
        return !(isAnyPlayerMovePossible(White) || isAnyPlayerMovePossible(Black));
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
255 256
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
257
bool KReversiGame::isAnyPlayerMovePossible(ChipColor player) const
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
258
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
259
    for (int r = 0; r < 8; ++r)
Denis Kuplyakov's avatar
Denis Kuplyakov committed
260 261
        for (int c = 0; c < 8; ++c) {
            if (m_cells[r][c] == NoColor) {
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
262
                // let's see if we can put chip here
Denis Kuplyakov's avatar
Denis Kuplyakov committed
263 264
                if (isMovePossible(KReversiMove(player, r, c)))
                    return true;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
265 266 267 268 269
            }
        }
    return false;
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
270
void KReversiGame::setDelay(int delay)
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
271
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
272
    m_delay = delay;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
273 274
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
275 276 277 278 279 280 281 282 283 284
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;
}

285 286 287 288 289 290 291 292
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;
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
293 294 295 296 297 298 299
bool KReversiGame::isHintAllowed() const
{
    if (m_curPlayer == NoColor)
        return false;
    return m_player[m_curPlayer]->isHintAllowed();
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
300
void KReversiGame::blackPlayerMove(KReversiMove move)
301
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
302 303 304
    if (move.color == White)
        return; // Black can't do White moves
    makeMove(move);
305 306
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
307
void KReversiGame::whitePlayerMove(KReversiMove move)
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
308
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
309 310 311
    if (move.color == Black)
        return; // White can't do Black moves
    makeMove(move);
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
312 313
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
314 315 316 317 318
void KReversiGame::onDelayTimer()
{
    startNextTurn();
}

319 320 321 322
void KReversiGame::blackReady()
{
    m_isReady[Black] = true;
    if (m_isReady[White])
323
        m_player[Black]->takeTurn();
324 325 326 327 328 329
}

void KReversiGame::whiteReady()
{
    m_isReady[White] = true;
    if (m_isReady[Black])
330
        m_player[Black]->takeTurn();
331 332
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
333 334 335
KReversiMove KReversiGame::getHint() const
{
    /// FIXME: dimsuz: don't use true, use m_competitive
Denis Kuplyakov's avatar
Denis Kuplyakov committed
336
    m_player[m_curPlayer]->hintUsed();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
337 338 339 340
    return m_engine->computeMove(*this, true);
}

KReversiMove KReversiGame::getLastMove() const
341 342
{
    // we'll take this move from changed list
Denis Kuplyakov's avatar
Denis Kuplyakov committed
343 344
    if (m_changedChips.isEmpty())
        return KReversiMove(); // invalid one
345 346 347 348 349

    // first item in this list is the actual move, rest is turned chips
    return m_changedChips.first();
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
350
MoveList KReversiGame::possibleMoves() const
351
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
352 353 354 355 356
    MoveList l;
    if (m_curPlayer == NoColor) // we are at animation period: no move is possible
        return l;

    for (int r = 0; r < 8; ++r)
Denis Kuplyakov's avatar
Denis Kuplyakov committed
357
        for (int c = 0; c < 8; ++c) {
Denis Kuplyakov's avatar
Denis Kuplyakov committed
358 359
            KReversiMove move(m_curPlayer, r, c);
            if (isMovePossible(move))
360 361 362 363 364
                l.append(move);
        }
    return l;
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
365
int KReversiGame::playerScore(ChipColor player) const
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
366
{
367 368 369
    return m_score[player];
}

Laurent Montel's avatar
Laurent Montel committed
370
void KReversiGame::setChipColor(KReversiMove move)
371
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
372 373 374
    // 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]]--;
375 376

    // and now replacing with chip of 'color'
Denis Kuplyakov's avatar
Denis Kuplyakov committed
377
    m_cells[move.row][move.col] = move.color;
378

Denis Kuplyakov's avatar
Denis Kuplyakov committed
379 380 381
    if (move.color != NoColor)
        m_score[move.color]++;
}
382

Denis Kuplyakov's avatar
Denis Kuplyakov committed
383 384 385
ChipColor KReversiGame::chipColorAt(KReversiPos pos) const
{
    return m_cells[pos.row][pos.col];
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
386 387
}

Denis Kuplyakov's avatar
Denis Kuplyakov committed
388 389

void KReversiGame::kickCurrentPlayer()
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
390
{
Denis Kuplyakov's avatar
Denis Kuplyakov committed
391
    if (m_curPlayer == White)
392
        Q_EMIT whitePlayerTurn();
Denis Kuplyakov's avatar
Denis Kuplyakov committed
393
    else
394
        Q_EMIT blackPlayerTurn();
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
395 396
}