PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/kdegames-4.8.97/kmines/minefielditem.cpp

#
C++ | 607 lines | 472 code | 72 blank | 63 comment | 175 complexity | 10bcbe8ac4482311c6a9de4bf897424a MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0
  1. /*
  2. Copyright 2007 Dmitry Suzdalev <dimsuz@gmail.com>
  3. Copyright 2010 Brian Croom <brian.s.croom@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  15. */
  16. #include "minefielditem.h"
  17. #include <kdebug.h>
  18. #include <QGraphicsScene>
  19. #include <QGraphicsSceneMouseEvent>
  20. #include "cellitem.h"
  21. #include "borderitem.h"
  22. MineFieldItem::MineFieldItem(KGameRenderer* renderer)
  23. : m_leftButtonPos(-1,-1), m_midButtonPos(-1,-1), m_gameOver(false),
  24. m_emulatingMidButton(false), m_renderer(renderer)
  25. {
  26. setFlag(QGraphicsItem::ItemHasNoContents);
  27. }
  28. void MineFieldItem::initField( int numRows, int numCols, int numMines )
  29. {
  30. numMines = qMin(numMines, numRows*numCols - MINIMAL_FREE );
  31. m_firstClick = true;
  32. m_gameOver = false;
  33. int oldSize = m_cells.size();
  34. int newSize = numRows*numCols;
  35. int oldBorderSize = m_borders.size();
  36. int newBorderSize = (numCols+2)*2 + (numRows+2)*2-4;
  37. // if field is being shrinked, delete elements at the end before resizing vector
  38. if(oldSize > newSize)
  39. {
  40. for( int i=newSize; i<oldSize; ++i )
  41. {
  42. // is this the best way to remove an item?
  43. scene()->removeItem(m_cells[i]);
  44. delete m_cells[i];
  45. }
  46. // adjust border item array too
  47. for( int i=newBorderSize; i<oldBorderSize; ++i)
  48. {
  49. scene()->removeItem(m_borders[i]);
  50. delete m_borders[i];
  51. }
  52. }
  53. m_cells.resize(newSize);
  54. m_borders.resize(newBorderSize);
  55. m_numRows = numRows;
  56. m_numCols = numCols;
  57. m_minesCount = numMines;
  58. m_numUnrevealed = m_numRows*m_numCols;
  59. m_midButtonPos = qMakePair(-1, -1);
  60. m_leftButtonPos = qMakePair(-1, -1);
  61. for(int i=0; i<newSize; ++i)
  62. {
  63. // reset old, create new
  64. if(i<oldSize)
  65. m_cells[i]->reset();
  66. else
  67. m_cells[i] = new CellItem(m_renderer, this);
  68. // let it be empty by default
  69. // generateField() will adjust needed cells
  70. // to hold digits or mines
  71. m_cells[i]->setDigit(0);
  72. }
  73. for(int i=oldBorderSize; i<newBorderSize; ++i)
  74. m_borders[i] = new BorderItem(m_renderer, this);
  75. setupBorderItems();
  76. adjustItemPositions();
  77. m_flaggedMinesCount = 0;
  78. emit flaggedMinesCountChanged(m_flaggedMinesCount);
  79. }
  80. void MineFieldItem::generateField(int clickedIdx)
  81. {
  82. // generating mines ensuring that clickedIdx won't hold mine
  83. // and that it will be an empty cell so the user don't have
  84. // to make random guesses at the start of the game
  85. QList<int> cellsWithMines;
  86. int minesToPlace = m_minesCount;
  87. int randomIdx = 0;
  88. CellItem* item = 0;
  89. FieldPos fp = rowColFromIndex(clickedIdx);
  90. // this is the list of items we don't want to put the mine in
  91. // to ensure that clickedIdx will stay an empty cell
  92. // (it will be empty if none of surrounding items holds mine)
  93. QList<CellItem*> neighbForClicked = adjasentItemsFor(fp.first, fp.second);
  94. while(minesToPlace != 0)
  95. {
  96. randomIdx = m_randomSeq.getLong( m_numRows*m_numCols );
  97. item = m_cells.at(randomIdx);
  98. if(!item->hasMine()
  99. && neighbForClicked.indexOf(item) == -1
  100. && randomIdx != clickedIdx)
  101. {
  102. // ok, let's mine this place! :-)
  103. item->setHasMine(true);
  104. cellsWithMines.append(randomIdx);
  105. minesToPlace--;
  106. }
  107. else
  108. continue;
  109. }
  110. foreach(int idx, cellsWithMines)
  111. {
  112. FieldPos rc = rowColFromIndex(idx);
  113. QList<CellItem*> neighbours = adjasentItemsFor(rc.first, rc.second);
  114. foreach( CellItem *item, neighbours )
  115. {
  116. if(!item->hasMine())
  117. item->setDigit( item->digit()+1 );
  118. }
  119. }
  120. }
  121. void MineFieldItem::setupBorderItems()
  122. {
  123. int i = 0;
  124. for(int row=0; row<m_numRows+2; ++row)
  125. for(int col=0; col<m_numCols+2; ++col)
  126. {
  127. if( row == 0 && col == 0)
  128. {
  129. m_borders.at(i)->setRowCol(0,0);
  130. m_borders.at(i)->setBorderType(KMinesState::BorderCornerNW);
  131. i++;
  132. }
  133. else if( row == 0 && col == m_numCols+1)
  134. {
  135. m_borders.at(i)->setRowCol(row,col);
  136. m_borders.at(i)->setBorderType(KMinesState::BorderCornerNE);
  137. i++;
  138. }
  139. else if( row == m_numRows+1 && col == 0 )
  140. {
  141. m_borders.at(i)->setRowCol(row,col);
  142. m_borders.at(i)->setBorderType(KMinesState::BorderCornerSW);
  143. i++;
  144. }
  145. else if( row == m_numRows+1 && col == m_numCols+1 )
  146. {
  147. m_borders.at(i)->setRowCol(row,col);
  148. m_borders.at(i)->setBorderType(KMinesState::BorderCornerSE);
  149. i++;
  150. }
  151. else if( row == 0 )
  152. {
  153. m_borders.at(i)->setRowCol(row,col);
  154. m_borders.at(i)->setBorderType(KMinesState::BorderNorth);
  155. i++;
  156. }
  157. else if( row == m_numRows+1 )
  158. {
  159. m_borders.at(i)->setRowCol(row,col);
  160. m_borders.at(i)->setBorderType(KMinesState::BorderSouth);
  161. i++;
  162. }
  163. else if( col == 0 )
  164. {
  165. m_borders.at(i)->setRowCol(row,col);
  166. m_borders.at(i)->setBorderType(KMinesState::BorderWest);
  167. i++;
  168. }
  169. else if( col == m_numCols+1 )
  170. {
  171. m_borders.at(i)->setRowCol(row,col);
  172. m_borders.at(i)->setBorderType(KMinesState::BorderEast);
  173. i++;
  174. }
  175. }
  176. }
  177. QRectF MineFieldItem::boundingRect() const
  178. {
  179. // +2 - because of border on each side
  180. return QRectF(0, 0, m_cellSize*(m_numCols+2), m_cellSize*(m_numRows+2));
  181. }
  182. void MineFieldItem::paint( QPainter * painter, const QStyleOptionGraphicsItem* opt, QWidget* w)
  183. {
  184. Q_UNUSED(painter);
  185. Q_UNUSED(opt);
  186. Q_UNUSED(w);
  187. }
  188. void MineFieldItem::resizeToFitInRect(const QRectF& rect)
  189. {
  190. prepareGeometryChange();
  191. // +2 in some places - because of border on each side
  192. // here follows "cooomplex" algorithm to choose which side to
  193. // take when calculating cell size by dividing this side by
  194. // numRows or numCols correspondingly
  195. // it's cooomplex, because I have to paint some figures on paper
  196. // to understand that criteria for choosing one side or another (for
  197. // determining cell size from it) is comparing
  198. // cols/r.width() and rows/r.height():
  199. bool chooseHorizontalSide = (m_numCols+2) / rect.width() > (m_numRows+2) / rect.height();
  200. qreal size = 0;
  201. if( chooseHorizontalSide )
  202. size = rect.width() / (m_numCols+2);
  203. else
  204. size = rect.height() / (m_numRows+2);
  205. m_cellSize = static_cast<int>(size);
  206. foreach( CellItem* item, m_cells )
  207. item->setRenderSize(QSize(m_cellSize, m_cellSize));
  208. foreach( BorderItem *item, m_borders)
  209. item->setRenderSize(QSize(m_cellSize, m_cellSize));
  210. adjustItemPositions();
  211. }
  212. void MineFieldItem::adjustItemPositions()
  213. {
  214. Q_ASSERT( m_cells.size() == m_numRows*m_numCols );
  215. for(int row=0; row<m_numRows; ++row)
  216. for(int col=0; col<m_numCols; ++col)
  217. {
  218. itemAt(row,col)->setPos((col+1)*m_cellSize, (row+1)*m_cellSize);
  219. }
  220. foreach( BorderItem* item, m_borders )
  221. {
  222. item->setPos( item->col()*m_cellSize, item->row()*m_cellSize );
  223. }
  224. }
  225. void MineFieldItem::onItemRevealed(int row, int col)
  226. {
  227. m_numUnrevealed--;
  228. if(itemAt(row,col)->hasMine())
  229. {
  230. revealAllMines();
  231. }
  232. else if(itemAt(row,col)->digit() == 0) // empty cell
  233. {
  234. revealEmptySpace(row,col);
  235. }
  236. // now let's check for possible win/loss
  237. checkLost();
  238. if(!m_gameOver) // checkLost might set it
  239. checkWon();
  240. }
  241. void MineFieldItem::revealEmptySpace(int row, int col)
  242. {
  243. // recursively reveal neighbour cells until we find cells with digit
  244. QList<FieldPos> list = adjasentRowColsFor(row,col);
  245. CellItem *item = 0;
  246. foreach( const FieldPos& pos, list )
  247. {
  248. // first is row, second is col
  249. item = itemAt(pos);
  250. if(item->isRevealed() || item->isFlagged() || item->isQuestioned())
  251. continue;
  252. if(item->digit() == 0)
  253. {
  254. item->reveal();
  255. m_numUnrevealed--;
  256. revealEmptySpace(pos.first,pos.second);
  257. }
  258. else
  259. {
  260. item->reveal();
  261. m_numUnrevealed--;
  262. }
  263. }
  264. }
  265. void MineFieldItem::mousePressEvent( QGraphicsSceneMouseEvent *ev )
  266. {
  267. if(m_gameOver)
  268. return;
  269. int row = static_cast<int>(ev->pos().y()/m_cellSize)-1;
  270. int col = static_cast<int>(ev->pos().x()/m_cellSize)-1;
  271. if( row <0 || row >= m_numRows || col < 0 || col >= m_numCols )
  272. return;
  273. CellItem* itemUnderMouse = itemAt(row,col);
  274. if(!itemUnderMouse)
  275. {
  276. kDebug() << "unexpected - no item under mouse";
  277. return;
  278. }
  279. m_emulatingMidButton = ( (ev->buttons() & Qt::LeftButton) && (ev->buttons() & Qt::RightButton) );
  280. bool midButtonPressed = (ev->button() == Qt::MidButton || m_emulatingMidButton );
  281. if(midButtonPressed)
  282. {
  283. // in case we just started mid-button emulation (first LeftClick then added a RightClick)
  284. // undo press that was made by LeftClick. in other cases it won't hurt :)
  285. itemUnderMouse->undoPress();
  286. QList<CellItem*> neighbours = adjasentItemsFor(row,col);
  287. foreach(CellItem* item, neighbours)
  288. {
  289. if(!item->isFlagged() && !item->isQuestioned() && !item->isRevealed())
  290. item->press();
  291. m_midButtonPos = qMakePair(row,col);
  292. m_leftButtonPos = qMakePair(-1,-1); // reset it
  293. }
  294. }
  295. else if(ev->button() == Qt::LeftButton)
  296. {
  297. itemUnderMouse->press();
  298. m_leftButtonPos = qMakePair(row,col);
  299. }
  300. }
  301. void MineFieldItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * ev)
  302. {
  303. if(m_gameOver)
  304. return;
  305. int row = static_cast<int>(ev->pos().y()/m_cellSize)-1;
  306. int col = static_cast<int>(ev->pos().x()/m_cellSize)-1;
  307. if( row <0 || row >= m_numRows || col < 0 || col >= m_numCols )
  308. {
  309. // there might be the case when player moved mouse outside game field
  310. // while holding mid button and released it outside the field
  311. // in this case we must unpress pressed buttons, let's do it here
  312. // and return
  313. if(m_midButtonPos.first != -1)
  314. {
  315. QList<CellItem*> neighbours = adjasentItemsFor(m_midButtonPos.first,m_midButtonPos.second);
  316. foreach(CellItem *item, neighbours)
  317. item->undoPress();
  318. m_midButtonPos = qMakePair(-1,-1);
  319. m_emulatingMidButton = false;
  320. }
  321. // same with left button
  322. if(m_leftButtonPos.first != -1)
  323. {
  324. itemAt(m_leftButtonPos)->undoPress();
  325. m_leftButtonPos = qMakePair(-1,-1);
  326. }
  327. return;
  328. }
  329. CellItem* itemUnderMouse = itemAt(row,col);
  330. bool midButtonReleased = (ev->button() == Qt::MidButton || m_emulatingMidButton);
  331. if( midButtonReleased )
  332. {
  333. m_midButtonPos = qMakePair(-1,-1);
  334. QList<CellItem*> neighbours = adjasentItemsFor(row,col);
  335. if(!itemUnderMouse->isRevealed())
  336. {
  337. foreach(CellItem *item, neighbours)
  338. item->undoPress();
  339. return;
  340. }
  341. int numFlags = 0;
  342. int numMines = 0;
  343. foreach(CellItem *item, neighbours)
  344. {
  345. if(item->isFlagged())
  346. numFlags++;
  347. if(item->hasMine())
  348. numMines++;
  349. }
  350. if(numFlags == numMines && numFlags != 0)
  351. {
  352. foreach(CellItem *item, neighbours)
  353. {
  354. if(!item->isRevealed()) // revealing only unrevealed ones
  355. {
  356. // force=true to omit Pressed check
  357. item->release(true);
  358. if(item->isRevealed())
  359. onItemRevealed(item);
  360. }
  361. }
  362. }
  363. else
  364. {
  365. foreach(CellItem *item, neighbours)
  366. item->undoPress();
  367. }
  368. }
  369. else if(ev->button() == Qt::LeftButton && (ev->buttons() & Qt::RightButton) == false)
  370. {
  371. if(m_midButtonPos.first != -1) // mid-button is already pressed
  372. {
  373. itemUnderMouse->undoPress();
  374. return;
  375. }
  376. // this can happen like this:
  377. // mid-button pressed, left-button pressed, mid-button released, left-button released
  378. // m_leftButtonPos never gets set in this scenario, so we must protect ourselves :)
  379. if(m_leftButtonPos.first == -1)
  380. return;
  381. if(!itemUnderMouse->isRevealed()) // revealing only unrevealed ones
  382. {
  383. if(m_firstClick)
  384. {
  385. m_firstClick = false;
  386. generateField( row*m_numCols + col );
  387. emit firstClickDone();
  388. }
  389. itemUnderMouse->release();
  390. if(itemUnderMouse->isRevealed())
  391. onItemRevealed(row,col);
  392. }
  393. m_leftButtonPos = qMakePair(-1,-1);//reset
  394. }
  395. else if(ev->button() == Qt::RightButton && (ev->buttons() & Qt::LeftButton) == false)
  396. {
  397. bool wasFlagged = itemUnderMouse->isFlagged();
  398. itemUnderMouse->mark();
  399. bool flagStateChanged = (itemUnderMouse->isFlagged() != wasFlagged);
  400. if(flagStateChanged)
  401. {
  402. if(itemUnderMouse->isFlagged())
  403. m_flaggedMinesCount++;
  404. else
  405. m_flaggedMinesCount--;
  406. emit flaggedMinesCountChanged(m_flaggedMinesCount);
  407. }
  408. }
  409. }
  410. void MineFieldItem::mouseMoveEvent( QGraphicsSceneMouseEvent *ev )
  411. {
  412. if(m_gameOver)
  413. return;
  414. int row = static_cast<int>(ev->pos().y()/m_cellSize)-1;
  415. int col = static_cast<int>(ev->pos().x()/m_cellSize)-1;
  416. if( row < 0 || row >= m_numRows || col < 0 || col >= m_numCols )
  417. return;
  418. bool midButtonPressed = ((ev->buttons() & Qt::MidButton) ||
  419. ( (ev->buttons() & Qt::LeftButton) && (ev->buttons() & Qt::RightButton) ) );
  420. if(midButtonPressed)
  421. {
  422. if((m_midButtonPos.first != -1 && m_midButtonPos.second != -1) &&
  423. (m_midButtonPos.first != row || m_midButtonPos.second != col))
  424. {
  425. // un-press previously pressed cells
  426. QList<CellItem*> prevNeighbours = adjasentItemsFor(m_midButtonPos.first,
  427. m_midButtonPos.second);
  428. foreach(CellItem *item, prevNeighbours)
  429. item->undoPress();
  430. // and press current neighbours
  431. QList<CellItem*> neighbours = adjasentItemsFor(row,col);
  432. foreach(CellItem *item, neighbours)
  433. item->press();
  434. m_midButtonPos = qMakePair(row,col);
  435. }
  436. }
  437. else if(ev->buttons() & Qt::LeftButton)
  438. {
  439. if((m_leftButtonPos.first != -1 && m_leftButtonPos.second != -1) &&
  440. (m_leftButtonPos.first != row || m_leftButtonPos.second != col))
  441. {
  442. itemAt(m_leftButtonPos)->undoPress();
  443. itemAt(row,col)->press();
  444. m_leftButtonPos = qMakePair(row,col);
  445. }
  446. }
  447. }
  448. void MineFieldItem::revealAllMines()
  449. {
  450. foreach( CellItem* item, m_cells )
  451. {
  452. if( (item->isFlagged() && !item->hasMine()) || (!item->isFlagged() && item->hasMine()) )
  453. {
  454. item->reveal();
  455. m_numUnrevealed--;
  456. }
  457. }
  458. }
  459. void MineFieldItem::onItemRevealed(CellItem* item)
  460. {
  461. int idx = m_cells.indexOf(item);
  462. if(idx == -1)
  463. {
  464. kDebug() << "really strange - item not found";
  465. return;
  466. }
  467. int row = idx / m_numCols;
  468. int col = idx - row*m_numCols;
  469. onItemRevealed(row,col);
  470. }
  471. void MineFieldItem::checkLost()
  472. {
  473. // for loss...
  474. foreach( CellItem* item, m_cells )
  475. {
  476. if(item->isExploded())
  477. {
  478. m_gameOver = true;
  479. emit gameOver(false);
  480. break;
  481. }
  482. }
  483. }
  484. void MineFieldItem::checkWon()
  485. {
  486. // this also takes into account the trivial case when
  487. // only some cells left unflagged and they
  488. // all contain bombs. this counts as win
  489. if(m_numUnrevealed == m_minesCount)
  490. {
  491. // mark not flagged cells (if any) with flags
  492. foreach( CellItem* item, m_cells )
  493. {
  494. if( !item->isRevealed() && !item->isFlagged() )
  495. item->mark();
  496. }
  497. m_gameOver = true;
  498. // now all mines should be flagged, notify about this
  499. emit flaggedMinesCountChanged(m_minesCount);
  500. emit gameOver(true);
  501. }
  502. }
  503. QList<FieldPos> MineFieldItem::adjasentRowColsFor(int row, int col)
  504. {
  505. QList<FieldPos> resultingList;
  506. if(row != 0 && col != 0) // upper-left diagonal
  507. resultingList.append( qMakePair(row-1,col-1) );
  508. if(row != 0) // upper
  509. resultingList.append(qMakePair(row-1, col));
  510. if(row != 0 && col != m_numCols-1) // upper-right diagonal
  511. resultingList.append(qMakePair(row-1, col+1));
  512. if(col != 0) // on the left
  513. resultingList.append(qMakePair(row,col-1));
  514. if(col != m_numCols-1) // on the right
  515. resultingList.append(qMakePair(row, col+1));
  516. if(row != m_numRows-1 && col != 0) // bottom-left diagonal
  517. resultingList.append(qMakePair(row+1, col-1));
  518. if(row != m_numRows-1) // bottom
  519. resultingList.append(qMakePair(row+1, col));
  520. if(row != m_numRows-1 && col != m_numCols-1) // bottom-right diagonal
  521. resultingList.append(qMakePair(row+1, col+1));
  522. return resultingList;
  523. }
  524. QList<CellItem*> MineFieldItem::adjasentItemsFor(int row, int col)
  525. {
  526. QList<FieldPos > rowcolList = adjasentRowColsFor(row,col);
  527. QList<CellItem*> resultingList;
  528. foreach( const FieldPos& pos, rowcolList )
  529. resultingList.append( itemAt(pos) );
  530. return resultingList;
  531. }
  532. #include "minefielditem.moc"