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.
2919 lines
72 KiB
2919 lines
72 KiB
/* Yo Emacs, this -*- C++ -*-
|
|
|
|
Copyright (C) 1999-2001 Jens Hoefkens
|
|
jens@hoefkens.com
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
This file contains the implementation of the KBgBoard class and
|
|
all related utility classes.
|
|
|
|
Effort has been made to keep this class general. Please comment on that
|
|
if you want to use it in your own project.
|
|
|
|
*/
|
|
|
|
#include <kapplication.h>
|
|
|
|
#include "kbgboard.h"
|
|
#include "kbgboard.moc"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <kcolordialog.h>
|
|
#include <klocale.h>
|
|
#include <tqlayout.h>
|
|
#include <tqgroupbox.h>
|
|
#include <tqbuttongroup.h>
|
|
#include <kconfig.h>
|
|
#include <tqwhatsthis.h>
|
|
#include <tqvbox.h>
|
|
#include <kiconloader.h>
|
|
#include <ktabctl.h>
|
|
#include <kpushbutton.h>
|
|
#include <kstdguiitem.h>
|
|
|
|
#include "version.h"
|
|
|
|
|
|
const int CUBE_UPPER = 3;
|
|
const int CUBE_LOWER = 4;
|
|
|
|
static const int MINIMUM_CHECKER_SIZE = 10;
|
|
|
|
/*
|
|
* Set the default settings in all user configurations
|
|
*/
|
|
void KBgBoardSetup::setupDefault()
|
|
{
|
|
// default background color
|
|
setBackgroundColor(TQColor(200, 200, 166));
|
|
pbc_1->setPalette(TQPalette(backgroundColor()));
|
|
|
|
// checker colors
|
|
baseColors[0] = black;
|
|
baseColors[1] = white;
|
|
pbc_2->setPalette(TQPalette(baseColors[0]));
|
|
pbc_3->setPalette(TQPalette(baseColors[1]));
|
|
|
|
// default font
|
|
setFont(TQFont("Serif", 18, TQFont::Normal));
|
|
kf->setFont(getFont());
|
|
|
|
// short moves
|
|
setShortMoveMode(SHORT_MOVE_DOUBLE);
|
|
for (int i = 0; i < 3; i++)
|
|
rbMove[i]->setChecked(i == SHORT_MOVE_DOUBLE);
|
|
|
|
// pip count
|
|
cbp->setChecked(computePipCount = true);
|
|
}
|
|
|
|
/*
|
|
* User committed the changes. Save them.
|
|
*/
|
|
void KBgBoardSetup::setupOk()
|
|
{
|
|
// font selection
|
|
setFont(kf->font());
|
|
|
|
// move strategy
|
|
for (int i = 0; i < 3; i++)
|
|
if (rbMove[i]->isChecked()) setShortMoveMode(i);
|
|
|
|
// pipcount
|
|
computePipCount = cbp->isChecked();
|
|
}
|
|
|
|
/*
|
|
* User cancelled the changes. Undo the color changes that become
|
|
* visible right away.
|
|
*/
|
|
void KBgBoardSetup::setupCancel()
|
|
{
|
|
// undo background color change
|
|
setBackgroundColor(saveBackgroundColor);
|
|
|
|
// undo checker color changes
|
|
baseColors[0] = saveBaseColors[0];
|
|
baseColors[1] = saveBaseColors[1];
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
cells[i]->update();
|
|
}
|
|
|
|
/*
|
|
* Fills configuration page in the dialog nb
|
|
*/
|
|
void KBgBoardSetup::getSetupPages(KDialogBase *nb)
|
|
{
|
|
/*
|
|
* Main Widget
|
|
* ===========
|
|
*/
|
|
TQVBox *vbp = nb->addVBoxPage(i18n("Board"), i18n("Here you can configure the backgammon board"),
|
|
kapp->iconLoader()->loadIcon(PROG_NAME, KIcon::Desktop));
|
|
|
|
/*
|
|
* Need more than one page
|
|
*/
|
|
KTabCtl *tc = new KTabCtl(vbp, "board tabs");
|
|
|
|
TQWidget *w = new TQWidget(tc);
|
|
TQGridLayout *gl = new TQGridLayout(w, 3, 1, nb->spacingHint());
|
|
|
|
/*
|
|
* Group boxes
|
|
* ===========
|
|
*/
|
|
TQGroupBox *ga = new TQGroupBox(w);
|
|
TQButtonGroup *gm = new TQButtonGroup(w);
|
|
TQGroupBox *go = new TQGroupBox(w);
|
|
|
|
ga->setTitle(i18n("Colors"));
|
|
gm->setTitle(i18n("Short Moves"));
|
|
go->setTitle(i18n("Options"));
|
|
|
|
gl->addWidget(ga, 0, 0);
|
|
gl->addWidget(gm, 1, 0);
|
|
gl->addWidget(go, 2, 0);
|
|
|
|
/*
|
|
* Appearance group
|
|
* ----------------
|
|
*/
|
|
TQGridLayout *blc = new TQGridLayout(ga, 2, 2, 20);
|
|
|
|
pbc_1 = new TQPushButton(i18n("Background"), ga);
|
|
pbc_1->setPalette(TQPalette(backgroundColor()));
|
|
|
|
pbc_2 = new TQPushButton(i18n("Color 1"), ga);
|
|
pbc_2->setPalette(TQPalette(baseColors[0]));
|
|
|
|
pbc_3 = new TQPushButton(i18n("Color 2"), ga);
|
|
pbc_3->setPalette(TQPalette(baseColors[1]));
|
|
|
|
blc->addWidget(pbc_2, 0, 0);
|
|
blc->addWidget(pbc_3, 0, 1);
|
|
blc->addMultiCellWidget(pbc_1, 1, 1, 0, 1);
|
|
|
|
connect(pbc_1, TQT_SIGNAL(clicked()), this, TQT_SLOT(selectBackgroundColor()));
|
|
connect(pbc_2, TQT_SIGNAL(clicked()), this, TQT_SLOT(selectBaseColorOne()));
|
|
connect(pbc_3, TQT_SIGNAL(clicked()), this, TQT_SLOT(selectBaseColorTwo()));
|
|
|
|
/*
|
|
* Moving style
|
|
* ------------
|
|
*/
|
|
TQBoxLayout *blm = new TQVBoxLayout(gm, nb->spacingHint());
|
|
|
|
blm->addSpacing(gm->fontMetrics().height());
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
rbMove[i] = new TQRadioButton(gm);
|
|
|
|
rbMove[SHORT_MOVE_NONE]->setText(i18n("&Disable short moves. Only drag and drop will move."));
|
|
rbMove[SHORT_MOVE_SINGLE]->setText(i18n("&Single clicks with the left mouse button will\n"
|
|
"move a checker the shortest possible distance."));
|
|
rbMove[SHORT_MOVE_DOUBLE]->setText(i18n("D&ouble clicks with the left mouse button will\n"
|
|
"move a checker the shortest possible distance."));
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
rbMove[i]->setMinimumSize(rbMove[i]->sizeHint());
|
|
blm->addWidget(rbMove[i]);
|
|
rbMove[i]->setChecked(i == getShortMoveMode());
|
|
}
|
|
|
|
/*
|
|
* Other options
|
|
* -------------
|
|
*/
|
|
TQGridLayout *glo = new TQGridLayout(go, 1, 1, 20);
|
|
|
|
cbp = new TQCheckBox(i18n("Show pip count in title bar"), go);
|
|
cbp->setChecked(computePipCount);
|
|
cbp->adjustSize();
|
|
cbp->setMinimumSize(cbp->size());
|
|
|
|
glo->addRowSpacing(0, cbp->height());
|
|
glo->addWidget(cbp, 0, 0);
|
|
|
|
gl->activate();
|
|
|
|
w->adjustSize();
|
|
w->setMinimumSize(w->size());
|
|
|
|
tc->addTab(w, i18n("&Board"));
|
|
|
|
/*
|
|
* Save current settings
|
|
* ---------------------
|
|
*/
|
|
saveBackgroundColor = backgroundColor();
|
|
saveBaseColors[0] = baseColors[0];
|
|
saveBaseColors[1] = baseColors[1];
|
|
|
|
/*
|
|
* Font selection page
|
|
* ===================
|
|
*/
|
|
w = new TQWidget(tc);
|
|
kf = new KFontChooser(w);
|
|
kf->setFont(getFont());
|
|
gl = new TQGridLayout(w, 1, 1, nb->spacingHint());
|
|
gl->addWidget(kf, 0, 0);
|
|
gl->activate();
|
|
w->adjustSize();
|
|
w->setMinimumSize(w->size());
|
|
tc->addTab(w, i18n("&Font"));
|
|
}
|
|
|
|
/*
|
|
* Empty constructor calls the board constructor
|
|
*/
|
|
KBgBoardSetup::KBgBoardSetup(TQWidget *parent, const char *name, TQPopupMenu *menu)
|
|
: KBgBoard(parent, name, menu)
|
|
{
|
|
// empty
|
|
}
|
|
|
|
/*
|
|
* User changed first checker color
|
|
*/
|
|
void KBgBoardSetup::selectBaseColorOne()
|
|
{
|
|
KColorDialog *c = new KColorDialog(this, "base-col-1", true);
|
|
c->setColor(baseColors[0]);
|
|
if (c->exec()) {
|
|
baseColors[0] = c->color();
|
|
pbc_2->setPalette(TQPalette(baseColors[0]));
|
|
for (int i = 0; i < 30; i++)
|
|
cells[i]->update();
|
|
}
|
|
delete c;
|
|
}
|
|
|
|
/*
|
|
* User changed second checker color
|
|
*/
|
|
void KBgBoardSetup::selectBaseColorTwo()
|
|
{
|
|
KColorDialog *c = new KColorDialog(this, "base-col-2", true);
|
|
c->setColor(baseColors[1]);
|
|
if (c->exec()) {
|
|
baseColors[1] = c->color();
|
|
pbc_3->setPalette(TQPalette(baseColors[1]));
|
|
for (int i = 0; i < 30; i++)
|
|
cells[i]->update();
|
|
}
|
|
delete c;
|
|
}
|
|
|
|
/*
|
|
* User changed background color
|
|
*/
|
|
void KBgBoardSetup::selectBackgroundColor()
|
|
{
|
|
KColorDialog *c = new KColorDialog(this, "bg-col", true);
|
|
c->setColor(backgroundColor());
|
|
if (c->exec()) {
|
|
setBackgroundColor(c->color());
|
|
pbc_1->setPalette(TQPalette(backgroundColor()));
|
|
for (int i = 0; i < 30; i++)
|
|
cells[i]->update();
|
|
}
|
|
delete c;
|
|
}
|
|
|
|
/*
|
|
* Saves the persistent settings of the board
|
|
*/
|
|
void KBgBoard::saveConfig()
|
|
{
|
|
KConfig* config = kapp->config();
|
|
config->setGroup(name());
|
|
|
|
config->writeEntry("bgcolor", backgroundColor());
|
|
config->writeEntry("color-1", baseColors[0]);
|
|
config->writeEntry("color-2", baseColors[1]);
|
|
config->writeEntry("font", getFont());
|
|
config->writeEntry("move", getShortMoveMode());
|
|
config->writeEntry("pip", computePipCount);
|
|
}
|
|
|
|
/*
|
|
* Restore the settings or use reasonable defaults
|
|
*/
|
|
void KBgBoard::readConfig()
|
|
{
|
|
TQColor col(200, 200, 166);
|
|
TQFont fon("Serif", 18, TQFont::Normal);
|
|
|
|
KConfig* config = kapp->config();
|
|
config->setGroup(name());
|
|
|
|
setBackgroundColor(config->readColorEntry("bgcolor", &col));
|
|
baseColors[0] = config->readColorEntry("color-1", &black);
|
|
baseColors[1] = config->readColorEntry("color-2", &white);
|
|
setFont(config->readFontEntry("font", &fon));
|
|
setShortMoveMode(config->readNumEntry("move", SHORT_MOVE_DOUBLE));
|
|
computePipCount = config->readBoolEntry("pip", true);
|
|
}
|
|
|
|
/*
|
|
* Get the font the board cells should use for the display of
|
|
* numbers and cube value.
|
|
*/
|
|
TQFont KBgBoard::getFont() const
|
|
{
|
|
return boardFont;
|
|
}
|
|
|
|
/*
|
|
* Allows the users of the board classe to set the font to be used
|
|
* on the board. Note that the fontsize is dynamically set
|
|
*/
|
|
void KBgBoard::setFont(const TQFont& f)
|
|
{
|
|
boardFont = f;
|
|
}
|
|
|
|
/*
|
|
* Ask the user for an updated cube value
|
|
*/
|
|
void KBgBoard::queryCube()
|
|
{
|
|
KBgStatus *st = new KBgStatus();
|
|
getState(st);
|
|
KBgBoardQCube *dlg =
|
|
new KBgBoardQCube(abs(st->cube()), (st->cube(US) > 0), (st->cube(THEM) > 0));
|
|
if (dlg->exec()) {
|
|
bool u = ((dlg->getCubeValue() == 0) || (dlg->getCubeOwner() == US ));
|
|
bool t = ((dlg->getCubeValue() == 0) || (dlg->getCubeOwner() == THEM));
|
|
st->setCube((int)rint(pow(2.0, dlg->getCubeValue())), u, t);
|
|
setState(*st); // JENS
|
|
}
|
|
delete dlg;
|
|
delete st;
|
|
}
|
|
|
|
/*
|
|
* Constructor, creates the dialog but does not show nor execute it.
|
|
*/
|
|
KBgBoardQCube::KBgBoardQCube(int val, bool us, bool them)
|
|
: TQDialog(0, 0, true)
|
|
{
|
|
setCaption(i18n("Set Cube Values"));
|
|
|
|
TQBoxLayout *vbox = new TQVBoxLayout(this, 17);
|
|
|
|
TQLabel *info = new TQLabel(this);
|
|
|
|
cb[0] = new TQComboBox(this, "first sb");
|
|
cb[1] = new TQComboBox(this, "second sb");
|
|
ok = new KPushButton(KStdGuiItem::ok(), this);
|
|
cancel = new KPushButton(KStdGuiItem::cancel(), this);
|
|
|
|
info->setText(i18n("Set the face value of the cube and select who should be able to\n"
|
|
"double. Note that a face value of 1 automatically allows both\n"
|
|
"players to double."));
|
|
|
|
info->setMinimumSize(info->sizeHint());
|
|
|
|
vbox->addWidget(info, 0);
|
|
|
|
TQBoxLayout *hbox_1 = new TQHBoxLayout();
|
|
TQBoxLayout *hbox_2 = new TQHBoxLayout();
|
|
|
|
vbox->addLayout(hbox_1);
|
|
vbox->addLayout(hbox_2);
|
|
|
|
hbox_1->addWidget(cb[1]);
|
|
hbox_1->addWidget(cb[0]);
|
|
|
|
hbox_2->addWidget(ok);
|
|
hbox_2->addWidget(cancel);
|
|
|
|
cb[0]->insertItem(" 1", 0);
|
|
cb[0]->insertItem(" 2", 1);
|
|
cb[0]->insertItem(" 4", 2);
|
|
cb[0]->insertItem(" 8", 3);
|
|
cb[0]->insertItem("16", 4);
|
|
cb[0]->insertItem("32", 5);
|
|
cb[0]->insertItem("64", 6);
|
|
|
|
switch(val) {
|
|
case 1:
|
|
cb[0]->setCurrentItem(0);
|
|
break;
|
|
case 2:
|
|
cb[0]->setCurrentItem(1);
|
|
break;
|
|
case 4:
|
|
cb[0]->setCurrentItem(2);
|
|
break;
|
|
case 8:
|
|
cb[0]->setCurrentItem(3);
|
|
break;
|
|
case 16:
|
|
cb[0]->setCurrentItem(4);
|
|
break;
|
|
case 32:
|
|
cb[0]->setCurrentItem(5);
|
|
break;
|
|
case 64:
|
|
cb[0]->setCurrentItem(6);
|
|
break;
|
|
}
|
|
|
|
cb[1]->insertItem(i18n("Lower Player"), US);
|
|
cb[1]->insertItem(i18n("Upper Player"), THEM);
|
|
cb[1]->insertItem(i18n("Open Cube"), BOTH);
|
|
|
|
if (us && them)
|
|
cb[1]->setCurrentItem(BOTH);
|
|
else if (us)
|
|
cb[1]->setCurrentItem(US);
|
|
else if (them)
|
|
cb[1]->setCurrentItem(THEM);
|
|
|
|
cb[0]->setMinimumSize(cb[0]->sizeHint());
|
|
cb[1]->setMinimumSize(cb[1]->sizeHint());
|
|
|
|
ok->setMinimumSize(ok->sizeHint());
|
|
cancel->setMinimumSize(cancel->sizeHint());
|
|
|
|
setMinimumSize(childrenRect().size());
|
|
|
|
vbox->activate();
|
|
|
|
resize(minimumSize());
|
|
|
|
ok->setAutoDefault (true);
|
|
ok->setDefault(true);
|
|
|
|
cb[0]->setFocus();
|
|
|
|
connect(ok, TQT_SIGNAL(clicked()), TQT_SLOT(accept()));
|
|
connect(cancel, TQT_SIGNAL(clicked()), TQT_SLOT(reject()));
|
|
|
|
connect(cb[0], TQT_SIGNAL(activated(int)), TQT_SLOT(changePlayer(int)));
|
|
connect(cb[1], TQT_SIGNAL(activated(int)), TQT_SLOT(changeValue (int)));
|
|
}
|
|
|
|
/*
|
|
* Deconstructor, empty.
|
|
*/
|
|
KBgBoardQCube::~KBgBoardQCube()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* Get the face value of the cube
|
|
*/
|
|
int KBgBoardQCube::getCubeValue()
|
|
{
|
|
return cb[0]->currentItem();
|
|
}
|
|
|
|
/*
|
|
* Get the owner of the cube
|
|
*/
|
|
int KBgBoardQCube::getCubeOwner()
|
|
{
|
|
return cb[1]->currentItem();
|
|
}
|
|
|
|
/*
|
|
* If the cube is open, the value can only be 1
|
|
*/
|
|
void KBgBoardQCube::changeValue(int player)
|
|
{
|
|
if (player == BOTH)
|
|
cb[0]->setCurrentItem(0);
|
|
|
|
}
|
|
|
|
/*
|
|
* If the value is 1, the cube has to be open; and if the value
|
|
* becomes bigger than 1, the player cannot stay open.
|
|
*/
|
|
void KBgBoardQCube::changePlayer(int val)
|
|
{
|
|
if (val == 0)
|
|
cb[1]->setCurrentItem(BOTH);
|
|
else if (cb[1]->currentItem() == BOTH)
|
|
cb[1]->setCurrentItem(US);
|
|
}
|
|
|
|
/*
|
|
* Constructor, creates the dialog but does not show nor execute it.
|
|
*/
|
|
KBgBoardQDice::KBgBoardQDice(const char *name)
|
|
: TQDialog(0, name, true)
|
|
{
|
|
setCaption(i18n("Set Dice Values"));
|
|
|
|
TQBoxLayout *vbox = new TQVBoxLayout(this, 17);
|
|
|
|
TQLabel *info = new TQLabel(this);
|
|
|
|
sb[0] = new TQSpinBox(this, "first sb");
|
|
sb[1] = new TQSpinBox(this, "second sb");
|
|
ok = new KPushButton(KStdGuiItem::ok(), this);
|
|
cancel = new KPushButton(KStdGuiItem::cancel(), this);
|
|
|
|
info->setText(i18n("Set the face values of the selected dice. The other player's\n"
|
|
"dice will be cleared and it will be the dice's owner's turn."));
|
|
|
|
info->setMinimumSize(info->sizeHint());
|
|
|
|
vbox->addWidget(info, 0);
|
|
|
|
TQBoxLayout *hbox_1 = new TQHBoxLayout();
|
|
TQBoxLayout *hbox_2 = new TQHBoxLayout();
|
|
|
|
vbox->addLayout(hbox_1);
|
|
vbox->addLayout(hbox_2);
|
|
|
|
hbox_1->addWidget(sb[0]);
|
|
hbox_1->addWidget(sb[1]);
|
|
|
|
hbox_2->addWidget(ok);
|
|
hbox_2->addWidget(cancel);
|
|
|
|
sb[0]->setMinimumSize(sb[0]->sizeHint());
|
|
sb[1]->setMinimumSize(sb[1]->sizeHint());
|
|
|
|
ok->setMinimumSize(ok->sizeHint());
|
|
cancel->setMinimumSize(cancel->sizeHint());
|
|
|
|
setMinimumSize(childrenRect().size());
|
|
|
|
vbox->activate();
|
|
|
|
resize(minimumSize());
|
|
|
|
ok->setAutoDefault (true);
|
|
ok->setDefault(true);
|
|
|
|
sb[0]->setFocus();
|
|
|
|
connect(ok, TQT_SIGNAL(clicked()), TQT_SLOT(accept()));
|
|
connect(cancel, TQT_SIGNAL(clicked()), TQT_SLOT(reject()));
|
|
|
|
sb[0]->setValue(1);
|
|
sb[1]->setValue(1);
|
|
|
|
sb[0]->setRange(1, 6);
|
|
sb[1]->setRange(1, 6);
|
|
}
|
|
|
|
/*
|
|
* Deconstructor, empty.
|
|
*/
|
|
KBgBoardQDice::~KBgBoardQDice()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* Get the face value of the dice
|
|
*/
|
|
int KBgBoardQDice::getDice(int n)
|
|
{
|
|
return sb[n]->value();
|
|
}
|
|
|
|
/*
|
|
* Allows for overriding the current turn color in edit mode.
|
|
*/
|
|
void KBgBoard::storeTurn(const int pcs)
|
|
{
|
|
storedTurn = ((pcs > 0) ? +1 : -1);
|
|
}
|
|
|
|
/*
|
|
* Switch edit mode on/off
|
|
*/
|
|
void KBgBoard::setEditMode(const bool m)
|
|
{
|
|
editMode = m;
|
|
}
|
|
|
|
/*
|
|
* Retrurns the current edit mode status.
|
|
*/
|
|
bool KBgBoard::getEditMode() const
|
|
{
|
|
return editMode;
|
|
}
|
|
|
|
/*
|
|
* This function takes a KBgStatus object and fills it with the current
|
|
* board status.
|
|
*/
|
|
KBgStatus* KBgBoard::getState(KBgStatus *st) const
|
|
{
|
|
st->setColor(color);
|
|
st->setDirection(direction);
|
|
|
|
st->setCube(cube, maydouble[US], maydouble[THEM]);
|
|
|
|
st->setBar(US, onbar[US]); st->setBar(THEM, onbar[THEM]);
|
|
st->setHome(US, onhome[US]); st->setHome(THEM, onhome[THEM]);
|
|
|
|
st->setDice(US, 0, dice[US][0]);
|
|
st->setDice(US, 1, dice[US][1]);
|
|
|
|
st->setDice(THEM, 0, dice[THEM][0]);
|
|
st->setDice(THEM, 1, dice[THEM][1]);
|
|
|
|
for (int i = 1; i < 25; ++i)
|
|
st->setBoard(i, ((color*board[i] < 0) ? THEM : US), abs(board[i]));
|
|
|
|
return st;
|
|
}
|
|
|
|
/*
|
|
* This function lets external users change the context menu
|
|
*/
|
|
void KBgBoard::setContextMenu(TQPopupMenu *menu)
|
|
{
|
|
contextMenu = menu;
|
|
}
|
|
|
|
/*
|
|
* This function prints all moves up to now in the extended FIBS command
|
|
* notation (that is moves that involved kicking have a "+" instead of "-".
|
|
*/
|
|
void KBgBoard::sendMove()
|
|
{
|
|
if (getEditMode())
|
|
return;
|
|
|
|
TQString s, t;
|
|
|
|
s.setNum(moveHistory.count());
|
|
s += " ";
|
|
|
|
TQPtrListIterator<KBgBoardMove> it(moveHistory);
|
|
for (; it.current(); ++it) {
|
|
KBgBoardMove *move = it.current();
|
|
if (move->source() == BAR_US || move->source() == BAR_THEM ) {
|
|
s += "bar";
|
|
} else {
|
|
t.setNum(move->source());
|
|
s += t;
|
|
}
|
|
if (move->wasKicked())
|
|
s += "+";
|
|
else
|
|
s += "-";
|
|
|
|
if ((move->destination() != HOME_THEM_LEFT) && (move->destination() != HOME_THEM_RIGHT) &&
|
|
(move->destination() != HOME_US_LEFT ) && (move->destination() != HOME_US_RIGHT )) {
|
|
t.setNum(move->destination());
|
|
s += t;
|
|
} else {
|
|
s += "off";
|
|
}
|
|
s += " ";
|
|
}
|
|
emit currentMove(&s);
|
|
}
|
|
|
|
/*
|
|
* This is overloaded from TQWidget, since it has to pass the new
|
|
* background color to the child widgets (the cells).
|
|
*/
|
|
void KBgBoard::setBackgroundColor(const TQColor &col)
|
|
{
|
|
if (col != backgroundColor()) {
|
|
TQWidget::setBackgroundColor(col);
|
|
for( int i = 0; i < 30; ++i)
|
|
cells[i]->setBackgroundColor(col);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Overloaded from TQWidget since we have to resize all cells
|
|
*/
|
|
void KBgBoard::resizeEvent(TQResizeEvent *)
|
|
{
|
|
int xo0 = 0;
|
|
int xo1, w;
|
|
int hu = height()/2;
|
|
int hl = height() - hu;
|
|
|
|
checkerDiam = (int)((width()/15-2)<(height()/10.0-2) ?
|
|
(width()/15-2) : (height()/10.0-2));
|
|
|
|
if (checkerDiam < MINIMUM_CHECKER_SIZE)
|
|
checkerDiam = MINIMUM_CHECKER_SIZE;
|
|
|
|
for (int i = 0; i < 14; ++i) {
|
|
xo1 = int((i+1)*width()/15.0);
|
|
w = xo1 - xo0;
|
|
cells[i ]->setGeometry(xo0, 0, w, hu);
|
|
cells[i+15]->setGeometry(xo0, hu, w, hl);
|
|
xo0 = xo1;
|
|
}
|
|
cells[14]->setGeometry(xo0, 0, width() - xo0, hu);
|
|
cells[29]->setGeometry(xo0, hu, width() - xo0, hl);
|
|
}
|
|
|
|
/*
|
|
* This function draws the whole board in black and white on the
|
|
* painter *p. It is very well suited for printing on paper.
|
|
* It scales the output according to the width of the widget.
|
|
* I.e. if the widget is insanely long (y-direction) this will look
|
|
* shitty. The upper 20% of the painter are not used. So the caller
|
|
* can print whatever she/he wants above the 0.2*p->viewport().height()
|
|
* margin (like game status information).
|
|
*/
|
|
void KBgBoard::print(TQPainter *p)
|
|
{
|
|
double sf = 0.8*p->viewport().width()/width();
|
|
int xo = int((p->viewport().width() - sf*width())/2);
|
|
int yo = int(0.2*p->viewport().height());
|
|
int hu = height()/2;
|
|
|
|
int xo0 = 0;
|
|
for (int i = 0; i < 15; ++i) {
|
|
cells[i ]->paintCell(p, xo+sf*xo0, yo , sf);
|
|
cells[i+15]->paintCell(p, xo+sf*xo0, yo+sf*(hu-1), sf);
|
|
xo0 = int((i+1)*width()/15.0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns the selected drawing color for a checker
|
|
* of the given sign(!). I.e. we distinguish checkers by whether
|
|
* they are negative or positive.
|
|
*/
|
|
TQColor KBgBoard::getCheckerColor(int p) const
|
|
{
|
|
return ((p < 0) ? baseColors[0] : baseColors[1]);
|
|
}
|
|
|
|
/*
|
|
* This small utility function returns the y-coordinate base
|
|
* of a checker. This is the offset in the y-coordinate at
|
|
* which we have toposition the upper corner of the first
|
|
* checker so that it is fully in the cell.
|
|
*/
|
|
int KBgBoardField::numberBase() const
|
|
{
|
|
return (cellID < 13) ? 0 : height()-20;
|
|
}
|
|
|
|
/*
|
|
* This function computes the proper diameter for checkers on this cell.
|
|
* It tries to stay within the horizontal boundaries and adjusts the
|
|
* diameter in such a way that 5 checkers fit on top of each other and
|
|
* there is still some room for stacked checkers.
|
|
*/
|
|
int KBgBoardCell::getCheckerDiameter() const
|
|
{
|
|
return board->checkerDiam;
|
|
}
|
|
|
|
/*
|
|
* Draws the cells content using the painter p.
|
|
* Reimplemented from TQLabel.
|
|
*/
|
|
void KBgBoardCell::drawContents(TQPainter *)
|
|
{
|
|
TQRect cr(0, 0, width(), height());
|
|
cr.moveBottomLeft(rect().bottomLeft());
|
|
TQPixmap pix(cr.size());
|
|
TQPainter tmp;
|
|
pix.fill(this, cr.topLeft());
|
|
tmp.begin(&pix);
|
|
paintCell(&tmp);
|
|
tmp.end();
|
|
bitBlt(this, 0, 0, &pix);
|
|
/*
|
|
* New state is now current.
|
|
* This avoids unnecessary redrawings.
|
|
*/
|
|
stateChanged = false;
|
|
}
|
|
|
|
/*
|
|
* This does the absolute bare minimum of painting a cell. It draws a small
|
|
* horizontal black line that marks the outer boundary of the cell and all
|
|
* overloaded paintCell() member are supposed to call this one after(!) they
|
|
* have painted themselves.
|
|
*/
|
|
void KBgBoardCell::paintCell(TQPainter *p, int xo, int yo, double sf) const
|
|
{
|
|
int x1 = xo; int x2 = xo;
|
|
int y1 = yo; int y2 = yo;
|
|
|
|
if ((cellID==HOME_THEM_LEFT || cellID==BAR_THEM) ||
|
|
(cellID<13 && cellID>0)) {
|
|
x2 += int(sf*width());
|
|
} else if ((cellID==HOME_US_LEFT || cellID==BAR_US) ||
|
|
(cellID<25 && cellID>12)) {
|
|
x2 += int(sf*width());
|
|
y1 = y2 += int(sf*(height()-1));
|
|
} else if (cellID == HOME_THEM_RIGHT) {
|
|
x2 += int(sf*(width()-1));
|
|
} else if (cellID == HOME_US_RIGHT) {
|
|
x2 += int(sf*(width()-1));
|
|
y1 = y2 += int(sf*(height()-1));
|
|
} else {
|
|
return; // do nothing if the cellID is wrong
|
|
}
|
|
|
|
// draw line in black
|
|
p->setBrush( black );
|
|
p->setPen( black );
|
|
p->drawLine(x1, y1, x2, y2);
|
|
}
|
|
|
|
/*
|
|
* This function draws vertical boundaries around a cell. This is used
|
|
* for bars and homes to get them separated from the rest of the board.
|
|
*/
|
|
void
|
|
KBgBoardCell::drawVertBorder(TQPainter *p, int xo, int yo, double sf) const
|
|
{
|
|
p->setBrush(black);
|
|
p->setPen(black);
|
|
p->drawLine(xo, yo, xo, yo+sf*(height()-1));
|
|
p->drawLine(xo+sf*(width()-1), yo, xo+sf*(width()-1), yo+sf*(height()-1));
|
|
}
|
|
|
|
/*
|
|
* This function draws the content of the homes on the painter *p. It
|
|
* starts at the upper left corner (xo, yo) and uses the scaling factor
|
|
* sf.
|
|
*/
|
|
void KBgBoardHome::paintCell(TQPainter *p, int xo, int yo, double sf) const
|
|
{
|
|
/*
|
|
* Only these homes contain checkers. The other ones contains dice and cube.
|
|
*/
|
|
if (((cellID == HOME_THEM_LEFT ) && (direction > 0)) ||
|
|
((cellID == HOME_THEM_RIGHT) && (direction < 0)) ||
|
|
((cellID == HOME_US_LEFT ) && (direction > 0)) ||
|
|
((cellID == HOME_US_RIGHT ) && (direction < 0))) {
|
|
|
|
drawOverlappingCheckers(p, xo, yo, sf);
|
|
|
|
} else {
|
|
|
|
drawDiceAndCube(p, ((cellID == HOME_THEM_LEFT ||
|
|
cellID == HOME_THEM_RIGHT) ?
|
|
THEM : US), xo, yo, sf);
|
|
|
|
}
|
|
|
|
/*
|
|
* Finally draw the boundaries
|
|
*/
|
|
drawVertBorder(p, xo, yo, sf);
|
|
KBgBoardCell::paintCell(p, xo, yo, sf);
|
|
}
|
|
|
|
/*
|
|
* This function draws the content of the bar cells. Bars may contain
|
|
* checkers and the cube. Please read the comments in the code on how
|
|
* and why the checkers and (especially) the cube is printed.
|
|
*/
|
|
void KBgBoardBar::paintCell(TQPainter *p, int xo, int yo, double sf) const
|
|
{
|
|
/*
|
|
* Put in the checkers.
|
|
*/
|
|
drawOverlappingCheckers(p, xo, yo, sf);
|
|
|
|
/*
|
|
* Now comes a slightly tricky part: the cube belongs in the center
|
|
* of the board if nobody has doubled yet. In the way we do the board
|
|
* the center belongs to two(!) fields - both bars.
|
|
*
|
|
* If we are not printing on paper we use the fact that
|
|
* TQt will clip the drawing for us. So we print the upper
|
|
* half of the cube and the lower half on different cells.
|
|
*
|
|
* Since there is no such thing as clipping when we print
|
|
* on paper we can only print one cube. It turns out that
|
|
* the lower one is sufficiently centered.
|
|
*/
|
|
if (board->canDouble(US) &&
|
|
board->canDouble(THEM) &&
|
|
!(abs(xo)+abs(yo) > 0 && cellID == BAR_THEM)) {
|
|
|
|
drawCube(p, cellID == BAR_THEM ? CUBE_UPPER :
|
|
CUBE_LOWER, xo, yo, sf);
|
|
|
|
}
|
|
|
|
/*
|
|
* Finally draw the boundaries
|
|
*/
|
|
drawVertBorder(p, xo, yo, sf);
|
|
KBgBoardCell::paintCell(p, xo, yo, sf);
|
|
}
|
|
|
|
|
|
/*
|
|
* This function draws a cube on the painetr p. The cube will be drawn in
|
|
* the coundaries given by cubeRect(...). The other parameters are like
|
|
* in the other functions.
|
|
*/
|
|
void KBgBoardCell::drawCube(TQPainter *p, int who, int xo, int yo,
|
|
double sf) const
|
|
{
|
|
TQRect r = cubeRect(who, true, sf);
|
|
r.moveTopLeft(TQPoint(xo+r.left(), yo+r.top()));
|
|
|
|
p->setBrush(black);
|
|
p->setPen(black);
|
|
p->drawRoundRect(r, 20, 20);
|
|
|
|
r = cubeRect(who, false, sf);
|
|
r.moveTopLeft(TQPoint(xo+r.left(), yo+r.top()));
|
|
|
|
p->setBrush(white);
|
|
p->setPen(white);
|
|
p->drawRoundRect(r, 20, 20);
|
|
|
|
p->setBrush(black);
|
|
p->setPen(black);
|
|
|
|
TQString cubeNum;
|
|
int v = board->getCube();
|
|
/*
|
|
* Ensure that the cube shows 64 initially
|
|
*/
|
|
if (v == 1) v = 64;
|
|
cubeNum.setNum(v);
|
|
|
|
/*
|
|
* Adjust the font size
|
|
*/
|
|
TQFont f = board->getFont();
|
|
f.setPointSizeFloat(0.75*f.pointSizeFloat());
|
|
p->setFont(f);
|
|
p->drawText(r, AlignCenter, cubeNum);
|
|
}
|
|
|
|
/*
|
|
* This function returns a boundary rectangle for the dice. It does so for both
|
|
* dice (i is either 0 or 1). It can return big and small rectangles and everything
|
|
* is scaled with a default value of 1.0. The scale parameter determines the the
|
|
* size of the dice relative to the checker diameter.
|
|
*/
|
|
TQRect KBgBoardCell::diceRect(int i, bool big, double sf, double scale) const
|
|
{
|
|
int d = int(scale*getCheckerDiameter());
|
|
int l = (1+width())%2;
|
|
int k = (big ? 0 : 1);
|
|
return(TQRect(sf*(width()/2-d+k),
|
|
sf*(height()/2-2*d-3+2*i*(d+3)-1+k),
|
|
sf*(2*(d-k)+1-l),
|
|
sf*(2*(d-k)+1-l)));
|
|
}
|
|
|
|
/*
|
|
* This function returns a bounding rectangle for the cube. This rectangle
|
|
* is moved to the correct place and scaled correctly. The cube is slightly
|
|
* smaller than the dice.
|
|
*/
|
|
TQRect KBgBoardCell::cubeRect(int who, bool big, double sf) const
|
|
{
|
|
TQRect r = diceRect(0, big, sf, 0.40);
|
|
|
|
int d = int(0.40*getCheckerDiameter());
|
|
int h = r.height();
|
|
int k = (big ? 1 : 0);
|
|
|
|
switch (who) {
|
|
case US:
|
|
r.setTop(sf*(height() - 3*d) - k);
|
|
break;
|
|
case THEM:
|
|
r.setTop(sf*d - k);
|
|
break;
|
|
case CUBE_UPPER:
|
|
r.setTop(height()-d*sf - k);
|
|
break;
|
|
case CUBE_LOWER:
|
|
r.setTop( -d*sf - k);
|
|
break;
|
|
default:
|
|
return(TQRect(0,0,0,0));
|
|
}
|
|
r.setHeight(h);
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* This function draws the face value on a given dice painter.
|
|
* If the painting of dice should be saved this is the place
|
|
* to modify.
|
|
*/
|
|
void KBgBoardHome::drawDiceFace(TQPainter *p, int col, int num, int who,
|
|
int xo, int yo, double sf) const
|
|
{
|
|
p->setBrush(board->getCheckerColor(col));
|
|
p->setPen(board->getCheckerColor(col));
|
|
|
|
TQRect r = diceRect(num, false, sf);
|
|
r.moveTopLeft(TQPoint(xo+r.left(), yo+r.top()));
|
|
|
|
int cx = r.width() /2;
|
|
int cy = r.height()/2;
|
|
int cx2 = cx/2;
|
|
int cy2 = cy/2;
|
|
int cx7 = int(0.7*cx);
|
|
int cy7 = int(0.7*cy);
|
|
|
|
switch (board->getDice(who, num)) {
|
|
case 5:
|
|
p->drawEllipse(r.x()+cx-cx7 , r.y()+cy+cy7-1, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx7-1, r.y()+cy-cy7 , 2, 2);
|
|
case 3: // fall through
|
|
p->drawEllipse(r.x()+cx-cx7 , r.y()+cy-cy7 , 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx7-1, r.y()+cy+cy7-1, 2, 2);
|
|
case 1: // fall through
|
|
p->drawEllipse(r.x()+cx , r.y()+cy , 2, 2);
|
|
break;
|
|
case 4:
|
|
p->drawEllipse(r.x()+cx-cx2, r.y()+cy+cy2-1, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy-cy2, 2, 2);
|
|
case 2: // fall through
|
|
p->drawEllipse(r.x()+cx-cx2, r.y()+cy-cy2, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy+cy2-1, 2, 2);
|
|
break;
|
|
case 6:
|
|
p->drawEllipse(r.x()+cx-cx2, r.y()+cy-cy7, 2, 2);
|
|
p->drawEllipse(r.x()+cx-cx2, r.y()+cy, 2, 2);
|
|
p->drawEllipse(r.x()+cx-cx2, r.y()+cy+cy7, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy-cy7, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy, 2, 2);
|
|
p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy+cy7, 2, 2);
|
|
break;
|
|
default: // nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function draws a nice little square on the painter p.
|
|
* The square is suited to contain a a face value as printed
|
|
* by drawDiceFace(...).
|
|
*/
|
|
void KBgBoardHome::drawDiceFrame(TQPainter *p, int col, int num,
|
|
int xo, int yo, bool big, double sf) const
|
|
{
|
|
p->setBrush(board->getCheckerColor(col));
|
|
p->setPen(board->getCheckerColor(col));
|
|
TQRect r = diceRect(num, big, sf);
|
|
r.moveTopLeft(TQPoint(xo+r.left(), yo+r.top()));
|
|
p->drawRoundRect(r, 20, 20);
|
|
}
|
|
|
|
/*
|
|
* If the event is left button we just store that. If the event is right
|
|
* button we ask the board to possibly display the popup menu.
|
|
*/
|
|
void KBgBoardCell::mousePressEvent(TQMouseEvent *e)
|
|
{
|
|
if (e->button() == Qt::RightButton)
|
|
board->showContextMenu();
|
|
else
|
|
mouseButton = e->button();
|
|
}
|
|
|
|
/*
|
|
* This function sets the short move mode of the board.
|
|
*/
|
|
void KBgBoard::setShortMoveMode(int m)
|
|
{
|
|
switch (m) {
|
|
case SHORT_MOVE_NONE:
|
|
case SHORT_MOVE_SINGLE:
|
|
shortMoveMode = m;
|
|
break;
|
|
case SHORT_MOVE_DOUBLE:
|
|
default:
|
|
shortMoveMode = SHORT_MOVE_DOUBLE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns the currently selected short move mode.
|
|
*/
|
|
int KBgBoard::getShortMoveMode()
|
|
{
|
|
return shortMoveMode;
|
|
}
|
|
|
|
/*
|
|
* This function checks if (a) the mouse event was a left button,
|
|
* (b) the parameter m equals the currently selected short move
|
|
* mode and (c) t a short move from this field is possible. If all
|
|
* tests are ok, the shortest possible move away from here is
|
|
* made.
|
|
*/
|
|
void KBgBoardCell::checkAndMakeShortMove(TQMouseEvent *e, int m)
|
|
{
|
|
if ((e->button() == Qt::LeftButton) &&
|
|
(board->getShortMoveMode() == m) &&
|
|
(dragPossible()) &&
|
|
(!board->getEditMode()))
|
|
makeShortMove();
|
|
}
|
|
|
|
/*
|
|
* This functions reacts on a double click.
|
|
*/
|
|
void KBgBoardCell::mouseDoubleClickEvent(TQMouseEvent *e)
|
|
{
|
|
checkAndMakeShortMove(e, SHORT_MOVE_DOUBLE);
|
|
}
|
|
|
|
/*
|
|
* This function reacts on a double click. Note that the bar knows
|
|
* about two different double clicks: double the cube and make a
|
|
* short move.
|
|
*/
|
|
void KBgBoardBar::mouseDoubleClickEvent(TQMouseEvent *e)
|
|
{
|
|
TQRect r = cubeRect(cellID == BAR_THEM ? CUBE_UPPER : CUBE_LOWER, true);
|
|
if (board->canDouble(US) &&
|
|
board->canDouble(THEM) && r.contains(e->pos())) {
|
|
if (board->getEditMode())
|
|
board->queryCube();
|
|
else
|
|
board->getDoubleCube(US);
|
|
return;
|
|
}
|
|
checkAndMakeShortMove(e, SHORT_MOVE_DOUBLE);
|
|
}
|
|
|
|
/*
|
|
* This is the destructor of the backgammon board. It frees
|
|
* all resources previously allocated.
|
|
*/
|
|
KBgBoard::~KBgBoard()
|
|
{
|
|
restoreCursor();
|
|
}
|
|
|
|
/*
|
|
* This function draws dice and cube on the painter for a home cell.
|
|
* who may be either US or THEM.
|
|
*/
|
|
void KBgBoardHome::drawDiceAndCube(TQPainter *p, int who, int xo, int yo,
|
|
double sf) const
|
|
{
|
|
int col = ((who == THEM) ? -color : color);
|
|
|
|
/*
|
|
* draw the empty squares and then put the face value in there
|
|
*/
|
|
for (int i = 0; i < 2; i++) {
|
|
drawDiceFrame(p,-col, i, xo, yo, true, sf);
|
|
drawDiceFrame(p, col, i, xo, yo, false, sf);
|
|
drawDiceFace(p,-col, i, who, xo, yo, sf);
|
|
}
|
|
/*
|
|
* if necessary draw the cube
|
|
*/
|
|
if (board->canDouble(who) &&
|
|
!(board->canDouble(US) && board->canDouble(THEM)))
|
|
drawCube(p, who, xo, yo, sf);
|
|
}
|
|
|
|
/*
|
|
* This function determines whether a drag off this home is possible.
|
|
* This is only possible if there are checkers and edit mode is on.
|
|
*/
|
|
bool KBgBoardHome::dragPossible() const
|
|
{
|
|
if (board->getEditMode())
|
|
return (pcs != 0);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This function determines whether a drag off this bar is possible.
|
|
* It checks in the follwoing order: (1) owner of this bar is the one
|
|
* whose turn it is now, (2) does the board allow moving right now is
|
|
* it in read-only mode?
|
|
*/
|
|
bool KBgBoardBar::dragPossible() const
|
|
{
|
|
if (board->getEditMode())
|
|
return (pcs != 0);
|
|
|
|
switch(board->getTurn()) {
|
|
case US:
|
|
if (pcs*color <= 0) return false;
|
|
break;
|
|
case THEM:
|
|
if (pcs*color >= 0) return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return board->movingAllowed();
|
|
}
|
|
|
|
/*
|
|
* This function checks whether a checker can be moved away from
|
|
* this field. It first checks whether the owner of this field is
|
|
* the one whose turn to move it it, then it is checked whether
|
|
* the players bar is empty and finally it is checked if the board
|
|
* is in read-only mode.
|
|
*/
|
|
bool KBgBoardField::dragPossible() const
|
|
{
|
|
if (board->getEditMode())
|
|
return (pcs != 0);
|
|
|
|
switch(board->getTurn()) {
|
|
case US:
|
|
if (pcs*color <= 0) return false;
|
|
break;
|
|
case THEM:
|
|
if (pcs*color >= 0) return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (board->getOnBar(board->getTurn()))
|
|
return false;
|
|
return board->movingAllowed();
|
|
}
|
|
|
|
/*
|
|
* This function returns the current read-write flag of the board.
|
|
* If this returns true the board doesn't accept user input. If
|
|
* allowmoving is true we will accept user events.
|
|
*/
|
|
bool KBgBoard::movingAllowed() const
|
|
{
|
|
return allowmoving;
|
|
}
|
|
|
|
/*
|
|
* This function sets the read-write or read-only flag of the
|
|
* board. See also movingAllowed().
|
|
*/
|
|
void KBgBoard::allowMoving(const bool fl)
|
|
{
|
|
allowmoving = fl;
|
|
}
|
|
|
|
/*
|
|
* This function returns the current pip count of the player w.
|
|
*/
|
|
int KBgBoard::getPipCount(const int& w) const
|
|
{
|
|
if (!computePipCount || (w != US && w != THEM))
|
|
return -1;
|
|
int pip = 25*abs(onbar[w]);
|
|
int d = ((w == US) ? 1 : -1);
|
|
for (int i = 1; i < 25; i++) {
|
|
if (d*board[i]*color > 0)
|
|
pip += ((d*direction < 0) ?
|
|
i*abs(board[i]) :
|
|
(25 - i)*abs(board[i]));
|
|
}
|
|
return pip;
|
|
}
|
|
|
|
/*
|
|
* This function handles double clicks on homes. It will ignore
|
|
* double clicks on the real home and only handle the ones on the
|
|
* "other" home - the one with the dice. It will propagate the event
|
|
* only if the the click happened within the boundaries of a
|
|
* dice or the cube.
|
|
*/
|
|
void KBgBoardHome::mouseDoubleClickEvent(TQMouseEvent * e)
|
|
{
|
|
if (e->button() != Qt::LeftButton)
|
|
return;
|
|
/*
|
|
* Check whether this is the bookkeeping home...
|
|
*/
|
|
if ((cellID == HOME_US_LEFT && direction < 0) ||
|
|
(cellID == HOME_US_RIGHT && direction > 0) ||
|
|
(cellID == HOME_THEM_LEFT && direction < 0) ||
|
|
(cellID == HOME_THEM_RIGHT && direction > 0)) {
|
|
|
|
int w = ((cellID == HOME_US_LEFT || cellID == HOME_US_RIGHT) ?
|
|
US : THEM);
|
|
for (int i = 0; i < 2; ++i) {
|
|
TQRect r = diceRect(i, true);
|
|
if (r.contains(e->pos())) {
|
|
if (board->getEditMode()) {
|
|
|
|
KBgBoardQDice *dlg = new KBgBoardQDice();
|
|
if (dlg->exec()) {
|
|
KBgStatus *st = new KBgStatus();
|
|
board->getState(st);
|
|
st->setDice(w, 0, dlg->getDice(0));
|
|
st->setDice(w, 1, dlg->getDice(1));
|
|
st->setDice(((w == US) ? THEM : US), 0, 0);
|
|
st->setDice(((w == US) ? THEM : US), 1, 0);
|
|
board->setState(*st); // JENS
|
|
delete st;
|
|
}
|
|
delete dlg;
|
|
|
|
} else
|
|
board->getRollDice(w);
|
|
return;
|
|
}
|
|
}
|
|
if (board->canDouble(w) &&
|
|
!(board->canDouble(US) && board->canDouble(THEM))) {
|
|
TQRect r = cubeRect(w, true);
|
|
if (r.contains(e->pos()))
|
|
if (board->getEditMode())
|
|
board->queryCube();
|
|
else
|
|
board->getDoubleCube(w);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function determines if a checker can be dropped on this field.
|
|
* It checks whether the field is already owned, empty or contains
|
|
* only one opponents piece. Then the dice are checked.
|
|
*/
|
|
bool KBgBoardField::dropPossible(int fromCellID, int newColor)
|
|
{
|
|
if ((newColor*pcs > 0) || (pcs == 0) || (abs(pcs) == 1))
|
|
// editMode is checked in diceAllowMove(...)
|
|
return board->diceAllowMove(fromCellID, cellID);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This function determines if a checker can be dropped on this field.
|
|
* Drops on the bar are never possible.
|
|
*/
|
|
bool KBgBoardBar::dropPossible(int fromCellID, int newColor)
|
|
{
|
|
if (!board->getEditMode())
|
|
return false;
|
|
|
|
if (newColor*pcs > 0)
|
|
return true;
|
|
if ((cellID == BAR_US) && (board->getTurn() == US))
|
|
return true;
|
|
if ((cellID == BAR_THEM) && (board->getTurn() == THEM))
|
|
return true;
|
|
|
|
return (fromCellID == -12345); // always false
|
|
}
|
|
|
|
/*
|
|
* This function checks if the current player can move a checker off.
|
|
* Check if we can move a piece off. This obviously only works if there
|
|
* are no pieces on the bar and all remaining pieces are in the home
|
|
* board. This does not check the dice and it doesn't work for multiple
|
|
* moves that start outside the home.
|
|
*/
|
|
bool KBgBoard::moveOffPossible() const
|
|
{
|
|
if (getEditMode())
|
|
return true;
|
|
|
|
int w = getTurn();
|
|
int d = ((w == THEM) ? -1 : 1);
|
|
if (onbar[w] == 0 && d*direction > 0) {
|
|
for (int i = 1; i < 19; ++i) {
|
|
if (d*color*board[i] > 0) return false;
|
|
}
|
|
return true;
|
|
|
|
} else if (onbar[w] == 0 && d*direction < 0) {
|
|
for (int i = 24; i > 6; --i) {
|
|
if (d*color*board[i] > 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This function tries to determine the field cell under the point p.
|
|
* The point needs to be in board coordinates and the function returns
|
|
* a pointer to the cell or NULL if there is no cell under the point.
|
|
*/
|
|
|
|
KBgBoardCell* KBgBoard::getCellByPos(const TQPoint& p) const
|
|
{
|
|
for (int i = 0; i < 30; ++i) {
|
|
if (TQT_TQRECT_OBJECT(cells[i]->rect()).contains(cells[i]->mapFromParent(p)))
|
|
return cells[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* This function takes a board number (1 to 24 and 0 or 25 depending on the
|
|
* direction) or a cell ID and returns a pointer to the corresponding cell.
|
|
* If the cell cannot be found it returns NULL.
|
|
*/
|
|
KBgBoardCell* KBgBoard::getCell(int num)
|
|
{
|
|
switch (num) {
|
|
case BAR_US:
|
|
return (KBgBoardCell *)cells[22];
|
|
case BAR_THEM:
|
|
return (KBgBoardCell *)cells[ 7];
|
|
case HOME_THEM_LEFT:
|
|
return (KBgBoardCell *)cells[ 0];
|
|
case HOME_THEM_RIGHT:
|
|
return (KBgBoardCell *)cells[14];
|
|
case HOME_US_LEFT:
|
|
return (KBgBoardCell *)cells[15];
|
|
case HOME_US_RIGHT:
|
|
return (KBgBoardCell *)cells[29];
|
|
default:
|
|
int cell;
|
|
if (num < 0 || num > 25)
|
|
return NULL;
|
|
else if (num < 7)
|
|
cell = ((direction > 0) ? num : 29 - num);
|
|
else if (num < 13)
|
|
cell = ((direction > 0) ? num + 1 : 28 - num);
|
|
else if (num < 19)
|
|
cell = ((direction > 0) ? 41 - num : num - 12);
|
|
else
|
|
cell = ((direction > 0) ? 40 - num : num - 11);
|
|
return (KBgBoardCell *)cells[cell];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function translates a field ID to the field number or just
|
|
* returns the ID for bars and homes.
|
|
*/
|
|
int KBgBoard::IDtoNum(const int ID) const
|
|
{
|
|
if (ID > 0 && ID < 25) {
|
|
if (ID < 13)
|
|
return ((direction > 0) ? ID : 12 + ID);
|
|
else
|
|
return ((direction > 0) ? 37 - ID : 25 - ID);
|
|
}
|
|
return ID;
|
|
}
|
|
|
|
/*
|
|
* This function takes a checker from the cell if possible. It also
|
|
* updates the bookkeeping of the board and redraws itself.
|
|
*/
|
|
bool KBgBoardCell::getPiece()
|
|
{
|
|
if (pcs != 0) {
|
|
((pcs > 0) ? --pcs : ++pcs);
|
|
stateChanged = true;
|
|
refresh();
|
|
board->updateField(getNumber(), pcs);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This function stores the current cursor and replaces it with the
|
|
* supplied one c.
|
|
*/
|
|
void KBgBoard::replaceCursor(const TQCursor& c)
|
|
{
|
|
if (savedCursor)
|
|
delete savedCursor;
|
|
savedCursor = new TQCursor(cursor());
|
|
setCursor(c);
|
|
}
|
|
|
|
/*
|
|
* This function restores the previously set cursor to the stored one.
|
|
*/
|
|
void KBgBoard::restoreCursor()
|
|
{
|
|
if (savedCursor) {
|
|
setCursor(*savedCursor);
|
|
delete savedCursor;
|
|
savedCursor = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function puts a checker of color newColor on the cell. It handles
|
|
* all necessary updates including the kicking. It will however not properly
|
|
* handle illegal moves!
|
|
*/
|
|
void KBgBoardCell::putPiece(int newColor)
|
|
{
|
|
if (newColor*pcs > 0) {
|
|
pcs > 0 ? ++pcs : --pcs;
|
|
} else if (pcs == 0) {
|
|
newColor > 0 ? pcs = 1 : pcs = -1;
|
|
} else if (newColor*pcs < 0) {
|
|
board->kickedPiece();
|
|
newColor > 0 ? pcs = 1 : pcs = -1;
|
|
}
|
|
stateChanged = true;
|
|
refresh();
|
|
board->updateField(getNumber(), pcs);
|
|
board->sendMove();
|
|
}
|
|
|
|
/*
|
|
* This function handles mouse release events. It is important to know that
|
|
* the cell where the first mousePressEvent occurred receives the release event.
|
|
* The release event marks the end of a drag or a single click short move.
|
|
*/
|
|
void KBgBoardCell::mouseReleaseEvent(TQMouseEvent *e)
|
|
{
|
|
if (dragInProgress) {
|
|
|
|
KBgBoardCell *dest = board->getCellByPos
|
|
(mapToParent(e->pos()));
|
|
board->restoreCursor();
|
|
if ((dest != NULL) && (dest->dropPossible(cellID, ((board->getTurn() == US) ?
|
|
color : -color)))) {
|
|
if (!board->getEditMode())
|
|
board->makeMove(getNumber(), dest->getNumber());
|
|
dest->putPiece(((board->getTurn() == US) ? color : -color));
|
|
} else {
|
|
putPiece(((board->getTurn() == US) ? color : -color));
|
|
}
|
|
dragInProgress = false;
|
|
|
|
} else {
|
|
|
|
checkAndMakeShortMove(e, SHORT_MOVE_SINGLE);
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is the destructor of the home cells. It doesn't do anything.
|
|
*/
|
|
KBgBoardHome::~KBgBoardHome()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* This is the destructor of the bar cells. It doesn't do anything.
|
|
*/
|
|
KBgBoardBar::~KBgBoardBar()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* This is the destructor of regular fields. It doesn't do anything.
|
|
*/
|
|
KBgBoardField::~KBgBoardField()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* This is the constructor of the bars. It calls the base class' constructor
|
|
* and defines the TQWhatsThis string.
|
|
*/
|
|
KBgBoardBar::KBgBoardBar(TQWidget * parent, int numID)
|
|
: KBgBoardCell(parent, numID)
|
|
{
|
|
TQWhatsThis::add(this, i18n("This is the bar of the backgammon board.\n\n"
|
|
"Checkers that have been kicked from the board are put "
|
|
"on the bar and remain there until they can be put back "
|
|
"on the board. Checkers can be moved by dragging them to "
|
|
"their destination or by using the 'short move' feature.\n\n"
|
|
"If the cube hasn't been doubled yet and if it can be used, "
|
|
"its face shows 64 and if the cube can be doubled, double "
|
|
"clicking it will do so."));
|
|
}
|
|
|
|
/*
|
|
* This is the constructor of regular fields. It calls the base class' constructor
|
|
* and defines the TQWhatsThis string.
|
|
*/
|
|
KBgBoardField::KBgBoardField(TQWidget * parent, int numID)
|
|
: KBgBoardCell(parent, numID)
|
|
{
|
|
TQWhatsThis::add(this, i18n("This is a regular field of the backgammon board.\n\n"
|
|
"Checkers can be placed on this field and if the current state "
|
|
"of the game and the dice permit this, they can be moved by "
|
|
"dragging them to their destination or by using the 'short "
|
|
"move' feature."));
|
|
}
|
|
|
|
/*
|
|
* This is the constructor of the homes. It calls the base class' constructor
|
|
* and defines the TQWhatsThis string.
|
|
*/
|
|
KBgBoardHome::KBgBoardHome(TQWidget * parent, int numID)
|
|
: KBgBoardCell(parent, numID)
|
|
{
|
|
TQWhatsThis::add(this, i18n("This part of the backgammon board is the home.\n\n"
|
|
"Depending on the direction of the game, one of the homes "
|
|
"contains the dice and the other one contains checkers that "
|
|
"have been moved off the board. Checkers can never be moved "
|
|
"away from the home. If this home contains the dice and the "
|
|
"current state of the game permits this, double clicking on "
|
|
"the dice will roll them. Moreover, the cube might be placed "
|
|
"on the home bar and if it can be doubled, double clicking it "
|
|
"will do so."));
|
|
savedDice[0] = -1;
|
|
savedDice[1] = -1;
|
|
}
|
|
|
|
/*
|
|
* This function updates the number of checkers on the bar and also updates
|
|
* the cell if the cube has changed (this is more often than necessary...)
|
|
*/
|
|
void KBgBoardBar::cellUpdate(const int p, const bool cubechanged)
|
|
{
|
|
stateChanged = (cubechanged || colorChanged);
|
|
if (pcs != p) {
|
|
stateChanged = true;
|
|
pcs = p;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function updates the number of checkers on the field.
|
|
*/
|
|
void KBgBoardField::cellUpdate(const int p, const bool cubechanged)
|
|
{
|
|
if (p != pcs) {
|
|
pcs = p;
|
|
stateChanged = true;
|
|
}
|
|
bool f = stateChanged; // useless, avoids compiler warning
|
|
stateChanged = cubechanged;
|
|
stateChanged = (f || colorChanged);
|
|
}
|
|
|
|
/*
|
|
* This function updates the number of checkers on the home if it
|
|
* actually contains checkers. It will also redraw if the cube or dice
|
|
* have changed.
|
|
*/
|
|
void KBgBoardHome::cellUpdate(const int p, const bool cubechanged)
|
|
{
|
|
if ((cellID == HOME_THEM_LEFT && direction > 0) ||
|
|
(cellID == HOME_THEM_RIGHT && direction < 0) ||
|
|
(cellID == HOME_US_LEFT && direction > 0) ||
|
|
(cellID == HOME_US_RIGHT && direction < 0)) {
|
|
|
|
if (pcs != p) {
|
|
pcs = p;
|
|
stateChanged = true;
|
|
}
|
|
|
|
} else {
|
|
|
|
int who = ((cellID == HOME_THEM_LEFT || cellID == HOME_THEM_RIGHT) ? THEM : US);
|
|
|
|
stateChanged = ((savedDice[0] != board->getDice(who, 0)) ||
|
|
(savedDice[1] != board->getDice(who, 1)));
|
|
|
|
savedDice[0] = board->getDice(who, 0);
|
|
savedDice[1] = board->getDice(who, 1);
|
|
|
|
stateChanged = (stateChanged || cubechanged || colorChanged || directionChanged);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns whose players turn it is.
|
|
*/
|
|
int KBgBoard::getTurn() const
|
|
{
|
|
if (getEditMode())
|
|
return ((storedTurn*color > 0) ? US : THEM);
|
|
|
|
if (getDice(US , 0) != 0 && getDice(US , 1) != 0)
|
|
return US;
|
|
if (getDice(THEM, 0) != 0 && getDice(THEM, 1) != 0)
|
|
return THEM;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* This is the constructor of the basic cells. It initializes the cell
|
|
* to a sane state and connects a signal to the board.
|
|
*/
|
|
KBgBoardCell::KBgBoardCell(TQWidget * parent, int numID)
|
|
: TQLabel(parent)
|
|
{
|
|
board = (KBgBoard *)parent;
|
|
|
|
direction = +1;
|
|
color = -1;
|
|
pcs = 0;
|
|
cellID = numID;
|
|
stateChanged = false;
|
|
colorChanged = false;
|
|
directionChanged = false;
|
|
mouseButton = Qt::NoButton;
|
|
dragInProgress = false;
|
|
|
|
connect(parent, TQT_SIGNAL(finishedUpdate()), this, TQT_SLOT(refresh()));
|
|
}
|
|
|
|
/*
|
|
* This is the destructor of the cells. It doesn't do anything.
|
|
*/
|
|
KBgBoardCell::~KBgBoardCell()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
/*
|
|
* This function returns the color of the checkers on this cell.
|
|
*/
|
|
int KBgBoardCell::getCellColor()
|
|
{
|
|
return ((pcs < 0) ? -1 : +1);
|
|
}
|
|
|
|
/*
|
|
* This function updates the basic board settings color and direction
|
|
* and signals a redraw if necessary.
|
|
*/
|
|
void KBgBoardCell::statusUpdate(int dir, int col)
|
|
{
|
|
if (direction != dir || color != col) {
|
|
colorChanged = (color != col);
|
|
directionChanged = (direction != dir);
|
|
color = col;
|
|
direction = dir;
|
|
stateChanged = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function refreshes the content of the cell if necessary.
|
|
*/
|
|
void KBgBoardCell::refresh()
|
|
{
|
|
if (stateChanged) {
|
|
update();
|
|
stateChanged = false;
|
|
colorChanged = false;
|
|
directionChanged = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns the board number of this cell as given by the board.
|
|
*/
|
|
int KBgBoardCell::getNumber() const
|
|
{
|
|
return board->IDtoNum(cellID);
|
|
}
|
|
|
|
/*
|
|
* This function returns the number of checkers of player who on the bar.
|
|
*/
|
|
int KBgBoard::getOnBar(int who) const
|
|
{
|
|
return ((who == US || who == THEM) ? onbar[who] : 0);
|
|
}
|
|
|
|
/*
|
|
* This function returns the face value of the n-th dice of player w
|
|
*/
|
|
int KBgBoard::getDice( int w, int n ) const
|
|
{
|
|
return (((w == US || w == THEM) && (n == 0 || n == 1)) ? dice[w][n] : 0);
|
|
}
|
|
|
|
/*
|
|
* This function returns the current cube value.
|
|
*/
|
|
int KBgBoard::getCube() const
|
|
{
|
|
return cube;
|
|
}
|
|
|
|
/*
|
|
* This function updates the stored number of pieces on field f to v.
|
|
*/
|
|
void KBgBoard::updateField(int f, int v)
|
|
{
|
|
switch (f) {
|
|
case BAR_US:
|
|
case BAR_THEM:
|
|
onbar[((f == BAR_US) ? US : THEM)] = v;
|
|
break;
|
|
case HOME_US_RIGHT:
|
|
case HOME_US_LEFT:
|
|
onhome[US] = v;
|
|
break;
|
|
case HOME_THEM_RIGHT:
|
|
case HOME_THEM_LEFT:
|
|
onhome[THEM] = v;
|
|
break;
|
|
default:
|
|
if (0 < f && f < 25)
|
|
board[f] = v;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function displays the context menu our parent may have given us
|
|
*/
|
|
void KBgBoard::showContextMenu()
|
|
{
|
|
if (contextMenu) contextMenu->popup(TQCursor::pos());
|
|
}
|
|
|
|
/*
|
|
* This function determines if the player who can double.
|
|
*/
|
|
bool KBgBoard::canDouble(int who) const
|
|
{
|
|
return ((who == US || who == THEM) ? maydouble[who] : false);
|
|
}
|
|
|
|
/*
|
|
* This function is a simple utility for makeMove. It takes care
|
|
* of all the bookeeeping needed for a move.
|
|
*/
|
|
int KBgBoard::makeMoveHelper(int si, int sf, int delta)
|
|
{
|
|
moveHistory.append(new KBgBoardMove(si, sf, abs(delta)));
|
|
--possMoves[abs(delta)];
|
|
return delta;
|
|
}
|
|
|
|
/*
|
|
* This function makes a move from src to dest for the current player.
|
|
* It can handle illegal moves but the move should have been checked.
|
|
*/
|
|
void KBgBoard::makeMove(int src, int dest)
|
|
{
|
|
int m[4];
|
|
int l;
|
|
|
|
int d = direction*((getTurn() == US) ? +1 : -1);
|
|
|
|
if (src == BAR_US || src == BAR_THEM ) {
|
|
|
|
int start = ((d > 0) ? 0 : 25);
|
|
l = checkMultiMove(start, dest, m);
|
|
moveHistory.append(new KBgBoardMove(src, start+d*m[0], m[0]));
|
|
src = start+d*m[0];
|
|
--possMoves[m[0]];
|
|
for (int i = 1; i < l; i++)
|
|
src += makeMoveHelper(src, src+d*m[i], d*m[i]);
|
|
|
|
} else if (0 < src && src < 25 && 0 < dest && dest < 25) {
|
|
|
|
l = checkMultiMove(src, dest, m);
|
|
for (int i = 0; i < l; i++)
|
|
src += makeMoveHelper(src, src+d*m[i], d*m[i]);
|
|
|
|
} else {
|
|
|
|
int s = src;
|
|
int final = ((d > 0) ? 25 : 0);
|
|
while (((l = checkMultiMove(s, final, m)) == 0) && (0 < s && s < 25))
|
|
s -= d;
|
|
|
|
for (int i = 0; i < l-1; i++)
|
|
src += makeMoveHelper(src, src+d*m[i], d*m[i]);
|
|
|
|
moveHistory.append(new KBgBoardMove(src, dest, ((d > 0) ? 25 - src : src)));
|
|
--possMoves[m[l-1]];
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function checks if there is any possibility (based on the dice)
|
|
* to move from src to dest. It takes the ownership of the intermediate
|
|
* fields into account. The function returns the number of steps necessary
|
|
* to perform the move (or 0 if the move is not possible) and the actual
|
|
* dice values used for the steps are returned in the array m.
|
|
*
|
|
* The values src and dest are expected to be in board coordinates and the
|
|
* homes and/or bars should already be mapped to the corresponding values
|
|
* 0 and 25 (based onb direction and whose turn it is).
|
|
*/
|
|
int KBgBoard::checkMultiMove(int src, int dest, int m[4])
|
|
{
|
|
m[0] = 0; m[1] = 0; m[2] = 0; m[3] = 0;
|
|
|
|
int mcolor = ((getTurn() == US) ? color : -color);
|
|
int d = ((src > dest) ? -1 : 1);
|
|
|
|
/*
|
|
* These are very easy special cases: move length is 0 or
|
|
* player cannot move to the destination field.
|
|
*/
|
|
if ((src == dest) || (mcolor*board[dest] < -1)) return 0;
|
|
|
|
int diceToUse[4];
|
|
int dice = 0;
|
|
/*
|
|
* Get the available step sizes for this move
|
|
*/
|
|
for (int i = 1; i < 7; i++) {
|
|
for (int j = 0; j < possMoves[i]; j++) {
|
|
diceToUse[dice++] = i;
|
|
/*
|
|
* If this happens there is something wrong
|
|
*/
|
|
if (dice > 4) return 0;
|
|
}
|
|
}
|
|
/*
|
|
* And start all possible combination of dices.
|
|
*/
|
|
switch (dice) {
|
|
case 4: if (src+4*d*diceToUse[0] == dest) {
|
|
if ((mcolor*board[src+1*d*diceToUse[0]] >= 0) &&
|
|
(mcolor*board[src+2*d*diceToUse[0]] >= 0) &&
|
|
(mcolor*board[src+3*d*diceToUse[0]] >= 0)) {
|
|
m[0] = m[1] = m[2] = m[3] = diceToUse[0];
|
|
return 4;
|
|
}
|
|
}
|
|
case 3: if (src+3*d*diceToUse[0] == dest) {
|
|
if ((mcolor*board[src+1*d*diceToUse[0]] >= 0) &&
|
|
(mcolor*board[src+2*d*diceToUse[0]] >= 0)) {
|
|
m[0] = m[1] = m[2] = diceToUse[0];
|
|
return 3;
|
|
}
|
|
}
|
|
case 2: if ((src+d*(diceToUse[0]+diceToUse[1])) == dest) {
|
|
if (mcolor*board[src+d*diceToUse[0]] >= 0) {
|
|
m[0] = diceToUse[0];
|
|
m[1] = diceToUse[1];
|
|
return 2;
|
|
}
|
|
if (mcolor*board[src+d*diceToUse[1]] >= 0) {
|
|
m[0] = diceToUse[1];
|
|
m[1] = diceToUse[0];
|
|
return 2;
|
|
}
|
|
}
|
|
case 1: if (abs(src-dest) < 7 && possMoves[abs(src-dest)] > 0) {
|
|
m[0] = abs(src-dest);
|
|
return 1;
|
|
}
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function determines if a checker can be dropped on this home field.
|
|
* It first checks whether this is the proper of the four home fields (belongs
|
|
* to the player and not the one with dice and cube). Then we check if the move
|
|
* itself is possible.
|
|
*/
|
|
bool KBgBoardHome::dropPossible(int fromCellID, int newColor)
|
|
{
|
|
if ((cellID==HOME_US_LEFT && board->getTurn() == US && direction > 0) ||
|
|
(cellID==HOME_THEM_LEFT && board->getTurn() == THEM && direction > 0) ||
|
|
(cellID==HOME_US_RIGHT && board->getTurn() == US && direction < 0) ||
|
|
(cellID==HOME_THEM_RIGHT && board->getTurn() == THEM && direction < 0))
|
|
return (board->moveOffPossible() &&
|
|
board->diceAllowMove(fromCellID, cellID));
|
|
return (newColor == -12345); // always false
|
|
}
|
|
|
|
/*
|
|
* This function is a simple boolean interface to checkMultiMove.
|
|
* It takes car of directions and bar/home mappings. If necessary
|
|
* it also handles the case of bearing off.
|
|
*/
|
|
bool KBgBoard::diceAllowMove(int src, int dest)
|
|
{
|
|
int m[4];
|
|
int w = getTurn();
|
|
int k = ((w == US) ? +1 : -1);
|
|
int t = ((k*direction > 0) ? 25 : 0);
|
|
int d = ((k*direction > 0) ? +1 : -1);
|
|
|
|
if (getEditMode())
|
|
return true;
|
|
|
|
if ((w == US && src == BAR_US) || (w == THEM && src == BAR_THEM)) {
|
|
/*
|
|
* Move comes from a bar. Hence it has to end on a field
|
|
* and not on bars or homes. If there are checkers left
|
|
* on the bar we don't accept multi moves.
|
|
*/
|
|
if (0 < dest && dest < 25) {
|
|
int r = checkMultiMove((k*direction > 0) ? 0 : 25,
|
|
IDtoNum(dest), m);
|
|
return((abs(onbar[w]) == 0) ? (r != 0) : (r == 1));
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (0 < dest && dest < 25 && 0 < src && src < 25) {
|
|
/*
|
|
* Move from a field to a field
|
|
*/
|
|
if (direction*k*(IDtoNum(dest)-IDtoNum(src)) > 0) {
|
|
return(checkMultiMove(IDtoNum(src), IDtoNum(dest), m));
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
/*
|
|
* Move from a field on the home. First we try exact dice.
|
|
*/
|
|
if (checkMultiMove(IDtoNum(src), t, m) > 0) return true;
|
|
|
|
/*
|
|
* Then maybe we could bear the checker off ?
|
|
*/
|
|
int i = IDtoNum(src);
|
|
while (0 < i && i < 25) {
|
|
i -= d;
|
|
if (k*color*board[i] > 0) return false;
|
|
}
|
|
|
|
/*
|
|
* Indeed we are bearing off. So find the highest dice and use it.
|
|
* Start from all the way back to catch double 6 from the start.
|
|
*/
|
|
int j = 24;
|
|
while (checkMultiMove(t-d*j, t, m) == 0 && j > 0) {--j;}
|
|
return (j >= t-d*IDtoNum(src));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This is the most important of all members of the board class. It takes
|
|
* a single board status object and initializes the internal status.
|
|
*/
|
|
void KBgBoard::setState(const KBgStatus &st)
|
|
{
|
|
color = st.color();
|
|
direction = st.direction();
|
|
|
|
cubechanged = (cube != abs(st.cube()));
|
|
cube = abs(st.cube());
|
|
maydouble[US ] = (st.cube(US ) > 0);
|
|
maydouble[THEM] = (st.cube(THEM) > 0);
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
cells[i]->statusUpdate(direction, color);
|
|
|
|
for (int i = 1; i < 25; ++i)
|
|
board[i] = st.board(i);
|
|
|
|
onbar[US ] = st.bar(US );
|
|
onbar[THEM] = st.bar(THEM);
|
|
|
|
onhome[US] = st.home(US );
|
|
onhome[THEM] = st.home(THEM);
|
|
|
|
dice[US ][0] = st.dice(US , 0);
|
|
dice[US ][1] = st.dice(US , 1);
|
|
dice[THEM][0] = st.dice(THEM, 0);
|
|
dice[THEM][1] = st.dice(THEM, 1);
|
|
|
|
for (int i = 0; i < 7; ++i)
|
|
possMoves[i] = 0;
|
|
|
|
int w = getTurn();
|
|
if (getEditMode())
|
|
w = ((dice[US][0] && dice[US][1]) ? US : THEM);
|
|
|
|
if (w == US || w == THEM) {
|
|
++possMoves[dice[w][0]];
|
|
++possMoves[dice[w][1]];
|
|
if (dice[w][0] == dice[w][1])
|
|
possMoves[dice[w][0]] *= 2;
|
|
}
|
|
|
|
board[ 0] = 0;
|
|
board[25] = 0;
|
|
for (int i=1; i<25; ++i)
|
|
(getCell(i))->cellUpdate(board[i]);
|
|
|
|
(getCell(BAR_US ))->cellUpdate(st.bar(US ), cubechanged);
|
|
(getCell(BAR_THEM))->cellUpdate(st.bar(THEM), cubechanged);
|
|
|
|
(getCell(HOME_US_LEFT ))->cellUpdate(st.home(US ), cubechanged);
|
|
(getCell(HOME_US_RIGHT ))->cellUpdate(st.home(US ), cubechanged);
|
|
(getCell(HOME_THEM_LEFT ))->cellUpdate(st.home(THEM), cubechanged);
|
|
(getCell(HOME_THEM_RIGHT))->cellUpdate(st.home(THEM), cubechanged);
|
|
|
|
moveHistory.clear();
|
|
redoHistory.clear();
|
|
|
|
emit finishedUpdate();
|
|
}
|
|
|
|
/*
|
|
* This function starts a drag from this cell if possible. It asks the board to
|
|
* change the mouse pointer and takes a checker away from this cell.
|
|
*/
|
|
void KBgBoardCell::mouseMoveEvent(TQMouseEvent *)
|
|
{
|
|
if ((mouseButton == Qt::LeftButton) && dragPossible()) {
|
|
dragInProgress = true;
|
|
TQRect cr(0, 0, 1+getCheckerDiameter(), 1+getCheckerDiameter());
|
|
cr.moveBottomLeft(rect().bottomLeft());
|
|
TQPixmap pix(cr.size());
|
|
TQPainter tmp;
|
|
pix.fill(this, cr.topLeft());
|
|
tmp.begin(&pix);
|
|
board->drawSimpleChecker(&tmp, 0, 0, pcs, getCheckerDiameter());
|
|
tmp.end();
|
|
pix.setMask(pix.createHeuristicMask());
|
|
TQBitmap mask = *(pix.mask());
|
|
TQBitmap newCursor;
|
|
newCursor = pix;
|
|
board->replaceCursor(TQCursor(newCursor, mask));
|
|
if (board->getEditMode())
|
|
board->storeTurn(pcs);
|
|
getPiece();
|
|
}
|
|
mouseButton = Qt::NoButton;
|
|
}
|
|
|
|
/*
|
|
* This function draws a checker on the painter p. It is painted
|
|
* in the ractangle with the upper left corner (x,y) and has a
|
|
* maximum diameter of diam. This checker has only two colors and
|
|
* as such it is suited for the mouse cursor and printing.
|
|
*/
|
|
void KBgBoard::drawSimpleChecker(TQPainter *p, int x, int y, int pcs,
|
|
int diam) const
|
|
{
|
|
p->setBrush(getCheckerColor(pcs));
|
|
p->setPen(getCheckerColor(pcs));
|
|
p->drawEllipse(x+1, y+0, diam-0, diam-0);
|
|
p->setBrush(getCheckerColor(-pcs));
|
|
p->setPen(getCheckerColor(-pcs));
|
|
p->drawEllipse(x+2, y+1, diam-2, diam-2);
|
|
p->setBrush(getCheckerColor(pcs));
|
|
p->setPen(getCheckerColor(pcs));
|
|
p->drawEllipse(x+3, y+2, diam-4, diam-4);
|
|
}
|
|
|
|
/*
|
|
* This function draws an anti-aliased checker on the painter p. It
|
|
* is painted in the ractangle with the upper left corner (x,y) and
|
|
* has a diameter of diam. col indicates the color of the cell this
|
|
* checker is painted on. Special values for col are 0 and 100 that
|
|
* indicate that the checker is stacked (bars and homes) or stacked
|
|
* on a field respectively. upper indicates whether the checker is
|
|
* in the upper half of the board or not.
|
|
*/
|
|
void KBgBoard::drawChecker(TQPainter *p, int x, int y, int pcs, int diam,
|
|
int col, bool upper) const
|
|
{
|
|
drawCircle(p, x, y, pcs, diam , col, upper, true );
|
|
drawCircle(p, x+1, y+1,-pcs, diam-2, col, upper, false);
|
|
drawCircle(p, x+2, y+2, pcs, diam-4, col, upper, false);
|
|
}
|
|
|
|
/*
|
|
* This function draws checkers on the painter *p. They overlap so that
|
|
* up to fifteen checkers fit on the cell. This is used by homes and
|
|
* bars.
|
|
*/
|
|
void KBgBoardCell::drawOverlappingCheckers(TQPainter *p, int xo, int yo,
|
|
double sf) const
|
|
{
|
|
int d = getCheckerDiameter();
|
|
bool upper =
|
|
cellID == HOME_THEM_LEFT ||
|
|
cellID == HOME_THEM_RIGHT ||
|
|
cellID == BAR_THEM;
|
|
double xp = xo + sf*((width()-d-1)/2);
|
|
double ra = sf*d;
|
|
for (int i = 0; i < abs(pcs); ++i) {
|
|
double yp = yo + (upper ? 1+i*sf*height()/25.0 :
|
|
sf*(height()-d-i*height()/25.0));
|
|
board->drawChecker(p, xp, yp, pcs, ra, 0, upper);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function paints the content of a regular cell on the painter p.
|
|
* It does so by first drawing a triangle (depending on whether we draw
|
|
* on the screen or not this will be antialiased). Then on top of that
|
|
* we draw the field number in inverse color. Finally we draw all the
|
|
* checkers in such a way that always five are in one level and the next
|
|
* level is slightly shifted.
|
|
*/
|
|
void KBgBoardField::paintCell(TQPainter *p, int xo, int yo, double sf) const
|
|
{
|
|
TQColor color, alphaColor, background = backgroundColor();
|
|
bool printing = abs(xo)+abs(yo) > 0;
|
|
|
|
if (printing) {
|
|
/*
|
|
* This is the code for black and white printing on
|
|
* paper. This justs draws a triangle and surrounds
|
|
* it by a black triangle. Easy but works.
|
|
*/
|
|
TQPointArray pa(3);
|
|
|
|
color = (getNumber()%2 ? white : black);
|
|
|
|
if (cellID < 13) {
|
|
pa.setPoint( 0, xo , yo );
|
|
pa.setPoint( 1, xo + sf*width()/2, yo + 0.9*sf*height());
|
|
pa.setPoint( 2, xo + sf*width() , yo );
|
|
} else {
|
|
pa.setPoint( 0, xo , yo + sf*(height()-1));
|
|
pa.setPoint( 1, xo + sf*width()/2, yo + 0.1*sf*height());
|
|
pa.setPoint( 2, xo + sf*width() , yo + sf*(height()-1));
|
|
}
|
|
|
|
p->setBrush(color);
|
|
p->setPen(color);
|
|
p->drawPolygon(pa);
|
|
|
|
p->setBrush(black);
|
|
p->setPen(black);
|
|
p->drawPolyline(pa);
|
|
|
|
} else {
|
|
/*
|
|
* This is the code for antialiased triangles. This code has
|
|
* been written by Bo Thorsen.
|
|
*/
|
|
color = board->getCheckerColor(getNumber()%2-1);
|
|
|
|
int topX, topY, bottomX1, bottomX2, bottomY, incrY;
|
|
topX = xo + (int)(sf*width()/2.0);
|
|
bottomX1 = xo;
|
|
bottomX2 = xo + (int)(sf*width());
|
|
if (cellID < 13) {
|
|
topY = yo + (int)(0.9*sf*height());
|
|
bottomY = yo;
|
|
incrY = 1;
|
|
} else {
|
|
topY = yo + (int)(0.1*sf*height());
|
|
bottomY = yo + (int)(sf*height());
|
|
incrY = -1;
|
|
}
|
|
|
|
float x1 = bottomX1, x2 = bottomX2;
|
|
float dx1 = (float)(topX-bottomX1) / (topY-bottomY);
|
|
float dx2 = (float)(topX-bottomX2) / (topY-bottomY);
|
|
if (dx1 < 0) dx1 = -dx1;
|
|
if (dx2 < 0) dx2 = -dx2;
|
|
|
|
p->setPen( color );
|
|
p->drawLine(bottomX1, bottomY, bottomX2, bottomY);
|
|
x1 += dx1;
|
|
x2 -= dx2;
|
|
|
|
/*
|
|
* The scaling factor (0.99) cuts off the top op the points
|
|
*/
|
|
for (int y=bottomY; x1 < x2*0.99; y+=incrY) {
|
|
int ix1 = (int)x1, ix2 = (int)x2;
|
|
float a1 = x1 - ix1, a2 = x2 - ix2;
|
|
|
|
/*
|
|
* This is a simple linear interpolation between
|
|
* the two colors
|
|
*/
|
|
int red1 = (int)
|
|
((1-a1)*color.red() + a1*background.red());
|
|
int green1 = (int)
|
|
((1-a1)*color.green() + a1*background.green());
|
|
int blue1 = (int)
|
|
((1-a1)*color.blue() + a1*background.blue());
|
|
int red2 = (int)
|
|
(a2*color.red() + (1-a2)*background.red());
|
|
int green2 = (int)
|
|
(a2*color.green() + (1-a2)*background.green());
|
|
int blue2 = (int)
|
|
(a2*color.blue() + (1-a2)*background.blue());
|
|
|
|
/*
|
|
* Draw the antialiasing pixels
|
|
*/
|
|
alphaColor.setRgb(red1, green1, blue1);
|
|
p->setPen(alphaColor);
|
|
p->drawPoint(ix1, y);
|
|
alphaColor.setRgb(red2, green2, blue2);
|
|
p->setPen(alphaColor);
|
|
p->drawPoint(ix2, y);
|
|
|
|
ix1++;
|
|
ix2--;
|
|
x1 += dx1;
|
|
x2 -= dx2;
|
|
|
|
if (ix1 <= ix2 && x1 < x2*0.99) {
|
|
/*
|
|
* Draw the line
|
|
*/
|
|
p->setPen(color);
|
|
p->drawLine(ix1, y, ix2, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print the field number in inverted color
|
|
*/
|
|
color = board->getCheckerColor((1+getNumber())%2-1);
|
|
|
|
p->setBrush(color);
|
|
p->setPen(color);
|
|
|
|
TQString t;
|
|
t.setNum(getNumber());
|
|
|
|
p->setFont(board->getFont());
|
|
int textHeight = TQFontMetrics(p->font()).height();
|
|
p->drawText(xo, yo+((cellID < 13) ? 5 : height()-5-textHeight),
|
|
width()*sf, textHeight, AlignCenter, t);
|
|
|
|
/*
|
|
* Put the checkers on the field.
|
|
*/
|
|
int d = getCheckerDiameter();
|
|
double yp, xp = xo + sf*((width()-d-1)/2);
|
|
double ra = sf*d;
|
|
bool upper = cellID < 13;
|
|
int col = (getNumber()%2) ? 1 : -1;
|
|
|
|
for (int i = 0; i < abs(pcs); ++i) {
|
|
/*
|
|
* There is hard work in these formulas. Unless you have
|
|
* tried _ALL_ possible windowsizes: don't touch!
|
|
*/
|
|
yp = yo + (upper ? sf*((i%5)+(i/5)/4.0)*(d-1) :
|
|
sf*(height()-((1+i%5)*d)-int(i/5)*0.25*d)-1);
|
|
if (printing) {
|
|
board->drawSimpleChecker(p, xp, yp, pcs, ra);
|
|
} else {
|
|
board->drawChecker(p, xp, yp, pcs, ra,
|
|
((i < 5) ? col : 100), upper);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Finally draw the horizontal boundaries
|
|
*/
|
|
KBgBoardCell::paintCell(p, xo, yo, sf);
|
|
}
|
|
|
|
/*
|
|
* This function draws an anti-aliased circle on the painter p. It is painted
|
|
* in the ractangle with the upper left corner (x,y) and has a maximum diameter
|
|
* of diam. col and upper are as in drawChecker(). outer indicates if this
|
|
* circle blends with the background. Note that this function needs knowledge
|
|
* about the triangles on the cells. This is long but it is just a big if
|
|
* construct.
|
|
*/
|
|
void KBgBoard::drawCircle(TQPainter *p, int x, int y, int pcs, int diam,
|
|
int col, bool upper, bool outer) const
|
|
{
|
|
TQColor fColor = getCheckerColor(pcs);
|
|
TQColor alphaColor;
|
|
TQColor bColor;
|
|
|
|
int red, green, blue;
|
|
int rad = diam/2;
|
|
int xoff = 0;
|
|
|
|
float sn = 4;
|
|
float rs = 0.25*diam*diam;
|
|
float cf, a;
|
|
|
|
for (int ys = rad; ys >= 0; ys--) {
|
|
for (int xs = xoff; cf = 0, xs < rad; xs++) {
|
|
|
|
/*
|
|
* perform super-sample this pixel
|
|
*/
|
|
for (int s1 = 0; s1 < sn; s1++)
|
|
for (int s2 = 0; s2 < sn; s2++)
|
|
if ((rad-xs+s1/sn)*(rad-xs+s1/sn)+
|
|
(rad-ys+s2/sn)*(rad-ys+s2/sn) < rs)
|
|
cf += 1;
|
|
a = cf/sn/sn;
|
|
|
|
if (outer && (col == 0 || col == 100)) {
|
|
|
|
if (col == 0)
|
|
bColor = backgroundColor();
|
|
else
|
|
bColor = fColor;
|
|
|
|
red = (int)
|
|
((1-a)*bColor.red()+a*fColor.red());
|
|
green = (int)
|
|
((1-a)*bColor.green()+a*fColor.green());
|
|
blue = (int)
|
|
((1-a)*bColor.blue()+a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
if (upper) {
|
|
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
|
|
p->setBrush(fColor);
|
|
p->setPen(fColor);
|
|
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
|
|
} else {
|
|
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
|
|
p->setBrush(fColor);
|
|
p->setPen(fColor);
|
|
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
|
|
}
|
|
|
|
} else if (outer) {
|
|
|
|
if (upper) {
|
|
|
|
bColor = getCheckerColor(col);
|
|
|
|
red = (int)((1-a)*bColor.red()+
|
|
a*fColor.red());
|
|
green = (int)((1-a)*bColor.green()+
|
|
a*fColor.green());
|
|
blue = (int)((1-a)*bColor.blue()+
|
|
a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
|
|
bColor = backgroundColor();
|
|
|
|
red = (int)((1-a)*bColor.red()+
|
|
a*fColor.red());
|
|
green = (int)((1-a)*bColor.green()+
|
|
a*fColor.green());
|
|
blue = (int)((1-a)*bColor.blue()+
|
|
a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
if (x+xs < rad*(y+ys)/(0.45*height())) {
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
}
|
|
if (x+xs<rad*(y+diam-ys)/(0.45*height())) {
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
}
|
|
|
|
} else {
|
|
|
|
bColor = getCheckerColor(col);
|
|
|
|
red = (int)((1-a)*bColor.red()+
|
|
a*fColor.red());
|
|
green = (int)((1-a)*bColor.green()+
|
|
a*fColor.green());
|
|
blue = (int)((1-a)*bColor.blue()+
|
|
a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
|
|
bColor = backgroundColor();
|
|
|
|
red = (int)((1-a)*bColor.red()+
|
|
a*fColor.red());
|
|
green = (int)((1-a)*bColor.green()+
|
|
a*fColor.green());
|
|
blue = (int)((1-a)*bColor.blue()+
|
|
a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
if (x+xs<rad*(0.5-(y+ys)/
|
|
(1.0*height()))/0.45) {
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
}
|
|
if (x+xs < rad*(0.5-(y+diam-ys)/
|
|
(1.0*height()))/0.45) {
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
bColor = getCheckerColor(-pcs);
|
|
|
|
red = (int)((1-a)*bColor.red()+
|
|
a*fColor.red());
|
|
green = (int)((1-a)*bColor.green()+
|
|
a*fColor.green());
|
|
blue = (int)((1-a)*bColor.blue()+
|
|
a*fColor.blue());
|
|
|
|
alphaColor.setRgb(red, green, blue);
|
|
p->setBrush(alphaColor);
|
|
p->setPen(alphaColor);
|
|
|
|
p->drawPoint(x+xs, y+ys);
|
|
p->drawPoint(x+diam-xs, y+ys);
|
|
p->drawPoint(x+xs, y+diam-ys);
|
|
p->drawPoint(x+diam-xs, y+diam-ys);
|
|
|
|
}
|
|
|
|
if (fabs(cf-sn*sn) < 0.0001) {
|
|
|
|
p->moveTo(x+xs, y+ys);
|
|
p->lineTo(x+diam-xs, y+ys);
|
|
p->moveTo(x+xs, y+diam-ys);
|
|
p->lineTo(x+diam-xs, y+diam-ys);
|
|
|
|
xoff = xs;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function redoes a previously undone move
|
|
*/
|
|
void KBgBoard::redoMove()
|
|
{
|
|
if (getEditMode())
|
|
return;
|
|
|
|
int w = getTurn();
|
|
int mcolor = ((w == US) ? color : -color);
|
|
KBgBoardMove *move = redoHistory.last();
|
|
if (move && (w == US || w == THEM)) {
|
|
/*
|
|
* Make changes at source
|
|
*/
|
|
if (move->source() == BAR_US || move->source() == BAR_THEM) {
|
|
onbar[w] -= mcolor;
|
|
(getCell(move->source()))->cellUpdate(onbar[w], false);
|
|
} else {
|
|
board[move->source()] -= mcolor;
|
|
(getCell(move->source()))->cellUpdate(board[move->source()]);
|
|
}
|
|
/*
|
|
* Make changes at the destination
|
|
*/
|
|
if ((move->destination() == HOME_THEM_LEFT ) || (move->destination() == HOME_THEM_RIGHT) ||
|
|
(move->destination() == HOME_US_LEFT ) || (move->destination() == HOME_US_RIGHT )) {
|
|
onhome[w] += mcolor;
|
|
(getCell(move->destination()))->cellUpdate(onhome[w], false);
|
|
} else {
|
|
board[move->destination()] += mcolor;
|
|
if (move->wasKicked()) {
|
|
board[move->destination()] = mcolor;
|
|
onbar[((w == US) ? THEM : US)] -= mcolor;
|
|
(getCell(((w == US) ? BAR_THEM : BAR_US)))->cellUpdate
|
|
(onbar[((w == US) ? THEM : US)], false);
|
|
}
|
|
(getCell(move->destination()))->cellUpdate(board[move->destination()]);
|
|
}
|
|
makeMove(move->source(), move->destination());
|
|
redoHistory.remove();
|
|
emit finishedUpdate();
|
|
}
|
|
sendMove();
|
|
}
|
|
|
|
/*
|
|
* This function performs and undo for the last move and updates the parent
|
|
* of the board by calling sendMove() after the undo.
|
|
*/
|
|
void KBgBoard::undoMove()
|
|
{
|
|
if (getEditMode())
|
|
return;
|
|
|
|
int w = getTurn();
|
|
int mcolor = ((w == US) ? color : -color);
|
|
KBgBoardMove *move = moveHistory.last();
|
|
if (move && (w == US || w == THEM)) {
|
|
/*
|
|
* Undo changes at source
|
|
*/
|
|
if (move->source() == BAR_US || move->source() == BAR_THEM) {
|
|
onbar[w] += mcolor;
|
|
(getCell(move->source()))->cellUpdate(onbar[w], false);
|
|
} else {
|
|
board[move->source()] += mcolor;
|
|
(getCell(move->source()))->cellUpdate
|
|
(board[move->source()]);
|
|
}
|
|
/*
|
|
* Undo changes at the destination
|
|
*/
|
|
if ( (move->destination() == HOME_THEM_LEFT ) ||
|
|
(move->destination() == HOME_THEM_RIGHT) ||
|
|
(move->destination() == HOME_US_LEFT ) ||
|
|
(move->destination() == HOME_US_RIGHT )) {
|
|
onhome[w] -= mcolor;
|
|
(getCell(move->destination()))->cellUpdate
|
|
(onhome[w], false);
|
|
} else {
|
|
board[move->destination()] -= mcolor;
|
|
if (move->wasKicked()) {
|
|
board[move->destination()] = -mcolor;
|
|
onbar[((w == US) ? THEM : US)] += mcolor;
|
|
(getCell(((w == US) ?
|
|
BAR_THEM : BAR_US)))->cellUpdate
|
|
(onbar[((w == US) ? THEM : US)], false);
|
|
}
|
|
(getCell(move->destination()))->cellUpdate
|
|
(board[move->destination()]);
|
|
}
|
|
++possMoves[move->length()];
|
|
redoHistory.append(new KBgBoardMove(*move));
|
|
moveHistory.remove();
|
|
emit finishedUpdate();
|
|
}
|
|
sendMove();
|
|
}
|
|
|
|
/*
|
|
* While putting a piece on a cell the cell has noticed that it changed
|
|
* ownership and hence needs a piece to be kicked. Since cells don't
|
|
* know where the opponents bar is we handle this here.
|
|
*/
|
|
void KBgBoard::kickedPiece()
|
|
{
|
|
int w = ((getTurn()) == US ? THEM : US);
|
|
|
|
if (w == US) {
|
|
onbar[w] += color;
|
|
(getCell(BAR_US ))->cellUpdate(onbar[w], false);
|
|
} else {
|
|
onbar[w] -= color;
|
|
(getCell(BAR_THEM))->cellUpdate(onbar[w], false);
|
|
}
|
|
if (!getEditMode()) {
|
|
KBgBoardMove *move = moveHistory.last();
|
|
move->setKicked(true);
|
|
}
|
|
emit finishedUpdate();
|
|
}
|
|
|
|
/*
|
|
* This is a very short utility function for makeShortMove().
|
|
*/
|
|
void KBgBoardCell::makeShortMoveHelper(int s, int d)
|
|
{
|
|
if (getPiece()) {
|
|
board->makeMove(s, d);
|
|
KBgBoardCell *dest = board->getCell(d);
|
|
dest->putPiece(((board->getTurn() == US) ? color : -color));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function makes the shortes possible move from this cell. It
|
|
* uses only one dice and and it will kick opponent checkers.
|
|
*/
|
|
void KBgBoardCell::makeShortMove()
|
|
{
|
|
int m[4];
|
|
|
|
int dir = ((board->getTurn() == US) ? direction : -direction);
|
|
int src = board->IDtoNum(cellID);
|
|
|
|
if (src == BAR_US || src == BAR_THEM) {
|
|
|
|
int s = (dir > 0) ? 0 : 25;
|
|
for (int i = 1; i < 7; i++) {
|
|
int d = (dir > 0) ? i : 25 - i;
|
|
if (board->checkMultiMove(s, d, m) == 1) {
|
|
makeShortMoveHelper(src, d);
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 1; i < 7; i++) {
|
|
int d = src + dir*i;
|
|
if (d > 25) d = 25;
|
|
if (d < 0) d = 0;
|
|
if (0 < d && d < 25) {
|
|
|
|
if (board->checkMultiMove(src, d, m) == 1) {
|
|
makeShortMoveHelper(src, d);
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (board->moveOffPossible()) {
|
|
int whichHome;
|
|
if (board->getTurn() == US)
|
|
whichHome = ((direction > 0) ?
|
|
HOME_US_LEFT :
|
|
HOME_US_RIGHT);
|
|
else
|
|
whichHome = ((direction > 0) ?
|
|
HOME_THEM_LEFT :
|
|
HOME_THEM_RIGHT);
|
|
|
|
if (board->diceAllowMove
|
|
(cellID, whichHome)) {
|
|
makeShortMoveHelper(src, whichHome);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ask the current backgammon engine for a doubled cube.
|
|
*/
|
|
void KBgBoard::getDoubleCube(const int w)
|
|
{
|
|
emit doubleCube(w);
|
|
}
|
|
|
|
/*
|
|
* Ask the current backgammon engine rolling the dice.
|
|
*/
|
|
void KBgBoard::getRollDice(const int w)
|
|
{
|
|
emit rollDice(w);
|
|
}
|
|
|
|
/*
|
|
* This is the constructor of the KBgBoard class. It creates
|
|
* a backgammon board with an initial distribution of checkers, empty
|
|
* dice and a cube with face value 1. The initial board is not usable!
|
|
* You have to change the status by passing a KBgStatus
|
|
* object to setState(...) before you can play!
|
|
*/
|
|
KBgBoard::KBgBoard(TQWidget *parent, const char *name, TQPopupMenu *menu)
|
|
: TQWidget(parent, name)
|
|
{
|
|
/*
|
|
* The following lines set up internal bookkeeping data.
|
|
*/
|
|
moveHistory.setAutoDelete(true);
|
|
redoHistory.setAutoDelete(true);
|
|
cube = 1;
|
|
allowMoving(true);
|
|
setEditMode(false);
|
|
savedCursor = NULL;
|
|
checkerDiam = MINIMUM_CHECKER_SIZE;
|
|
|
|
/*
|
|
* We may be initialized with a popup menu by our parent.
|
|
*/
|
|
contextMenu = menu;
|
|
|
|
baseColors[0] = black;
|
|
baseColors[1] = white;
|
|
|
|
/*
|
|
* Get the 30 cells that constitute the board and initialize
|
|
* them properly.
|
|
*/
|
|
cells[ 0] = new KBgBoardHome(this, HOME_THEM_LEFT);
|
|
cells[14] = new KBgBoardHome(this, HOME_THEM_RIGHT);
|
|
cells[15] = new KBgBoardHome(this, HOME_US_LEFT);
|
|
cells[29] = new KBgBoardHome(this, HOME_US_RIGHT);
|
|
|
|
cells[ 7] = new KBgBoardBar(this, BAR_THEM);
|
|
cells[22] = new KBgBoardBar(this, BAR_US);
|
|
|
|
for (int i=1; i<7; ++i) {
|
|
cells[ i] = new KBgBoardField(this, i);
|
|
cells[ 7+i] = new KBgBoardField(this, 6+i);
|
|
cells[15+i] = new KBgBoardField(this, 12+i);
|
|
cells[22+i] = new KBgBoardField(this, 18+i);
|
|
}
|
|
|
|
/*
|
|
* Get the default seeting of the board and initialize the
|
|
* state of it.
|
|
*/
|
|
KBgStatus *st = new KBgStatus();
|
|
|
|
st->setCube(1, true, true);
|
|
st->setDirection(+1);
|
|
st->setColor(+1);
|
|
|
|
st->setBoard( 1, US, 2); st->setBoard( 6, THEM, 5);
|
|
st->setBoard( 8, THEM, 3); st->setBoard(12, US, 5);
|
|
st->setBoard(13, THEM, 5); st->setBoard(17, US, 3);
|
|
st->setBoard(19, US, 5); st->setBoard(24, THEM, 2);
|
|
|
|
st->setHome(US, 0);
|
|
|
|
st->setDice(US , 0, 0); st->setDice(US , 1, 0);
|
|
st->setDice(THEM, 0, 0); st->setDice(THEM, 1, 0);
|
|
|
|
setState(*st);
|
|
|
|
delete st;
|
|
|
|
/*
|
|
* This line simplifies the checkMultiMove(...) function a lot.
|
|
*/
|
|
board[0] = board[25] = 0;
|
|
|
|
/*
|
|
* User interface design settings come here. These may be
|
|
* overwritten by the user.
|
|
*/
|
|
shortMoveMode = SHORT_MOVE_DOUBLE;
|
|
setBackgroundColor(TQColor(200, 200, 166));
|
|
computePipCount = true;
|
|
|
|
/*
|
|
* Set initial font
|
|
*/
|
|
setFont(TQApplication::font());
|
|
}
|
|
|
|
TQSize KBgBoard::minimumSizeHint() const
|
|
{
|
|
return TQSize(MINIMUM_CHECKER_SIZE * 15, MINIMUM_CHECKER_SIZE * 11);
|
|
}
|
|
|
|
TQSize KBgBoard::sizeHint() const {
|
|
return TQSize(MINIMUM_CHECKER_SIZE *15*4,MINIMUM_CHECKER_SIZE*11*2);
|
|
}
|
|
|