Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

minefield.cpp

Go to the documentation of this file.
00001 /**********************************************************************
00002 ** Copyright (C) 2000-2002 Trolltech AS.  All rights reserved.
00003 **
00004 ** This file is part of the Qtopia Environment.
00005 **
00006 ** This file may be distributed and/or modified under the terms of the
00007 ** GNU General Public License version 2 as published by the Free Software
00008 ** Foundation and appearing in the file LICENSE.GPL included in the
00009 ** packaging of this file.
00010 **
00011 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00012 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00013 **
00014 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
00015 **
00016 ** Contact info@trolltech.com if any conditions of this licensing are
00017 ** not clear to you.
00018 **
00019 **********************************************************************/
00020 #include "minefield.h"
00021 
00022 #include <qtopia/config.h>
00023 #include <qtopia/qpeapplication.h>
00024 
00025 #include <qtimer.h>
00026 
00027 #include <stdlib.h>
00028 
00029 static const char *pix_flag[]={
00030 "13 13 3 1",
00031 "# c #000000",
00032 "x c #ff0000",
00033 ". c None",
00034 ".............",
00035 ".............",
00036 ".....#xxxxxx.",
00037 ".....#xxxxxx.",
00038 ".....#xxxxxx.",
00039 ".....#xxxxxx.",
00040 ".....#.......",
00041 ".....#.......",
00042 ".....#.......",
00043 ".....#.......",
00044 "...#####.....",
00045 "..#######....",
00046 "............."};
00047 
00048 static const char *pix_mine[]={
00049 "13 13 3 1",
00050 "# c #000000",
00051 ". c None",
00052 "a c #ffffff",
00053 "......#......",
00054 "......#......",
00055 "..#.#####.#..",
00056 "...#######...",
00057 "..##aa#####..",
00058 "..##aa#####..",
00059 "#############",
00060 "..#########..",
00061 "..#########..",
00062 "...#######...",
00063 "..#.#####.#..",
00064 "......#......",
00065 "......#......"};
00066 
00067 
00068 static const int maxGrid = 28;
00069 static const int minGrid = 12;
00070 
00071 
00072 
00073 class Mine : public Qt
00074 {
00075 public:
00076     enum MineState {
00077         Hidden = 0,
00078         Empty,
00079         Mined,
00080         Flagged,
00081 #ifdef MARK_UNSURE
00082         Unsure,
00083 #endif
00084         Exploded,
00085         Wrong
00086     };
00087 
00088     Mine( MineField* );
00089     void paint( QPainter * p, const QColorGroup & cg, const QRect & cr );
00090 
00091     QSize sizeHint() const { return QSize( maxGrid, maxGrid ); }
00092 
00093     void activate( bool sure = TRUE );
00094     void setHint( int );
00095 
00096     void setState( MineState );
00097     MineState state() const { return st; }
00098 
00099     bool isMined() const { return mined; }
00100     void setMined( bool m ) { mined = m; }
00101 
00102     static void paletteChange();
00103 
00104 private:
00105     bool mined;
00106     int hint;
00107 
00108     MineState st;
00109     MineField *field;
00110     
00111     static QPixmap* knownField;
00112     static QPixmap* unknownField;
00113     static QPixmap* flag_pix;
00114     static QPixmap* mine_pix;
00115 };
00116 
00117 QPixmap* Mine::knownField = 0;
00118 QPixmap* Mine::unknownField = 0;
00119 QPixmap* Mine::flag_pix = 0;
00120 QPixmap* Mine::mine_pix = 0;
00121 
00122 Mine::Mine( MineField *f )
00123 {
00124     mined = FALSE;
00125     st = Hidden;
00126     hint = 0;
00127     field = f;
00128 }
00129 
00130 void Mine::activate( bool sure )
00131 {
00132     if ( !sure ) {
00133         switch ( st ) {
00134         case Hidden:
00135             setState( Flagged );
00136             break;
00137         case Flagged:
00138 #ifdef MARK_UNSURE
00139             setState( Unsure );
00140             break;
00141         case Unsure:
00142 #endif
00143             setState( Hidden );
00144         default:
00145             break;
00146         }
00147     } else if ( st == Flagged ) {
00148         return;
00149     } else {
00150         if ( mined ) {
00151             setState( Exploded );
00152         } else {
00153             setState( Empty );
00154         }
00155     }
00156 }
00157 
00158 void Mine::setState( MineState s )
00159 {
00160     st = s;
00161 }
00162 
00163 void Mine::setHint( int h )
00164 {
00165     hint = h;
00166 }
00167 
00168 void Mine::paletteChange()
00169 {
00170     delete knownField;
00171     knownField = 0;
00172     delete unknownField;
00173     unknownField = 0;
00174     delete mine_pix;
00175     mine_pix = 0;
00176     delete flag_pix;
00177     flag_pix = 0;
00178 }
00179 
00180 void Mine::paint( QPainter* p, const QColorGroup &cg, const QRect& cr )
00181 {
00182     int x = cr.x();
00183     int y = cr.y();
00184     if ( !knownField || knownField->width() != cr.width() ||
00185          knownField->height() != cr.height() ) {
00186         delete knownField;
00187         knownField = new QPixmap( cr.width(), cr.height() );
00188         QPainter pp( knownField );
00189         QBrush br( cg.button().dark(115) );
00190         qDrawWinButton( &pp, QRect( 0, 0, cr.width(), cr.height() ), cg, TRUE, &br );
00191     }
00192 
00193     const int pmmarg=cr.width()/5;
00194 
00195     if ( !unknownField || unknownField->width() != cr.width() ||
00196          unknownField->height() != cr.height() ) {
00197         delete unknownField;
00198         unknownField = new QPixmap( cr.width(), cr.height() );
00199         QPainter pp( unknownField );
00200         QBrush br( cg.button() );
00201         qDrawWinButton( &pp, QRect( 0, 0, cr.width(), cr.height() ), cg, FALSE, &br );
00202     }
00203 
00204     if ( !flag_pix || flag_pix->width() != cr.width()-pmmarg*2 ||
00205          flag_pix->height() != cr.height()-pmmarg*2 ) {
00206         delete flag_pix;
00207         flag_pix = new QPixmap( cr.width()-pmmarg*2, cr.height()-pmmarg*2 );
00208         flag_pix->convertFromImage( QImage(pix_flag).smoothScale(cr.width()-pmmarg*2, cr.height()-pmmarg*2) );
00209     }
00210 
00211     if ( !mine_pix || mine_pix->width() != cr.width()-pmmarg*2 ||
00212          mine_pix->height() != cr.height()-pmmarg*2 ) {
00213         delete mine_pix;
00214         mine_pix = new QPixmap( cr.width()-pmmarg*2, cr.height()-pmmarg*2 );
00215         mine_pix->convertFromImage( QImage(pix_mine).smoothScale(cr.width()-pmmarg*2, cr.height()-pmmarg*2) );
00216     }
00217 
00218     p->save();
00219 
00220     switch(st) {
00221     case Hidden:
00222         p->drawPixmap( x, y, *unknownField );
00223         break;
00224     case Empty:
00225         p->drawPixmap( x, y, *knownField );
00226         if ( hint > 0 ) {
00227             switch( hint ) {
00228             case 1:
00229                 p->setPen( blue );
00230                 break;
00231             case 2:
00232                 p->setPen( green.dark() );
00233                 break;
00234             case 3:
00235                 p->setPen( red );
00236                 break;
00237             case 4:
00238                 p->setPen( darkYellow.dark() );
00239                 break;
00240             case 5:
00241                 p->setPen( darkMagenta );
00242                 break;
00243             case 6:
00244                 p->setPen( darkRed );
00245                 break;
00246             default:
00247                 p->setPen( black );
00248                 break;
00249             }
00250             p->drawText( cr, AlignHCenter | AlignVCenter, QString::number( hint ) );
00251         }
00252         break;
00253     case Mined:
00254         p->drawPixmap( x, y, *knownField );
00255         p->drawPixmap( x+pmmarg, y+pmmarg, *mine_pix );
00256         break;
00257     case Exploded:
00258         p->drawPixmap( x, y, *knownField );
00259         p->drawPixmap( x+pmmarg, y+pmmarg, *mine_pix );
00260         p->setPen( red );
00261         p->drawText( cr, AlignHCenter | AlignVCenter, "X" );
00262         break;
00263     case Flagged:
00264         p->drawPixmap( x, y, *unknownField );
00265         p->drawPixmap( x+pmmarg, y+pmmarg, *flag_pix );
00266         break;
00267 #ifdef MARK_UNSURE
00268     case Unsure:
00269         p->drawPixmap( x, y, *unknownField );
00270         p->drawText( cr, AlignHCenter | AlignVCenter, "?" );
00271         break;
00272 #endif
00273     case Wrong:
00274         p->drawPixmap( x, y, *unknownField );
00275         p->drawPixmap( x+pmmarg, y+pmmarg, *flag_pix );
00276         p->setPen( red );
00277         p->drawText( cr, AlignHCenter | AlignVCenter, "X" );
00278         break;
00279     }
00280 
00281     p->restore();
00282 }
00283 
00284 /*
00285   MineField implementation
00286 */
00287 
00288 MineField::MineField( QWidget* parent, const char* name )
00289 : QScrollView( parent, name )
00290 {
00291     viewport()->setBackgroundMode( NoBackground );
00292     setState( GameOver );
00293 
00294     setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
00295     
00296     setFocusPolicy( QWidget::NoFocus );
00297 
00298     holdTimer = new QTimer( this );
00299     connect( holdTimer, SIGNAL( timeout() ), this, SLOT( held() ) );
00300 
00301     flagAction = NoAction;
00302     ignoreClick = FALSE;
00303     currRow = currCol = -1;
00304     minecount=0;
00305     mineguess=0;
00306     nonminecount=0;
00307     cellSize = -1;
00308 
00309     numRows = numCols = 0;
00310     mines = NULL;
00311 }
00312 
00313 MineField::~MineField()
00314 {
00315     for ( int i = 0; i < numCols*numRows; i++ )
00316         delete mines[i];
00317     delete[] mines;
00318 }
00319 
00320 void MineField::setState( State st )
00321 {
00322     stat = st;
00323 }
00324 
00325 void MineField::setup( int level )
00326 {
00327     lev = level;
00328     setState( Waiting );
00329     //viewport()->setUpdatesEnabled( FALSE );
00330 
00331     int i;
00332     for ( i = 0; i < numCols*numRows; i++ )
00333         delete mines[i];
00334     delete[] mines;
00335 
00336     switch( lev ) {
00337     case 1:
00338         numRows = 9 ;
00339         numCols = 9 ;
00340         minecount = 12;
00341         break;
00342     case 2:
00343         numRows = 13;
00344         numCols = 13;
00345         minecount = 33;
00346         break;
00347     case 3:
00348         numCols = 18;
00349         numRows = 18;
00350         minecount = 66 ;
00351         break;
00352     }
00353     mines = new Mine* [numRows*numCols];
00354     for ( i = 0; i < numCols*numRows; i++ )
00355         mines[i] = new Mine( this );
00356 
00357     
00358     nonminecount = numRows*numCols - minecount;
00359     mineguess = minecount;
00360     emit mineCount( mineguess );
00361     Mine::paletteChange();
00362 
00363     if ( availableRect.isValid() )
00364         setCellSize(findCellSize());
00365     //    viewport()->setUpdatesEnabled( TRUE );
00366     //viewport()->repaint( TRUE );
00367     updateContents( 0, 0, numCols*cellSize, numRows*cellSize );
00368     updateGeometry();
00369 }
00370 
00371 void MineField::drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ) 
00372 {
00373     int c1 = clipx / cellSize;
00374     int c2 = ( clipx + clipw - 1 ) / cellSize;
00375     int r1 = clipy / cellSize;
00376     int r2 = ( clipy + cliph - 1 ) / cellSize;
00377     
00378     for ( int c = c1; c <= c2 ; c++ ) {
00379         for ( int r = r1; r <= r2 ; r++ ) {
00380             int x = c * cellSize;
00381             int y = r * cellSize;
00382             Mine *m = mine( r, c );
00383             if ( m )
00384                 m->paint( p, colorGroup(), QRect(x, y, cellSize, cellSize ) );
00385         }
00386     }
00387 }
00388 
00389 
00390 // Chicken and egg problem: We need to know how big the parent is
00391 // before we can decide how big to make the table.
00392 
00393 void MineField::setAvailableRect( const QRect &r )
00394 {
00395     availableRect = r;
00396     int newCellSize = findCellSize();
00397 
00398 
00399     if ( newCellSize == cellSize ) {
00400         setCellSize( cellSize );
00401     } else {
00402         viewport()->setUpdatesEnabled( FALSE );
00403         setCellSize( newCellSize );
00404         viewport()->setUpdatesEnabled( TRUE );
00405         viewport()->repaint( TRUE );
00406     }
00407 }
00408 
00409 int MineField::findCellSize()
00410 {
00411     int w = availableRect.width() - 2;
00412     int h = availableRect.height() - 2;
00413     int cellsize;
00414     
00415     cellsize = QMIN( w/numCols, h/numRows );
00416     cellsize = QMIN( QMAX( cellsize, minGrid ), maxGrid );
00417     return cellsize;
00418 }
00419 
00420 
00421 void MineField::setCellSize( int cellsize )
00422 {
00423     int b = 2;
00424     
00425     int w2 = cellsize*numCols;
00426     int h2 = cellsize*numRows;
00427     
00428     int w = QMIN( availableRect.width(), w2+b );
00429     int h = QMIN( availableRect.height(), h2+b );
00430 
00431     //
00432     // Don't rely on the change in cellsize to force a resize,
00433     // as it's possible to have the same size cells when going
00434     // from a large play area to a small one.
00435     //
00436     resizeContents(w2, h2);
00437 
00438     if ( availableRect.height() < h2 &&
00439          availableRect.width() - w > style().scrollBarExtent().width() ) {
00440         w += style().scrollBarExtent().width();
00441     }
00442     
00443     setGeometry( availableRect.x() + (availableRect.width()-w)/2,
00444             availableRect.y() + (availableRect.height()-h)/2, w, h );
00445     cellSize = cellsize;
00446 }
00447 
00448 
00449 void MineField::placeMines()
00450 {
00451     int mines = minecount;
00452     while ( mines ) {
00453         int col = int((double(rand()) / double(RAND_MAX)) * numCols);
00454         int row = int((double(rand()) / double(RAND_MAX)) * numRows);
00455 
00456         Mine* m = mine( row, col );
00457 
00458         if ( m && !m->isMined() && m->state() == Mine::Hidden ) {
00459             m->setMined( TRUE );
00460             mines--;
00461         }
00462     }
00463 }
00464 
00465 
00466 void MineField::updateCell( int r, int c )
00467 {
00468     updateContents( c*cellSize, r*cellSize, cellSize, cellSize );
00469 }
00470 
00471 
00472 void MineField::contentsMousePressEvent( QMouseEvent* e )
00473 {
00474     int c = e->pos().x() / cellSize;
00475     int r = e->pos().y() / cellSize;
00476     if ( onBoard( r, c ) )
00477         cellPressed( r, c );
00478     else
00479         currCol = currRow = -1;
00480 }
00481 
00482 void MineField::contentsMouseReleaseEvent( QMouseEvent* e )
00483 {
00484     int c = e->pos().x() / cellSize;
00485     int r = e->pos().y() / cellSize;
00486     if ( onBoard( r, c ) && c == currCol && r == currRow )
00487         cellClicked( r, c );
00488     
00489     
00490     if ( flagAction == FlagNext ) {
00491         flagAction = NoAction;
00492     }
00493 }
00494 
00495 
00496 
00497 /*
00498  state == Waiting means no "hold"
00499 
00500  
00501 */
00502 void MineField::cellPressed( int row, int col )
00503 {
00504     if ( state() == GameOver ) 
00505         return;
00506     currRow = row;
00507     currCol = col;
00508     if ( state() == Playing )
00509         holdTimer->start( 150, TRUE );
00510 }
00511 
00512 void MineField::held()
00513 {
00514     flagAction = FlagNext;
00515     updateMine( currRow, currCol );
00516     ignoreClick = TRUE;
00517 }
00518 
00519 
00520 
00521 
00522 void MineField::keyPressEvent( QKeyEvent* e )
00523 {
00524 #if defined(Q_WS_QWS) || defined(_WS_QWS_)
00525     flagAction = ( e->key() == Key_Up ) ? FlagOn : NoAction;
00526 #else
00527     flagAction = ( ( e->state() & ShiftButton ) ==  ShiftButton ) ? FlagOn : NoAction;
00528 #endif
00529 }
00530 
00531 void MineField::keyReleaseEvent( QKeyEvent* )
00532 {
00533     flagAction = NoAction;
00534 }
00535 
00536 int MineField::getHint( int row, int col )
00537 {
00538     int hint = 0;
00539     for ( int c = col-1; c <= col+1; c++ )
00540         for ( int r = row-1; r <= row+1; r++ ) {
00541             Mine* m = mine( r, c );
00542             if ( m && m->isMined() )
00543                 hint++;
00544         }
00545 
00546     return hint;
00547 }
00548 
00549 void MineField::setHint( int row, int col )
00550 {
00551     Mine *m = mine( row, col );
00552     if ( !m )
00553         return;
00554 
00555     int hint = getHint( row, col );
00556 
00557     if ( !hint ) {
00558         for ( int c = col-1; c <= col+1; c++ )
00559             for ( int r = row-1; r <= row+1; r++ ) {
00560                 Mine* m = mine( r, c );
00561                 if ( m && m->state() == Mine::Hidden ) {
00562                     m->activate( TRUE );
00563                     nonminecount--;
00564                     setHint( r, c );
00565                     updateCell( r, c );
00566                 }
00567             }
00568     }
00569 
00570     m->setHint( hint );
00571     updateCell( row, col );
00572 }
00573 
00574 /*
00575   Only place mines after first click, since it is pointless to
00576   kill the player before the game has started.
00577 */
00578 
00579 void MineField::cellClicked( int row, int col )
00580 {
00581     if ( state() == GameOver )
00582         return;
00583     if ( state() == Waiting ) {
00584         Mine* m = mine( row, col );
00585         if ( !m )
00586             return;
00587         m->setState( Mine::Empty );
00588         nonminecount--;
00589         placeMines();
00590         setState( Playing );
00591         emit gameStarted();
00592         updateMine( row, col );
00593     } else { // state() == Playing
00594         holdTimer->stop();
00595         if ( ignoreClick )
00596             ignoreClick = FALSE;
00597         else
00598             updateMine( row, col );
00599     }
00600 }
00601 
00602 void MineField::updateMine( int row, int col )
00603 {
00604     Mine* m = mine( row, col );
00605     if ( !m )
00606         return;
00607 
00608     bool wasFlagged = m->state() == Mine::Flagged;
00609     bool wasEmpty =  m->state() == Mine::Empty;
00610     
00611     m->activate( flagAction == NoAction );
00612 
00613     if ( m->state() == Mine::Exploded ) {
00614         emit gameOver( FALSE );
00615         setState( GameOver );
00616         return;
00617     } else if ( m->state() == Mine::Empty ) {
00618         setHint( row, col );
00619         if ( !wasEmpty )
00620             nonminecount--;
00621     }
00622 
00623     if ( flagAction != NoAction ) {
00624         if ( m->state() == Mine::Flagged ) {
00625             if (mineguess > 0) {
00626                 --mineguess;
00627                 emit mineCount( mineguess );
00628                 if ( m->isMined() )
00629                     --minecount;
00630             } else {
00631                 m->setState(Mine::Hidden);
00632             }
00633         } else if ( wasFlagged ) {
00634             ++mineguess;
00635             emit mineCount( mineguess );
00636             if ( m->isMined() )
00637                 ++minecount;
00638         }
00639     }
00640 
00641     updateCell( row, col );
00642 
00643     if ( !minecount && !mineguess || !nonminecount ) {
00644         emit gameOver( TRUE );
00645         setState( GameOver );
00646     }
00647 }
00648 
00649 void MineField::showMines()
00650 {
00651     for ( int c = 0; c < numCols; c++ )
00652         for ( int r = 0; r < numRows; r++ ) {
00653             Mine* m = mine( r, c );
00654             if ( !m )
00655                 continue;
00656             if ( m->isMined() && m->state() == Mine::Hidden )
00657                 m->setState( Mine::Mined );
00658             if ( !m->isMined() && m->state() == Mine::Flagged )
00659                 m->setState( Mine::Wrong );
00660 
00661             updateCell( r, c );
00662         }
00663 }
00664 
00665 void MineField::paletteChange( const QPalette &o )
00666 {
00667     Mine::paletteChange();
00668     QScrollView::paletteChange( o );
00669 }
00670 
00671 void MineField::writeConfig(Config& cfg) const
00672 {
00673     cfg.setGroup("Field");
00674     cfg.writeEntry("Level",lev);
00675     QString grid="";
00676     if ( stat == Playing ) {
00677         for ( int x = 0; x < numCols; x++ )
00678             for ( int y = 0; y < numRows; y++ ) {
00679                 char code='A'+(x*17+y*101)%21; // Reduce the urge to cheat
00680                 const Mine* m = mine( y, x );
00681                 int st = (int)m->state(); if ( m->isMined() ) st+=5;
00682                 grid += code + st;
00683             }
00684     }
00685     cfg.writeEntry("Grid",grid);
00686 }
00687 
00688 void MineField::readConfig(Config& cfg)
00689 {
00690     cfg.setGroup("Field");
00691     lev = cfg.readNumEntry("Level",1);
00692     setup(lev);
00693     flagAction = NoAction;
00694     ignoreClick = FALSE;
00695     currRow = currCol = 0;
00696     QString grid = cfg.readEntry("Grid");
00697     int x;
00698     if ( !grid.isEmpty() ) {
00699         int i=0;
00700         minecount=0;
00701         mineguess=0;
00702         for ( x = 0; x < numCols; x++ ) {
00703             for ( int y = 0; y < numRows; y++ ) {
00704                 char code='A'+(x*17+y*101)%21; // Reduce the urge to cheat
00705                 int st = (char)(QChar)grid[i++]-code;
00706                 Mine* m = mine( y, x );
00707                 if ( st >= 5 ) {
00708                     st-=5;
00709                     m->setMined(TRUE);
00710                     minecount++;
00711                     mineguess++;
00712                 }
00713                 m->setState((Mine::MineState)st);
00714                 switch ( m->state() ) {
00715                   case Mine::Flagged:
00716                     if (m->isMined())
00717                         minecount--;
00718                     mineguess--;
00719                     break;
00720                   case Mine::Empty:
00721                     --nonminecount;
00722                     break;
00723                 default:
00724                     break;
00725                 }
00726             }
00727         }
00728         for ( x = 0; x < numCols; x++ ) {
00729             for ( int y = 0; y < numRows; y++ ) {
00730                 Mine* m = mine( y, x );
00731                 if ( m->state() == Mine::Empty )
00732                     m->setHint(getHint(y,x));
00733             }
00734         }
00735     }
00736     setState( Playing );
00737     emit mineCount( mineguess );
00738 }
00739 
00740 QSize MineField::sizeHint() const
00741 {
00742     if ( qApp->desktop()->width() >= 240 )
00743         return QSize(200,200);
00744     else
00745         return QSize(160, 160);
00746 }
00747 

Generated on Sat Nov 5 16:17:22 2005 for OPIE by  doxygen 1.4.2