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.
430 lines
11 KiB
430 lines
11 KiB
#include "board.h"
|
|
#include "board.moc"
|
|
|
|
#include <knotifyclient.h>
|
|
#include <tdelocale.h>
|
|
#include <kzoommainwindow.h>
|
|
|
|
#include "piece.h"
|
|
#include "factory.h"
|
|
#include "baseprefs.h"
|
|
|
|
using namespace KGrid2D;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
FixedCanvasView::FixedCanvasView(TQWidget *parent, const char *name)
|
|
: TQCanvasView(parent, name, WNoAutoErase)
|
|
{}
|
|
|
|
TQSize FixedCanvasView::sizeHint() const
|
|
{
|
|
if ( canvas()==0 ) return TQSize();
|
|
return canvas()->size() + 2 * TQSize(frameWidth(), frameWidth());
|
|
}
|
|
|
|
void FixedCanvasView::adjustSize()
|
|
{
|
|
setFixedSize(sizeHint());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const BaseBoard::DirectionData BaseBoard::DIRECTION_DATA[Nb_Direction] = {
|
|
{ SquareBase::Left, Left },
|
|
{ SquareBase::Right, Right },
|
|
{ SquareBase::Down, Up },
|
|
{ SquareBase::Up, Down }
|
|
};
|
|
|
|
BaseBoard::BaseBoard(bool graphic, TQWidget *parent)
|
|
: FixedCanvasView(parent, "board"),
|
|
GenericTetris(bfactory->bbi.width, bfactory->bbi.height,
|
|
bfactory->bbi.withPieces, graphic),
|
|
state(GameOver), timer(this), sequences(0), main(0), _next(0),
|
|
_arcade(false)
|
|
{
|
|
if (graphic) {
|
|
setVScrollBarMode(AlwaysOff);
|
|
setHScrollBarMode(AlwaysOff);
|
|
setFrameStyle( TQFrame::Panel | TQFrame::Sunken );
|
|
|
|
sequences = new SequenceArray;
|
|
main = new BlockInfo(*sequences);
|
|
setCanvas(main);
|
|
if (bfactory->bbi.withPieces)
|
|
_next = new BlockInfo(*sequences);
|
|
setBlockInfo(main, _next);
|
|
|
|
connect(&timer, TQT_SIGNAL(timeout()), TQT_SLOT(timeout()));
|
|
|
|
Piece::info().loadColors();
|
|
KZoomMainWindow::addWidget(this);
|
|
}
|
|
}
|
|
|
|
void BaseBoard::copy(const GenericTetris &g)
|
|
{
|
|
GenericTetris::copy(g);
|
|
state = static_cast<const BaseBoard &>(g).state;
|
|
}
|
|
|
|
void BaseBoard::settingsChanged()
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
Piece::info().loadColors();
|
|
}
|
|
|
|
void BaseBoard::adjustSize()
|
|
{
|
|
int size = BasePrefs::blockSize();
|
|
|
|
sequences->setBlockSize(size);
|
|
main->resize(matrix().width() * size, matrix().height() * size);
|
|
for (uint i=0; i<matrix().width(); i++)
|
|
for (uint j=0; j<firstClearLine(); j++) {
|
|
Coord c(i, j);
|
|
if ( matrix()[c]==0 ) continue;
|
|
partialMoveBlock(c, TQPoint(0, 0));
|
|
}
|
|
|
|
if (_next) {
|
|
Coord c = Piece::info().maxSize() + Coord(2, 2);
|
|
_next->resize(c.first * size, c.second * size);
|
|
_nextPiece->moveCenter();
|
|
}
|
|
|
|
FixedCanvasView::adjustSize();
|
|
}
|
|
|
|
BaseBoard::~BaseBoard()
|
|
{
|
|
if ( graphic() ) {
|
|
setBlockInfo(0, 0); // destruct all sprites before deleting canvas
|
|
delete _next;
|
|
delete main;
|
|
delete sequences;
|
|
}
|
|
}
|
|
|
|
void BaseBoard::init(bool arcade)
|
|
{
|
|
_arcade = arcade;
|
|
_arcadeStageDone = false;
|
|
}
|
|
|
|
void BaseBoard::start(const GTInitData &data)
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
if ( !_arcadeStageDone || _arcadeStage==bfactory->bbi.nbArcadeStages )
|
|
_arcadeStage = 0;
|
|
_arcadeStageDone = false;
|
|
state = Normal;
|
|
GenericTetris::start(data); // NB: the timer is started by updateLevel !
|
|
if (_arcade) arcadePrepare();
|
|
}
|
|
|
|
void BaseBoard::stop()
|
|
{
|
|
timer.stop();
|
|
state = GameOver;
|
|
}
|
|
|
|
void BaseBoard::pause()
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
timer.stop();
|
|
_oldState = state;
|
|
state = Paused;
|
|
showBoard(false);
|
|
}
|
|
|
|
void BaseBoard::gameOver()
|
|
{
|
|
stop();
|
|
emit gameOverSignal();
|
|
}
|
|
|
|
void BaseBoard::showCanvas(TQCanvas *c, bool show)
|
|
{
|
|
TQCanvasItemList l = c->allItems();
|
|
TQCanvasItemList::Iterator it;
|
|
for (it=l.begin(); it!=l.end(); ++it) {
|
|
if (show) (*it)->show();
|
|
else (*it)->hide();
|
|
}
|
|
c->update();
|
|
}
|
|
|
|
void BaseBoard::showBoard(bool show)
|
|
{
|
|
showCanvas(main, show);
|
|
}
|
|
|
|
void BaseBoard::unpause()
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
showBoard(true);
|
|
state = _oldState;
|
|
startTimer();
|
|
}
|
|
|
|
void BaseBoard::updateRemoved(uint newRemoved)
|
|
{
|
|
GenericTetris::updateRemoved(newRemoved);
|
|
emit removedUpdated();
|
|
}
|
|
|
|
void BaseBoard::updateScore(uint newScore)
|
|
{
|
|
GenericTetris::updateScore(newScore);
|
|
emit scoreUpdated();
|
|
}
|
|
|
|
int BaseBoard::firstColumnBlock(uint col) const
|
|
{
|
|
for (int j=firstClearLine()-1; j>=0; j--) {
|
|
Coord c(col, j);
|
|
if ( matrix()[c]!=0 ) return j;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void BaseBoard::_beforeRemove(bool first)
|
|
{
|
|
if ( graphic() ) {
|
|
state = ( beforeRemove(first) ? BeforeRemove : Normal );
|
|
if ( state==BeforeRemove ) {
|
|
startTimer();
|
|
return;
|
|
}
|
|
}
|
|
remove();
|
|
_afterRemove(true);
|
|
}
|
|
|
|
void BaseBoard::remove()
|
|
{
|
|
for (uint j=0; j<firstClearLine(); j++)
|
|
for (uint i=0; i<matrix().width(); i++) {
|
|
Coord c(i, j);
|
|
if ( matrix()[c]==0 || !toBeRemoved(c) ) continue;
|
|
removeBlock(c);
|
|
}
|
|
computeInfos();
|
|
if ( graphic() ) {
|
|
main->update();
|
|
KNotifyClient::event(winId(), "removed", i18n("Blocks removed"));
|
|
}
|
|
}
|
|
|
|
bool BaseBoard::doFall(bool doAll, bool first, bool lineByLine)
|
|
{
|
|
Q_ASSERT( !lineByLine || !doAll );
|
|
|
|
if ( !doAll ) {
|
|
if (first) loop = 0;
|
|
else loop++;
|
|
}
|
|
bool final = (doAll || lineByLine
|
|
|| loop==bfactory->bbi.nbFallStages);
|
|
|
|
for (uint i=0; i<matrix().width(); i++) {
|
|
// compute heights
|
|
// we must separate this computation since toFall() can depend
|
|
// directly on the disposition of blocks under the current one
|
|
// (for e.g. in kfouleggs)
|
|
// we do not rely on firstClearLine() here since this method is
|
|
// used in kfouleggs to make gift blocks fall down ...
|
|
uint h = 0;
|
|
TQMemArray<uint> heights(matrix().height());
|
|
for (uint j=1; j<matrix().height(); j++) { // first line cannot fall
|
|
Coord src(i, j);
|
|
if ( toFall(src) ) h++;
|
|
heights[j] = h;
|
|
}
|
|
|
|
// do move
|
|
for (uint j=1; j<matrix().height(); j++) {
|
|
Coord src(i, j);
|
|
if( heights[j]==0 || matrix()[src]==0 ) continue;
|
|
if (lineByLine) final = false;
|
|
uint k = j - (lineByLine ? 1 : heights[j]);
|
|
Coord dest(i, k);
|
|
if ( final || lineByLine ) moveBlock(src, dest);
|
|
else partialBlockFall(src, dest);
|
|
}
|
|
}
|
|
|
|
if (final) computeInfos();
|
|
return final;
|
|
}
|
|
|
|
void BaseBoard::_afterRemove(bool first)
|
|
{
|
|
AfterRemoveResult r = afterRemove(!graphic(), first);
|
|
switch (r) {
|
|
case Done:
|
|
state = Normal;
|
|
_afterAfterRemove();
|
|
return;
|
|
case NeedAfterRemove:
|
|
state = AfterRemove;
|
|
startTimer();
|
|
return;
|
|
case NeedRemoving:
|
|
_beforeRemove(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
BaseBoard::AfterRemoveResult BaseBoard::afterRemove(bool doAll, bool first)
|
|
{
|
|
return (doFall(doAll, first, false) ? Done : NeedAfterRemove);
|
|
}
|
|
|
|
void BaseBoard::_afterAfterRemove()
|
|
{
|
|
if ( isArcade() && arcadeDone()>=arcadeTodo() ) {
|
|
_arcadeStage++;
|
|
_arcadeStageDone = true;
|
|
gameOver();
|
|
return;
|
|
}
|
|
if ( !afterAfterRemove() ) gameOver();
|
|
else if ( graphic() ) startTimer();
|
|
}
|
|
|
|
bool BaseBoard::timeout()
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
if ( state==GameOver ) return true;
|
|
switch (state) {
|
|
case BeforeRemove: _beforeRemove(FALSE); break;
|
|
case AfterRemove: _afterRemove(FALSE); break;
|
|
default: return false;
|
|
}
|
|
main->update();
|
|
return true;
|
|
}
|
|
|
|
bool BaseBoard::startTimer()
|
|
{
|
|
Q_ASSERT( graphic() );
|
|
if ( state==GameOver ) return true;
|
|
switch (state) {
|
|
case BeforeRemove:
|
|
timer.start(bfactory->bbi.beforeRemoveTime, true);
|
|
break;
|
|
case AfterRemove:
|
|
timer.start(bfactory->bbi.afterRemoveTime, true);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BaseBoard::beforeRemove(bool first)
|
|
{
|
|
if (first) loop = 0;
|
|
else loop++;
|
|
|
|
for (uint j=0; j<firstClearLine(); j++)
|
|
for (uint i=0; i<matrix().width(); i++) {
|
|
Coord c(i, j);
|
|
if ( toBeRemoved(c) ) matrix()[c]->toggleLight();
|
|
}
|
|
|
|
return ( loop!=bfactory->bbi.nbToggles );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void BaseBoard::partialBlockFall(const Coord &src, const Coord &dest)
|
|
{
|
|
Q_ASSERT( loop<bfactory->bbi.nbFallStages );
|
|
|
|
float c = float(loop+1) / bfactory->bbi.nbFallStages * BasePrefs::blockSize();
|
|
int xdec = dest.first - src.first;
|
|
int ydec = src.second - dest.second;
|
|
TQPoint p(int(xdec * c), int(ydec * c));
|
|
partialMoveBlock(src, p);
|
|
}
|
|
|
|
uint BaseBoard::findGroup(Square<int> &field, const Coord &c) const
|
|
{
|
|
uint nb = 0;
|
|
_findGroup(field, c, nb, false);
|
|
return nb;
|
|
}
|
|
|
|
void BaseBoard::setGroup(Square<int> &field, const Coord &c, uint nb) const
|
|
{
|
|
_findGroup(field, c, nb, true);
|
|
}
|
|
|
|
void BaseBoard::_findGroup(Square<int> &field, const Coord &c,
|
|
uint &nb, bool set) const
|
|
{
|
|
if (!set) nb++;
|
|
field[c] = (set ? (int)nb : -1);
|
|
uint value = matrix()[c]->value();
|
|
CoordList n = matrix().neighbours(c, true, true);
|
|
for (CoordList::const_iterator i = n.begin(); i!=n.end(); ++i)
|
|
blockInGroup(field, *i, value, nb, set);
|
|
}
|
|
|
|
void BaseBoard::blockInGroup(Square<int> &field, const Coord &c, uint value,
|
|
uint &nb, bool set) const
|
|
{
|
|
if ( matrix()[c]==0 ) return;
|
|
if ( matrix()[c]->value()!=value ) return;
|
|
if ( field[c]!=(set ? -1 : 0) ) return;
|
|
_findGroup(field, c, nb, set);
|
|
}
|
|
|
|
TQMemArray<uint> BaseBoard::findGroups(Square<int> &field, uint minSize,
|
|
bool exitAtFirstFound) const
|
|
{
|
|
field.fill(0);
|
|
TQMemArray<uint> groups;
|
|
for (uint j=0; j<firstClearLine(); j++)
|
|
for (uint i=0; i<matrix().width(); i++) {
|
|
Coord c(i, j);
|
|
if ( matrix()[c]==0 || matrix()[c]->isGarbage() ) continue;
|
|
if ( field[c]!=0 ) continue;
|
|
uint nb = findGroup(field, c);
|
|
setGroup(field, c, nb);
|
|
if ( nb>=minSize ) {
|
|
uint s = groups.size();
|
|
groups.resize(s+1);
|
|
groups[s] = nb;
|
|
if (exitAtFirstFound) return groups;
|
|
}
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
uint BaseBoard::drawCode(const Coord &c) const
|
|
{
|
|
uint v = matrix()[c]->value();
|
|
uint code = 0;
|
|
for (uint i=0; i<Nb_Direction; i++) {
|
|
Coord nc = SquareBase::neighbour(c, DIRECTION_DATA[i].neighbour);
|
|
if ( !matrix().inside(nc) || matrix()[nc]==0
|
|
|| matrix()[nc]->value()!=v ) continue;
|
|
code |= DIRECTION_DATA[i].direction;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
void BaseBoard::computeNeighbours()
|
|
{
|
|
for (uint j=0; j<firstClearLine(); j++)
|
|
for (uint i=0; i<matrix().width(); i++) {
|
|
Coord c(i, j);
|
|
if ( matrix()[c]==0 || matrix()[c]->isGarbage() ) continue;
|
|
matrix()[c]->sprite()->setFrame( drawCode(c) );
|
|
}
|
|
}
|