The agora example

agora

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.

agora.h


// =================================================================== //
// 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

agoraViewer.h


#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_;
};

agoraWindow.ui.h


/****************************************************************************
** 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.
*****************************************************************************/

agora_class.h


// =================================================================== //
// 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

agora_container.h


// =================================================================== //
// 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

agora_coup.h


// =================================================================== //
// 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

agora_io.h


// =================================================================== //
// 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

agora_types.h


// =================================================================== //
// 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

container_stream.h


// =================================================================== //
// 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

givtimer.h


#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

agoraViewer.cpp


#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);
}

main.cpp


#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

Valid XHTML 1.0! Valid CSS! Last modified on Thursday, February 5, 2004.