diff --git a/mainwindow.cpp b/mainwindow.cpp index 045e180..83ba99b 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,195 +1,199 @@ /* Copyright 2007 Dmitry Suzdalev 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 */ #include "mainwindow.h" #include "scene.h" #include "settings.h" #include "renderer.h" #include #include #include #include #include #include #include #include #include #include "ui_customgame.h" class CustomGameConfig : public QWidget { public: CustomGameConfig(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } private: Ui::CustomGameConfig ui; }; KMinesMainWindow::KMinesMainWindow() : m_scoreDialog(0) { m_scene = new KMinesScene(this); connect(m_scene, SIGNAL(minesCountChanged(int)), SLOT(onMinesCountChanged(int))); connect(m_scene, SIGNAL(gameOver(bool)), SLOT(onGameOver(bool))); connect(m_scene, SIGNAL(firstClickDone()), SLOT(onFirstClick())); m_view = new KMinesView( m_scene, this ); m_view->setCacheMode( QGraphicsView::CacheBackground ); m_view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_view->setFrameStyle(QFrame::NoFrame); m_view->setOptimizationFlags( QGraphicsView::DontClipPainter | QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing ); m_gameClock = new KGameClock(this, KGameClock::MinSecOnly); connect(m_gameClock, SIGNAL(timeChanged(const QString&)), SLOT(advanceTime(const QString&))); statusBar()->insertItem( i18n("Mines: 0/0"), 0 ); statusBar()->insertItem( i18n("Time: 00:00"), 1); setCentralWidget(m_view); setupActions(); m_scoreDialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Custom1, this); m_scoreDialog->addField(KScoreDialog::Custom1, "Time", "time"); m_scoreDialog->hideField(KScoreDialog::Score); // TODO: load this from config KGameDifficulty::setLevel( KGameDifficulty::easy ); newGame(); } void KMinesMainWindow::setupActions() { KStandardGameAction::gameNew(this, SLOT(newGame()), actionCollection()); KStandardGameAction::highscores(this, SLOT(showHighscores()), actionCollection()); KStandardGameAction::quit(this, SLOT(close()), actionCollection()); KStandardAction::preferences( this, SLOT( configureSettings() ), actionCollection() ); KGameDifficulty::init(this, this, SLOT(levelChanged(KGameDifficulty::standardLevel)), SLOT(customLevelChanged(int))); KGameDifficulty::addStandardLevel(KGameDifficulty::easy); KGameDifficulty::addStandardLevel(KGameDifficulty::medium); KGameDifficulty::addStandardLevel(KGameDifficulty::hard); KGameDifficulty::addCustomLevel(0, i18n("Custom")); setupGUI(); } void KMinesMainWindow::onMinesCountChanged(int count) { statusBar()->changeItem( i18n("Mines %1/%2", count, m_scene->totalMines()), 0 ); } -void KMinesMainWindow::levelChanged(KGameDifficulty::standardLevel level) +void KMinesMainWindow::levelChanged(KGameDifficulty::standardLevel) { - switch(level) +} + +void KMinesMainWindow::customLevelChanged(int) +{ +} + +void KMinesMainWindow::newGame() +{ + switch(KGameDifficulty::level()) { case KGameDifficulty::easy: m_scene->startNewGame(9, 9, 10); break; case KGameDifficulty::medium: m_scene->startNewGame(16,16,40); break; case KGameDifficulty::hard: m_scene->startNewGame(16,30,99); break; + case KGameDifficulty::custom: + m_scene->startNewGame(Settings::self()->customHeight(), + Settings::self()->customWidth(), + Settings::self()->customMines()); default: //unsupported break; } -} - -void KMinesMainWindow::customLevelChanged(int) -{ - // TESTING - m_scene->startNewGame(35,60,30*20); -} - -void KMinesMainWindow::newGame() -{ - levelChanged(KGameDifficulty::level()); statusBar()->changeItem( i18n("Time: 00:00"), 1); } void KMinesMainWindow::onGameOver(bool won) { m_gameClock->pause(); if(won) { - m_scoreDialog->setConfigGroup( KGameDifficulty::levelString() ); + QString group = KGameDifficulty::levelString(); + if(group.isEmpty()) + group = "Custom"; + m_scoreDialog->setConfigGroup( group ); KScoreDialog::FieldInfo scoreInfo; // score-in-seconds will be hidden scoreInfo[KScoreDialog::Score].setNum(m_gameClock->seconds()); //score-as-time will be shown scoreInfo[KScoreDialog::Custom1] = m_gameClock->timeString(); // we keep highscores as number of seconds if( m_scoreDialog->addScore(scoreInfo, KScoreDialog::LessIsMore) != 0 ) m_scoreDialog->exec(); } } void KMinesMainWindow::advanceTime(const QString& timeStr) { statusBar()->changeItem( i18n("Time: %1", timeStr), 1 ); } void KMinesMainWindow::onFirstClick() { m_gameClock->restart(); } void KMinesMainWindow::showHighscores() { m_scoreDialog->setConfigGroup( KGameDifficulty::levelString() ); m_scoreDialog->exec(); } void KMinesMainWindow::configureSettings() { if ( KConfigDialog::showDialog( "settings" ) ) return; KConfigDialog *dialog = new KConfigDialog( this, "settings", Settings::self() ); dialog->addPage( new KGameThemeSelector( dialog, Settings::self() ), i18n( "Theme" ), "game_theme" ); dialog->addPage( new CustomGameConfig( dialog ), i18n("Custom Game"), "configure" ); connect( dialog, SIGNAL( settingsChanged(const QString&) ), this, SLOT( loadSettings() ) ); dialog->show(); } void KMinesMainWindow::loadSettings() { if ( !KMinesRenderer::self()->loadTheme(Settings::theme()) ) { KMessageBox::error( this, i18n( "Failed to load \"%1\" theme. Please check your installation.", Settings::theme() ) ); return; } m_view->resetCachedContent(); // trigger complete redraw m_scene->resizeScene( (int)m_scene->sceneRect().width(), (int)m_scene->sceneRect().height() ); } diff --git a/minefielditem.cpp b/minefielditem.cpp index baa0da2..58e3635 100644 --- a/minefielditem.cpp +++ b/minefielditem.cpp @@ -1,443 +1,443 @@ /* Copyright 2007 Dmitry Suzdalev 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 */ #include "minefielditem.h" #include #include #include #include "cellitem.h" #include "renderer.h" MineFieldItem::MineFieldItem( int numRows, int numCols, int numMines ) : m_rowcolMousePress(-1,-1), m_rowcolMidButton(-1,-1), m_gameOver(false) { regenerateField(numRows, numCols, numMines); } void MineFieldItem::regenerateField( int numRows, int numCols, int numMines ) { - Q_ASSERT( numMines < numRows*numRows ); + Q_ASSERT( numMines < numRows*numCols ); m_firstClick = true; m_gameOver = false; int oldSize = m_cells.size(); int newSize = numRows*numCols; // if field is being shrinked, delete elements at the end before resizing vector if(oldSize > newSize) { for( int i=newSize; iremoveItem(m_cells[i]); delete m_cells[i]; } } m_cells.resize(newSize); m_numRows = numRows; m_numCols = numCols; m_minesCount = numMines; m_numUnrevealed = m_numRows*m_numCols; for(int i=0; ireset(); else m_cells[i] = new CellItem(this); } // generating mines int minesToPlace = m_minesCount; int randomIdx = 0; while(minesToPlace != 0) { randomIdx = m_randomSeq.getLong( m_numRows*m_numCols ); if(!m_cells.at(randomIdx)->hasMine()) { m_cells.at(randomIdx)->setHasMine(true); minesToPlace--; } else continue; } // calculating digits for cells around mines for(int row=0; row < m_numRows; ++row) for(int col=0; col < m_numCols; ++col) { if(itemAt(row,col)->hasMine()) continue; // simply looking at all 8 neighbour cells and adding +1 for each // mine we found int resultingDigit = 0; QList neighbours = adjasentItemsFor(row,col); foreach(CellItem* item, neighbours) { if(item->hasMine()) resultingDigit++; } // having 0 is ok here - it'll be empty itemAt(row,col)->setDigit(resultingDigit); } adjustItemPositions(); m_flaggedMinesCount = 0; emit flaggedMinesCountChanged(m_flaggedMinesCount); } QRectF MineFieldItem::boundingRect() const { // we assume that all items have the same size // so let's take the first item's size qreal cellSize = KMinesRenderer::self()->cellSize(); return QRectF(0, 0, cellSize*m_numCols, cellSize*m_numRows); } void MineFieldItem::paint( QPainter * painter, const QStyleOptionGraphicsItem* opt, QWidget* w) { Q_UNUSED(painter); Q_UNUSED(opt); Q_UNUSED(w); } void MineFieldItem::resizeToFitInRect(const QRectF& rect) { prepareGeometryChange(); // here follows "cooomplex" algorithm to choose which side to // take when calculating cell size by dividing this side by // numRows or numCols correspondingly // it's cooomplex, because I have to paint some figures on paper // to understand that criteria for choosing one side or another (for // determining cell size from it) is comparing // cols/r.width() and rows/r.height(): bool chooseHorizontalSide = m_numCols / rect.width() > m_numRows / rect.height(); qreal size = 0; if( chooseHorizontalSide ) size = rect.width() / m_numCols; else size = rect.height() / m_numRows; KMinesRenderer::self()->setCellSize( static_cast(size) ); foreach( CellItem* item, m_cells ) item->updatePixmap(); adjustItemPositions(); } void MineFieldItem::adjustItemPositions() { Q_ASSERT( m_cells.size() == m_numRows*m_numCols ); int itemSize = KMinesRenderer::self()->cellSize(); for(int row=0; rowsetPos(col*itemSize, row*itemSize); } } void MineFieldItem::onItemRevealed(int row, int col) { m_numUnrevealed--; if(itemAt(row,col)->hasMine()) { revealAllMines(); } else if(itemAt(row,col)->digit() == 0) // empty cell { revealEmptySpace(row,col); } // now let's check for possible win/loss checkLost(); if(!m_gameOver) // checkLost might set it checkWon(); } void MineFieldItem::revealEmptySpace(int row, int col) { // recursively reveal neighbour cells until we find cells with digit typedef QPair RowColPair; QList list = adjasentRowColsFor(row,col); CellItem *item = 0; foreach( const RowColPair& pair, list ) { // first is row, second is col item = itemAt(pair.first,pair.second); if(item->isRevealed() || item->isFlagged()) continue; if(item->digit() == 0) { item->reveal(); m_numUnrevealed--; revealEmptySpace(pair.first,pair.second); } else if(!item->isFlagged()) { item->reveal(); m_numUnrevealed--; } } } void MineFieldItem::mousePressEvent( QGraphicsSceneMouseEvent *ev ) { if(m_gameOver) return; if(m_firstClick) { m_firstClick = false; emit firstClickDone(); } int itemSize = KMinesRenderer::self()->cellSize(); int row = static_cast(ev->pos().y()/itemSize); int col = static_cast(ev->pos().x()/itemSize); if(ev->button() == Qt::LeftButton) { if(m_rowcolMidButton.first != -1) // mid-button is already pressed return; itemAt(row,col)->press(); // TODO: rename rowcolLeftButton m_rowcolMousePress = qMakePair(row,col); } else if(ev->button() == Qt::MidButton) { QList neighbours = adjasentItemsFor(row,col); foreach(CellItem* item, neighbours) { if(!item->isFlagged() && !item->isRevealed()) item->press(); m_rowcolMidButton = qMakePair(row,col); } } } void MineFieldItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * ev) { if(m_gameOver) return; int itemSize = KMinesRenderer::self()->cellSize(); int row = static_cast(ev->pos().y()/itemSize); int col = static_cast(ev->pos().x()/itemSize); CellItem* itemUnderMouse = itemAt(row,col); if(ev->button() == Qt::LeftButton) { if(m_rowcolMidButton.first != -1) // mid-button is already pressed return; CellItem *item = itemAt(m_rowcolMousePress.first, m_rowcolMousePress.second); if(!item->isRevealed()) // revealing only unrevealed ones { item->release(); if(item->isRevealed()) onItemRevealed(m_rowcolMousePress.first,m_rowcolMousePress.second); } m_rowcolMousePress = qMakePair(-1,-1);//reset } else if(ev->button() == Qt::RightButton) { if(m_rowcolMidButton.first != -1) // mid-button is already pressed return; bool wasFlagged = itemUnderMouse->isFlagged(); itemUnderMouse->mark(); bool flagStateChanged = (itemUnderMouse->isFlagged() != wasFlagged); if(flagStateChanged) { if(itemUnderMouse->isFlagged()) m_flaggedMinesCount++; else m_flaggedMinesCount--; emit flaggedMinesCountChanged(m_flaggedMinesCount); } m_rowcolMousePress = qMakePair(-1,-1);//reset } else if( ev->button() == Qt::MidButton ) { m_rowcolMidButton = qMakePair(-1,-1); QList neighbours = adjasentItemsFor(row,col); if(!itemUnderMouse->isRevealed()) { foreach(CellItem *item, neighbours) item->undoPress(); return; } int numFlags = 0; int numMines = 0; foreach(CellItem *item, neighbours) { if(item->isFlagged()) numFlags++; if(item->hasMine()) numMines++; } if(numFlags == numMines && numFlags != 0) { foreach(CellItem *item, neighbours) { if(!item->isRevealed()) // revealing only unrevealed ones { // force=true to omit Pressed check item->release(true); if(item->isRevealed()) onItemRevealed(item); } } } else { foreach(CellItem *item, neighbours) item->undoPress(); } } } void MineFieldItem::mouseMoveEvent( QGraphicsSceneMouseEvent *ev ) { if(m_gameOver) return; int itemSize = KMinesRenderer::self()->cellSize(); int row = static_cast(ev->pos().y()/itemSize); int col = static_cast(ev->pos().x()/itemSize); if(ev->buttons() & Qt::MidButton) { if(m_rowcolMidButton.first != row || m_rowcolMidButton.second != col) { // un-press previously pressed cells QList prevNeighbours = adjasentItemsFor(m_rowcolMidButton.first, m_rowcolMidButton.second); foreach(CellItem *item, prevNeighbours) item->undoPress(); // and press current neighbours QList neighbours = adjasentItemsFor(row,col); foreach(CellItem *item, neighbours) item->press(); m_rowcolMidButton = qMakePair(row,col); } } } void MineFieldItem::revealAllMines() { foreach( CellItem* item, m_cells ) { if( (item->isFlagged() && !item->hasMine()) || (!item->isFlagged() && item->hasMine()) ) { item->reveal(); m_numUnrevealed--; } } } void MineFieldItem::onItemRevealed(CellItem* item) { int idx = m_cells.indexOf(item); if(idx == -1) { kDebug() << "really strange - item not found" << endl; return; } int row = idx / m_numCols; int col = idx - row*m_numCols; onItemRevealed(row,col); } void MineFieldItem::checkLost() { // for loss... foreach( CellItem* item, m_cells ) { if(item->isExploded()) { m_gameOver = true; emit gameOver(false); break; } } } void MineFieldItem::checkWon() { // let's check the trivial case when // only some cells left unflagged and they // all contain bombs. this counts as win if(m_numUnrevealed == m_minesCount) emit gameOver(true); } QList > MineFieldItem::adjasentRowColsFor(int row, int col) { QList > resultingList; if(row != 0 && col != 0) // upper-left diagonal resultingList.append( qMakePair(row-1,col-1) ); if(row != 0) // upper resultingList.append(qMakePair(row-1, col)); if(row != 0 && col != m_numCols-1) // upper-right diagonal resultingList.append(qMakePair(row-1, col+1)); if(col != 0) // on the left resultingList.append(qMakePair(row,col-1)); if(col != m_numCols-1) // on the right resultingList.append(qMakePair(row, col+1)); if(row != m_numRows-1 && col != 0) // bottom-left diagonal resultingList.append(qMakePair(row+1, col-1)); if(row != m_numRows-1) // bottom resultingList.append(qMakePair(row+1, col)); if(row != m_numRows-1 && col != m_numCols-1) // bottom-right diagonal resultingList.append(qMakePair(row+1, col+1)); return resultingList; } QList MineFieldItem::adjasentItemsFor(int row, int col) { QList > rowcolList = adjasentRowColsFor(row,col); QList resultingList; typedef QPair RowColPair; foreach( const RowColPair& pair, rowcolList ) resultingList.append( itemAt(pair.first, pair.second) ); return resultingList; } diff --git a/scene.cpp b/scene.cpp index ec45748..ae652d8 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,91 +1,96 @@ /* Copyright 2007 Dmitry Suzdalev 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 */ #include "scene.h" #include #include #include #include "minefielditem.h" #include "renderer.h" // --------------- KMinesView --------------- KMinesView::KMinesView( KMinesScene* scene, QWidget *parent ) : QGraphicsView(scene, parent), m_scene(scene) { } void KMinesView::resizeEvent( QResizeEvent *ev ) { m_scene->resizeScene( ev->size().width(), ev->size().height() ); } // -------------- KMinesScene -------------------- KMinesScene::KMinesScene( QObject* parent ) : QGraphicsScene(parent) { setItemIndexMethod( NoIndex ); m_fieldItem = new MineFieldItem(9, 9, 10); connect(m_fieldItem, SIGNAL(flaggedMinesCountChanged(int)), SIGNAL(minesCountChanged(int))); connect(m_fieldItem, SIGNAL(firstClickDone()), SIGNAL(firstClickDone())); connect(m_fieldItem, SIGNAL(gameOver(bool)), SLOT(onGameOver(bool))); // and re-emit it for others connect(m_fieldItem, SIGNAL(gameOver(bool)), SIGNAL(gameOver(bool))); addItem(m_fieldItem); m_messageItem = new KGamePopupItem; m_messageItem->setMessageTimeout(4000); addItem(m_messageItem); } void KMinesScene::resizeScene(int width, int height) { setSceneRect(0, 0, width, height); m_fieldItem->resizeToFitInRect( sceneRect() ); m_fieldItem->setPos( sceneRect().width()/2 - m_fieldItem->boundingRect().width()/2, sceneRect().height()/2 - m_fieldItem->boundingRect().height()/2 ); } void KMinesScene::drawBackground( QPainter* p, const QRectF& ) { p->drawPixmap( 0, 0, KMinesRenderer::self()->backgroundPixmap(sceneRect().size().toSize()) ); } void KMinesScene::startNewGame(int rows, int cols, int numMines) { + if(numMines >= rows*cols) + { + m_messageItem->showMessage(i18n("Custom game can not be started.
Number of mines is too big for current field."), KGamePopupItem::BottomLeft); + return; + } if(m_messageItem->isVisible()) m_messageItem->forceHide(KGamePopupItem::AnimatedHide); m_fieldItem->regenerateField(rows, cols, numMines); // reposition items resizeScene((int)sceneRect().width(), (int)sceneRect().height()); } int KMinesScene::totalMines() const { return m_fieldItem->minesCount(); } void KMinesScene::onGameOver(bool won) { if(won) m_messageItem->showMessage(i18n("Congratulatons! You have won!"), KGamePopupItem::Center); else m_messageItem->showMessage(i18n("You have lost."), KGamePopupItem::Center); }