/*************************************************************************** * Copyright (C) 2009 by Stefan Bühler * * stbuehler@web.de * * * * This program 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 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "toruschess.h" #include "ai.h" namespace toruschess { static void init() { static bool done = FALSE; if (done) return; done = TRUE; qRegisterMetaType("Move"); qRegisterMetaType("Pos"); } QString state2string(GameState state) { switch (state) { case TURN_WHITE: return "White's turn"; case TURN_BLACK: return "Black's turn"; case WON_WHITE: return "White is the winner"; case WON_BLACK: return "Black is the winner"; case DRAW: return "Draw game"; } return "Invalid GameState"; } Move::Move(const Field* field, const Pos &from, const Pos &to) : m_from(from), m_to(to), m_prevFrom(field->place(from)), m_prevTo(field->place(to)), m_player(field->player(from)) { } Move::Move() : m_from(), m_to(), m_prevFrom(0), m_prevTo(0), m_player(NOPLAYER) { } static const int start_field[8][8] = { { -KNIGHT, -PAWN, -ROOK, 0, 0, 0, 0, 0 }, { -BISHOP, -KING, -QUEEN, 0, 0, 0, 0, 0 }, { -ROOK, -PAWN, -KNIGHT, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, ROOK, PAWN, KNIGHT, 0 }, { 0, 0, 0, 0, QUEEN, KING, BISHOP, 0 }, { 0, 0, 0, 0, KNIGHT, PAWN, ROOK, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } }; Field::Field() : m_wking(5, 5), m_bking(1, 1) { memcpy(m_places, start_field, sizeof(start_field)); } void Field::reset() { m_wking = Pos(5, 5); m_bking = Pos(1, 1); memcpy(m_places, start_field, sizeof(start_field)); } void tryDirection(QList &moves, const Field *field, int thePlace, const Pos &from, int dx, int dy) { for (int d = 1; d < 8; d++) { Pos to = Pos(from.x() + d*dx, from.y() + d*dy); int toPlace = field->place(to); if (0 == toPlace) { // qDebug("move 1, %i", d); moves.push_back(Move(field, from, to)); /* move */ } else { if (0 > toPlace * thePlace) { // qDebug("beat 1, %i", d); moves.push_back(Move(field, from, to)); /* beat */ } else { // qDebug("hit friend, %i", d); } for (int c = 7; c > d; c--) { to = Pos(from.x() + c*dx, from.y() + c*dy); toPlace = field->place(to); if (0 == toPlace) { // qDebug("move 2, %i %i", d, c); moves.push_back(Move(field, from, to)); /* move */ } else if (0 > toPlace * thePlace) { // qDebug("beat 2, %i %i", d, c); moves.push_back(Move(field, from, to)); /* beat */ break; } else { // qDebug("hit friend, %i %i, (%i, %i) = %i", d, c, to.x(), to.y(), toPlace); break; } } break; } } } /* 7 == -1 mod 8, 6 == -2: but positive values are needed for (a + b) % 8 >= 0 */ typedef struct { int x, y; } move_delta; static const move_delta moves_pawn_walk[4] = { { 1, 0 }, { 0, 1 }, { 7, 0 }, { 0, 7 } }; static const move_delta moves_pawn_beat[4] = { { 1, 1 }, { 1, 7 }, { 7, 7 }, { 7, 1 } }; static const move_delta moves_knight[8] = { { 1, 2 }, { 1, 6 }, { 7, 2 }, { 7, 6 }, { 2, 1 }, { 2, 7 }, { 6, 1 }, { 6, 7 } }; static const move_delta moves_king[8] = { { 1, 1 }, { 1, 0 }, { 1, 7 }, { 0, 7 }, { 7, 7 }, { 7, 0 }, { 7, 1 }, { 0, 1 } }; static Pos operator + (const Pos &p, move_delta d) { return Pos(p.x() + d.x, p.y() + d.y); } QList Field::simpleValidMoves(const Pos &from) const { QList moves; int thePlace = place(from); Piece thePiece = place2piece(thePlace); // qDebug("simpleValidMoves: %i", thePiece); switch (thePiece) { case NOPIECE: break; case PAWN: for (int i = 0; i < 4; i++) { if (0 == place(from + moves_pawn_walk[i])) { moves.push_back(Move(this, from, from + moves_pawn_walk[i])); } if (0 > thePlace * place(from + moves_pawn_beat[i])) { moves.push_back(Move(this, from, from + moves_pawn_beat[i])); } } break; case KNIGHT: for (int i = 0; i < 8; i++) { if (0 >= thePlace * place(from + moves_knight[i])) { moves.push_back(Move(this, from, from + moves_knight[i])); } } break; case BISHOP: tryDirection(moves, this, thePlace, from, 1, 1); tryDirection(moves, this, thePlace, from, 1, 7); break; case ROOK: tryDirection(moves, this, thePlace, from, 1, 0); tryDirection(moves, this, thePlace, from, 0, 1); break; case QUEEN: tryDirection(moves, this, thePlace, from, 1, 1); tryDirection(moves, this, thePlace, from, 1, 7); tryDirection(moves, this, thePlace, from, 1, 0); tryDirection(moves, this, thePlace, from, 0, 1); break; case KING: for (int i = 0; i < 8; i++) { if (0 >= thePlace * place(from + moves_king[i])) { moves.push_back(Move(this, from, from + moves_king[i])); } } break; } // qDebug("simpleValidMoves: found %i", moves.size()); return moves; } QList Field::validMoves(const Pos &from) const { QList moves = simpleValidMoves(from); QList resMoves; Field testField(*this); Player pl = player(from); foreach(Move m, moves) { Q_ASSERT(validMove(m)); testField.move_unchecked(m); if (!testField.inCheck(pl)) { resMoves.push_back(m); } testField.undo_unchecked(m); } return resMoves; } static bool checkReach(const Field *field, const Pos &from, const Pos &dest, int dx, int dy) { for (int d = 1; d < 8; d++) { Pos to = Pos(from.x() + d*dx, from.y() + d*dy); if (dest == to) return true; int toPlace = field->place(to); if (0 == toPlace) { continue; } else { for (int c = 7; c > d; c--) { to = Pos(from.x() + c*dx, from.y() + c*dy); if (dest == to) return true; toPlace = field->place(to); if (0 == toPlace) { continue; } else { return false; } } return false; } } return false; } bool Field::validMove(const Move &m) const { if (player(m.from()) != m.player()) return false; if (m.player() == NOPLAYER) return false; if (place(m.from()) != m.prevFrom() || place(m.to()) != m.prevTo()) return false; if (player(m.to()) == m.player()) return false; switch (piece(m.from())) { case NOPIECE: return false; case PAWN: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; if ((dx > 1 && dx < 7) || (dy > 1 && dy < 7)) return false; if (dx != 0 && dy != 0) return player(m.to()) != NOPLAYER; /* beat */ return player(m.to()) == NOPLAYER; /* walk */ } case KNIGHT: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; return ((dx == 2 || dx == 6) && (dy == 1 || dy == 7)) || ((dx == 1 || dx == 7) && (dy == 2 || dy == 6)); } case BISHOP: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; if (dx == dy) { if (dx == 4) { return checkReach(this, m.from(), m.to(), 1, 1) || checkReach(this, m.from(), m.to(), 1, 7); } else { return checkReach(this, m.from(), m.to(), 1, 1); } } else if (dx + dy == 8) { return checkReach(this, m.from(), m.to(), 1, 7); } else { return false; } } case ROOK: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; if (dy == 0) { return checkReach(this, m.from(), m.to(), 1, 0); } else if (dx == 0) { return checkReach(this, m.from(), m.to(), 0, 1); } else { return false; } } case QUEEN: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; if (dx == dy) { if (dx == 4) { return checkReach(this, m.from(), m.to(), 1, 1) || checkReach(this, m.from(), m.to(), 1, 7); } else { return checkReach(this, m.from(), m.to(), 1, 1); } } else if (dx + dy == 8) { return checkReach(this, m.from(), m.to(), 1, 7); } else if (dy == 0) { return checkReach(this, m.from(), m.to(), 1, 0); } else if (dx == 0) { return checkReach(this, m.from(), m.to(), 0, 1); } else { return false; } } case KING: { int dx = (8 + m.from().x() - m.to().x()) % 8, dy = (8 + m.from().y() - m.to().y()) % 8; if ((dx > 1 && dx < 7) || (dy > 1 && dy < 7)) return false; return true; } } return false; } void Field::move_unchecked(const Move &m) { place(m.from()) = 0; place(m.to()) = m.prevFrom(); if (m.prevFrom() == KING) { m_wking = m.to(); } else if (m.prevFrom() == -KING) { m_bking = m.to(); } } void Field::undo_unchecked(const Move &m) { place(m.from()) = m.prevFrom(); place(m.to()) = m.prevTo(); if (m.prevFrom() == KING) { m_wking = m.from(); } else if (m.prevFrom() == -KING) { m_bking = m.from(); } } bool Field::move(const Move &m) { if (!m.valid()) return false; if (!validMove(m)) return false; move_unchecked(m); if (inCheck(m.player())) { undo_unchecked(m); return false; } return true; } bool Field::undo(const Move &m) { if (place(m.from()) != 0 || place(m.to()) != m.prevFrom()) return false; undo_unchecked(m); return true; } bool Field::inCheck(Player player, const Pos &pking) const { for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) { Pos curp(x, y); if (0 > place(curp) * player && validMove(Move(this, curp, pking))) { return true; } } return false; } bool Field::inCheck(Player p) const { const Pos &pos = (p == WHITE) ? m_wking : m_bking; if (player(pos) != p || piece(pos) != KING) return false; return inCheck(p, pos); } Game::Game(QObject *parent) : QObject(parent), m_field(new Field()), m_state(TURN_WHITE), m_gameMode(HUMAN_COMPUTER), m_aiStrength(2), m_aiThread(0) { init(); } Game::~Game() { delete m_field; } bool Game::move(const Move &m) { if (m_aiThread) return false; if (!m.valid()) return false; switch (m_state) { case TURN_WHITE: if (m.player() != WHITE) return false; break; case TURN_BLACK: if (m.player() != BLACK) return false; break; default: return false; } if (m_field->move(m)) { m_moves.push_back(m); if (m_state == TURN_WHITE) { m_state = TURN_BLACK; if (possibleMoves().empty()) { if (m_field->inCheck(BLACK)) { m_state = WON_WHITE; } else { m_state = DRAW; } } } else { m_state = TURN_WHITE; if (possibleMoves().empty()) { if (m_field->inCheck(WHITE)) { m_state = WON_BLACK; } else { m_state = DRAW; } } } checkAI(); emit moved(m); emit changed(m_state); emit updated(); return true; } return false; } void Game::restart() { m_state = TURN_WHITE; m_field->reset(); checkAI(); emit changed(m_state); emit started(); emit updated(); } QList Game::possibleMoves() const { Player player = NOPLAYER; switch (m_state) { case TURN_WHITE: player = WHITE; break; case TURN_BLACK: player = BLACK; break; default: return QList(); } QList moves; for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) { Pos curp(x, y); if (0 < m_field->place(curp) * player) moves += m_field->validMoves(curp); } return moves; } void Game::setGameMode(GameMode gm) { m_gameMode = gm; checkAI(); emit changedMode(m_gameMode); } void Game::setAiStrength(int s) { m_aiStrength = qMin(7, qMax(2, s)); emit changedAIStrength(m_aiStrength); } void Game::checkAI() { if (m_aiThread) { switch (m_gameMode) { case HUMAN_HUMAN: m_ignoreAI = true; break; case HUMAN_COMPUTER: if (m_state != TURN_BLACK) m_ignoreAI = true; break; case COMPUTER_HUMAN: if (m_state != TURN_WHITE) m_ignoreAI = true; break; case COMPUTER_COMPUTER: break; } } else { Player curp = NOPLAYER; switch (m_state) { case TURN_WHITE: curp = WHITE; break; case TURN_BLACK: curp = BLACK; break; default: return; } switch (m_gameMode) { case HUMAN_HUMAN: return; case HUMAN_COMPUTER: if (m_state != TURN_BLACK) return; break; case COMPUTER_HUMAN: if (m_state != TURN_WHITE) return; break; case COMPUTER_COMPUTER: break; } m_aiThread = new GameAIThread(*m_field, curp, m_aiStrength, this); m_ignoreAI = false; connect(m_aiThread, SIGNAL(aiFinished(Move)), this, SLOT(finishedAI(Move))); m_aiThread->start(); } } void Game::finishedAI(Move m) { m_aiThread->wait(); delete m_aiThread; m_aiThread = 0; if (!m_ignoreAI) { move(m); } checkAI(); } GameAIThread::GameAIThread(const Field &field, Player curp, int depth, QObject *parent) : QThread(parent), m_field(field), m_curp(curp), m_depth(depth) { } void GameAIThread::run() { Move m = ai::getMove(m_field, m_curp, m_depth); emit aiFinished(m); } }