Skip to content

Commit

Permalink
Another piece of random useless code in QGIS. Happy April 1st!
Browse files Browse the repository at this point in the history
Type "bored" into the coordinates box (best results with a map loaded in canvas)

Click on tiles to move them to the empty space.
Click on the empty tile to toggle tile numbers.
  • Loading branch information
wonder-sk committed Apr 5, 2018
1 parent 7d65ee9 commit 63130d4
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ SET(QGIS_APP_SRCS
qgsmapcanvasdockwidget.cpp
qgsmaplayerstyleguiutils.cpp
qgsmapsavedialog.cpp
qgspuzzlewidget.cpp
qgsversionmigration.cpp
qgsrulebasedlabelingwidget.cpp
qgssavestyletodbdialog.cpp
Expand Down Expand Up @@ -269,6 +270,7 @@ SET (QGIS_APP_MOC_HDRS
qgsmapcanvasdockwidget.h
qgsmaplayerstyleguiutils.h
qgsmapsavedialog.h
qgspuzzlewidget.h
qgsrulebasedlabelingwidget.h
qgssavestyletodbdialog.h
qgssnappinglayertreemodel.h
Expand Down
14 changes: 14 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgspluginmanager.h"
#include "qgspluginregistry.h"
#include "qgspointxy.h"
#include "qgspuzzlewidget.h"
#include "qgsruntimeprofiler.h"
#include "qgshandlebadlayers.h"
#include "qgsprintlayout.h"
Expand Down Expand Up @@ -1335,6 +1336,19 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
connect( QgsProject::instance(), &QgsProject::isDirtyChanged, mActionRevertProject, toggleRevert );
connect( QgsProject::instance(), &QgsProject::fileNameChanged, mActionRevertProject, toggleRevert );

// the most important part of the initialization: make sure that people can play puzzle if they need
QgsPuzzleWidget *puzzleWidget = new QgsPuzzleWidget( mMapCanvas );
mCentralContainer->insertWidget( 2, puzzleWidget );
connect( mCoordsEdit, &QgsStatusBarCoordinatesWidget::weAreBored, this, [ this, puzzleWidget ]
{
puzzleWidget->letsGetThePartyStarted();
mCentralContainer->setCurrentIndex( 2 );
} );
connect( puzzleWidget, &QgsPuzzleWidget::done, this, [ this ]
{
mCentralContainer->setCurrentIndex( 0 );
} );

} // QgisApp ctor

QgisApp::QgisApp()
Expand Down
185 changes: 185 additions & 0 deletions src/app/qgspuzzlewidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/***************************************************************************
qgspuzzlewidget.cpp
--------------------------------------
Date : 1st of April 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#include "qgspuzzlewidget.h"

#include "qgsmapcanvas.h"

#include <QGraphicsPixmapItem>
#include <QMessageBox>


static bool testSolved( QVector<int> &positions )
{
for ( int i = 0; i < positions.count() - 1; ++i )
{
if ( positions[i] != i )
return false;
}
return true;
}

static int findEmpty( QVector<int> &positions )
{
for ( int i = 0; i < positions.count(); ++i )
{
if ( positions[i] == -1 )
return i;
}
Q_ASSERT( false );
return -1;
}

// half of the possible permutations lead to an unsolvable puzzle, so we initialize the puzzle by
// doing valid moves
// https://en.wikipedia.org/wiki/15_puzzle
static void shuffle( QVector<int> &positions, int count )
{
int size = sqrt( positions.count() );
int idxEmpty = findEmpty( positions );
int cEmpty = idxEmpty % size, rEmpty = idxEmpty / size;
int moveX[] = { 0, 0, 1, -1 };
int moveY[] = { 1, -1, 0, 0 };
int cOther, rOther;
for ( int i = 0; i < count; ++i )
{
do
{
int move = qrand() % 4;
cOther = cEmpty + moveX[move];
rOther = rEmpty + moveY[move];
}
while ( cOther < 0 || cOther >= size || rOther < 0 || rOther >= size );

int idxOther = rOther * size + cOther;
std::swap( positions[idxEmpty], positions[idxOther] );
idxEmpty = idxOther;
cEmpty = idxEmpty % size;
rEmpty = idxEmpty / size;
}
}

QgsPuzzleWidget::QgsPuzzleWidget( QgsMapCanvas *canvas )
: mCanvas( canvas )
{
setInteractive( false );
setBackgroundBrush( QBrush( Qt::lightGray ) );
}

void QgsPuzzleWidget::mousePressEvent( QMouseEvent *event )
{
int idxEmpty = findEmpty( mPositions );
int rEmpty = idxEmpty / mSize;
int cEmpty = idxEmpty % mSize;
int cMouse = event->pos().x() / mTileWidth;
int rMouse = event->pos().y() / mTileHeight;
int idxMouse = rMouse * mSize + cMouse;
int dx = cMouse - cEmpty;
int dy = rMouse - rEmpty;

if ( ( dx == 0 && qAbs( dy ) == 1 ) || ( dy == 0 && qAbs( dx ) == 1 ) )
{
std::swap( mPositions[idxEmpty], mPositions[idxMouse] );
updateTilePositions();
if ( testSolved( mPositions ) )
{
QMessageBox::information( nullptr, tr( "QGIS" ), tr( "Well done!\n\nNow let's get back to work, shall we?" ) );
emit done();
}
}
else if ( dx == 0 && dy == 0 )
{
// toggle text help when clicked on empty tile
for ( int i = 0; i < mTextItems.count(); ++i )
mTextItems[i]->setVisible( !mTextItems[i]->isVisible() );
}
}

void QgsPuzzleWidget::letsGetThePartyStarted()
{
mPositions.clear();
delete mScene;
mItems.clear();
mTextItems.clear();

mScene = new QGraphicsScene;

QTemporaryFile f;
f.open();
f.close();

QString filename( f.fileName() );
mCanvas->saveAsImage( filename );

QPixmap pixmap;
pixmap.load( filename );

int tileWidth = pixmap.width() / mSize;
int tileHeight = pixmap.height() / mSize;
mTileWidth = tileWidth;
mTileHeight = tileHeight;

for ( int row = 0; row < mSize; ++row )
for ( int col = 0; col < mSize; ++col )
{
QGraphicsPixmapItem *item = new QGraphicsPixmapItem;
item->setPixmap( pixmap.copy( col * tileWidth, row * tileHeight, tileWidth, tileHeight ) );
mScene->addItem( item ); // takes ownership
mItems.append( item );

QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem( QString::number( row * mSize + col + 1 ), item );
QRectF textRect = textItem->boundingRect();
textItem->setPos( ( tileWidth - textRect.width() ) / 2, ( tileHeight - textRect.height() ) / 2 );
textItem->setVisible( false );
mTextItems.append( textItem );
}

// extra lines to show tile split points
for ( int i = 1; i < mSize; ++i )
{
QGraphicsLineItem *lineHorz = new QGraphicsLineItem( 0, i * tileHeight, tileWidth * mSize, i * tileHeight );
QGraphicsLineItem *lineVert = new QGraphicsLineItem( i * tileWidth, 0, i * tileWidth, tileHeight * mSize );
lineHorz->setZValue( 10 );
lineVert->setZValue( 10 );
mScene->addItem( lineHorz );
mScene->addItem( lineVert );
}

// initialize positions
for ( int i = 0; i < mSize * mSize; ++i )
mPositions << i;
mPositions[mSize * mSize - 1] = -1;
shuffle( mPositions, 1000 );

mItems[mSize * mSize - 1]->setVisible( false ); // hide item for the missing piece

updateTilePositions();

setScene( mScene );
}

void QgsPuzzleWidget::updateTilePositions()
{
for ( int i = 0; i < mSize * mSize; ++i )
{
int itemIndex = mPositions[i];
if ( itemIndex == -1 )
continue; // empty tile

int r = i / mSize, c = i % mSize;
int x = c * mTileWidth, y = r * mTileHeight;
mItems[itemIndex]->setPos( x, y );
}
}
52 changes: 52 additions & 0 deletions src/app/qgspuzzlewidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/***************************************************************************
qgspuzzlewidget.h
--------------------------------------
Date : 1st of April 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#ifndef QGSPUZZLEWIDGET_H
#define QGSPUZZLEWIDGET_H

#include <QGraphicsView>

class QgsMapCanvas;


class QgsPuzzleWidget : public QGraphicsView
{
Q_OBJECT
public:
explicit QgsPuzzleWidget( QgsMapCanvas *canvas = nullptr );

protected:
void mousePressEvent( QMouseEvent *event ) override;

signals:
void done();

public slots:
void letsGetThePartyStarted();

private:
void updateTilePositions();

private:
QgsMapCanvas *mCanvas = nullptr;
QGraphicsScene *mScene = nullptr;
int mSize = 4; //!< Number of items in one row/column
int mTileWidth = 0, mTileHeight = 0;
QVector<QGraphicsItem *> mItems;
QVector<QGraphicsItem *> mTextItems;
QVector<int> mPositions; //!< Indices of items (-1 where the piece is missing)
};

#endif // QGSPUZZLEWIDGET_H
5 changes: 5 additions & 0 deletions src/app/qgsstatusbarcoordinateswidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ void QgsStatusBarCoordinatesWidget::validateCoordinates()
refreshMapCanvas();
return;
}
else if ( mLineEdit->text() == QStringLiteral( "bored" ) )
{
// it's friday afternoon and too late to start another piece of work...
emit weAreBored();
}

bool xOk = false;
bool yOk = false;
Expand Down
1 change: 1 addition & 0 deletions src/app/qgsstatusbarcoordinateswidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class APP_EXPORT QgsStatusBarCoordinatesWidget : public QWidget

signals:
void coordinatesChanged();
void weAreBored();

private slots:
void showMouseCoordinates( const QgsPointXY &p );
Expand Down

0 comments on commit 63130d4

Please sign in to comment.