Home | Documentation | Download | Screenshots | Developer |
Implementation of the game of Agora.
Agora is a strategy game for two players. The code is rather complex and can be divided in two parts : a QGLViewer implementation for the viewer and an artificial intelligence that allows you to play against the computer.
You should find the rules of the game on the web. Questions relative to the artificial
intelligence implementation should be asked to Jean-Guillaume dot Dumas at imag dot fr
.
// =================================================================== // // Time-stamp: <01 Jul 03 16:27:25 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_Plateau__ #define __Agora_Plateau__ #include <iostream> #include <vector> #include "givtimer.h" #define SHORTCOULEURMASK 32768 #define WHITE false #define BLACK true #define ADVANCEDRULES #include "agora_class.h" #include "agora_io.h" #include "agora_types.h" #include "agora_coup.h" #include "agora_container.h" template<class Ints> class Agora : public std::vector<Ints> { public: typedef Agora< Ints > Self_t; typedef Ints Pile_t; typedef std::vector<Pile_t> Plateau_t; typedef std::vector< std::vector< Case_t > > Voisin_t; typedef AgoraCoup Coup; typedef AgoraContainer<256, Coup> Possible_t; typedef std::vector< Possible_t > Table_Possible_t; typedef std::vector<Eval_t> Table_Eval_t; private: Case_t _row, _size; Possible_t TopPM; Table_Possible_t _PM; Hauteur_t _bits; Size_t _bsize; Table_t _hauteurs, _uns, _pris, _altitude; Table_Eval_t _eval; Plateau_t _enleve, _ajoute_noir, _ajoute_blanc, _ajoute_prison; Voisin_t _voisins; public: Agora(int n = 6) ; void reinit() ; // Tabulées bool couleur(const Pile_t p) const ; Hauteur_t taille( const Pile_t i ) const; Hauteur_t uns( const Pile_t i ) const; Hauteur_t pris( const Pile_t i ) const; Eval_t eval( const Pile_t i ) const; // Calculée bool ArriveeRevolution( const Coup& c) const; bool DepartRevolution( const Coup& c) const; // Tabulées Pile_t enleve( const Pile_t p ) const; Pile_t ajoute_noir( const Pile_t p ) const ; Pile_t ajoute_blanc( const Pile_t p ) const ; Pile_t ajoute_prison( const Pile_t p) const ; // Initialisation Pile_t init( const Case_t i, const Pile_t p ) ; // Jouer bool jouer( const Coup& c ) ; bool jouer( const Case_t depart, const Case_t arrivee, const bool dessus ); bool jouer( const Case_t depart, Pile_t& ancien_d, const Case_t arrivee, Pile_t& ancien_a, const bool dessus ); bool jouer_dessus( const Case_t depart, Pile_t& ancien_d, const Case_t arrivee, Pile_t& ancien_a ) ; bool jouer_dessous( const Case_t depart, Pile_t& ancien_d, const Case_t arrivee, Pile_t& ancien_a ); void jouer( const Case_t depart, Pile_t& ancien_d, Pile_t& new_d, const Case_t arrivee, Pile_t& ancien_a, Pile_t& new_a, const bool dessus ); void jouer_dessus( const Case_t depart, Pile_t& ancien_d, Pile_t& new_d, const Case_t arrivee, Pile_t& ancien_a, Pile_t& new_a ) ; void jouer_dessous( const Case_t depart, Pile_t& ancien_d, Pile_t& new_d, const Case_t arrivee, Pile_t& ancien_a, Pile_t& new_a ); void remettre( const Case_t depart, const Pile_t ancien_d, const Case_t arrivee, const Pile_t ancien_a ) ; // Fonction d'évaluation double& eval(double& e, const Pile_t p) const; double& uneval(double& e, const Pile_t p) const; double& eval(double& e, const Pile_t dep, const Pile_t ndep, const Pile_t arr, const Pile_t narr) const ; double eval() const ; // Meilleur coup // Default : temps limité, mais si depth > 0 --> profondeur fixée Coup& Suggest(Coup& bp, bool ordi_color, double temps_max, int depth = 0); // Coups possibles bool gameIsOver() ; bool gameIsOver(bool color) ; Possible_t& CoupsVoisinsBlancs(Possible_t& Liste, const Case_t i) const ; Possible_t& CoupsVoisinsNoirs(Possible_t& Liste, const Case_t i) const ; Possible_t& CoupsVoisins(Possible_t& Liste, const Case_t i) const ; Possible_t& CoupsPossibles(Possible_t& Liste, const bool c) const ; private: #ifdef __ERRORS__ unsigned long long _count; #endif // Construction void voisin_g(const Case_t j, typename Voisin_t::value_type & loc) const ; void voisin_d(const Case_t j, typename Voisin_t::value_type & loc) const ; void voisin(const Case_t j, typename Voisin_t::value_type & loc) const ; // Calculs initiaux pour tabulation Pile_t& calcule_enleve(Pile_t & r, const Hauteur_t t, const Hauteur_t u, const Pile_t p) const ; Pile_t& calcule_prison(Pile_t& a, const Hauteur_t t, const Hauteur_t u, const Pile_t p) const ; Pile_t& calcule_ajoute(Pile_t & r_noir, Pile_t& r_blanc, const Hauteur_t t, const Hauteur_t u, const Pile_t p) const ; Hauteur_t& calcule_hauteur(Hauteur_t& h, Hauteur_t& u, Hauteur_t& pris, const Pile_t p) const ; Pile_t& calcule_revol(Pile_t& r, const Hauteur_t t, const Pile_t p) const ; Eval_t& calcule_eval(Eval_t& ev, const Hauteur_t t, const Hauteur_t u, const Pile_t p) const ; // Optimisations Branch and Cut double ami_ab(int depth, double alpha, double beta, bool ordi_color); double ennemi_ab(int depth, double alpha, double beta, bool ordi_color) ; double ami_ab(int depth, double alpha, double beta, bool ordi_color, double currev, const Pile_t ad, const Pile_t nd, const Pile_t aa, const Pile_t na); double ennemi_ab(int depth, double alpha, double beta, bool ordi_color, double currev, const Pile_t ad, const Pile_t nd, const Pile_t aa, const Pile_t na) ; // Meilleur coup en temps limité Coup& Iteratif(Coup& bp, bool ordi_color, double temps_max, int StartProf) ; // Meilleur coup à profondeur fixée double BestPlay(Coup& bp, bool ordi_color, int depth); inline double MTDFdouble(int depth, double f, bool ordi_color) ; double OnebyOneBP(Coup& bp, bool ordi_color, int depth, double temps_max, Timer& global) ; // Possiblité de coup // Pour l'instant isolé interdit bool calcule_poss_dessus_noir(const bool c, const Hauteur_t t) const ; bool calcule_poss_dessous_noir(const bool c, const Hauteur_t t) const ; bool calcule_poss_dessus_blanc(const bool c, const Hauteur_t t) const ; bool calcule_poss_dessous_blanc(const bool c, const Hauteur_t t) const ; Hauteur_t calcule_poss_blancs(Possible_t& Liste, const Case_t depart, const Hauteur_t hdepalt, const Case_t arrivee) const ; Hauteur_t calcule_poss_noirs(Possible_t& Liste, const Case_t depart, const Hauteur_t hdepalt, const Case_t arrivee) const ; // Entrées/Sorties friend std::ostream& operator<< <>(std::ostream& out, const Agora& p) ; friend std::ostream& affiche <>(std::ostream& out, const Agora& p) ; friend std::ostream& details <>(std::ostream& out, const Agora& p) ; friend std::istream& operator>> <>(std::istream& in, Agora& p) ; }; #include "agora_init.inl" #include "agora_io.inl" #include "agora_jouer.inl" #include "agora_evaluation.inl" #include "agora_alphabeta.inl" #include "agora_coupspossibles.inl" #endif
#include "QGLViewer/qglviewer.h" #include "agora.h" typedef Agora<unsigned short> Agora_t; typedef Agora_t::Pile_t Pile; typedef Agora_t::Coup Play; typedef Case_t Position; typedef Agora_t::Possible_t Possibles; class AgoraViewer : public QGLViewer { Q_OBJECT public: AgoraViewer(QWidget* parent=NULL, const char* name=0); public slots: // F i l e m e n u void load(); void save(); void saveAs(); void print() { std::cout << "Print not yet implemented" << std::endl; } // G a m e m e n u void undo(); void redo(); void startNewGame() { initGame(); updateGL(); } void suggestPlay() { play_ = agora.Suggest(play_, blackPlay_, gameLevel_.computerMaxReflexionTime_, gameLevel_.computerMaxDepth_); play(); } void togglePlayAgainstComputer(bool on); void levelIsEasy() { gameLevel_.computerMaxDepth_ = 3; } void levelIsAverage() { gameLevel_.computerMaxReflexionTime_ = 1.5; gameLevel_.computerMaxDepth_ = 0; } void levelIsDifficult() { gameLevel_.computerMaxReflexionTime_ = 8.0; gameLevel_.computerMaxDepth_ = 0; } void toggleComputerIsBlack(bool black) { computerIsBlack_ = black; } // D i s p l a y m e n u void toggleAnimation(bool on) { animatePlays_ = on; } void toggleLight(bool on) { if (on) glEnable(GL_LIGHT1); else glDisable(GL_LIGHT1); updateGL(); } void toggleTexture(bool on) { textures_ = on; updateGL(); } void toggleShowPossible(bool on) { displayPossibleDestination_ = on; updateGL(); } // H e l p m e n u void help(); void about(); protected : void draw(); void mouseDoubleClickEvent(QMouseEvent *) {}; void keyPressEvent(QKeyEvent *) {}; void select(const QMouseEvent*); void init(); void play(); private slots: void simplePlay(); private : // D r a w i n g f u n c t i o n s void drawAgora() const; void drawAllPieces(bool select = false) const; void drawPiece() const; void highlightSelectedPiece() const; void drawPossibleDestinations(bool select = false) const; // D r a w i n g u t i l s void drawBorder(float width, float height, bool out=true, float heightMin=0.0) const; void drawBorderLines(float width, float height, bool out=true, float heightMin=0.0) const; void drawFace(float width, float height, bool up) const; void drawRing(float width, float height, float thickness=1.0) const; void drawSeparatingBars(float width, float height, float thickness=1.0) const; void drawLeveLines(float width, float height) const; // I n i t i a l i z a t i o n void initSpotLight(); void initCamera(); void initViewer(); void initGame(); // G a m e i n t e r f a c e void reactToSelection(int selected, bool onTop); void updatePiecePositions(); int higherPieceOnPosition(Position pos) const; void animatePlay(); void deselect(); // Size of one Agora position is 1.0, entire board is hence 6.0 x 6.0. static const float pieceSize = 29.0 / 38.0; static const float pieceHeight = 5.0 / 38.0; qglviewer::Vec normal[5]; Agora_t agora; Play play_; bool blackPlay_; bool computerIsBlack_; struct AgoraLevel { float computerMaxReflexionTime_; int computerMaxDepth_; } gameLevel_; int selectedPiece_; bool playerIsComputer_; bool gameIsOver_; // D i s p l a y F l a g s bool displayPossibleDestination_; bool animatePlays_; bool textures_; struct Piece { Position square; bool isBlack; qglviewer::Frame frame; float scale; int level; }; Piece piece_[16]; qglviewer::Vec casePosition[36]; Possibles possibleDest_; qglviewer::KeyFrameInterpolator kfi_[16]; QString fileName; struct Undo { Undo() {}; Undo(const Play& play, const Agora_t& agora); Position pos1, pos2; Pile before1, before2; Pile after1, after2; }; std::vector<Undo> history_; void updateUndoHistory(bool before); unsigned int undoIndex_, maxUndoIndex_; };
/****************************************************************************
** ui.h extension file, included from the uic-generated form implementation.
**
** If you wish to add, delete or rename slots use Qt Designer which will
** update this file, preserving your code. Create an init() slot in place of
** a constructor, and a destroy() slot in place of a destructor.
*****************************************************************************/
// =================================================================== // // Time-stamp: <19 Jun 03 15:53:22 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_Class__ #define __Agora_Class__ template<class Ints = unsigned short> class Agora ; #endif
// =================================================================== // // Time-stamp: <23 Jun 03 10:42:45 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_Container__ #define __Agora_Container__ // #include <vector> // typedef std::vector AgoraContainer; #include <iostream> template<int POSS_SIZE, typename Elem> struct AgoraContainer { private: typedef AgoraContainer<POSS_SIZE, Elem> Self_t; Elem _container[ POSS_SIZE ]; Elem * _finish; public: typedef Elem* iterator; typedef const Elem* const_iterator; AgoraContainer() : _finish(_container) { } AgoraContainer(int s) : _finish(_container+s) { } Case_t size() const { return Case_t(_finish - _container); } void clear() { _finish = _container; } void resize(int s) { _finish = _container + s; } iterator begin() { return _container; } iterator end() { return _finish; } const_iterator begin() const { return const_iterator(_container); } const_iterator end() const { return const_iterator(_finish); } void push_back(const Elem& c) { *_finish = c; ++_finish; } }; #endif
// =================================================================== // // Time-stamp: <20 Jun 03 18:45:45 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_Coup__ #define __Agora_Coup__ #include "agora_types.h" #include <iostream> struct AgoraCoup { AgoraCoup() {} AgoraCoup(Case_t d, Case_t a, bool b) : _depart(d), _arrivee(a), _dessus(b) {} Case_t depart() const { return _depart; } Case_t arrivee() const { return _arrivee; } bool dessus() const { return _dessus; } void copy( const AgoraCoup& c) { _depart = c._depart; _arrivee = c._arrivee; _dessus = c._dessus; } bool operator==( const AgoraCoup& c) { return ( (_depart == c._depart) && (_arrivee == c._arrivee) && (_dessus == c._dessus) ); } friend std::ostream& operator<< (std::ostream& out, const AgoraCoup& c) { return out << (Size_t)(c._depart) << (c._dessus?"/":"\\") << (Size_t)(c._arrivee); } friend std::istream& operator>> (std::istream& in, AgoraCoup& c) { char s; Size_t d, a; in >> d >> s >> a; c._depart = (Case_t)d; c._arrivee = (Case_t)a; if (s == '/') c._dessus = 1; else c._dessus = 0; return in; } private: Case_t _depart, _arrivee; bool _dessus; }; #endif
// =================================================================== // // Time-stamp: <24 Jun 03 14:51:09 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_IO_H__ #define __Agora_IO_H__ #include "agora_class.h" template<class Ints> std::ostream& operator<< (std::ostream& out, const Agora<Ints>& p) ; template<class Ints> std::ostream& details (std::ostream& out, const Agora<Ints>& p) ; template<class Ints> std::ostream& affiche (std::ostream& out, const Agora<Ints>& p) ; template<class Ints> std::istream& operator>> (std::istream& in, Agora<Ints>& p) ; #endif
// =================================================================== // // Time-stamp: <30 Jun 03 18:50:44 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __Agora_Types__ #define __Agora_Types__ #include <vector> typedef short Eval_t; typedef unsigned short Hauteur_t; typedef unsigned long Size_t; typedef unsigned short Case_t; typedef std::vector<Hauteur_t> Table_t; #endif
// =================================================================== // // Time-stamp: <20 Jun 03 18:42:27 Jean-Guillaume.Dumas@imag.fr> // =================================================================== // #ifndef __CONTAINER_STREAM__ #define __CONTAINER_STREAM__ #include <iostream> template<class T, template <class T> class Container > std::ostream& operator<< (std::ostream& o, const Container<T>& v) { o << "["; typename Container<T>::const_iterator vi = v.begin(); for( ; vi != v.end(); ++vi) o << *vi << " "; return o << "]"; } template<int I, class T, template <int I, class T> class Container > std::ostream& operator<< (std::ostream& o, const Container<I,T>& v) { o << "["; typename Container<I,T>::const_iterator vi = v.begin(); for( ; vi != v.end(); ++vi) o << *vi << " "; return o << "]"; } #endif
#ifndef _TIMER_H_ #define _TIMER_H_ // ========================================================================== // $Source: /local/cvs/Agora/givtimer.h,v $ // Copyright(c)'94-97 by Givaro Team // see the copyright file. // Authors: T. Gautier // $Id: givtimer.h,v 1.1.1.1 2003/06/20 13:03:43 jgdumas Exp $ // ========================================================================== // Description: #include <iostream> // class RealTimer; class SysTimer; class UserTimer; class BaseTimer { public: enum { MSPSEC = 1000000 // microsecond per second }; // -- Clear timer : inline void clear() { _t = 0; } // -- total amount of second spent inline double time() const { return _t; } // -- Return a value to initialize random generator static long seed(); // -- basic methods: std::ostream& print( std::ostream& ) const; // -- Some arithmetic operators to compute cumulative time : BaseTimer& operator = (const BaseTimer & T) ; const BaseTimer operator - (const BaseTimer & T) const; const BaseTimer operator - () ; const BaseTimer operator + (const BaseTimer & T) const; const BaseTimer operator += (const BaseTimer & T) { return *this = *this + T; }; const BaseTimer operator -= (const BaseTimer & T) { return *this = *this - T; }; public: double _t; // time }; inline std::ostream& operator<< (std::ostream& o, const BaseTimer& BT) { return BT.print(o);} class RealTimer : public BaseTimer { public: inline RealTimer( const BaseTimer& BT ): BaseTimer(BT) {}; inline RealTimer( ){}; void start(); void stop(); }; class UserTimer : public BaseTimer { public: inline UserTimer( const BaseTimer& BT ): BaseTimer(BT) {}; inline UserTimer( ) {}; void start(); void stop(); }; class SysTimer : public BaseTimer { public: inline SysTimer( const BaseTimer& BT ): BaseTimer(BT) {}; inline SysTimer( ) {}; void start(); void stop(); }; class Timer { public : // Clear timer : void clear(); // Start timer void start(); // Stop timer void stop(); // total amount of second spent in user mode double usertime() const { return ut.time(); } // total amount of second spent in system mode double systime () const { return st.time(); } // real total amount of second spent. double realtime () const { return rt.time(); } // retourne une petite graine // long seed() const { return RealTimer::seed(); } // Some arithmetic operators to compute cumulative time : Timer& operator = (const Timer & T) ; const Timer operator - (const Timer & T) const; const Timer operator - () ; const Timer operator + (const Timer & T) const; const Timer operator += (const Timer & T) { return *this = *this + T; }; const Timer operator -= (const Timer & T) { return *this = *this - T; }; // -- methods : std::ostream& print( std::ostream& ) const; public: RealTimer rt; UserTimer ut; SysTimer st; }; // inline std::ostream& operator<<( std::ostream& o, const Timer& T) // { return T.print(o);} inline std::ostream& operator<<( std::ostream& o, const Timer& T) { return o << T.realtime() << "s (" << T.usertime() << " cpu)"; } #endif
#include "agoraViewer.h" #include "agoraWindow.h" #include <fstream> #include <math.h> #include <qmessagebox.h> #include <qimage.h> #include <qfile.h> #include <qfileinfo.h> #include <qfiledialog.h> #include <qaction.h> using namespace std; using namespace qglviewer; AgoraViewer::AgoraViewer(QWidget* parent, const char* name) : QGLViewer(parent, name), computerIsBlack_(true), selectedPiece_(-1), playerIsComputer_(true), displayPossibleDestination_(true), animatePlays_(true), textures_(true), fileName("savedGame.ago"), undoIndex_(0), maxUndoIndex_(0) { // Stored once and for all normal[0] = Vec(-1.0, 0.0, 0.0); normal[1] = Vec( 0.0, 1.0, 0.0); normal[2] = Vec( 1.0, 0.0, 0.0); normal[3] = Vec( 0.0, -1.0, 0.0); normal[4] = normal[0]; levelIsEasy(); // Prevent too many updateGL when several KFI are runned together. Only kfi_[0] updatesGL. for (int i=1; i<16; ++i) QObject::disconnect(&kfi_[i], SIGNAL(interpolated()), this, SLOT(updateGL())); QObject::connect(&kfi_[0], SIGNAL(finished()), this, SLOT(simplePlay())); } // I n i t i a l i z a t i o n f u n c t i o n s // void AgoraViewer::init() { initViewer(); initSpotLight(); initCamera(); initGame(); setMouseBinding(Qt::RightButton, CAMERA, ManipulatedFrame::ROTATE); setMouseBinding(Qt::LeftButton, SELECT); } void AgoraViewer::initSpotLight() { glMatrixMode(GL_MODELVIEW); glEnable(GL_LIGHT1); glLoadIdentity(); // Light default parameters const GLfloat light_ambient[4] = {2.0, 2.0, 2.0, 1.0}; const GLfloat light_specular[4] = {2.0, 2.0, 2.0, 1.0}; const GLfloat light_diffuse[4] = {2.0, 2.0, 2.0, 1.0}; glLightf( GL_LIGHT1, GL_SPOT_EXPONENT, 2.0); glLightf( GL_LIGHT1, GL_SPOT_CUTOFF, 60.0); glLightf( GL_LIGHT1, GL_CONSTANT_ATTENUATION, 0.1); glLightf( GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.3); glLightf( GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.3); glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); } 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); } }; void AgoraViewer::initCamera() { camera()->setUpVector(Vec(0.0, 0.0, 1.0)); camera()->setPosition(Vec(-6.0, -6.0, 4.0)); camera()->lookAt(sceneCenter()); setSceneRadius(3.5); showEntireScene(); // Limit camera rotation motion camera()->frame()->setConstraint(new BoardConstraint()); } void AgoraViewer::initGame() { agora.reinit(); updatePiecePositions(); blackPlay_ = false; gameIsOver_ = false; } void AgoraViewer::initViewer() { int count = 0; for (int i=0; i<6; ++i) for (int j=0; j<6; ++j) { float height; if ((i==0) || (j==0) || (i==5) || (j==5)) height = 6.0 * pieceHeight; else if ((i==1) || (j==1) || (i==4) || (j==4)) height = 3.0 * pieceHeight; else height = pieceHeight; casePosition[count++] = Vec(i-2.5, j-2.5, height); } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Enable GL textures glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glEnable( GL_TEXTURE_2D ); // Nice texture coordinate interpolation glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); const QString texFileName("wood.png"); QImage img(texFileName); if (img.isNull()) { cerr << "Unable to load wood texture from " << texFileName << endl; exit(1); } // 1E-3 needed. Just try with width=128 and see ! if ( ( img.width() != 1<<(int)(1+log(img.width() -1+1E-3) / log(2.0)) ) || ( img.height() != 1<<(int)(1+log(img.height()-1+1E-3) / log(2.0))) ) { cerr << "Texture dimensions are not powers of 2 in " << texFileName << endl; exit(1); } QImage glImg = QGLWidget::convertToGLFormat(img); // flipped 32bit RGBA // Bind the img texture... glTexImage2D(GL_TEXTURE_2D, 0, 4, glImg.width(), glImg.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, glImg.bits()); } // D r a w i n g f u n c t i o n s // void AgoraViewer::draw() { glColor3f(0.7, 0.7, 0.7); glDisable(GL_LIGHTING); if (blackPlay_) drawText(20, 20, "Blacks play"); else drawText(20, 20, "Whites play"); glEnable(GL_LIGHTING); drawAgora(); drawAllPieces(); if (selectedPiece_ >= 0) { highlightSelectedPiece(); if (displayPossibleDestination_) drawPossibleDestinations(); } } void AgoraViewer::drawAgora() const { static const GLfloat pos[4] = {0.0, 0.0, 3.0, 1.0}; const GLfloat spot_dir[3] = {0.0, 0.0, -1.0}; glLightfv(GL_LIGHT1, GL_POSITION, pos); glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spot_dir); glColor3f(0.5, 0.5, 0.5); if (textures_) glEnable(GL_TEXTURE_2D); else glDisable(GL_TEXTURE_2D); drawBorder(6.0, 6.0 * pieceHeight); drawBorder(4.0, 6.0 * pieceHeight, false, 3.0 * pieceHeight); drawBorder(2.0, 3.0 * pieceHeight, false, pieceHeight); drawRing(6.0, 6.0 * pieceHeight); drawRing(4.0, 3.0 * pieceHeight); drawRing(2.0, pieceHeight); drawFace(6.0, 0.0, false); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glLineWidth(3.0); glColor3f(0.4, 0.4, 0.4); drawBorderLines(6.0, 6.0 * pieceHeight); drawBorderLines(4.0, 6.0 * pieceHeight, false, 3.0 * pieceHeight); drawBorderLines(2.0, 3.0 * pieceHeight, false, pieceHeight); drawLeveLines(4.0, 5.0 * pieceHeight); drawLeveLines(4.0, 4.0 * pieceHeight); drawLeveLines(2.0, 2.0 * pieceHeight); glLineWidth(4.0); glColor3f(0.1, 0.1, 0.1); drawSeparatingBars(6.0, 6.0 * pieceHeight); drawSeparatingBars(4.0, 3.0 * pieceHeight); drawSeparatingBars(2.0, pieceHeight); glEnable(GL_LIGHTING); } void AgoraViewer::drawPiece() const { drawBorder(pieceSize, pieceHeight); drawFace(pieceSize, pieceHeight, true); drawFace(pieceSize, 0.0, false); glLineWidth(2.0); glDisable(GL_LIGHTING); glColor3f(0.4, 0.4, 0.4); drawBorderLines(pieceSize, pieceHeight); glEnable(GL_LIGHTING); } void AgoraViewer::highlightSelectedPiece() const { glEnable(GL_BLEND); const float s = 1.1 * piece_[selectedPiece_].scale; glColor4f(0.3, 0.9, 0.3, 0.3); glPushMatrix(); glMultMatrixd(piece_[selectedPiece_].frame.matrix()); glScalef(s, s, s); drawPiece(); glPopMatrix(); glDisable(GL_BLEND); } void AgoraViewer::drawAllPieces(bool select) const { for (int i=0; i<16; ++i) { glPushMatrix(); glMultMatrixd(piece_[i].frame.matrix()); glScalef(piece_[i].scale, piece_[i].scale, 1.0); if (piece_[i].isBlack) glColor3f(0.2, 0.2, 0.2); else glColor3f(0.9, 0.9, 0.9); if (select) glPushName(i); drawPiece(); if (select) glPopName(); glPopMatrix(); } } void AgoraViewer::drawPossibleDestinations(bool select) const { glEnable(GL_BLEND); glColor4f(0.3, 0.3, 0.9, 0.5); for (Possibles::const_iterator it=possibleDest_.begin(), end=possibleDest_.end(); it != end; ++it) { const int dest = (*it).arrivee(); glPushMatrix(); glTranslatef(casePosition[dest].x, casePosition[dest].y, casePosition[dest].z + 0.01); if (select) glPushName(dest); drawFace(0.9, 0.0, true); if (select) for (int i=0; i<16; ++i) if (piece_[i].square == dest) { glPushMatrix(); glTranslatef(0.0, 0.0, piece_[i].level * pieceHeight); drawPiece(); glPopMatrix(); } if (select) glPopName(); glPopMatrix(); } glDisable(GL_BLEND); } void AgoraViewer::drawBorder(float width, float height, bool out, float heightMin) const { const Vec up(0.0, 0.0, 1.0); const float coef = sqrt(2.0) / 2.0; for (int i=0; i<4; ++i) { if (out) glNormal3fv(normal[i].address()); else glNormal3fv((-normal[i]).address()); glBegin(GL_QUADS); glTexCoord2f(-width*coef, heightMin); glVertex3fv((width/2.0*(normal[i] - normal[i+1]) + heightMin*up).address()); glTexCoord2f(width*coef, heightMin); glVertex3fv((width/2.0*(normal[i] + normal[i+1]) + heightMin*up).address()); glTexCoord2f(width*coef, height); glVertex3fv((width/2.0*(normal[i] + normal[i+1]) + height*up).address()); glTexCoord2f(-width*coef,height); glVertex3fv((width/2.0*(normal[i] - normal[i+1]) + height*up).address()); glEnd(); } } void AgoraViewer::drawBorderLines(float width, float height, bool out, float heightMin) const { const Vec up(0.0, 0.0, 1.0); // Avoid aliassing on inside agora if (!out) { heightMin += 0.01; width -= 0.01; } for (int i=0; i<4; ++i) { glBegin(GL_LINE_LOOP); glVertex3fv((width/2.0*(normal[i] - normal[i+1]) + heightMin*up).address()); glVertex3fv((width/2.0*(normal[i] + normal[i+1]) + heightMin*up).address()); glVertex3fv((width/2.0*(normal[i] + normal[i+1]) + height*up).address()); glVertex3fv((width/2.0*(normal[i] - normal[i+1]) + height*up).address()); glEnd(); } } void AgoraViewer::drawRing(float width, float height, float thickness) const { const Vec up(0.0, 0.0, 1.0); const float dist1 = width/2.0; const float dist2 = width/2.0-thickness; glNormal3fv(up.address()); for (int i=0; i<4; ++i) { const Vec dir1 = normal[i] - normal[i+1]; const Vec dir2 = normal[i] + normal[i+1]; glBegin(GL_QUADS); glTexCoord2f(dist1*dir1.x, dist1*dir1.y); glVertex3fv((dist1*dir1 + height*up).address()); glTexCoord2f(dist1*dir2.x, dist1*dir2.y); glVertex3fv((dist1*dir2 + height*up).address()); glTexCoord2f(dist2*dir2.x, dist2*dir2.y); glVertex3fv((dist2*dir2 + height*up).address()); glTexCoord2f(dist2*dir1.x, dist2*dir1.y); glVertex3fv((dist2*dir1 + height*up).address()); glEnd(); } } void AgoraViewer::drawSeparatingBars(float width, float height, float thickness) const { const Vec up(0.0, 0.0, 1.0); height += 0.01; for (int i=0; i<4; ++i) { for (int j=1; j<width-0.9; ++j) { glBegin(GL_LINES); glVertex3fv((width/2.0*normal[i] + (j*thickness-width/2.0)*normal[i+1] + height*up).address()); glVertex3fv(((width/2.0-thickness)*normal[i] + (j-width/2.0)*normal[i+1] + height*up).address()); glEnd(); } } } void AgoraViewer::drawLeveLines(float width, float height) const { const Vec up(0.0, 0.0, 1.0); width -= 0.01; glBegin(GL_LINE_LOOP); for (int i=0; i<4; ++i) glVertex3fv((width/2.0*(normal[i] + normal[i+1]) + height*up).address()); glEnd(); } void AgoraViewer::drawFace(float width, float height, bool up) const { if (up) glNormal3f(0.0, 0.0, 1.0); else glNormal3f(0.0, 0.0, -1.0); glBegin(GL_QUADS); glVertex3f(-width/2.0, -width/2.0, height); glVertex3f(-width/2.0, width/2.0, height); glVertex3f( width/2.0, width/2.0, height); glVertex3f( width/2.0, -width/2.0, height); glEnd(); } void AgoraViewer::animatePlay() { float start = 0.0; float end = 0.8; int hpops = higherPieceOnPosition(play_.depart()); int hpope = higherPieceOnPosition(play_.arrivee()); if (hpops < 0) cerr << "Internal error" << endl; int nbKfi = 0; if (play_.dessus()) { for (int i=0; i<16; ++i) if ((piece_[i].square == play_.arrivee()) && (piece_[i].isBlack != piece_[hpops].isBlack) && (piece_[hpops].isBlack != piece_[hpope].isBlack)) { // piece_[i].isBlack = piece_[hpops].isBlack; nbKfi++; kfi_[nbKfi].deletePath(); kfi_[nbKfi].setFrame(&piece_[i].frame); kfi_[nbKfi].addKeyFrame(piece_[i].frame, 0.0); Frame midFrame(piece_[i].frame.position()+Vec(0.0, 0.0, 4*pieceHeight*(piece_[i].level+2)), Quaternion(Vec(1.0, 0.0, 0.0), 2.0)); kfi_[nbKfi].addKeyFrame(midFrame, 0.3); Frame endFrame(piece_[i].frame.position()+Vec(0.0, 0.0, pieceHeight), Quaternion(Vec(1.0, 0.0, 0.0), M_PI)); kfi_[nbKfi].addKeyFrame(endFrame, 0.6); start = 0.6; } } else { for (int i=0; i<16; ++i) if (piece_[i].square == play_.arrivee()) { nbKfi++; kfi_[nbKfi].deletePath(); kfi_[nbKfi].setFrame(&piece_[i].frame); kfi_[nbKfi].addKeyFrame(piece_[i].frame, 0.0); Frame midFrame, endFrame; if ((agora.ArriveeRevolution(play_)) && (piece_[i].isBlack != piece_[hpops].isBlack)) { midFrame = Frame(piece_[i].frame.position()+Vec(0.0, 0.0, 3.0*pieceHeight*(piece_[i].level+2)), Quaternion(Vec(1.0, 0.0, 0.0), 2.0)); endFrame = Frame(piece_[i].frame.position()+Vec(0.0, 0.0, 2.0*pieceHeight), Quaternion(Vec(1.0, 0.0, 0.0), M_PI)); // piece_[i].isBlack = piece_[hpops].isBlack; } else { midFrame.setPosition(piece_[i].frame.position()+Vec(0.0, 0.0, 3.0*pieceHeight*(piece_[i].level+2))); endFrame.setPosition(piece_[i].frame.position()+Vec(0.0, 0.0, pieceHeight)); } kfi_[nbKfi].addKeyFrame(midFrame, 0.4); kfi_[nbKfi].addKeyFrame(endFrame, 1.0); end = 1.0; } } // Revolution on starting case if (agora.DepartRevolution(play_)) for (int i=0; i<16; ++i) if ((piece_[i].square == play_.depart()) && (i!=hpops) && (piece_[i].isBlack == piece_[hpops].isBlack)) { nbKfi++; kfi_[nbKfi].deletePath(); kfi_[nbKfi].setFrame(&piece_[i].frame); kfi_[nbKfi].addKeyFrame(piece_[i].frame, 0.0); kfi_[nbKfi].addKeyFrame(piece_[i].frame, 0.6); Frame midFrame(piece_[i].frame.position()+Vec(0.0, 0.0, 3.0*pieceHeight*(piece_[i].level+2)), Quaternion(Vec(1.0, 0.0, 0.0), 2.0)); Frame endFrame(piece_[i].frame.position()+Vec(0.0, 0.0, pieceHeight), Quaternion(Vec(1.0, 0.0, 0.0), M_PI)); // piece_[i].isBlack = !piece_[hpops].isBlack; kfi_[nbKfi].addKeyFrame(midFrame, 0.9); kfi_[nbKfi].addKeyFrame(endFrame, 1.5); end = 1.5; } // Selected piece kfi_[0].deletePath(); kfi_[0].setFrame(&piece_[hpops].frame); Frame arrival; if ((hpope >= 0) && (play_.dessus())) arrival.setPosition(piece_[hpope].frame.position() + Vec(0.0, 0.0, pieceHeight)); else arrival.setPosition(casePosition[play_.arrivee()]); Vec mid(0.5 * (piece_[hpops].frame.position() + arrival.position())); mid.z = max(piece_[hpops].frame.position().z, arrival.position().z) + 2.0 * pieceHeight; const Frame intermediate(mid, Quaternion()); kfi_[0].addKeyFrame(piece_[higherPieceOnPosition(play_.depart())].frame, 0.0); kfi_[0].addKeyFrame(piece_[higherPieceOnPosition(play_.depart())].frame, start); kfi_[0].addKeyFrame(intermediate, start+0.3); kfi_[0].addKeyFrame(arrival, start+0.8); kfi_[0].addKeyFrame(arrival, start+end); for (int i=1; i<=nbKfi; ++i) kfi_[i].startInterpolation(); kfi_[0].startInterpolation(); } int AgoraViewer::higherPieceOnPosition(Position pos) const { float levelMax = -1.0; int res = -1; for (int i=0; i<16; ++i) if ((piece_[i].square == pos) && (piece_[i].level > levelMax)) { res = i; levelMax = piece_[i].level; } return res; } // G a m e I n t e r f a c e // void AgoraViewer::play() { if (animatePlays_) animatePlay(); else simplePlay(); } void AgoraViewer::deselect() { selectedPiece_ = -1; possibleDest_.clear(); } void AgoraViewer::simplePlay() { deselect(); updateUndoHistory(true); agora.jouer(play_); updateUndoHistory(false); updatePiecePositions(); blackPlay_ = !blackPlay_; updateGL(); gameIsOver_ = agora.gameIsOver(); if (gameIsOver_) { if (agora.gameIsOver(BLACK)) QMessageBox::information(this, "Whites won !", "The whites won !"); else QMessageBox::information(this, "Blacks won !", "The blacks won !"); } else if ((playerIsComputer_) && (computerIsBlack_==blackPlay_)) { play_ = agora.Suggest(play_, blackPlay_, gameLevel_.computerMaxReflexionTime_, gameLevel_.computerMaxDepth_); play(); } } void AgoraViewer::reactToSelection(int selected, bool onTop) { if (selectedPiece_ >= 0) { if (selected >= 0) { bool playOnTopIsValid = false; bool playPrisonnerIsValid = false; Play playOnTop, playPrisonner; for (Possibles::const_iterator it=possibleDest_.begin(), end=possibleDest_.end(); it != end; ++it) if (((*it).depart() == piece_[selectedPiece_].square) && ((int)((*it).arrivee()) == selected)) if ((*it).dessus()) { playOnTopIsValid = true; playOnTop = (*it); } else { playPrisonnerIsValid = true; playPrisonner = (*it); } if (playOnTopIsValid) if (playPrisonnerIsValid) play_ = (onTop) ? playOnTop : playPrisonner; else play_ = playOnTop; else if (playPrisonnerIsValid) play_ = playPrisonner; else cerr << "Internal error : invalid play in reactToSelection." << endl; play(); } else deselect(); } else // New piece selection. Color must correspond to current player if ((selected >= 0) && (piece_[selected].isBlack == blackPlay_)) { // Search for the piece at highest level on this heap selectedPiece_ = higherPieceOnPosition(piece_[selected].square); agora.CoupsVoisins( possibleDest_, piece_[selectedPiece_].square ); } } void AgoraViewer::select(const QMouseEvent* e) { if (gameIsOver_) return; // Make openGL context current makeCurrent(); const int SENSITIVITY = 4; const int NB_HITS_MAX = 50; // Prepare the selection mode static GLuint hits[NB_HITS_MAX]; glSelectBuffer(NB_HITS_MAX, hits); glRenderMode(GL_SELECT); glInitNames(); // Loads the matrices glMatrixMode(GL_PROJECTION); glLoadIdentity(); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT,viewport); gluPickMatrix(static_cast<GLdouble>(e->x()), static_cast<GLdouble>(viewport[3] - e->y()), SENSITIVITY, SENSITIVITY, viewport); // loadProjectionMatrix() first resets the GL_PROJECTION matrix with a glLoadIdentity. // Give false as a parameter in order to prevent this and to combine the matrices. camera()->loadProjectionMatrix(false); camera()->loadModelViewMatrix(); // Render scene with objects ids if (selectedPiece_ >= 0) drawPossibleDestinations(true); else drawAllPieces(true); glFlush(); // Get the results GLint nb_hits = glRenderMode(GL_RENDER); // Interpret results unsigned int zMin = UINT_MAX; int id = -1; for (int i=0; i<nb_hits; ++i) if (hits[i*4+1] < zMin) { zMin = hits[i*4+1]; id = hits[i*4+3]; } reactToSelection(id, e->state() == Qt::NoButton); } void AgoraViewer::updatePiecePositions() { int nb = 0; for (int i=0; i<36; ++i) { Pile p = agora[i]; bool topIsBlack = agora.couleur(p); for (int j=0; j<agora.taille(p); ++j) { if (j == agora.uns(p)) topIsBlack = !topIsBlack; piece_[nb].square = i; piece_[nb].isBlack = topIsBlack; piece_[nb].level = agora.taille(p)-1-j; piece_[nb].frame.setPosition(casePosition[i] + Vec(0.0, 0.0, pieceHeight * piece_[nb].level)); piece_[nb].frame.setOrientation(Quaternion()); piece_[nb].scale = 1.0 - 0.1*(agora.taille(p)-j-1)/(float)agora.taille(p); nb++; } } if (nb > 16) cerr << "More than 16 pieces in the Agora !!!" << endl; } // F i l e m e n u f u n c t i o n s void AgoraViewer::load() { fileName = QFileDialog::getOpenFileName("", "Agora files (*.ago);;All files (*)", this); // In case of Cancel if (fileName.isEmpty()) return; std::ifstream f(fileName.latin1()); f >> agora; f >> undoIndex_ >> maxUndoIndex_; history_.clear(); for (unsigned int i=0; i<maxUndoIndex_; ++i) { Undo u; f >> u.pos1 >> u.pos2 >> u.before1 >> u.before2 >> u.after1 >> u.after2; history_.push_back(u); } f.close(); blackPlay_ = undoIndex_%2 == 1; updatePiecePositions(); deselect(); } void AgoraViewer::save() { std::ofstream f(fileName.latin1()); f << agora; // save undo history f << undoIndex_ << " " << maxUndoIndex_ << endl; for (std::vector<Undo>::iterator it=history_.begin(), end=history_.end(); it != end; ++it) f << (*it).pos1 << " " << (*it).pos2 << " " << (*it).before1 << " " << (*it).before2 << " " << (*it).after1 << " " << (*it).after2 << endl; f.close(); } void AgoraViewer::saveAs() { fileName = QFileDialog::getSaveFileName("", "Agora files (*.ago);;All files (*)", this, fileName.latin1()); QFileInfo fi(fileName); if (fi.extension().isEmpty()) { fileName += ".ago"; fi.setFile(fileName); } 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()) // CHECK THIS { QMessageBox::warning(this ,"Cannot save", "File "+fi.fileName()+" is not writeable."); return; } save(); } // U n d o a n d R e do AgoraViewer::Undo::Undo(const Play& play, const Agora_t& agora) { pos1 = play.depart(); pos2 = play.arrivee(); before1 = agora[pos1]; before2 = agora[pos2]; } void AgoraViewer::updateUndoHistory(bool before) { if (before) { if (undoIndex_ < history_.size()) history_[undoIndex_] = Undo(play_, agora); else history_.push_back(Undo(play_, agora)); } else { history_[undoIndex_].after1 = agora[play_.depart()]; history_[undoIndex_].after2 = agora[play_.arrivee()]; undoIndex_++; maxUndoIndex_ = undoIndex_; } } void AgoraViewer::undo() { if (undoIndex_ > 0) undoIndex_--; else return; agora.remettre(history_[undoIndex_].pos1, history_[undoIndex_].before1, history_[undoIndex_].pos2, history_[undoIndex_].before2); updatePiecePositions(); blackPlay_ = !blackPlay_; updateGL(); } void AgoraViewer::redo() { if (undoIndex_ >= maxUndoIndex_) { QMessageBox::warning(this, "No more redo", "No more redo is possible"); return; } agora.remettre(history_[undoIndex_].pos1, history_[undoIndex_].after1, history_[undoIndex_].pos2, history_[undoIndex_].after2); undoIndex_++; updatePiecePositions(); blackPlay_ = !blackPlay_; updateGL(); } void AgoraViewer::help() { QMessageBox::information(this, " A g o r a", "Rules of Agora\nNot yet available, please browse the web."); } void AgoraViewer::about() { QMessageBox::about(this, " A g o r a", "A g o r a\nCreated by Gilles Debunne and Jean-Guillaume Dumas\nVersion 1.0 - June 2003"); } void AgoraViewer::togglePlayAgainstComputer(bool on) { playerIsComputer_ = on; // Disable menu items AgoraWindow* window = dynamic_cast<AgoraWindow*>(parent()->parent()); if (!window) { qWarning("Unable to disable menu items"); return; } window->levelActionGroup->setEnabled(on); window->computerBlackAction->setEnabled(on); }
#include <qapplication.h> #include "agoraWindow.h" int main(int argc, char * argv[]) { // Read command lines arguments. QApplication application(argc,argv); AgoraWindow aw; aw.show(); application.setMainWidget(&aw); // Run main loop. return application.exec(); }
Go back to the examples main page