/**************************************************************************** Copyright (C) 2002-2008 Gilles Debunne. All rights reserved. This file is part of the QGLViewer library version 2.3.9. http://www.libqglviewer.com - contact@libqglviewer.com This file may be used under the terms of the GNU General Public License versions 2.0 or 3.0 as published by the Free Software Foundation and appearing in the LICENSE file included in the packaging of this file. In addition, as a special exception, Gilles Debunne gives you certain additional rights, described in the file GPL_EXCEPTION in this package. libQGLViewer uses dual licensing. Commercial/proprietary software must purchase a libQGLViewer Commercial License. This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. *****************************************************************************/ #include "blobWarViewer.h" #include "board.h" #include <fstream> #include <qmessagebox.h> #include <qfileinfo.h> #include <qfiledialog.h> #include <qstatusbar.h> #include <qtextedit.h> #include <qtimer.h> #if QT_VERSION >= 0x040000 # include "ui_blobWarWindow.Qt4.h" class BlobWarWindow : public QMainWindow, public Ui::BlobWarWindow {}; #else # include <qmainwindow.h> #endif using namespace std; using namespace qglviewer; static QString colorString[2] = { "Red", "Blue" }; class BoardConstraint : public Constraint { public: virtual void constrainRotation(Quaternion& q, Frame * const fr) { const Vec up = fr->transformOf(Vec(0.0, 0.0, 1.0)); Vec axis = q.axis(); float angle = 2.0*acos(q[3]); if (fabs(axis*up) > fabs(axis.x)) axis.projectOnAxis(up); else { angle = (axis.x > 0.0) ? angle : -angle; axis.setValue(fabs(axis.x), 0.0, 0.0); const float currentAngle = asin(fr->inverseTransformOf(Vec(0.0, 0.0, -1.0)).z); if (currentAngle + angle > -0.2) angle = -0.2 - currentAngle; // Not too low if (currentAngle + angle < -M_PI/2.0) angle = -M_PI/2.0 - currentAngle; // Do not pass on the other side } q = Quaternion(axis, angle); } }; #if QT_VERSION < 0x040000 BlobWarViewer::BlobWarViewer(QWidget* parent, const char* name) : QGLViewer(parent, name), #else BlobWarViewer::BlobWarViewer(QWidget* parent) : QGLViewer(parent), #endif boardFileName_("4x4.bwb"), selectedPiece_(-1), displayPossibleMoves_(true), animatePlays_(true), animationStep_(0) { kfi_ = new KeyFrameInterpolator(new Frame()); QObject::connect(kfi_, SIGNAL(interpolated()), this, SLOT(updateGL())); QObject::connect(kfi_, SIGNAL(endReached()), this, SLOT(flipColor())); undoTimer_ = new QTimer(); #if QT_VERSION >= 0x040000 undoTimer_->setSingleShot(true); #endif QObject::connect(undoTimer_, SIGNAL(timeout()), this, SLOT(playNextMove())); for (int i=0; i<2; ++i) connect(&(computerPlayer_[i]), SIGNAL(moveMade(QString, int)), this, SLOT(playComputerMove(QString, int))); QStringList programFileNames; programFileNames << "blobWarAI" << "blobWarAI.exe" << "../AI/blobWarAI" << "../../AI/release/blobWarAI.exe" << "../../AI/debug/blobWarAI.exe"; for (int i=0; i<programFileNames.size(); ++i) { if (QFileInfo(programFileNames.at(i)).isExecutable()) { computerPlayer_[0].setProgramFileName(programFileNames.at(i)); computerPlayer_[0].setIsActive(true); break; } } } // I n i t i a l i z a t i o n f u n c t i o n s void BlobWarViewer::init() { initViewer(); fitCameraToBoard(); setMouseBinding(Qt::RightButton, CAMERA, ROTATE); setMouseBinding(Qt::LeftButton, SELECT); #if QT_VERSION >= 0x040000 // Signals and slots connections BlobWarWindow* bww = (BlobWarWindow*)(window()); connect(bww->fileExitAction, SIGNAL(activated()), bww, SLOT(close())); connect(bww->fileOpenAction, SIGNAL(activated()), this, SLOT(load())); connect(bww->fileSaveAction, SIGNAL(activated()), this, SLOT(save())); connect(bww->fileSaveAsAction, SIGNAL(activated()), this, SLOT(saveAs())); connect(bww->gameNewGameAction, SIGNAL(activated()), this, SLOT(newGame())); connect(bww->gameUndoAction, SIGNAL(activated()), this, SLOT(undo())); connect(bww->gameRedoAction, SIGNAL(activated()), this, SLOT(redo())); connect(bww->gameBlueIsHumanAction, SIGNAL(toggled(bool)), this, SLOT(bluePlayerIsHuman(bool))); connect(bww->gameRedIsHumanAction, SIGNAL(toggled(bool)), this, SLOT(redPlayerIsHuman(bool))); connect(bww->gameBlueIsHumanAction, SIGNAL(toggled(bool)), bww->gameConfigureBluePlayerAction, SLOT(setDisabled(bool))); connect(bww->gameRedIsHumanAction, SIGNAL(toggled(bool)), bww->gameConfigureRedPlayerAction, SLOT(setDisabled(bool))); connect(bww->gameConfigureBluePlayerAction, SIGNAL(activated()), this, SLOT(configureBluePlayer())); connect(bww->gameConfigureRedPlayerAction, SIGNAL(activated()), this, SLOT(configureRedPlayer())); connect(bww->togglePossibleMoveAction, SIGNAL(toggled(bool)), this, SLOT(toggleDisplayPossibleMoves(bool))); connect(bww->toggleAnimationAction, SIGNAL(toggled(bool)), this, SLOT(toggleAnimation(bool))); connect(bww->helpAboutAction, SIGNAL(activated()), this, SLOT(about())); connect(bww->helpRulesAction, SIGNAL(activated()), this, SLOT(displayRules())); #endif } void BlobWarViewer::initViewer() { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); board_ = new Board(); QStringList boardDir; boardDir << "BlobWarBoards" << "../BlobWarBoards" << "../../BlobWarBoards"; for (int i=0; i<boardDir.size(); ++i) { if (QFileInfo(boardDir.at(i)).isDir()) { boardFileName_ = boardDir.at(i)+"/4x4.bwb"; break; } } newGame(); } void BlobWarViewer::newGame() { if (!QFileInfo(boardFileName_).isReadable()) { QMessageBox::warning(NULL ,"Unreadable board file", "Unable to read board file ("+boardFileName_+").\nLoad another board."); return; } #if QT_VERSION < 0x040000 std::ifstream f(boardFileName_.latin1()); #else std::ifstream f(boardFileName_.toLatin1().constData()); #endif f >> *board_; f.close(); selectedPiece_ = -1; playNextMove(); } void BlobWarViewer::fitCameraToBoard() { static BoardConstraint* boardConstraint = new BoardConstraint(); // Free camera rotation motion camera()->frame()->setConstraint(NULL); setSceneCenter(Vec(board_->size().width()/2.0, board_->size().height()/2.0, 0.0)); setSceneRadius(sceneCenter().norm()); camera()->setUpVector(Vec(0.0, 0.0, 1.0)); camera()->setPosition(Vec(2.0*sceneCenter().x, -1.5*sceneCenter().y, 0.8*(sceneCenter().x+sceneCenter().y))); camera()->lookAt(sceneCenter()); showEntireScene(); // Limit camera rotation motion camera()->frame()->setConstraint(boardConstraint); } // D r a w i n g f u n c t i o n s void BlobWarViewer::draw() { glEnable(GL_LIGHTING); if (animationStep_ > 0) { glPushMatrix(); glMultMatrixd(kfi_->frame()->matrix()); Board::drawPiece(board_->bluePlays()); glPopMatrix(); if (animationStep_ > 1) board_->drawFlippingPieces(currentMove_.end(), animationStep_%2); } board_->draw(); if (selectedPiece_ >= 0) { board_->drawSelectedPiece(selectedPiece_); if (displayPossibleMoves_) board_->drawPossibleDestinations(selectedPiece_); } board_->drawShadows(); } // G a m e I n t e r f a c e void BlobWarViewer::play(const Move& m) { currentMove_ = m; if (animatePlays_) animatePlay(); else simplePlay(); } void BlobWarViewer::simplePlay() { if ((!currentMove_.isClose()) && (animatePlays_)) board_->doDrawPiece(currentMove_.start()); board_->play(currentMove_); updateGL(); playNextMove(); } void BlobWarViewer::playNextMove() { static const QString stateFileName = "board.bwb"; // Updates statusBar #if QT_VERSION < 0x040000 ((QMainWindow*)(((QWidget*)parent())->parent()))->statusBar()->message(board_->statusMessage()); #else ((QMainWindow*)(((QWidget*)parent())->parent()))->statusBar()->showMessage(board_->statusMessage()); #endif if (board_->gameIsOver()) QMessageBox::information(this, "Game over", board_->statusMessage()); else { // Save board state #if QT_VERSION < 0x040000 std::ofstream f(stateFileName.latin1()); #else std::ofstream f(stateFileName.toLatin1().constData()); #endif f << *board_; f.close(); computerPlayer_[board_->bluePlays()].play(board_->bluePlays(), stateFileName); } } void BlobWarViewer::playComputerMove(QString move, int duration) { QString message = colorString[board_->bluePlays()]+" program played %1 in %2 out of %3 seconds."; message = message.arg(move).arg(duration/1000.0, 0, 'f', 1).arg(computerPlayer_[board_->bluePlays()].allowedTime()); #if QT_VERSION < 0x040000 qWarning(message.latin1()); #else qWarning("%s", message.toLatin1().constData()); #endif Move m(move); if (m.isValid(board_)) play(m); else QMessageBox::warning(this, "Wrong move", "The move "+move+" is not legal for "+colorString[board_->bluePlays()]); } void BlobWarViewer::animatePlay() { kfi_->deletePath(); // Cut/pasted from board posOf(). Not shared to prevent QGLViewer dependency in board.h if (currentMove_.isClose()) { kfi_->addKeyFrame(Frame(Vec(currentMove_.start().x()+0.5,currentMove_.start().y()+0.5,0.0), Quaternion())); kfi_->addKeyFrame(Frame(Vec(currentMove_.end().x()+0.5,currentMove_.end().y()+0.5,0.0), Quaternion())); } else { kfi_->addKeyFrame(Frame(Vec(currentMove_.start().x()+0.5,currentMove_.start().y()+0.5,0.0), Quaternion())); kfi_->addKeyFrame(Frame(Vec((currentMove_.start().x()+currentMove_.end().x()+1.0)/2.0, (currentMove_.start().y()+currentMove_.end().y()+1.0)/2.0, 1.0), Quaternion()), 0.6f); kfi_->addKeyFrame(Frame(Vec(currentMove_.end().x()+0.5,currentMove_.end().y()+0.5,0.0), Quaternion()), 1.2f); } if (!currentMove_.isClose()) board_->doNotDrawPiece(currentMove_.start()); animationStep_ = 1; kfi_->startInterpolation(); } void BlobWarViewer::flipColor() { updateGL(); if (animationStep_++ < 10) QTimer::singleShot(100, this, SLOT(flipColor())); else { animationStep_ = 0; simplePlay(); } } // M o v e S e l e c t i o n void BlobWarViewer::drawWithNames() { // Selection enabled only when the computer program is not active if ((!computerPlayer_[board_->bluePlays()].isActive()) && (animationStep_ ==0)) { if (selectedPiece_ >= 0) board_->drawPossibleDestinations(selectedPiece_, true); // Always drawned, so that a new selection can occur board_->drawSelectablePieces(); } } void BlobWarViewer::postSelection(const QPoint&) { if (selectedPiece_ >= 0) { if (selectedName() >= 0) if (selectedName() == selectedPiece_) selectedPiece_ = -1; else { Move m(board_, selectedPiece_, selectedName()); if (m.isValid(board_)) { selectedPiece_ = -1; play(m); } else if (board_->canBeSelected(selectedName())) selectedPiece_ = selectedName(); else selectedPiece_ = -1; } else selectedPiece_ = -1; } else if (selectedName() >= 0) selectedPiece_ = selectedName(); } // F i l e m e n u f u n c t i o n s void BlobWarViewer::selectBoardFileName() { #if QT_VERSION < 0x040000 boardFileName_ = QFileDialog::getOpenFileName("Boards", "BlobWar board files (*.bwb);;All files (*)", this); #else boardFileName_ = QFileDialog::getOpenFileName(this, "Select a saved game", boardFileName_, "BlobWar board files (*.bwb);;All files (*)"); #endif } void BlobWarViewer::load() { selectBoardFileName(); // In case of Cancel if (boardFileName_.isEmpty()) return; newGame(); fitCameraToBoard(); } void BlobWarViewer::save() { #if QT_VERSION < 0x040000 std::ofstream f(boardFileName_.latin1()); #else std::ofstream f(boardFileName_.toLatin1().constData()); #endif f << *board_; f.close(); } void BlobWarViewer::saveAs() { #if QT_VERSION < 0x040000 boardFileName_ = QFileDialog::getSaveFileName("Boards", "BlobWar board files (*.bwb);;All files (*)", this, boardFileName_.latin1()); #else boardFileName_ = QFileDialog::getSaveFileName(this, "Save game", boardFileName_, "BlobWar board files (*.bwb);;All files (*)"); #endif QFileInfo fi(boardFileName_); #if QT_VERSION < 0x040000 if (fi.extension().isEmpty()) #else if (fi.suffix().isEmpty()) #endif { boardFileName_ += ".bwb"; fi.setFile(boardFileName_); } #if QT_VERSION < 0x040000 if (fi.exists()) { if (QMessageBox::warning(this ,"Existing file", "File "+fi.fileName()+" already exists.\nSave anyway ?", QMessageBox::Yes,QMessageBox::Cancel) == QMessageBox::Cancel) return; if (!fi.isWritable()) { QMessageBox::warning(this ,"Cannot save", "File "+fi.fileName()+" is not writeable."); return; } } #endif save(); } // G a m e m e n u m e t h o d s void BlobWarViewer::bluePlayerIsHuman(bool on) { setPlayerIsHuman(on, true); } void BlobWarViewer::redPlayerIsHuman(bool on) { setPlayerIsHuman(on, false); } void BlobWarViewer::configureBluePlayer() { configurePlayer(true); } void BlobWarViewer::configureRedPlayer() { configurePlayer(false); } void BlobWarViewer::setPlayerIsHuman(bool on, bool blue) { computerPlayer_[blue].setIsActive(!on); playNextMove(); } void BlobWarViewer::configurePlayer(bool blue) { computerPlayer_[blue].configure(); } // U n d o a n d R e do void BlobWarViewer::undo() { if ((animationStep_==0) && (board_->undo())) finalizeUndoRedo(); } void BlobWarViewer::redo() { if ((animationStep_==0) && (board_->redo())) finalizeUndoRedo(); } void BlobWarViewer::finalizeUndoRedo() { selectedPiece_ = -1; updateGL(); #if QT_VERSION < 0x040000 undoTimer_->start(1000, true); #else undoTimer_->start(1000); #endif } void BlobWarViewer::about() { QMessageBox::about(this, "B l o b W a r", "B l o b W a r\nCreated by Gilles Debunne\nVersion 1.0 - January 2006"); } void BlobWarViewer::displayRules() { static QTextEdit* te; static const QString rules = "<b>B l o b W a r</b><br><br>" \ "Blob war is a strategy game for two players.<br><br>" \ "The goal is to have the maximum number of pieces when the board is full.<br><br>" \ "A piece can be moved on a empty square at a distance of one (i.e. adjacent) or two squares. " \ "If the distance is one, the piece is cloned, otherwise it jumps to its destination.<br><br>" \ "In both cases, all the pieces located in the neighborhood of the destination square change their color " \ "to that of the current player.<br><br>" \ "Enjoy !"; if (!te) { te = new QTextEdit(rules); te->resize(500,300); } te->show(); }