You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
8.9 KiB
357 lines
8.9 KiB
#include "ai.h"
|
|
#include "ai.moc"
|
|
|
|
#include <assert.h>
|
|
|
|
#include <tqlabel.h>
|
|
#include <tqhbox.h>
|
|
#include <tqvbox.h>
|
|
#include <layout.h>
|
|
#include <tqgrid.h>
|
|
#include <tqwhatsthis.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, TQPoint(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, TQT_SIGNAL(timeout()), TQT_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 && tqRound(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 TQValueVector<AI::Element> &elements)
|
|
: TQWidget(0, "ai config")
|
|
{
|
|
TQGridLayout *top = new TQGridLayout(this, 3, 2, KDialog::marginHint(),
|
|
KDialog::spacingHint());
|
|
|
|
TQLabel *label = new TQLabel(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());
|
|
|
|
TQGrid *grid = new TQGrid(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;
|
|
TQLabel *label = new TQLabel(i18n(data.label), grid);
|
|
if (data.whatsthis) TQWhatsThis::add(label, i18n(data.whatsthis));
|
|
label->setFrameStyle(TQFrame::Panel | TQFrame::Plain);
|
|
|
|
TQVBox *vb = new TQVBox(grid);
|
|
if (data.whatsthis) TQWhatsThis::add(vb, i18n(data.whatsthis));
|
|
vb->setMargin(KDialog::spacingHint());
|
|
vb->setSpacing(KDialog::spacingHint());
|
|
vb->setFrameStyle(TQFrame::Panel | TQFrame::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);
|
|
}
|
|
}
|
|
|
|
TQCString AIConfig::triggerKey(const char *name)
|
|
{
|
|
return "kcfg_Trigger_" + TQCString(name);
|
|
}
|
|
|
|
TQCString AIConfig::coefficientKey(const char *name)
|
|
{
|
|
return "kcfg_Coefficient_" + TQCString(name);
|
|
}
|
|
|
|
double AIConfig::coefficient(const AI::Data &data)
|
|
{
|
|
KConfigSkeletonItem *item = CommonPrefs::self()->findItem( TQString("Coefficient_%1").arg(data.name) );
|
|
assert(item);
|
|
return item->property().toDouble();
|
|
}
|
|
|
|
int AIConfig::trigger(const AI::Data &data)
|
|
{
|
|
KConfigSkeletonItem *item = CommonPrefs::self()->findItem( TQString("Trigger_%1").arg(data.name) );
|
|
assert(item);
|
|
return item->property().toInt();
|
|
}
|