diff --git a/src/ai.cpp b/src/ai.cpp new file mode 100644 index 0000000..c7aade9 --- /dev/null +++ b/src/ai.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + * 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 "ai.h" + +namespace toruschess { + +// NOPIECE = 0, PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6 + static int piece_value[] = { + 0, 1, 3, 3, 5, 9, 10 + }; + static const int MAXVAL = 10000; + static const int NOMOVE = -MAXVAL - 1; + static const int DRAWVAL = -7; + + static int minimax(Field &field, int bal, Player curp, unsigned int depth, Move *move) { + if (depth == 0) return curp * bal; + + int mx = NOMOVE; + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { + Pos p(x, y); + if (field.player(p) != curp) continue; + int cbal = bal; + QList moves = field.validMoves(p); + foreach(Move m, moves) { + field.move_unchecked(m); + cbal -= m.prevTo(); + int val = -minimax(field, cbal, Player(-curp), depth-1, NULL); + if (val > mx) { + mx = val; + if (move) *move = m; + } + field.undo_unchecked(m); + if (mx == MAXVAL) return mx; + } + } + if (mx == NOMOVE) { + if (field.inCheck(curp)) return -MAXVAL; + return curp * DRAWVAL; + } + return mx; + } + + + Move ai::getMove(const Game *game) { + Player curp = NOPLAYER; + switch (game->state()) { + case TURN_WHITE: + curp = WHITE; + break; + case TURN_BLACK: + curp = BLACK; + break; + default: + return Move(); + } + + Field field = *game->field(); + int balance = 0; + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { + Pos p(x,y); + balance += field.player(p) * piece_value[field.piece(p)]; + } + + Move move; + minimax(field, balance, curp, 2, &move); + + return move; + } +} diff --git a/src/ai.h b/src/ai.h new file mode 100644 index 0000000..42e473d --- /dev/null +++ b/src/ai.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef TORUSCHESSAI_H +#define TORUSCHESSAI_H + +#include "toruschess.h" + +/** + @author Stefan Bühler +*/ +namespace toruschess { + class ai{ + public: + static Move getMove(const Game *game); + }; +} + +#endif diff --git a/src/field2d.cpp b/src/field2d.cpp index c905078..6fd83bc 100644 --- a/src/field2d.cpp +++ b/src/field2d.cpp @@ -29,15 +29,22 @@ namespace toruschess { Field2D::Field2D(Game *game, QWidget *parent) - : QWidget(parent), m_lib(new PieceLibrary(this)), m_game(game), m_fieldWidth(360), m_fieldHeight(360), m_originX(23), m_originY(23) { + : QWidget(parent), m_lib(new PieceLibrary(this)), m_game(game), m_fieldWidth(360), m_fieldHeight(360), m_originX(23), m_originY(23), m_fieldBuffer(0) { for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) m_marked[x][y] = false; m_lib->setSize(m_fieldWidth / 8, m_fieldHeight / 8); + checkSizes(); + connect(game, SIGNAL(updated()), this, SLOT(fieldUpdated())); + } + + Field2D::~Field2D() { + if (m_fieldBuffer) delete m_fieldBuffer; } void Field2D::markMoves(const QList &moves) { foreach(Move m, m_markedMoves) m_marked[m.to().x()][m.to().y()] = false; m_markedMoves = moves; foreach(Move m, m_markedMoves) m_marked[m.to().x()][m.to().y()] = true; + updateFieldBuffer(); update(); } @@ -47,27 +54,12 @@ namespace toruschess { void Field2D::paintEvent(QPaintEvent *) { QPainter pt(this); - int pieceWidth = m_fieldWidth / 8, pieceHeight = m_fieldHeight / 8; + int winWidth = width(), winHeight = height(); - for (int x = 0; x < 8; x++) - for (int y = 0; y < 8; y++) { - bool marked = m_marked[x][y]; - int place = m_game->field()->place(Pos(x, y)); - /* #D18B47 for dark, #FFCE9E for white */ - QBrush fieldBG = ((x+y) % 2 == 1) ? QBrush(QColor(0xD1, 0x8B, 0x47)) : QBrush(QColor(0xFF, 0xCE, 0x9E)); - - for (int px = m_originX - m_fieldWidth + x*pieceWidth; px < winWidth; px += m_fieldWidth) - for (int py = m_originY - m_fieldHeight + y*pieceHeight; py < winHeight; py += m_fieldHeight) { - QRect prect(px, py, pieceWidth - 1, pieceHeight - 1); - pt.fillRect(prect, fieldBG); - m_lib->paint(pt, place, prect); - if (marked) { - int r = qMax(1, (pieceWidth + pieceHeight) / 36); - pt.setBrush(place != 0 ? Qt::green : Qt::white); - pt.setPen(Qt::red); - pt.drawEllipse(QPoint(px + pieceWidth / 2, py + pieceHeight / 2), r, r); - } - } + + for (int px = m_originX - m_fieldWidth; px < winWidth; px += m_fieldWidth) + for (int py = m_originY - m_fieldHeight; py < winHeight; py += m_fieldHeight) { + pt.drawImage(QRect(px, py, m_fieldWidth, m_fieldHeight), *m_fieldBuffer); } } @@ -94,6 +86,8 @@ namespace toruschess { } void Field2D::mouseReleaseEvent(QMouseEvent *event) { + m_mouseLastX = event->x(); + m_mouseLastY = event->y(); if (event->button() == Qt::LeftButton) { if (m_markedMoves.size() == 0) return; Pos from = m_markedMoves[0].from(); @@ -103,8 +97,6 @@ namespace toruschess { Move m(m_game->field(), from, p); m_game->move(m); } - m_mouseLastX = event->x(); - m_mouseLastY = event->y(); } void Field2D::wheelEvent(QWheelEvent *event) { @@ -122,10 +114,45 @@ namespace toruschess { m_originY = m_originY % m_fieldHeight; if (m_originX < 0) m_originX += m_fieldWidth; if (m_originY < 0) m_originY += m_fieldHeight; + if (!m_fieldBuffer || m_fieldBuffer->width() != m_fieldWidth || m_fieldBuffer->height() != m_fieldHeight) { + if (m_fieldBuffer) delete m_fieldBuffer; + m_fieldBuffer = new QImage(QSize(m_fieldWidth, m_fieldHeight), QImage::Format_ARGB32_Premultiplied); + m_fieldBuffer->fill(Qt::black); + updateFieldBuffer(); + } + } + + void Field2D::updateFieldBuffer() { + QPainter pt(m_fieldBuffer); + + int pieceWidth = m_fieldWidth / 8; + int pieceHeight = m_fieldHeight / 8; + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { + bool marked = m_marked[x][y]; + int place = m_game->field()->place(Pos(x, y)); + /* #D18B47 for dark, #FFCE9E for white */ + QBrush fieldBG = ((x+y) % 2 == 1) ? QBrush(QColor(0xD1, 0x8B, 0x47)) : QBrush(QColor(0xFF, 0xCE, 0x9E)); + + QRect prect(x * pieceWidth, y * pieceHeight, pieceWidth - 1, pieceHeight - 1); + pt.fillRect(prect, fieldBG); + m_lib->paint(pt, place, prect); + if (marked) { + int r = qMax(1, (pieceWidth + pieceHeight) / 36); + pt.setBrush(place != 0 ? Qt::green : Qt::white); + pt.setPen(Qt::red); + pt.drawEllipse(QPoint(prect.x() + pieceWidth / 2, prect.y() + pieceHeight / 2), r, r); + } + } + update(); } Pos Field2D::findPos(int x, int y) { return Pos(8 * ((x - m_originX) % m_fieldWidth) / m_fieldWidth, 8 * ((y - m_originY) % m_fieldHeight) / m_fieldHeight); } + void Field2D::fieldUpdated() { + markMoves(QList()); + } + } diff --git a/src/field2d.h b/src/field2d.h index d3aa48a..9ce7347 100644 --- a/src/field2d.h +++ b/src/field2d.h @@ -35,6 +35,7 @@ namespace toruschess { Q_OBJECT public: Field2D(Game *game, QWidget *parent = 0); + virtual ~Field2D(); void markMoves(const QList &moves); @@ -48,9 +49,13 @@ namespace toruschess { virtual void wheelEvent(QWheelEvent *event); void checkSizes(); + void updateFieldBuffer(); Pos findPos(int x, int y); + protected slots: + void fieldUpdated(); + private: PieceLibrary *m_lib; Game *m_game; @@ -61,6 +66,8 @@ namespace toruschess { int m_originX, m_originY; int m_mouseLastX, m_mouseLastY; + + QImage *m_fieldBuffer; }; } diff --git a/src/piecelibrary.cpp b/src/piecelibrary.cpp index 65e0f1c..3f73328 100644 --- a/src/piecelibrary.cpp +++ b/src/piecelibrary.cpp @@ -138,7 +138,7 @@ typedef size_t (*Lib3dsIoWriteFunc)(void *self, const void *buffer, size_t size) } PieceLibrary::~PieceLibrary() { - lib3ds_file_free(m_pawn); + if (m_pawn) lib3ds_file_free(m_pawn); delete [] m_pieces; delete [] m_buffers; } diff --git a/src/src.pro b/src/src.pro index be8315d..649ccc0 100644 --- a/src/src.pro +++ b/src/src.pro @@ -3,7 +3,8 @@ SOURCES += main.cpp \ toruschess.cpp \ field2d.cpp \ piecelibrary.cpp \ - field3d.cpp + field3d.cpp \ + ai.cpp TEMPLATE = app CONFIG += warn_on \ thread \ @@ -20,8 +21,12 @@ HEADERS += testgame.h \ toruschess.h \ field2d.h \ piecelibrary.h \ - field3d.h -CONFIG -= release + field3d.h \ + ai.h LIBS += -l3ds +QMAKE_CXXFLAGS_RELEASE += -g + +CONFIG -= release + diff --git a/src/testgame.cpp b/src/testgame.cpp index 2b10395..7edf560 100644 --- a/src/testgame.cpp +++ b/src/testgame.cpp @@ -23,10 +23,13 @@ #include #include #include +#include #include "field2d.h" #include "field3d.h" +#include "ai.h" + namespace toruschess { TestPlace::TestPlace(QWidget *parent, const PieceLibrary *lib, const Field *field, const Pos &p) @@ -115,7 +118,21 @@ namespace toruschess { setWindowTitle("Torus Chess"); // PieceLibrary *lib = new PieceLibrary(this); // setCentralWidget(new TestField(this, lib, m_game)); - setCentralWidget(new Field3D(m_game, this)); + setCentralWidget(new Field2D(m_game, this)); + statusBar()->show(); + connect(m_game, SIGNAL(updated()), this, SLOT(gameUpdated())); + connect(m_game, SIGNAL(changed(GameState)), this, SLOT(gameChanged(GameState))); + m_game->restart(); + } + + void TestGame::gameUpdated() { + if (m_game->state() == TURN_BLACK) { + Move m = ai::getMove(m_game); + if (m.valid()) m_game->move(m); + } + } + + void TestGame::gameChanged(GameState state) { + statusBar()->showMessage(state2string(state)); } - } diff --git a/src/testgame.h b/src/testgame.h index fdd6d91..e5deed6 100644 --- a/src/testgame.h +++ b/src/testgame.h @@ -80,6 +80,10 @@ namespace toruschess { public: TestGame(QWidget *parent = 0); + private slots: + void gameUpdated(); + void gameChanged(GameState state); + private: Game *m_game; }; diff --git a/src/toruschess.cpp b/src/toruschess.cpp index 9cd6636..12d9199 100644 --- a/src/toruschess.cpp +++ b/src/toruschess.cpp @@ -35,6 +35,9 @@ namespace toruschess { 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 }, @@ -46,11 +49,14 @@ namespace toruschess { { 0, 0, 0, 0, 0, 0, 0, 0 } }; - Field::Field() { + 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)); } @@ -102,7 +108,7 @@ namespace toruschess { QList Field::simpleValidMoves(const Pos &from) const { QList moves; int thePlace = place(from); - Piece thePiece = (Piece) qAbs(thePlace); + Piece thePiece = place2piece(thePlace); // qDebug("simpleValidMoves: %i", thePiece); switch (thePiece) { case NOPIECE: @@ -152,69 +158,163 @@ namespace toruschess { QList Field::validMoves(const Pos &from) const { QList moves = simpleValidMoves(from); - int thePlace = place(from); - Pos pking(0,0); - /* find king */ - for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) { - if (piece(Pos(x, y)) == KING && 0 < place(Pos(x, y)) * thePlace) { - pking = Pos(x, y); - goto foundking; - } - } - return QList(); /* error, no king found */ -foundking: QList resMoves; Field testField(*this); - testField.place(from) = 0; - bool movedKing = (from == pking); - Player player = place2player(thePlace); + Player pl = player(from); foreach(Move m, moves) { - testField.place(m.to()) = thePlace; - if (!testField.inCheck(player, movedKing ? m.to() : pking)) { + Q_ASSERT(validMove(m)); + testField.move_unchecked(m); + if (!testField.inCheck(pl)) { resMoves.push_back(m); } - testField.place(m.to()) = m.prevTo(); + 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 { - QList moves = validMoves(m.from()); - return moves.contains(m); + 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; - place(m.from()) = 0; - place(m.to()) = m.prevFrom(); + 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; - place(m.from()) = m.prevFrom(); - place(m.to()) = m.prevTo(); - 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 && simpleValidMoves(curp).contains(Move(this, curp, pking))) { + if (0 > place(curp) * player && validMove(Move(this, curp, pking))) { return true; } } return false; } - bool Field::inCheck(Player player) const { - /* find king */ - for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) { - Pos curp(x, y); - if (piece(curp) == KING && 0 < place(curp) * player) { - return inCheck(player, curp); - } - } - return false; /* error, no king found */ + 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) @@ -225,6 +325,7 @@ foundking: } bool Game::move(const Move &m) { + if (!m.valid()) return false; switch (m_state) { case TURN_WHITE: if (m.player() != WHITE) return false; diff --git a/src/toruschess.h b/src/toruschess.h index 2f5db52..23012d0 100644 --- a/src/toruschess.h +++ b/src/toruschess.h @@ -64,6 +64,7 @@ namespace toruschess { class Move { public: + Move(); Move(const Field* field, const Pos &from, const Pos &to); const Pos& from() const { return m_from; } @@ -80,6 +81,8 @@ namespace toruschess { /* player is included in m_prevFrom */ } + bool valid() const { return m_prevFrom != 0; } + private: Pos m_from,m_to; int m_prevFrom, m_prevTo; @@ -98,16 +101,19 @@ namespace toruschess { int& place(const Pos &p) { return m_places[p.y()][p.x()]; } QList validMoves(const Pos &from) const; - bool validMove(const Move &m) const; + bool validMove(const Move &m) const; /* does not check for inCheck */ bool move(const Move &m); bool undo(const Move &m); bool inCheck(Player player, const Pos &pking) const; bool inCheck(Player player) const; + void move_unchecked(const Move &m); + void undo_unchecked(const Move &m); private: QList simpleValidMoves(const Pos &from) const; - + + Pos m_wking, m_bking; int m_places[8][8]; }; diff --git a/toruschess.kdevelop b/toruschess.kdevelop index 800e167..e2f6c5e 100644 --- a/toruschess.kdevelop +++ b/toruschess.kdevelop @@ -152,13 +152,13 @@ false - + /home/stefan/studium/7. semester/fapra_vis/src/toruschess/.. - +