1 /****************************************************************************
2
3 Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.
4
5 This file is part of the QGLViewer library version 2.7.2.
6
7 http://www.libqglviewer.com - contact@libqglviewer.com
8
9 This file may be used under the terms of the GNU General Public License
10 versions 2.0 or 3.0 as published by the Free Software Foundation and
11 appearing in the LICENSE file included in the packaging of this file.
12 In addition, as a special exception, Gilles Debunne gives you certain
13 additional rights, described in the file GPL_EXCEPTION in this package.
14
15 libQGLViewer uses dual licensing. Commercial/proprietary software must
16 purchase a libQGLViewer Commercial License.
17
18 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20
21 *****************************************************************************/
22
23 #include "agoraViewer.h"
24 #include "board.h"
25
26 #include <fstream>
27 #include <qfiledialog.h>
28 #include <qfileinfo.h>
29 #include <qmessagebox.h>
30 #include <qstatusbar.h>
31 #include <qtextedit.h>
32 #include <qtimer.h>
33
34 #include <QGLViewer/manipulatedCameraFrame.h>
35
36 #include "ui_agoraWindow.h"
37 class AgoraWindow : public QMainWindow, public Ui::AgoraWindow {};
38
39 using namespace std;
40 using namespace qglviewer;
41
42 static QString colorString[2] = {"White", "Black"};
43
44 class BoardConstraint : public Constraint {
45 public:
constrainRotation(Quaternion & q,Frame * const fr)46 virtual void constrainRotation(Quaternion &q, Frame *const fr) {
47 const Vec up = fr->transformOf(Vec(0.0, 0.0, 1.0));
48 Vec axis = q.axis();
49 float angle = 2.0 * acos(q[3]);
50 if (fabs(axis * up) > fabs(axis.x))
51 axis.projectOnAxis(up);
52 else {
53 angle = (axis.x > 0.0) ? angle : -angle;
54 axis.setValue(fabs(axis.x), 0.0, 0.0);
55 const float currentAngle =
56 asin(fr->inverseTransformOf(Vec(0.0, 0.0, -1.0)).z);
57 if (currentAngle + angle > -0.2)
58 angle = -0.2 - currentAngle; // Not too low
59 if (currentAngle + angle < -M_PI / 2.0)
60 angle = -M_PI / 2.0 - currentAngle; // Do not pass on the other side
61 }
62 q = Quaternion(axis, angle);
63 }
64 };
65
66 #if QT_VERSION < 0x040000
AgoraViewer(QWidget * parent,const char * name)67 AgoraViewer::AgoraViewer(QWidget *parent, const char *name)
68 : QGLViewer(parent, name),
69 #else
70 AgoraViewer::AgoraViewer(QWidget *parent)
71 : QGLViewer(parent),
72 #endif
73 boardFileName_("standard.ago"), selectedPiece_(-1),
74 displayPossibleMoves_(true), animatePlays_(false /*true*/),
75 animationStep_(0) {
76 kfi_ = new KeyFrameInterpolator(new Frame());
77
78 QObject::connect(kfi_, SIGNAL(interpolated()), this, SLOT(update()));
79 QObject::connect(kfi_, SIGNAL(endReached()), this, SLOT(simplePlay()));
80
81 undoTimer_ = new QTimer();
82 #if QT_VERSION >= 0x040000
83 undoTimer_->setSingleShot(true);
84 #endif
85 QObject::connect(undoTimer_, SIGNAL(timeout()), this, SLOT(playNextMove()));
86
87 for (int i = 0; i < 2; ++i)
88 connect(&(computerPlayer_[i]), SIGNAL(moveMade(QString, int)), this,
89 SLOT(playComputerMove(QString, int)));
90
91 computerPlayer_[0].setIsActive(false);
92 computerPlayer_[1].setIsActive(false);
93
94 QStringList programFileNames;
95 programFileNames << "agoraAI"
96 << "agoraAI.exe"
97 << "../AI/agoraAI"
98 << "../../AI/release/agoraAI.exe"
99 << "../../AI/debug/agoraAI.exe"
100 << "../AI/release/agoraAI.exe"
101 << "../AI/debug/agoraAI.exe";
102 for (int i = 0; i < programFileNames.size(); ++i) {
103 if (QFileInfo(programFileNames.at(i)).isExecutable()) {
104 for (int c = 0; c < 2; ++c)
105 computerPlayer_[c].setProgramFileName(programFileNames.at(i));
106 break;
107 }
108 }
109 }
110
111 // I n i t i a l i z a t i o n f u n c t i o n s
112
init()113 void AgoraViewer::init() {
114 initViewer();
115 initSpotLight();
116 initBoard();
117 fitCameraToBoard();
118
119 setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, ROTATE);
120 setMouseBinding(Qt::NoModifier, Qt::LeftButton, SELECT);
121
122 #if QT_VERSION >= 0x040000
123 // Signals and slots connections
124 AgoraWindow *agoWin = (AgoraWindow *)(window());
125 connect(agoWin->fileExitAction, SIGNAL(activated()), agoWin, SLOT(close()));
126 connect(agoWin->fileOpenAction, SIGNAL(activated()), this, SLOT(load()));
127 connect(agoWin->fileSaveAction, SIGNAL(activated()), this, SLOT(save()));
128 connect(agoWin->fileSaveAsAction, SIGNAL(activated()), this, SLOT(saveAs()));
129 connect(agoWin->gameNewGameAction, SIGNAL(activated()), this,
130 SLOT(newGame()));
131 connect(agoWin->gameUndoAction, SIGNAL(activated()), this, SLOT(undo()));
132 connect(agoWin->gameRedoAction, SIGNAL(activated()), this, SLOT(redo()));
133
134 connect(agoWin->gameBlackIsHumanAction, SIGNAL(toggled(bool)), this,
135 SLOT(blackPlayerIsHuman(bool)));
136 connect(agoWin->gameWhiteIsHumanAction, SIGNAL(toggled(bool)), this,
137 SLOT(whitePlayerIsHuman(bool)));
138
139 connect(agoWin->gameBlackIsHumanAction, SIGNAL(toggled(bool)),
140 agoWin->gameConfigureBlackPlayerAction, SLOT(setDisabled(bool)));
141 connect(agoWin->gameWhiteIsHumanAction, SIGNAL(toggled(bool)),
142 agoWin->gameConfigureWhitePlayerAction, SLOT(setDisabled(bool)));
143
144 connect(agoWin->gameConfigureBlackPlayerAction, SIGNAL(activated()), this,
145 SLOT(configureBlackPlayer()));
146 connect(agoWin->gameConfigureWhitePlayerAction, SIGNAL(activated()), this,
147 SLOT(configureWhitePlayer()));
148
149 connect(agoWin->togglePossibleMoveAction, SIGNAL(toggled(bool)), this,
150 SLOT(toggleDisplayPossibleMoves(bool)));
151 connect(agoWin->toggleAnimationAction, SIGNAL(toggled(bool)), this,
152 SLOT(toggleAnimation(bool)));
153
154 connect(agoWin->helpAboutAction, SIGNAL(activated()), this, SLOT(about()));
155 connect(agoWin->helpRulesAction, SIGNAL(activated()), this,
156 SLOT(displayRules()));
157 #endif
158 }
159
initViewer()160 void AgoraViewer::initViewer() {
161 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
162
163 // Enable GL textures
164 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
165 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
166
167 // Nice texture coordinate interpolation
168 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
169
170 const QString texFileName("wood.png");
171 QImage img(texFileName);
172
173 if (img.isNull()) {
174 QMessageBox::warning(
175 NULL, QString("Texture file not found"),
176 QString("Unable to load wood texture (%1).").arg(texFileName));
177 return;
178 }
179
180 // 1E-3 needed. Just try with width=128 and see !
181 if ((img.width() != 1 << (int)(1 + log(img.width() - 1 + 1E-3) / log(2.0))) ||
182 (img.height() !=
183 1 << (int)(1 + log(img.height() - 1 + 1E-3) / log(2.0)))) {
184 QMessageBox::warning(
185 NULL, QString("Incorrect texture"),
186 QString("Wood texture dimensions are not powers of 2."));
187 return;
188 }
189
190 QImage glImg = QGLWidget::convertToGLFormat(img); // flipped 32bit RGBA
191
192 // Bind the img texture...
193 glTexImage2D(GL_TEXTURE_2D, 0, 4, glImg.width(), glImg.height(), 0, GL_RGBA,
194 GL_UNSIGNED_BYTE, glImg.bits());
195 glDisable(GL_TEXTURE_2D);
196 }
197
initBoard()198 void AgoraViewer::initBoard() {
199 board_ = new Board();
200
201 QStringList boardDir;
202 boardFileName_ = "";
203 boardDir << "AgoraBoards"
204 << "../AgoraBoards"
205 << "../../AgoraBoards";
206 for (int i = 0; i < boardDir.size(); ++i) {
207 if (QFileInfo(boardDir.at(i)).isDir()) {
208 boardFileName_ = boardDir.at(i) + "/standard.ago";
209 break;
210 }
211 }
212
213 if (boardFileName_.isEmpty()) {
214 QMessageBox::warning(NULL, "Unable to find agora board directory",
215 "Unable to find agora board directory\n(tried " +
216 boardDir.join(", ") + ")");
217 return;
218 }
219
220 newGame();
221 }
222
initSpotLight()223 void AgoraViewer::initSpotLight() {
224 glMatrixMode(GL_MODELVIEW);
225 glEnable(GL_LIGHT1);
226 glLoadIdentity();
227
228 // Light default parameters
229 const GLfloat light_ambient[4] = {2.0, 2.0, 2.0, 1.0};
230 const GLfloat light_specular[4] = {1.0, 1.0, 1.0, 1.0};
231 const GLfloat light_diffuse[4] = {2.0, 2.0, 2.0, 1.0};
232
233 glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);
234 glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 60.0);
235 glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 0.1f);
236 glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.3f);
237 glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.3f);
238 glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);
239 glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular);
240 glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
241 }
242
newGame()243 void AgoraViewer::newGame() {
244 if (!QFileInfo(boardFileName_).isReadable()) {
245 QMessageBox::warning(NULL, "Unreadable board file",
246 "Unable to read board file (" + boardFileName_ +
247 ").\nLoad another board.");
248 return;
249 }
250
251 #if QT_VERSION < 0x040000
252 std::ifstream f(boardFileName_.latin1());
253 #else
254 std::ifstream f(boardFileName_.toLatin1().constData());
255 #endif
256 f >> *board_;
257 f.close();
258
259 selectedPiece_ = -1;
260
261 playNextMove();
262 }
263
fitCameraToBoard()264 void AgoraViewer::fitCameraToBoard() {
265 static BoardConstraint *boardConstraint = new BoardConstraint();
266
267 // Free camera rotation motion
268 camera()->frame()->setConstraint(NULL);
269
270 setSceneCenter(
271 Vec(board_->size().width() / 2.0, board_->size().height() / 2.0, 0.0));
272 setSceneRadius(sceneCenter().norm());
273
274 camera()->setUpVector(Vec(0.0, 0.0, 1.0));
275 camera()->setPosition(Vec(2.0f * sceneCenter().x, -1.5f * sceneCenter().y,
276 0.9f * (sceneCenter().x + sceneCenter().y)));
277 camera()->lookAt(sceneCenter());
278 // showEntireScene();
279
280 // Limit camera rotation motion
281 camera()->frame()->setConstraint(boardConstraint);
282 }
283
284 // D r a w i n g f u n c t i o n s
draw()285 void AgoraViewer::draw() {
286 glEnable(GL_LIGHTING);
287
288 if (animationStep_ > 0) {
289 glPushMatrix();
290 glMultMatrixd(kfi_->frame()->matrix());
291 // Board::drawPiece(board_->blackPlays());
292 glPopMatrix();
293
294 // if (animationStep_ > 1)
295 // board_->drawFlippingPieces(currentMove_.end(), animationStep_%2);
296 }
297
298 const GLfloat pos[4] = {board_->size().width() / 2.0,
299 board_->size().height() / 2.0, 3.0, 1.0};
300 const GLfloat spot_dir[3] = {0.0, 0.0, -1.0};
301 glLightfv(GL_LIGHT1, GL_POSITION, pos);
302 glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spot_dir);
303
304 board_->draw();
305
306 if (selectedPiece_ >= 0) {
307 board_->drawSelectedPiece(selectedPiece_);
308 if (displayPossibleMoves_)
309 board_->drawPossibleDestinations(selectedPiece_);
310 }
311 }
312
313 // G a m e I n t e r f a c e
play(const Move & m)314 void AgoraViewer::play(const Move &m) {
315 currentMove_ = m;
316 if (animatePlays_)
317 animatePlay();
318 else
319 simplePlay();
320 }
321
simplePlay()322 void AgoraViewer::simplePlay() {
323 board_->play(currentMove_);
324 update();
325 playNextMove();
326 }
327
playNextMove()328 void AgoraViewer::playNextMove() {
329 static const QString stateFileName = "board.ago";
330
331 // Updates statusBar
332 #if QT_VERSION < 0x040000
333 ((QMainWindow *)(((QWidget *)parent())->parent()))
334 ->statusBar()
335 ->message(board_->statusMessage());
336 #else
337 ((QMainWindow *)(((QWidget *)parent())->parent()))
338 ->statusBar()
339 ->showMessage(board_->statusMessage());
340 #endif
341
342 if (board_->gameIsOver())
343 QMessageBox::information(this, "Game over", board_->statusMessage());
344 else {
345 // Save board state
346 #if QT_VERSION < 0x040000
347 std::ofstream f(stateFileName.latin1());
348 #else
349 std::ofstream f(stateFileName.toLatin1().constData());
350 #endif
351 f << *board_;
352 f.close();
353
354 computerPlayer_[board_->blackPlays()].play(
355 board_->blackPlays(), stateFileName, board_->nbMovesLeft());
356 }
357 }
358
playComputerMove(QString move,int duration)359 void AgoraViewer::playComputerMove(QString move, int duration) {
360 QString message = colorString[board_->blackPlays()] +
361 " program played %1 in %2 out of %3 seconds.";
362 message =
363 message.arg(move)
364 .arg(duration / 1000.0, 0, 'f', 1)
365 .arg(computerPlayer_[board_->blackPlays()].allowedTime() / 1000.0, 0,
366 'f', 1);
367 #if QT_VERSION < 0x040000
368 qWarning(message.latin1());
369 #else
370 qWarning("%s", message.toLatin1().constData());
371 #endif
372 Move m(move);
373 if (m.isValid(board_))
374 play(m);
375 else
376 QMessageBox::warning(this, "Unvalid move",
377 "The move " + move + " is not valid for " +
378 colorString[board_->blackPlays()]);
379 }
380
animatePlay()381 void AgoraViewer::animatePlay() {
382 kfi_->deletePath();
383 // Cut/pasted from board posOf(). Not shared to prevent QGLViewer dependency
384 // in board.h
385
386 // kfi_->addKeyFrame(Frame(Vec(currentMove_.start().x()+0.5,currentMove_.start().y()+0.5,0.0),
387 //Quaternion()));
388 // kfi_->addKeyFrame(Frame(Vec((currentMove_.start().x()+currentMove_.end().x()+1.0)/2.0,
389 // (currentMove_.start().y()+currentMove_.end().y()+1.0)/2.0, 1.0),
390 //Quaternion()), 0.6f);
391 // kfi_->addKeyFrame(Frame(Vec(currentMove_.end().x()+0.5,currentMove_.end().y()+0.5,0.0),
392 // Quaternion()), 1.2f);
393 kfi_->addKeyFrame(Frame(
394 Vec(currentMove_.end().x() + 0.5, currentMove_.end().y() + 0.5, 0.0),
395 Quaternion()));
396
397 animationStep_ = 1;
398 kfi_->startInterpolation();
399 }
400
401 // M o v e S e l e c t i o n
drawWithNames()402 void AgoraViewer::drawWithNames() {
403 // Selection enabled only when the computer program is not active
404 if ((!computerPlayer_[board_->blackPlays()].isActive()) &&
405 (animationStep_ == 0)) {
406 if (selectedPiece_ >= 0)
407 board_->drawPossibleDestinations(selectedPiece_, true);
408
409 // Always drawned, so that a new selection can occur
410 board_->drawSelectablePieces();
411 }
412 }
413
postSelection(const QPoint &)414 void AgoraViewer::postSelection(const QPoint &) {
415 if (selectedPiece_ >= 0) {
416 if (selectedName() >= 0)
417 if (selectedName() == selectedPiece_)
418 selectedPiece_ = -1;
419 else {
420 Move m(board_, selectedPiece_, selectedName(),
421 qApp->keyboardModifiers() != Qt::NoModifier);
422 if (m.isValid(board_)) {
423 selectedPiece_ = -1;
424 play(m);
425 } else if (board_->canBeSelected(selectedName()))
426 selectedPiece_ = selectedName();
427 else
428 selectedPiece_ = -1;
429 }
430 else
431 selectedPiece_ = -1;
432 } else if (selectedName() >= 0)
433 selectedPiece_ = selectedName();
434 }
435
436 // F i l e m e n u f u n c t i o n s
selectBoardFileName()437 void AgoraViewer::selectBoardFileName() {
438 #if QT_VERSION < 0x040000
439 boardFileName_ = QFileDialog::getOpenFileName(
440 "Boards", "Agora files (*.ago);;All files (*)", this);
441 #else
442 boardFileName_ =
443 QFileDialog::getOpenFileName(this, "Select a saved game", boardFileName_,
444 "Agora files (*.ago);;All files (*)");
445 #endif
446 }
447
load()448 void AgoraViewer::load() {
449 selectBoardFileName();
450
451 // In case of Cancel
452 if (boardFileName_.isEmpty())
453 return;
454
455 newGame();
456 fitCameraToBoard();
457 }
458
save()459 void AgoraViewer::save() {
460 #if QT_VERSION < 0x040000
461 std::ofstream f(boardFileName_.latin1());
462 #else
463 std::ofstream f(boardFileName_.toLatin1().constData());
464 #endif
465 f << *board_;
466 f.close();
467 }
468
saveAs()469 void AgoraViewer::saveAs() {
470 #if QT_VERSION < 0x040000
471 boardFileName_ = QFileDialog::getSaveFileName(
472 "Boards", "Agora files (*.ago);;All files (*)", this,
473 boardFileName_.latin1());
474 #else
475 boardFileName_ = QFileDialog::getSaveFileName(
476 this, "Save game", boardFileName_, "Agora files (*.ago);;All files (*)");
477 #endif
478
479 QFileInfo fi(boardFileName_);
480
481 #if QT_VERSION < 0x040000
482 if (fi.extension().isEmpty())
483 #else
484 if (fi.suffix().isEmpty())
485 #endif
486 {
487 boardFileName_ += ".ago";
488 fi.setFile(boardFileName_);
489 }
490
491 #if QT_VERSION < 0x040000
492 if (fi.exists()) {
493 if (QMessageBox::warning(
494 this, "Existing file",
495 "File " + fi.fileName() + " already exists.\nSave anyway ?",
496 QMessageBox::Yes, QMessageBox::Cancel) == QMessageBox::Cancel)
497 return;
498
499 if (!fi.isWritable()) {
500 QMessageBox::warning(this, "Cannot save",
501 "File " + fi.fileName() + " is not writeable.");
502 return;
503 }
504 }
505 #endif
506
507 save();
508 }
509
510 // G a m e m e n u m e t h o d s
511
blackPlayerIsHuman(bool on)512 void AgoraViewer::blackPlayerIsHuman(bool on) { setPlayerIsHuman(on, true); }
513
whitePlayerIsHuman(bool on)514 void AgoraViewer::whitePlayerIsHuman(bool on) { setPlayerIsHuman(on, false); }
515
configureBlackPlayer()516 void AgoraViewer::configureBlackPlayer() { configurePlayer(true); }
517
configureWhitePlayer()518 void AgoraViewer::configureWhitePlayer() { configurePlayer(false); }
519
setPlayerIsHuman(bool on,bool black)520 void AgoraViewer::setPlayerIsHuman(bool on, bool black) {
521 computerPlayer_[black].setIsActive(!on);
522 playNextMove();
523 }
524
configurePlayer(bool black)525 void AgoraViewer::configurePlayer(bool black) {
526 computerPlayer_[black].configure();
527 }
528
529 // U n d o a n d R e do
undo()530 void AgoraViewer::undo() {
531 if ((animationStep_ == 0) && (board_->undo()))
532 finalizeUndoRedo();
533 }
534
redo()535 void AgoraViewer::redo() {
536 if ((animationStep_ == 0) && (board_->redo()))
537 finalizeUndoRedo();
538 }
539
finalizeUndoRedo()540 void AgoraViewer::finalizeUndoRedo() {
541 selectedPiece_ = -1;
542 update();
543 #if QT_VERSION < 0x040000
544 undoTimer_->start(1000, true);
545 #else
546 undoTimer_->start(1000);
547 #endif
548 }
549
about()550 void AgoraViewer::about() {
551 QMessageBox::about(
552 this, "A g o r a",
553 "A g o r a\nCreated by Gilles Debunne\nVersion 2.0 - January 2008");
554 }
555
displayRules()556 void AgoraViewer::displayRules() {
557 static QTextEdit *te;
558
559 static const QString rules =
560 "<b>A g o r a</b><br><br>"
561 "Agora is a strategy game for two players.<br><br>"
562 "The goal is to have the maximum number of pieces of your color when the "
563 "number of allowed moves is reached "
564 "(or to convert all the pieces into your color before that).<br><br>"
565 "A piece can be moved on a nearby square... "
566 "details to come.<br><br>"
567 "Enjoy !";
568
569 if (!te) {
570 te = new QTextEdit(rules);
571 te->resize(500, 300);
572 }
573
574 te->show();
575 }
576