diff options
Diffstat (limited to 'libksirtet/common/ai.cpp')
-rw-r--r-- | libksirtet/common/ai.cpp | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/libksirtet/common/ai.cpp b/libksirtet/common/ai.cpp new file mode 100644 index 00000000..bc1c6722 --- /dev/null +++ b/libksirtet/common/ai.cpp @@ -0,0 +1,356 @@ +#include "ai.h" +#include "ai.moc" + +#include <assert.h> + +#include <qlabel.h> +#include <qhbox.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qgrid.h> +#include <qwhatsthis.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kapplication.h> + +#include "commonprefs.h" +#include "board.h" +#include "base/piece.h" +#include "base/factory.h" + + +//----------------------------------------------------------------------------- +AIPiece::AIPiece() + : _current(0) +{} + +AIPiece::~AIPiece() +{ + delete _current; +} + +void AIPiece::init(const Piece *piece, Board *b) +{ + _piece = piece; + _board = b; + if ( _current==0 ) _current = new Piece; + reset(); +} + +void AIPiece::reset() +{ + curPos = 0; + curRot = 0; + if (_piece) _current->copy(_piece); +// else _current->generateNext(0); + nbRot = _current->nbConfigurations() - 1; + nbPos = _board->matrix().width() - _current->size().first + 1; +} + +bool AIPiece::increment() +{ + curPos++; + if ( curPos==nbPos ) { + if ( curRot==nbRot ) { +// if ( _piece || _current->type()==Piece::info().nbTypes() ) { + reset(); + return false; +// } +// _current->generateNext(_current->type()+1); +// nbRot = _current->nbConfigurations() - 1; +// curRot = 0; + } + _current->rotate(true, QPoint(0, 0)); + nbPos = _board->matrix().width() - _current->size().first + 1; + curRot++; + curPos = 0; + } + return true; +} + +bool AIPiece::place() +{ + if ( curRot==3 ) { + if ( !_board->rotateRight() ) return false; + } else for (uint i=0; i<curRot; i++) + if ( !_board->rotateLeft() ) return false; + curDec = curPos - _board->currentPos().first - _current->min().first; + if ( curDec!=0 && _board->moveRight(curDec)!=(uint)kAbs(curDec) ) + return false; + _board->dropDown(); + return !_board->isGameOver(); +} + +//----------------------------------------------------------------------------- +const AI::Data AI::LastData = { 0, 0, 0, false, 0 }; + +AI::AI(uint tTime, uint oTime, const Data *DATA) + : timer(this), thinkTime(tTime), orderTime(oTime), stopped(false), + board(0) +{ + connect(&timer, SIGNAL(timeout()), SLOT(timeout())); + + for (uint i=0; DATA[i].name; i++) { + Element element; + element.coefficient = 0.; + element.trigger = 0; + element.data = &DATA[i]; + _elements.append(element); + } + settingsChanged(); +} + +void AI::resizePieces(uint size) +{ + uint oldSize = pieces.size(); + for (uint i=size; i<oldSize; i++) delete pieces[i]; + pieces.resize(size); + for (uint i=oldSize; i<size; i++) pieces[i] = new AIPiece; +} + +AI::~AI() +{ + delete board; + resizePieces(0); +} + +void AI::initThink() +{ + board->copy(*main); +} + +void AI::launch(Board *m) +{ + main = m; + if ( board==0 ) + board = static_cast<Board *>(bfactory->createBoard(false, 0)); + + pieces[0]->init(main->currentPiece(), board); // current + if ( pieces.size()>=2 ) pieces[1]->init(main->nextPiece(), board); // next + + state = Thinking; + hasBestPoints = false; + startTimer(); +} + +void AI::stop() +{ + timer.stop(); + stopped = true; +} + +void AI::start() +{ + if (stopped) { + startTimer(); + stopped = false; + } +} + +void AI::startTimer() +{ + switch (state) { + case Thinking: timer.start(thinkTime, true); break; + case GivingOrders: timer.start(orderTime, true); break; + } +} + +void AI::timeout() +{ + switch (state) { + case Thinking: + if ( think() ) state = GivingOrders; + break; + case GivingOrders: + if ( emitOrder() ) return; + break; + } + + startTimer(); +} + +bool AI::emitOrder() +{ + if ( bestRot==3 ) { + bestRot = 0; + main->pRotateRight(); + } else if (bestRot) { + bestRot--; + main->pRotateLeft(); + } else if ( bestDec>0 ) { + bestDec--; + main->pMoveRight(); + } else if ( bestDec<0 ) { + bestDec++; + main->pMoveLeft(); + } else { + main->pDropDownStart(); + return true; + } + return false; +} + +bool AI::think() +{ + initThink(); + bool moveOk = true; + for (uint i=0; i<pieces.size(); i++) + if ( !pieces[i]->place() ) { + moveOk = false; + break; + } + if (moveOk) { + double p = points(); + if ( !hasBestPoints || p>bestPoints + || (p==hasBestPoints && random.getBool()) ) { + hasBestPoints = true; + bestPoints = p; + bestDec = pieces[0]->dec(); + bestRot = pieces[0]->rot(); + } + } + + for (uint i=pieces.size(); i>0; i--) + if ( pieces[i-1]->increment() ) return false; + return true; +} + +double AI::points() const +{ + double pts = 0; + for (uint i=0; i<_elements.size(); i++) { + if ( _elements[i].coefficient==0.0 ) continue; + double v = _elements[i].data->function(*main, *board); + if ( _elements[i].data->triggered && qRound(v)<_elements[i].trigger ) + continue; + pts += _elements[i].coefficient * v; + } + return pts; +} + +void AI::settingsChanged() +{ + int d = CommonPrefs::thinkingDepth(); + resizePieces(d); + for (uint i=0; i<_elements.size(); i++) { + const Data &data = *_elements[i].data; + _elements[i].coefficient = AIConfig::coefficient(data); + if (data.triggered) _elements[i].trigger = AIConfig::trigger(data); + } + if ( timer.isActive() ) launch(main); +} + +double AI::nbOccupiedLines(const Board &, const Board ¤t) +{ + return current.matrix().height() - current.nbClearLines(); +} + +double AI::nbHoles(const Board &, const Board ¤t) +{ + uint nb = 0; + for (uint i=0; i<current.matrix().width(); i++) { + for (int j=current.firstColumnBlock(i)-1; j>=0; j--) { + KGrid2D::Coord c(i, j); + if ( current.matrix()[c]==0 ) nb++; + } + } + return nb; +} + +double AI::peakToPeak(const Board &, const Board ¤t) +{ + int min = current.matrix().height()-1; + for (uint i=0; i<current.matrix().width(); i++) + min = kMin(min, current.firstColumnBlock(i)); + return (int)current.firstClearLine()-1 - min; +} + +double AI::mean(const Board &, const Board ¤t) +{ + double mean = 0; + for (uint i=0; i<current.matrix().width(); i++) + mean += current.firstColumnBlock(i); + return mean / current.matrix().width(); +} + +double AI::nbSpaces(const Board &main, const Board ¤t) +{ + double nb = 0; + double m = mean(main, current); + for (uint i=0; i<current.matrix().width(); i++) { + int j = current.firstColumnBlock(i); + if ( j<m ) nb += m - j; + } + return nb; +} + +double AI::nbRemoved(const Board &main, const Board ¤t) +{ + return current.nbRemoved() - main.nbRemoved(); +} + + +//----------------------------------------------------------------------------- +const uint AIConfig::minThinkingDepth = 1; +const uint AIConfig::maxThinkingDepth = 2; + +AIConfig::AIConfig(const QValueVector<AI::Element> &elements) + : QWidget(0, "ai config") +{ + QGridLayout *top = new QGridLayout(this, 3, 2, KDialog::marginHint(), + KDialog::spacingHint()); + + QLabel *label = new QLabel(i18n("Thinking depth:"), this); + top->addWidget(label, 0, 0); + KIntNumInput *in = new KIntNumInput(this, "kcfg_ThinkingDepth"); + in->setRange(minThinkingDepth, maxThinkingDepth); + top->addWidget(in, 0, 1); + + top->addRowSpacing(1, KDialog::spacingHint()); + + QGrid *grid = new QGrid(2, this); + top->addMultiCellWidget(grid, 2, 2, 0, 1); + for (uint i=0; i<elements.size(); i++) { + const AI::Data &data = *elements.at(i).data; + QLabel *label = new QLabel(i18n(data.label), grid); + if (data.whatsthis) QWhatsThis::add(label, i18n(data.whatsthis)); + label->setFrameStyle(QFrame::Panel | QFrame::Plain); + + QVBox *vb = new QVBox(grid); + if (data.whatsthis) QWhatsThis::add(vb, i18n(data.whatsthis)); + vb->setMargin(KDialog::spacingHint()); + vb->setSpacing(KDialog::spacingHint()); + vb->setFrameStyle(QFrame::Panel | QFrame::Plain); + if (data.triggered) { + KIntNumInput *trig = new KIntNumInput(vb, triggerKey(data.name)); + trig->setRange(0, 10, 1, true); + } + KDoubleNumInput *coeff = new KDoubleNumInput(vb, coefficientKey(data.name)); + coeff->setRange(0.0, 1.0, 1.0, true); + } +} + +QCString AIConfig::triggerKey(const char *name) +{ + return "kcfg_Trigger_" + QCString(name); +} + +QCString AIConfig::coefficientKey(const char *name) +{ + return "kcfg_Coefficient_" + QCString(name); +} + +double AIConfig::coefficient(const AI::Data &data) +{ + KConfigSkeletonItem *item = CommonPrefs::self()->findItem( QString("Coefficient_%1").arg(data.name) ); + assert(item); + return item->property().toDouble(); +} + +int AIConfig::trigger(const AI::Data &data) +{ + KConfigSkeletonItem *item = CommonPrefs::self()->findItem( QString("Trigger_%1").arg(data.name) ); + assert(item); + return item->property().toInt(); +} |