#include "boardwidget.h" #include "prefs.h" #include #include #include #include #include #include #include #include /** * Constructor. * Loads tileset and background bitmaps. */ BoardWidget::BoardWidget( TQWidget* parent, const char *name ) : TQWidget( parent, name ), theTiles(false) { setBackgroundColor( TQColor( 0,0,0 ) ); timer = new TQTimer(this); connect( timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(helpMoveTimeout()) ); TimerState = Stop; gamePaused = false; iTimerStep = 0; matchCount = 0; showMatch = false; showHelp = false; MouseClickPos1.e = BoardLayout::depth; // mark tile position as invalid MouseClickPos2.e = BoardLayout::depth; memset( &Game.Mask, 0, sizeof( Game.Mask ) ); Game.MaxTileNum = 0; gameGenerationNum = 0; // initially we force a redraw updateBackBuffer=true; // Load tileset. First try to load the last use tileset TQString tFile; getFileOrDefault(Prefs::tileSet(), "tileset", tFile); if (!loadTileset(tFile)){ KMessageBox::error(this, i18n("An error occurred when loading the tileset file %1\n" "KMahjongg will now terminate.").arg(tFile)); kapp->quit(); } getFileOrDefault(Prefs::background(), "bgnd", tFile); // Load background if( ! loadBackground(tFile, false ) ) { KMessageBox::error(this, i18n("An error occurred when loading the background image\n%1").arg(tFile)+ i18n("KMahjongg will now terminate.")); kapp->quit(); } getFileOrDefault(Prefs::layout(), "layout", tFile); if( ! loadBoardLayout(tFile) ) { KMessageBox::error(this, i18n("An error occurred when loading the board layout %1\n" "KMahjongg will now terminate.").arg(tFile)); kapp->quit(); } setDisplayedWidth(); loadSettings(); } BoardWidget::~BoardWidget(){ saveSettings(); } void BoardWidget::loadSettings(){ theBackground.tile = Prefs::tiledBackground(); setDisplayedWidth(); tileSizeChanged(); updateScaleMode(); drawBoard(true); } void BoardWidget::saveSettings(){ // Preview can't handle this. TODO //TDEConfig *config=kapp->config(); //config->setGroup("General"); //config->writePathEntry("Tileset_file", tileFile); //config->writePathEntry("Background_file", backgroundFile); //config->writePathEntry("Layout_file", layout); } void BoardWidget::getFileOrDefault(TQString filename, TQString type, TQString &res) { TQString picsPos = "pics/"; picsPos += "default."; picsPos += type; if (TQFile::exists(filename)) { res = filename; } else { res = locate("appdata", picsPos); } if (res.isEmpty()) { KMessageBox::error(this, i18n("KMahjongg could not locate the file: %1\n" "or the default file of type: %2\n" "KMahjongg will now terminate").arg(filename).arg(type) ); kapp->quit(); } } void BoardWidget::setDisplayedWidth() { if (Prefs::showRemoved()) setFixedSize( requiredWidth() , requiredHeight()); else setFixedSize( requiredWidth() - ((theTiles.width())*4) , requiredHeight()); } // for a given cell x y calc how that cell is shadowed // returnd left = width of left hand side shadow // t = height of top shadow // c = width and height of corner shadow void BoardWidget::calcShadow(int e, int y, int x, int &l, int &t, int &c) { l = t = c = 0; if ((Game.shadowHeight(e,y,x) != 0) || (Game.shadowHeight(e,y-1,x) != 0) || (Game.shadowHeight(e,y,x-1) != 0)) { return; } int a,b; a=Game.shadowHeight(e,y,x-2); b=Game.shadowHeight(e,y-1,x-2); if (a != 0 || b != 0) l = (a>b) ? a : b; a=Game.shadowHeight(e,y-2,x); b=Game.shadowHeight(e,y-2,x-1); if (a != 0 || b != 0) t = (a>b) ? a : b; c = Game.shadowHeight(e, y-2, x-2); } // draw a triangular shadow from the top right to the bottom left. // one such shadow is a right hand edge of a shadow line. // if a second shadow botton left to top right is rendered over it // then the shadow becomes a box (ie in the middle of the run) void BoardWidget::shadowTopLeft(int depth, int sx, int sy, int rx, int ry, TQPixmap *src, bool flag) { if (depth) { int shadowPixels= (depth+1) * theTiles.shadowSize(); int xOffset=theTiles.qWidth()-shadowPixels; for (int p=0; p 0)) bitBlt( &backBuffer, sx, sy, src, rx, ry, theTiles.qWidth() - shadowPixels, shadowPixels, CopyROP ); } } // Second triangular shadow generator see above void BoardWidget::shadowBotRight(int depth, int sx, int sy, int rx, int ry, TQPixmap *src, bool flag) { if (depth) { int shadowPixels= (depth+1) * theTiles.shadowSize(); int xOffset=theTiles.qWidth(); for (int p=0; p0)) bitBlt( &backBuffer, sx+xOffset-shadowPixels, sy+shadowPixels, src, rx+xOffset-shadowPixels, ry+shadowPixels, shadowPixels, theTiles.qHeight()-shadowPixels, CopyROP ); } } void BoardWidget::shadowArea(int z, int y, int x, int sx, int sy,int rx, int ry, TQPixmap *src) { // quick check to see if we are obscured if (z < BoardLayout::depth-1) { if ((x >= 0) && (yrect().left(); int xheight = pa->rect().height(); int xwidth = pa->rect().width(); back = theBackground.getBackground(); if (gamePaused) { // If the game is paused, then blank out the board. // We tolerate no cheats around here folks.. bitBlt( this, xx, pa->rect().top(), back, xx, pa->rect().top(), xwidth, xheight, CopyROP ); return; } // if the repaint is because of a window redraw after a move // or a menu roll up, then just blit in the last rendered image if (!updateBackBuffer) { bitBlt(this, xx,pa->rect().top(), &backBuffer, xx, pa->rect().top(), xwidth, xheight, CopyROP); return; } // update the complete drawArea backBuffer.resize(back->width(), back->height()); // erase out with the background bitBlt( &backBuffer, xx, pa->rect().top(), back, xx,pa->rect().top(), back->width(), back->height(), CopyROP ); // initial offset on the screen of tile 0,0 int xOffset = theTiles.width()/2; int yOffset = theTiles.height()/2; //short tile = 0; // shadow the background first if (Prefs::showShadows()) { for (int by=0; by =0; x--) { int sx = x*(theTiles.qWidth() )+xOffset; int sy = y*(theTiles.qHeight() )+yOffset; // skip if no tile to display if (!Game.tilePresent(z,y,x)) continue; TQPixmap *t; TQPixmap *s; if (Game.hilighted[z][y][x]) { t= theTiles.selectedPixmaps( Game.Board[z][y][x]-TILE_OFFSET); s= theTiles.selectedShadowPixmaps( Game.Board[z][y][x]-TILE_OFFSET); } else { t= theTiles.unselectedPixmaps( Game.Board[z][y][x]-TILE_OFFSET); s= theTiles.unselectedShadowPixmaps( Game.Board[z][y][x]-TILE_OFFSET); } // Only one compilcation. Since we render top to bottom , left // to right situations arise where...: // there exists a tile one q height above and to the left // in this situation we would draw our top left border over it // we simply split the tile draw so the top half is drawn // minus border if (x > 1 && y > 0 && Game.tilePresent(z, y-1, x-2)){ bitBlt( &backBuffer, sx+theTiles.shadowSize(), sy, t, theTiles.shadowSize() ,0, t->width()-theTiles.shadowSize(), t->height()/2, CopyROP ); bitBlt( &backBuffer, sx, sy+t->height()/2, t, 0,t->height()/2,t->width(),t->height()/2,CopyROP); } else { bitBlt( &backBuffer, sx, sy, t, 0,0, t->width(), t->height(), CopyROP ); } if (Prefs::showShadows() && z= 0 && pos < 3) { last = removedDragon[pos]; tile = TILE_DRAGON+pos; } else { //Wind? if (pos >= 3 && pos < 7) { last = removedWind[pos-3]; tile = TILE_WIND+pos-3; } else { if (pos == 7) { for (int t=0; t<4;t++) { if (removedFlower[t]) { last++; tile=TILE_FLOWER+t; } } } else { for (int t=0; t<4;t++) { if (removedSeason[t]) { last++; tile=TILE_SEASON+t; } } } } } stackTiles(tile, last, xPos, yPos); stackTiles(TILE_ROD+pos, removedRod[pos], xPos - (1*(theTiles.width() - theTiles.shadowSize())) , yPos); stackTiles(TILE_BAMBOO+pos, removedBamboo[pos], xPos - (2*(theTiles.width() - theTiles.shadowSize())) , yPos); stackTiles(TILE_CHARACTER+pos, removedCharacter[pos], xPos - (3*(theTiles.width() - theTiles.shadowSize())) , yPos); yPos += theTiles.height()-theTiles.shadowSize(); } updateBackBuffer=false; bitBlt(this, xx,pa->rect().top(), &backBuffer, xx, pa->rect().top(), xwidth, xheight, CopyROP); } void BoardWidget::stackTiles(unsigned char t, unsigned short h, unsigned short x,unsigned short y) { int ss = theTiles.shadowSize(); TQPainter p(&backBuffer); TQPen line; p.setBackgroundMode(TQt::OpaqueMode); p.setBackgroundColor(black); line.setWidth(1); line.setColor(white); p.setPen(line); int x2 = x+theTiles.width()-ss-1; int y2 = y+theTiles.height()-1; p.drawLine(x, y+ss, x2, y+ss); p.drawLine(x, y+ss, x, y2); p.drawLine(x2, y+ss, x2, y2); p.drawLine(x+1, y2, x2, y2); // p.fillRect(x+1, y+ss+1, theTiles.width()-ss-2, theTiles.height()-ss-2, TQBrush(lightGray)); for (unsigned short pos=0; pos < h; pos++) { TQPixmap *p = theTiles.unselectedPixmaps(t-TILE_OFFSET); bitBlt( &backBuffer, x+(pos*ss), y-(pos*ss), p, 0,0, p->width(), p->height(), CopyROP ); } } void BoardWidget::pause() { gamePaused = !gamePaused; drawBoard(true); } void BoardWidget::gameLoaded() { int i; initialiseRemovedTiles(); i = Game.TileNum; // use the history of moves to put in the removed tiles area the correct tiles while (i < Game.MaxTileNum ) { setRemovedTilePair(Game.MoveList[i], Game.MoveList[i+1]); i +=2; } drawBoard(); } // --------------------------------------------------------- int BoardWidget::undoMove() { cancelUserSelectedTiles(); if( Game.TileNum < Game.MaxTileNum ) { clearRemovedTilePair(Game.MoveList[Game.TileNum], Game.MoveList[Game.TileNum+1]); putTile( Game.MoveList[Game.TileNum], false ); Game.TileNum++; putTile( Game.MoveList[Game.TileNum] ); Game.TileNum++; drawTileNumber(); setStatusText( i18n("Undo operation done successfully.") ); return 1; } else { setStatusText(i18n("What do you want to undo? You have done nothing!")); return 0; } } // --------------------------------------------------------- void BoardWidget::helpMove() { cancelUserSelectedTiles(); if (showHelp) helpMoveStop(); if( findMove( TimerPos1, TimerPos2 ) ) { cheatsUsed++; iTimerStep = 1; showHelp = true; helpMoveTimeout(); } else setStatusText( i18n("Sorry, you have lost the game.") ); } // --------------------------------------------------------- void BoardWidget::helpMoveTimeout() { if( iTimerStep & 1 ) { hilightTile( TimerPos1, true, false ); hilightTile( TimerPos2, true ); } else { hilightTile( TimerPos1, false, false ); hilightTile( TimerPos2, false ); } // restart timer if( iTimerStep++ < 8 ) timer->start( ANIMSPEED , true ); else showHelp = false; } // --------------------------------------------------------- void BoardWidget::helpMoveStop() { timer->stop(); iTimerStep = 8; hilightTile( TimerPos1, false, false ); hilightTile( TimerPos2, false ); showHelp = false; } // --------------------------------------------------------- void BoardWidget::startDemoMode() { calculateNewGame(); if( TimerState == Stop ) { TimerState = Demo; iTimerStep = 0; emit demoModeChanged( true ); setStatusText( i18n("Demo mode. Click mousebutton to stop.") ); demoMoveTimeout(); } } // --------------------------------------------------------- void BoardWidget::stopDemoMode() { TimerState = Stop; // stop demo calculateNewGame(); setStatusText( i18n("Now it's you again.") ); emit demoModeChanged( false ); emit gameCalculated(); } // --------------------------------------------------------- void BoardWidget::demoMoveTimeout() { if( TimerState == Demo ) { switch( iTimerStep++ % 6 ) { // at firts, find new matching tiles case 0: if( ! findMove( TimerPos1, TimerPos2 ) ) { // if computer has won if( Game.TileNum == 0 ) { animateMoveList(); } // else computer has lost else { setStatusText( i18n("Your computer has lost the game.") ); while( Game.TileNum < Game.MaxTileNum ) { putTile( Game.MoveList[Game.TileNum], false ); Game.TileNum++; putTile( Game.MoveList[Game.TileNum] ); Game.TileNum++; drawTileNumber(); } } TimerState = Stop; startDemoMode(); return; } break; // hilight matching tiles two times case 1: case 3: hilightTile( TimerPos1, true, false ); hilightTile( TimerPos2, true ); break; case 2: case 4: hilightTile( TimerPos1, false, false ); hilightTile( TimerPos2, false ); break; // remove matching tiles from game board case 5: setRemovedTilePair(TimerPos1, TimerPos2); removeTile( TimerPos1, false ); removeTile( TimerPos2 ); drawTileNumber(); break; } // restart timer TQTimer::singleShot( ANIMSPEED, this, TQ_SLOT( demoMoveTimeout() ) ); } } // --------------------------------------------------------- void BoardWidget::setShowMatch( bool show ) { if( showMatch ) stopMatchAnimation(); showMatch = show; } // --------------------------------------------------------- void BoardWidget::matchAnimationTimeout() { if (matchCount == 0) return; if( iTimerStep++ & 1 ) { for(short Pos = 0; Pos < matchCount; Pos++) { hilightTile(PosTable[Pos], true); } } else { for(short Pos = 0; Pos < matchCount; Pos++) { hilightTile(PosTable[Pos], false); } } if( TimerState == Match ) TQTimer::singleShot( ANIMSPEED, this, TQ_SLOT( matchAnimationTimeout() ) ); } // --------------------------------------------------------- void BoardWidget::stopMatchAnimation() { for(short Pos = 0; Pos < matchCount; Pos++) { hilightTile(PosTable[Pos], false); } TimerState = Stop; matchCount = 0; } void BoardWidget::redoMove() { setRemovedTilePair(Game.MoveList[Game.TileNum-1],Game.MoveList[Game.TileNum-2]); removeTile(Game.MoveList[Game.TileNum-1], false); removeTile(Game.MoveList[Game.TileNum-1]); drawTileNumber(); } // --------------------------------------------------------- void BoardWidget::animateMoveList() { setStatusText( i18n("Congratulations. You have won!") ); if (Prefs::playAnimation()) { while( Game.TileNum < Game.MaxTileNum ) { // put back all tiles putTile(Game.MoveList[Game.TileNum]); Game.TileNum++; putTile(Game.MoveList[Game.TileNum], false); Game.TileNum++; drawTileNumber(); } while( Game.TileNum > 0 ) { // remove all tiles removeTile(Game.MoveList[Game.TileNum-1], false); removeTile(Game.MoveList[Game.TileNum-1]); drawTileNumber(); } } calculateNewGame(); } // --------------------------------------------------------- void BoardWidget::calculateNewGame( int gNumber) { cancelUserSelectedTiles(); stopMatchAnimation(); initialiseRemovedTiles(); setStatusText( i18n("Calculating new game...") ); if( !loadBoard()) { setStatusText( i18n("Error converting board information!") ); return; } if (gNumber == -1) { gameGenerationNum = kapp->random(); } else { gameGenerationNum = gNumber; } random.setSeed(gameGenerationNum); // Translate Game.Map to an array of POSITION data. We only need to // do this once for each new game. memset(tilePositions, 0, sizeof(tilePositions)); generateTilePositions(); // Now use the tile position data to generate tile dependency data. // We only need to do this once for each new game. generatePositionDepends(); // Now try to position tiles on the board, 64 tries max. for( short nr=0; nr<64; nr++ ) { if( generateStartPosition2() ) { drawBoard(); setStatusText( i18n("Ready. Now it is your turn.") ); cheatsUsed=0; return; } } drawBoard(); setStatusText( i18n("Error generating new game!") ); } // --------------------------------------------------------- // Generate the position data for the layout from contents of Game.Map. void BoardWidget::generateTilePositions() { numTiles = 0; for (int z=0; z< BoardLayout::depth; z++) { for (int y=0; y (numTiles*numTiles)) { return false; // bail } } while (tilePositions[position].e != 0); // If there are no other free positions on the same apparent // horizontal line, we can mark that position as free. if (onlyFreeInLine(position)) { positionDepends[position].free = true; } } // Check to make sure we really got them all. Very important for // this algorithm. for (int i = 0; i < numTiles; i++) { if (tilePositions[i].e == 0 && onlyFreeInLine(i)) { positionDepends[i].free = true; } } // Get ready to place the tiles int lastPosition = -1; int position = -1; int position2 = -1; // For each position, for (int i = 0; i < numTiles; i++) { // If this is the first tile in a 144 tile set, if ((i % 144) == 0) { // Initialise the faces to allocate. For the classic // dragon board there are 144 tiles. So we allocate and // randomise the assignment of 144 tiles. If there are > 144 // tiles we will reallocate and re-randomise as we run out. // One advantage of this method is that the pairs to assign are // non-linear. In kmahjongg 0.4, If there were > 144 the same // allocation series was followed. So 154 = 144 + 10 rods. // 184 = 144 + 40 rods (20 pairs) which overwhemed the board // with rods and made deadlock games more likely. randomiseFaces(); } // If this is the first half of a pair, there is no previous // position for the pair. if ((i & 1) == 0) { lastPosition = -1; } // Select a position for the tile, relative to the position of // the last tile placed. if ((position = selectPosition(lastPosition)) < 0) { return false; // bail } if (i < numTiles-1) { if ((position2 = selectPosition(lastPosition)) < 0) { return false; // bail } if (tilePositions[position2].e > tilePositions[position].e) { position = position2; // higher is better } } // Place the tile. placeTile(position, tilePair[i % 144]); // Remember the position lastPosition = position; } // The game is solvable. return true; } // --------------------------------------------------------- // Determines whether it is ok to mark this position as "free" because // there are no other positions marked "free" in its apparent horizontal // line. bool BoardWidget::onlyFreeInLine(int position) { int i, i0, w; int lin, rin, out; static int nextLeft[BoardLayout::maxTiles]; static int nextRight[BoardLayout::maxTiles]; /* Check left, starting at position */ lin = 0; out = 0; nextLeft[lin++] = position; do { w = nextLeft[out++]; if (positionDepends[w].free || positionDepends[w].filled) { return false; } if ((i = positionDepends[w].lhs_dep[0]) != -1) { nextLeft[lin++] = i; } i0 = i; if ((i = positionDepends[w].lhs_dep[1]) != -1 && i0 != i) { nextLeft[lin++] = i; } } while (lin > out) ; /* Check right, starting at position */ rin = 0; out = 0; nextRight[rin++] = position; do { w = nextRight[out++]; if (positionDepends[w].free || positionDepends[w].filled) { return false; } if ((i = positionDepends[w].rhs_dep[0]) != -1) { nextRight[rin++] = i; } i0 = i; if ((i = positionDepends[w].rhs_dep[1]) != -1 && i0 != i) { nextRight[rin++] = i; } } while (rin > out) ; // Here, the position can be marked "free" return true; } // --------------------------------------------------------- int BoardWidget::selectPosition(int lastPosition) { int position, cnt = 0; bool goodPosition = false; // while a good position has not been found, while (!goodPosition) { // Select a random, but free, position. do { position = random.getLong(numTiles); if (cnt++ > (numTiles*numTiles)) { return -1; // bail } } while (!positionDepends[position].free); // Found one. goodPosition = true; // If there is a previous position to take into account, if (lastPosition != -1) { // Check the new position against the last one. for (int i = 0; i < 4; i++) { if (positionDepends[position].place_dep[i] == lastPosition) { goodPosition = false; // not such a good position } } for (int i = 0; i < 2; i++) { if ((positionDepends[position].lhs_dep[i] == lastPosition) || (positionDepends[position].rhs_dep[i] == lastPosition)) { goodPosition = false; // not such a good position } } } } return position; } // --------------------------------------------------------- void BoardWidget::placeTile(int position, int tile) { // Install the tile in the specified position tilePositions[position].f = tile; Game.putTile(tilePositions[position]); // Update position dependency data positionDepends[position].filled = true; positionDepends[position].free = false; // Now examine the tiles near this to see if this makes them "free". int depend; for (int i = 0; i < 4; i++) { if ((depend = positionDepends[position].turn_dep[i]) != -1) { updateDepend(depend); } } for (int i = 0; i < 2; i++) { if ((depend = positionDepends[position].lhs_dep[i]) != -1) { updateDepend(depend); } if ((depend = positionDepends[position].rhs_dep[i]) != -1) { updateDepend(depend); } } } // --------------------------------------------------------- // Updates the free indicator in the dependency data for a position // based on whether the positions on which it depends are filled. void BoardWidget::updateDepend(int position) { // If the position is valid and not filled if (position >= 0 && !positionDepends[position].filled) { // Check placement depends. If they are not filled, the // position cannot become free. int depend; for (int i = 0; i < 4; i++) { if ((depend = positionDepends[position].place_dep[i]) != -1) { if (!positionDepends[depend].filled) { return ; } } } // If position is first free on apparent horizontal, it is // now free to be filled. if (onlyFreeInLine(position)) { positionDepends[position].free = true; return; } // Assume no LHS positions to fill bool lfilled = false; // If positions to LHS if ((positionDepends[position].lhs_dep[0] != -1) || (positionDepends[position].lhs_dep[1] != -1)) { // Assume LHS positions filled lfilled = true; for (int i = 0; i < 2; i++) { if ((depend = positionDepends[position].lhs_dep[i]) != -1) { if (!positionDepends[depend].filled) { lfilled = false; } } } } // Assume no RHS positions to fill bool rfilled = false; // If positions to RHS if ((positionDepends[position].rhs_dep[0] != -1) || (positionDepends[position].rhs_dep[1] != -1)) { // Assume LHS positions filled rfilled = true; for (int i = 0; i < 2; i++) { if ((depend = positionDepends[position].rhs_dep[i]) != -1) { if (!positionDepends[depend].filled) { rfilled = false; } } } } // If positions to left or right are filled, this position // is now free to be filled. positionDepends[position].free = (lfilled || rfilled); } } // --------------------------------------------------------- bool BoardWidget::generateStartPosition2() { // For each tile, for (int i = 0; i < numTiles; i++) { // Get its basic position data int x = tilePositions[i].x; int y = tilePositions[i].y; int z = tilePositions[i].e; // Clear Game.Board at that position Game.Board[z][y][x] = 0; // Clear tile placed/free indicator(s). positionDepends[i].filled = false; positionDepends[i].free = false; // Set tile face blank tilePositions[i].f = 254; } // If solvable games should be generated, if (Prefs::solvableGames()) { if (generateSolvableGame()) { Game.TileNum = Game.MaxTileNum; return true; } else { return false; } } // Initialise the faces to allocate. For the classic // dragon board there are 144 tiles. So we allocate and // randomise the assignment of 144 tiles. If there are > 144 // tiles we will reallocate and re-randomise as we run out. // One advantage of this method is that the pairs to assign are // non-linear. In kmahjongg 0.4, If there were > 144 the same // allocation series was followed. So 154 = 144 + 10 rods. // 184 = 144 + 40 rods (20 pairs) which overwhemed the board // with rods and made deadlock games more likely. int remaining = numTiles; randomiseFaces(); for (int tile=0; tile 2) { p2 = p1 = random.getLong(remaining-2); int bail = 0; while (p1 == p2) { p2 = random.getLong(remaining-2); if (bail >= 100) { if (p1 != p2) { break; } } if ((tilePositions[p1].y == tilePositions[p2].y) && (tilePositions[p1].e == tilePositions[p2].e)) { // skip if on same y line bail++; p2=p1; continue; } } } else { p1 = 0; p2 = 1; } POSITION a, b; a = tilePositions[p1]; b = tilePositions[p2]; tilePositions[p1] = tilePositions[remaining - 1]; tilePositions[p2] = tilePositions[remaining - 2]; remaining -= 2; getFaces(a, b); Game.putTile(a); Game.putTile(b); } Game.TileNum = Game.MaxTileNum; return 1; } void BoardWidget::getFaces(POSITION &a, POSITION &b) { a.f = tilePair[tilesUsed]; b.f = tilePair[tilesUsed+1]; tilesUsed += 2; if (tilesUsed >= 144) { randomiseFaces(); } } void BoardWidget::randomiseFaces() { int nr; int numAlloced=0; // stick in 144 tiles in pairsa. for( nr=0; nr<9*4; nr++) tilePair[numAlloced++] = TILE_CHARACTER+(nr/4); // 4*9 Tiles for( nr=0; nr<9*4; nr++) tilePair[numAlloced++] = TILE_BAMBOO+(nr/4); // 4*9 Tiles for( nr=0; nr<9*4; nr++) tilePair[numAlloced++] = TILE_ROD+(nr/4); // 4*9 Tiles for( nr=0; nr<4; nr++) tilePair[numAlloced++] = TILE_FLOWER+nr; // 4 Tiles for( nr=0; nr<4; nr++) tilePair[numAlloced++] = TILE_SEASON+nr; // 4 Tiles for( nr=0; nr<4*4; nr++) tilePair[numAlloced++] = TILE_WIND+(nr/4); // 4*4 Tiles for( nr=0; nr<3*4; nr++) tilePair[numAlloced++] = TILE_DRAGON+(nr/4); // 3*4 Tiles //randomise. Keep pairs together. Ie take two random //odd numbers (n,x) and swap n, n+1 with x, x+1 int at=0; int to=0; for (int r=0; r<200; r++) { to=at; while (to==at) { to = random.getLong(144); if ((to & 1) != 0) to--; } UCHAR tmp = tilePair[at]; tilePair[at] = tilePair[to]; tilePair[to] = tmp; tmp = tilePair[at+1]; tilePair[at+1] = tilePair[to+1]; tilePair[to+1] = tmp; at+=2; if (at >= 144) at =0; } tilesAllocated = numAlloced; tilesUsed = 0; } // --------------------------------------------------------- bool isFlower( UCHAR Tile ) { return( Tile >= TILE_FLOWER && Tile <=TILE_FLOWER+3 ); } bool isSeason( UCHAR Tile ) { return( Tile >= TILE_SEASON && Tile <=TILE_SEASON+3 ); } bool isBamboo(UCHAR t) { return( t >= TILE_BAMBOO && t = TILE_CHARACTER && t = TILE_ROD && t = TILE_DRAGON && t < TILE_DRAGON +3); } bool isWind(UCHAR t) { return( t >= TILE_WIND && t < TILE_WIND +4); } bool BoardWidget::isMatchingTile( POSITION& Pos1, POSITION& Pos2 ) { // don't compare 'equal' positions if( memcmp( &Pos1, &Pos2, sizeof(POSITION) ) ) { UCHAR FA = Pos1.f; UCHAR FB = Pos2.f; if( (FA == FB) || ( isFlower( FA ) && isFlower( FB ) ) || ( isSeason( FA ) && isSeason( FB ) ) ) return( true ); } return( false ); } // --------------------------------------------------------- bool BoardWidget::findMove( POSITION& posA, POSITION& posB ) { short Pos_Ende = Game.MaxTileNum; // Ende der PosTable for( short E=0; E=2 ) { random.setSeed(0); // WABA: Why is the seed reset? short Pos = random.getLong(iPosCount) & -2; // Gerader Wert posA = PosTable[Pos]; posB = PosTable[Pos+1]; return( true ); } else return( false ); } int BoardWidget::moveCount( ) { short Pos_Ende = Game.MaxTileNum; // end of PosTable for( short E=0; Ebutton() == TQt::LeftButton ) { if( TimerState == Demo ) { stopDemoMode(); } else if( showMatch ) { stopMatchAnimation(); } if( showHelp ) // stop hilighting tiles helpMoveStop(); if( MouseClickPos1.e == BoardLayout::depth ) // first tile { transformPointToPosition( event->pos(), MouseClickPos1 ); if( MouseClickPos1.e != BoardLayout::depth && showMatch ) { matchCount = findAllMatchingTiles( MouseClickPos1 ); TimerState = Match; iTimerStep = 1; matchAnimationTimeout(); cheatsUsed++; } } else // second tile { transformPointToPosition( event->pos(), MouseClickPos2 ); if( MouseClickPos2.e == BoardLayout::depth ) { cancelUserSelectedTiles(); } else { if( isMatchingTile( MouseClickPos1, MouseClickPos2 ) ) { // update the removed tiles (we do this before the remove below // so that we only require 1 screen paint for both actions) setRemovedTilePair(MouseClickPos1, MouseClickPos2); // now we remove the tiles from the board removeTile(MouseClickPos1, false); removeTile(MouseClickPos2); // removing a tile means redo is impossible without // a further undo. Game.allow_redo=false; demoModeChanged(false); drawTileNumber(); // if no tiles are left, the player has `won`, so celebrate if( Game.TileNum == 0 ) { gameOver(Game.MaxTileNum,cheatsUsed); } // else if no more moves are possible, display the sour grapes dialog else if( ! findMove( TimerPos1, TimerPos2 ) ) { KMessageBox::information(this, i18n("Game over: You have no moves left.")); } } else { // redraw tiles in normal state hilightTile( MouseClickPos1, false, false ); hilightTile( MouseClickPos2, false ); } MouseClickPos1.e = BoardLayout::depth; // mark tile position as invalid MouseClickPos2.e = BoardLayout::depth; } } } } // ---------------------------------------------------------- /** Transform window point to board position. @param point Input: Point in window coordinates @param MouseClickPos Output: Position in game board */ void BoardWidget::transformPointToPosition( const TQPoint& point, POSITION& MouseClickPos ) { short E,X,Y; // iterate over E coordinate from top to bottom for( E=BoardLayout::depth-1; E>=0; E-- ) { // calculate mouse coordiantes --> position in game board // the factor -theTiles.width()/2 must keep track with the // offset for blitting in the print Event (FIX ME) X = ((point.x()-theTiles.width()/2)- (E+1)*theTiles.shadowSize()) / theTiles.qWidth(); Y = ((point.y()-theTiles.height()/2) + E*theTiles.shadowSize()) / theTiles.qHeight(); // changed to allow x == 0 // skip when position is illegal if (X<0 || X>=BoardLayout::width || Y<0 || Y>=BoardLayout::height) continue; // switch( Game.Mask[E][Y][X] ) { case (UCHAR)'3': X--;Y--; break; case (UCHAR)'2': X--; break; case (UCHAR)'4': Y--; break; case (UCHAR)'1': break; default : continue; } // if gameboard is empty, skip if ( ! Game.Board[E][Y][X] ) continue; // tile must be 'free' (nothing left, right or above it) if( E < 4 ) { if( Game.Board[E+1][Y][X] || Game.Board[E+1][Y+1][X] || (X 0) && (Game.Board[E][Y][X-1] || Game.Board[E][Y+1][X-1])) { if ((X