bovo/game/game.cc

421 lines
11 KiB
C++

/*******************************************************************
*
* Copyright 2007 Aron Boström <c02ab@efd.lth.se>
*
* Bovo 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.
*
* Bovo 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 Bovo; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
********************************************************************/
/** @file game.cc implements class Game in namespace bovo */
#include "game.h"
#include <QTimer>
#include <QString>
#include <QStringList>
#include "ai.h"
#include "aifactory.h"
#include "board.h"
#include "coord.h"
#include "dimension.h"
#include "move.h"
using namespace ai;
/** namespace for gui stuff */
namespace bovo
{
Game::Game(const Dimension& dimension, Player startingPlayer,
KgDifficultyLevel::StandardLevel skill, DemoMode demoMode,
unsigned int playTime, AiFactory* aiFactory)
: m_aiFactory(aiFactory), m_curPlayer(startingPlayer),m_computerMark(O),
m_demoMode(demoMode), m_inUndoState(false), m_playerMark(X),
m_playTime(playTime), m_replaying(false) {
m_board = new Board(dimension);
m_ai = m_aiFactory->createAi(dimension, skill, m_computerMark, demoMode);
m_winDir = -1;
m_gameOver = false;
m_stepCount = 0;
connect(this, &Game::boardChanged,
m_ai, &Ai::changeBoard);
connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
Qt::QueuedConnection);
connect(m_ai, SIGNAL(move(Move)),
this, SLOT(move(Move)));
}
Game::Game(const Dimension& dimension, const QStringList &restoreGame,
KgDifficultyLevel::StandardLevel skill, unsigned int playTime,
AiFactory* aiFactory)
: m_aiFactory(aiFactory), m_computerMark(O), m_demoMode(NotDemo),
m_inUndoState(false), m_playerMark(X), m_playTime(playTime),
m_replaying(false) {
m_board = new Board(dimension);
m_ai = m_aiFactory->createAi(dimension, skill, m_computerMark, NotDemo);
m_winDir = -1;
m_gameOver = false;
m_stepCount = 0;
m_curPlayer = No;
for (const QString &turn : restoreGame) {
QStringList tmp = turn.split(QLatin1Char(':'));
if (tmp.count() != 2) {
qFatal("Wrong save file format!");
}
Player tmpPlayer = (tmp[0] == QLatin1String("1")) ? X : O;
if (m_curPlayer == No) {
m_curPlayer = tmpPlayer;
}
tmp = tmp[1].split(QLatin1Char(','));
if (tmp.count() != 2) {
qFatal("Wrong save file format!");
}
bool ok;
uint x = tmp[0].toUInt(&ok);
if (!ok) {
qFatal("Wrong save file format!");
}
uint y = tmp[1].toUInt(&ok);
if (!ok) {
qFatal("Wrong save file format!");
}
Move tmpMove(tmpPlayer, Coord(x, y));
m_board->setPlayer(tmpMove);
m_stepCount++;
m_history << tmpMove;
}
}
Game::~Game() {
delete m_board;
delete m_ai;
}
bool Game::computerTurn() const {
return m_curPlayer == m_computerMark;
}
DemoMode Game::demoMode() const {
return m_demoMode;
}
bool Game::isGameOver() const {
return m_gameOver || m_demoMode;
}
QList<Move> Game::history() const {
return m_history;
}
Move Game::latestMove() const {
if (m_history.empty()) {
return Move();
} else {
return m_history.back();
}
}
bool Game::ok(const Coord& coord) const {
return m_board->ok(coord);
}
Player Game::player() const {
return m_playerMark;
}
Player Game::player(const Coord& coord) const {
return m_board->player(coord);
}
bool Game::save(const QString& filename) const {
Q_UNUSED( filename );
QString fileContent;
fileContent.append(QStringLiteral("<bovo width=\"%1\" height=\"%2\">")
.arg(QStringLiteral("")).arg(QStringLiteral("")));
for (const Move &move : qAsConst(m_history)) {
fileContent.append(QStringLiteral("<move player=\"%1\" x=\"%2\" y=\"%3\" />").
arg(move.player()).arg(move.x()).arg(move.y()));
}
fileContent.append(QLatin1String("</bovo>"));
return false;
}
QStringList Game::saveLast() const {
QStringList save;
for (const Move &move : qAsConst(m_history)) {
save << QStringLiteral("%1:%2,%3").arg(move.player())
.arg(move.x()).arg(move.y());
}
return save;
}
void Game::setSkill(KgDifficultyLevel::StandardLevel skill) {
if (m_ai!=nullptr)
m_ai->setSkill(skill);
}
void Game::start() {
if (computerTurn()) {
Q_EMIT oposerTurn();
} else {
Q_EMIT playerTurn();
}
}
void Game::startRestored() {
connect(this, &Game::boardChanged,
m_ai, &Ai::changeBoard);
for (const Move &move : qAsConst(m_history)) {
Q_EMIT boardChanged(move);
}
connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
Qt::QueuedConnection);
connect(m_ai, SIGNAL(move(Move)),
this, SLOT(move(Move)));
if (!m_history.isEmpty() && m_history.last().player() == X) {
m_curPlayer = O;
Q_EMIT oposerTurn();
} else {
m_curPlayer = X;
Q_EMIT playerTurn();
}
if (!m_history.isEmpty()) {
Q_EMIT undoAble();
}
}
short Game::winDir() const {
return m_winDir;
}
bool Game::boardFull() const {
return m_stepCount >= NUMCOLS * NUMCOLS;
}
void Game::cancelAndWait() {
m_ai->cancelAndWait();
}
/* public slots */
void Game::move(const Move& move) {
bool tmp_emptyHistory = m_history.empty();
if (!m_board->empty(move.coord()) || move.player() != m_curPlayer
|| m_inUndoState) {
return;
}
makeMove(move);
if (tmp_emptyHistory && !m_history.empty() && !m_demoMode) {
Q_EMIT undoAble();
}
}
void Game::replay() {
if (m_gameOver && !m_replaying) {
m_replaying = true;
m_replayIterator = m_history.constBegin();
m_replayIteratorEnd = m_history.constEnd();
disconnect(this, &Game::replayBegin, this, &Game::replayNext);
connect(this, &Game::replayBegin, this, &Game::replayNext);
Q_EMIT replayBegin();
}
}
void Game::undoLatest() {
m_inUndoState = true;
if (m_history.empty() || m_demoMode) {
m_inUndoState = false;
return;
}
if (m_gameOver) {
m_gameOver = false;
m_winDir = -1;
connect(this, &Game::boardChanged,
m_ai, &Ai::changeBoard);
connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
Qt::QueuedConnection);
connect(m_ai, SIGNAL(move(Move)),
this, SLOT(move(Move)));
}
if (m_curPlayer == m_computerMark) {
m_ai->cancelAndWait();
Move move(No, m_history.last().coord());
m_history.removeLast();
m_board->setPlayer(move);
m_stepCount--;
Q_EMIT boardChanged(move);
m_curPlayer = m_playerMark;
Q_EMIT playerTurn();
} else if (m_curPlayer == m_playerMark) {
Move move(No, m_history.last().coord());
m_history.removeLast();
m_board->setPlayer(move);
m_stepCount--;
Q_EMIT boardChanged(move);
if (m_history.count() == 0) {
m_curPlayer = m_computerMark;
Q_EMIT oposerTurn();
} else {
Move move2(No, m_history.last().coord());
m_history.removeLast();
m_board->setPlayer(move2);
m_stepCount--;
Q_EMIT boardChanged(move2);
Q_EMIT playerTurn();
}
}
if (m_history.empty() && !m_demoMode) {
Q_EMIT undoNotAble();
}
m_inUndoState = false;
}
/* private slots */
void Game::replayNext() {
if (m_replayIterator != m_replayIteratorEnd) {
QTimer::singleShot(m_playTime, this, &Game::replayNext);
Q_EMIT boardChanged(*m_replayIterator);
++m_replayIterator;
} else {
m_replaying = false;
Q_EMIT replayEnd(winningMoves()); // FIX:!!!!!!!
}
}
/* private methods */
void Game::makeMove(const Move& move) {
if (move.player() != m_curPlayer) {
return;
}
m_board->setPlayer(move);
m_stepCount++;
m_winDir = win(move.coord());
if (m_winDir != -1) {
m_gameOver = true;
} else {
if (boardFull()) {
m_gameOver = true;
}
}
m_history << move;
m_curPlayer = (m_curPlayer == X ? O : X );
Q_EMIT boardChanged(move);
if (m_gameOver) {
QList<Move> moves = winningMoves();
Q_EMIT gameOver(moves);
this->disconnect(m_ai);
} else {
if (computerTurn()) {
if (m_demoMode) {
QTimer::singleShot(m_playTime, this, &Game::oposerTurn);
} else {
Q_EMIT oposerTurn();
}
} else {
if (m_demoMode) {
QTimer::singleShot(m_playTime, this, &Game::playerTurn);
} else {
Q_EMIT playerTurn();
}
}
}
}
Coord Game::next(const Coord& coord, usi dir) const {
const usi LEFT = 1;
const usi UP = 2;
const usi RIGHT = 4;
const usi DOWN = 8;
Coord tmp = coord;
if (dir & LEFT) {
tmp = tmp.left();
} else if (dir & RIGHT) {
tmp = tmp.right();
}
if (dir & UP) {
tmp = tmp.up();
} else if (dir & DOWN) {
tmp = tmp.down();
}
return tmp;
}
short Game::win(const Coord& c) const {
const usi LEFT = 1;
const usi UP = 2;
const usi RIGHT = 4;
const usi DOWN = 8;
usi DIR[8] = {LEFT, RIGHT, UP, DOWN, LEFT | UP, RIGHT | DOWN,
LEFT | DOWN, RIGHT | UP};
Player p = player(c);
for (int i = 0; i < 4; ++i) {
usi count = 1;
Coord tmp = next(c, DIR[2*i]);
while (m_board->ok(tmp) && player(tmp) == p) {
++count;
tmp = next(tmp, DIR[2*i]);
}
tmp = next(c, DIR[2*i+1]);
while (m_board->ok(tmp) && player(tmp) == p) {
++count;
tmp = next(tmp, DIR[2*i+1]);
}
if (count >= 5) {
return i;
}
}
return -1;
}
QList<Move> Game::winningMoves() const {
if (m_winDir == -1) {
return {};
}
QList<Move> moves;
short dy, dx;
switch (m_winDir) {
case 0: dx = 1; dy = 0; break;
case 1: dx = 0; dy = 1; break;
case 2: dx = 1; dy = 1; break;
default: dx = 1; dy = -1; break;
}
usi x = latestMove().x();
usi y = latestMove().y();
Player winner = player(Coord(x, y));
Player tmp;
while ((tmp = player(Coord(x, y))) == winner) {
moves << Move(player(Coord(x, y)), Coord(x, y));
x += dx;
y += dy;
}
x = latestMove().x() - dx;
y = latestMove().y() - dy;
while ((tmp = player(Coord(x, y))) == winner) {
moves << Move(player(Coord(x, y)), Coord(x, y));
x -= dx;
y -= dy;
}
return moves;
}
}