diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c60677..e029c29 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,42 +1,43 @@ set(kmahjongg_SRCS main.cpp kmahjongg_debug.cpp kmahjongg.cpp boardlayout.cpp gamedata.cpp kmahjongglayoutselector.cpp kmahjongglayout.cpp editor.cpp frameimage.cpp gameitem.cpp + gamebackground.cpp gameview.cpp gamescene.cpp selectionanimation.cpp demoanimation.cpp movelistanimation.cpp ) ki18n_wrap_ui(kmahjongg_SRCS settings.ui gametype.ui) ecm_qt_declare_logging_category(kmahjongg_SRCS HEADER kmahjongg_debug.h IDENTIFIER KMAHJONGG_LOG CATEGORY_NAME log_kmahjongg) kconfig_add_kcfg_files(kmahjongg_SRCS prefs.kcfgc) add_executable(kmahjongg ${kmahjongg_SRCS}) target_link_libraries(kmahjongg Qt5::Gui KF5::CoreAddons KF5::XmlGui KF5::DBusAddons KF5::Crash KF5KDEGames KF5KDEGamesPrivate KF5KMahjongglib ) install(TARGETS kmahjongg ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kmahjongg.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES kmahjonggui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kmahjongg) diff --git a/src/gamebackground.cpp b/src/gamebackground.cpp new file mode 100644 index 0000000..3110481 --- /dev/null +++ b/src/gamebackground.cpp @@ -0,0 +1,62 @@ +/* Copyright (C) 2019 Christian Krippendorf + * + * Kmahjongg 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. */ + +// own +#include "gamebackground.h" + +// Qt +#include +#include +#include +#include + + +GameBackground::GameBackground(QGraphicsObject * item) + : QGraphicsObject(item) + , m_background(new QPixmap()) +{ +} + +GameBackground::~GameBackground() +{ + delete m_background; +} + +void GameBackground::prepareForGeometryChange() +{ + prepareGeometryChange(); +} + +void GameBackground::paint(QPainter *painter, const QStyleOptionGraphicsItem *, + QWidget *) +{ + painter->drawPixmap(pos(), *m_background); +} + +void GameBackground::setBackground(QPixmap *background) +{ + *m_background = *background; +} + +QRectF GameBackground::boundingRect() const +{ + return QRectF(pos(), m_background->size()); +} + +QRectF GameBackground::rect() const +{ + return boundingRect(); +} diff --git a/src/gamebackground.h b/src/gamebackground.h new file mode 100644 index 0000000..2e62cfd --- /dev/null +++ b/src/gamebackground.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2019 Christian Krippendorf + * + * Kmahjongg 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. */ + +#ifndef GAMEBACKGROUND_H +#define GAMEBACKGROUND_H + +// Qt +#include +#include + +// KMahjongg +#include "kmtypes.h" + + +/** + * The background item of the kmahjongg board. + * @author Christian Krippendorf + */ +class GameBackground : public QGraphicsObject +{ + Q_OBJECT + +public: + /** + * Constructor + * @param item The parent item + */ + explicit GameBackground(QGraphicsObject *item = nullptr); + ~GameBackground() override; + + /** + * Set the background + * @param facePix The pixmap of the face + */ + void setBackground(QPixmap *background); + + /** + * Get the current background + * @return Background image as pixmap + */ + QPixmap getBackground() const; + + /** + * Overrides the paint method of QGraphicsItem + */ + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) override; + + /** + * Overrides the boundingRect method of QGraphicsItem + */ + QRectF boundingRect() const override; + + /** + * Returns the rect of the item + * @return The rect of the item + */ + QRectF rect() const; + + /** + * Called in GameView::resizeTileset() before reloading the tiles + */ + void prepareForGeometryChange(); + +private: + QPixmap *m_background; +}; + +#endif // GAMEBACKGROUND_H diff --git a/src/gameview.cpp b/src/gameview.cpp index f1b6f11..580126f 100644 --- a/src/gameview.cpp +++ b/src/gameview.cpp @@ -1,979 +1,982 @@ /* Copyright (C) 2012 Christian Krippendorf * * Kmahjongg 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. */ // own #include "gameview.h" // Qt #include #include // KDE #include #include #include // KMahjongg #include "demoanimation.h" #include "gamedata.h" #include "gameitem.h" #include "gamescene.h" #include "kmahjongg_debug.h" #include "kmahjonggbackground.h" #include "kmahjongglayout.h" #include "kmahjonggtileset.h" #include "movelistanimation.h" #include "prefs.h" #include "selectionanimation.h" +#include "gamebackground.h" + GameView::GameView(GameScene * gameScene, GameData * gameData, QWidget * parent) : QGraphicsView(gameScene, parent) , m_cheatsUsed(0) , m_gameNumber(0) , m_gamePaused(false) , m_match(false) , m_gameGenerated(false) , m_gameData(gameData) , m_selectedItem(nullptr) + , m_gameBackground(nullptr) , m_tilesetPath(new QString()) , m_backgroundPath(new QString()) , m_helpAnimation(new SelectionAnimation(this)) , m_moveListAnimation(new MoveListAnimation(this)) , m_demoAnimation(new DemoAnimation(this)) , m_tiles(new KMahjonggTileset()) , m_background(new KMahjonggBackground()) { // Some settings to the QGraphicsView. setFocusPolicy(Qt::NoFocus); setStyleSheet(QStringLiteral("QGraphicsView { border-style: none; }")); setAutoFillBackground(true); // Read in some settings. m_angle = static_cast(Prefs::angle()); // Init HelpAnimation m_helpAnimation->setAnimationSpeed(ANIMATION_SPEED); m_helpAnimation->setRepetitions(3); // Init DemoAnimation m_demoAnimation->setAnimationSpeed(ANIMATION_SPEED); // Init MoveListAnimation m_moveListAnimation->setAnimationSpeed(ANIMATION_SPEED); m_selectionChangedConnect = connect(scene(), &GameScene::selectionChanged, this, &GameView::selectionChanged); connect(m_demoAnimation, &DemoAnimation::changeItemSelectedState, this, &GameView::changeItemSelectedState); connect(m_demoAnimation, &DemoAnimation::removeItem, this, &GameView::removeItem); connect(m_demoAnimation, &DemoAnimation::gameOver, this, &GameView::demoGameOver); connect(m_moveListAnimation, &MoveListAnimation::removeItem, this, &GameView::removeItem); connect(m_moveListAnimation, &MoveListAnimation::addItem, this, &GameView::addItemAndUpdate); connect(scene(), &GameScene::clearSelectedTile, this, &GameView::clearSelectedTile); } GameView::~GameView() { delete m_helpAnimation; delete m_demoAnimation; delete m_moveListAnimation; delete m_background; delete m_backgroundPath; delete m_tilesetPath; delete m_tiles; } GameScene * GameView::scene() const { return dynamic_cast(QGraphicsView::scene()); } bool GameView::checkUndoAllowed() { return (m_gameData->m_allowUndo && !checkDemoAnimationActive() && !checkMoveListAnimationActive()); } bool GameView::checkRedoAllowed() { return (m_gameData->m_allowRedo && !checkDemoAnimationActive() && !checkMoveListAnimationActive()); } long GameView::getGameNumber() const { return m_gameNumber; } void GameView::setGameNumber(long gameNumber) { m_gameNumber = gameNumber; setStatusText(i18n("Ready. Now it is your turn.")); } bool GameView::undo() { // Clear user selections. clearSelectedTile(); if (m_gameData->m_tileNum < m_gameData->m_maxTileNum) { m_gameData->clearRemovedTilePair(m_gameData->MoveListData(m_gameData->m_tileNum + 1), m_gameData->MoveListData(m_gameData->m_tileNum + 2)); ++m_gameData->m_tileNum; addItemAndUpdate(m_gameData->MoveListData(m_gameData->m_tileNum)); ++m_gameData->m_tileNum; addItemAndUpdate(m_gameData->MoveListData(m_gameData->m_tileNum)); ++m_gameData->m_allowRedo; setStatusText(i18n("Undo operation done successfully.")); return true; } setStatusText(i18n("What do you want to undo? You have done nothing!")); return false; } bool GameView::redo() { if (m_gameData->m_allowRedo > 0) { m_gameData->setRemovedTilePair(m_gameData->MoveListData(m_gameData->m_tileNum), m_gameData->MoveListData(m_gameData->m_tileNum - 1)); removeItem(m_gameData->MoveListData(m_gameData->m_tileNum)); removeItem(m_gameData->MoveListData(m_gameData->m_tileNum)); --m_gameData->m_allowRedo; // Test whether the game is over or not. if (m_gameData->m_tileNum == 0) { emit gameOver(m_gameData->m_maxTileNum, m_cheatsUsed); } else { // The game is not over, so test if there are any valid moves. validMovesAvailable(); } return true; } return false; } void GameView::demoGameOver(bool won) { if (won) { startMoveListAnimation(); } else { setStatusText(i18n("Your computer has lost the game.")); emit demoOrMoveListAnimationOver(true); } } void GameView::createNewGame(long gameNumber) { setStatusText(i18n("Calculating new game...")); // Check any animations are running and stop them. checkHelpAnimationActive(true); checkDemoAnimationActive(true); checkMoveListAnimationActive(true); // Create a random game number, if no one was given. if (gameNumber == -1) { m_gameNumber = KRandom::random(); } else { m_gameNumber = gameNumber; } m_gameData->m_allowUndo = 0; m_gameData->m_allowRedo = 0; m_gameData->random.setSeed(m_gameNumber); // Translate m_pGameData->Map to an array of POSITION data. We only need to // do this once for each new game. m_gameData->generateTilePositions(); // Now use the tile position data to generate tile dependency data. // We only need to do this once for each new game. m_gameData->generatePositionDepends(); // TODO: This is really bad... the generatedStartPosition2() function should never fail!!!! // Now try to position tiles on the board, 64 tries max. for (short sNr = 0; sNr < 64; ++sNr) { if (m_gameData->generateStartPosition2()) { m_gameGenerated = true; // No cheats are used until now. m_cheatsUsed = 0; addItemsFromBoardLayout(); populateItemNumber(); setStatusText(i18n("Ready. Now it is your turn.")); return; } } // Couldn't generate the game. m_gameGenerated = false; // Hide all generated tiles. foreach (GameItem * item, items()) { item->hide(); } setStatusText(i18n("Error generating new game!")); } void GameView::selectionChanged() { QList selectedGameItems = scene()->selectedItems(); // When no item is selected or help animation is running, there is nothing to do. if (selectedGameItems.size() < 1 || checkHelpAnimationActive() || checkDemoAnimationActive()) { return; } // If no item was already selected... if (m_selectedItem == nullptr) { // ...set the selected item. m_selectedItem = selectedGameItems.at(0); // Display the matching ones if wanted. if (m_match) { helpMatch(m_selectedItem); } } else { // The selected item is already there, so this is the second selected item. // If the same item was clicked, clear the selection and return. if (m_selectedItem == selectedGameItems.at(0)) { clearSelectedTile(); return; } // Get both items and their positions. POSITION stFirstPos = m_selectedItem->getGridPos(); POSITION stSecondPos = selectedGameItems.at(0)->getGridPos(); // Test if the items are the same... if (m_gameData->isMatchingTile(stFirstPos, stSecondPos)) { // Update the removed tiles in GameData. m_gameData->setRemovedTilePair(stFirstPos, stSecondPos); // One tile pair is removed, so we are not allowed to redo anymore. m_gameData->m_allowRedo = 0; // Remove the items. removeItem(stFirstPos); removeItem(stSecondPos); // Reset the selected item variable. m_selectedItem = nullptr; // Test whether the game is over or not. if (m_gameData->m_tileNum == 0) { emit gameOver(m_gameData->m_maxTileNum, m_cheatsUsed); } else { // The game is not over, so test if there are any valid moves. validMovesAvailable(); } } else { // The second tile keeps selected and becomes the first one. m_selectedItem = selectedGameItems.at(0); // Display the matching ones if wanted. if (m_match) { helpMatch(m_selectedItem); } } } } void GameView::removeItem(POSITION & stItemPos) { // Adding the data to the protocoll. m_gameData->setMoveListData(m_gameData->m_tileNum, stItemPos); // Put an empty item in the data object. (data part) m_gameData->putTile(stItemPos.z, stItemPos.y, stItemPos.x, 0); // Remove the item from the scene object. (graphic part) scene()->removeItem(stItemPos); // Decrement the tilenum variable from GameData. m_gameData->m_tileNum = m_gameData->m_tileNum - 1; // If TileNum is % 2 then update the number in the status bar. if (!(m_gameData->m_tileNum % 2)) { // The item numbers changed, so we need to populate the new information. populateItemNumber(); } } void GameView::startDemo() { qCDebug(KMAHJONGG_LOG) << "Starting demo mode"; // Create a new game with the actual game number. createNewGame(m_gameNumber); if (m_gameGenerated) { // Start the demo mode. m_demoAnimation->start(m_gameData); // Set the status text. setStatusText(i18n("Demo mode. Click mousebutton to stop.")); } } void GameView::startMoveListAnimation() { qCDebug(KMAHJONGG_LOG) << "Starting move list animation"; // Stop any helping animation. checkHelpAnimationActive(true); // Stop demo animation, if anyone is running. checkDemoAnimationActive(true); m_moveListAnimation->start(m_gameData); } void GameView::clearSelectedTile() { scene()->clearSelection(); m_selectedItem = nullptr; } void GameView::changeItemSelectedState(POSITION & stItemPos, bool selected) { GameItem * gameItem = scene()->getItemOnGridPos(stItemPos); if (gameItem != nullptr) { gameItem->setSelected(selected); } } void GameView::helpMove() { POSITION stItem1; POSITION stItem2; // Stop a running help animation. checkHelpAnimationActive(true); if (m_gameData->findMove(stItem1, stItem2)) { clearSelectedTile(); m_helpAnimation->addGameItem(scene()->getItemOnGridPos(stItem1)); m_helpAnimation->addGameItem(scene()->getItemOnGridPos(stItem2)); // Increase the cheat variable. ++m_cheatsUsed; m_helpAnimation->start(); } } void GameView::helpMatch(GameItem const * const gameItem) { int matchCount = 0; POSITION stGameItemPos = gameItem->getGridPos(); // Stop a running help animation. checkHelpAnimationActive(true); // Find matching items... if ((matchCount = m_gameData->findAllMatchingTiles(stGameItemPos))) { // ...add them to the animation object... for (int i = 0; i < matchCount; ++i) { if (scene()->getItemOnGridPos(m_gameData->getFromPosTable(i)) != gameItem) { m_helpAnimation->addGameItem(scene()->getItemOnGridPos( m_gameData->getFromPosTable(i))); } } // Increase the cheat variable. ++m_cheatsUsed; // ...and start the animation. m_helpAnimation->start(); } } bool GameView::checkHelpAnimationActive(bool stop) { bool active = m_helpAnimation->isActive(); // If animation is running and it should be closed, do so. if (active && stop) { m_helpAnimation->stop(); } return active; } bool GameView::checkMoveListAnimationActive(bool stop) { bool active = m_moveListAnimation->isActive(); // If animation is running and it should be closed, do so. if (active && stop) { m_moveListAnimation->stop(); } return active; } bool GameView::checkDemoAnimationActive(bool stop) { bool active = m_demoAnimation->isActive(); // If animation is running and it should be closed, do so. if (active && stop) { m_demoAnimation->stop(); } return active; } bool GameView::validMovesAvailable(bool silent) { POSITION stItem1; POSITION stItem2; if (!m_gameData->findMove(stItem1, stItem2)) { if (!silent) { emit noMovesAvailable(); } return false; } return true; } void GameView::pause(bool isPaused) { if (isPaused) { foreach (GameItem * item, items()) { item->hide(); } } else { foreach (GameItem * item, items()) { item->show(); } } } bool GameView::gameGenerated() { return m_gameGenerated; } void GameView::shuffle() { if (!gameGenerated()) { return; } if (checkDemoAnimationActive() || checkMoveListAnimationActive()) { return; } // Fix bug 156022 comment 5: Redo after shuffle can cause invalid states. m_gameData->m_allowRedo = 0; m_gameData->shuffle(); // Update the item images. updateItemsImages(items()); // Cause of using the shuffle function... increase the cheat used variable. m_cheatsUsed += 15; // Populate the new item numbers. populateItemNumber(); // Test if any moves are available validMovesAvailable(); // Clear any tile selection done proir to the shuffle. clearSelectedTile(); } void GameView::populateItemNumber() { // Update the allow_undo variable, cause the item number changes. m_gameData->m_allowUndo = (m_gameData->m_maxTileNum != m_gameData->m_tileNum); emit itemNumberChanged(m_gameData->m_maxTileNum, m_gameData->m_tileNum, m_gameData->moveCount()); } void GameView::addItemsFromBoardLayout() { // The QGraphicsScene::selectionChanged() signal can be emitted when deleting or removing // items, so disconnect from this signal to prevent our selectionChanged() slot being // triggered and trying to access those items when we clear the scene. // The signal is reconnected at the end of the function. disconnect(m_selectionChangedConnect); // Remove all existing items. scene()->clear(); // Create the items and add them to the scene. for (int iZ = 0; iZ < m_gameData->m_depth; ++iZ) { for (int iY = m_gameData->m_height - 1; iY >= 0; --iY) { for (int iX = m_gameData->m_width - 1; iX >= 0; --iX) { // Skip if no tile should be displayed on this position. if (!m_gameData->tilePresent(iZ, iY, iX)) { continue; } POSITION stItemPos; stItemPos.x = iX; stItemPos.y = iY; stItemPos.z = iZ; stItemPos.f = (m_gameData->BoardData(iZ, iY, iX) - TILE_OFFSET); addItem(stItemPos, false, false, false); } } } updateItemsImages(items()); updateItemsOrder(); // Reconnect our selectionChanged() slot. m_selectionChangedConnect = connect(scene(), &GameScene::selectionChanged, this, &GameView::selectionChanged); } void GameView::addItem(GameItem * gameItem, bool updateImage, bool updateOrder, bool updatePosition) { // Add the item to the scene. scene()->addItem(gameItem); // If TileNum is % 2 then update the number in the status bar. if (!(m_gameData->m_tileNum % 2)) { // The item numbers changed, so we need to populate the new information. populateItemNumber(); } QList gameItems; gameItems.append(gameItem); if (updateImage) { updateItemsImages(gameItems); } if (updatePosition) { // When updating the order... the position will automatically be updated after. if (updateOrder) { updateItemsOrder(); } else { updateItemsPosition(gameItems); } } } void GameView::addItem(POSITION & stItemPos, bool updateImage, bool updateOrder, bool updatePosition) { GameItem * gameItem = new GameItem(m_gameData->HighlightData(stItemPos.z, stItemPos.y, stItemPos.x)); gameItem->setGridPos(stItemPos); gameItem->setFlag(QGraphicsItem::ItemIsSelectable); m_gameData->putTile(stItemPos.z, stItemPos.y, stItemPos.x, stItemPos.f + TILE_OFFSET); addItem(gameItem, updateImage, updateOrder, updatePosition); } void GameView::addItemAndUpdate(POSITION & stItemPos) { addItem(stItemPos, true, true, true); } void GameView::updateItemsPosition(const QList &gameItems) { // These factor are needed for the different angles. So we simply can // calculate to move the items to the left or right and up or down. int angleXFactor = (m_angle == NE || m_angle == SE) ? -1 : 1; int angleYFactor = (m_angle == NW || m_angle == NE) ? -1 : 1; // Get half width and height of tile faces: minimum spacing = 1 pixel. qreal tileWidth = m_tiles->qWidth() + 0.5; qreal tileHeight = m_tiles->qHeight() + 0.5; // Get half height and width of tile-layout: ((n - 1) faces + full tile)/2. qreal tilesWidth = tileWidth * (m_gameData->m_width - 2) / 2 + m_tiles->width() / 2; qreal tilesHeight = tileHeight * (m_gameData->m_height - 2) / 2 + m_tiles->height() / 2; // Get the top-left offset required to center the items in the view. qreal xFrame = (width() / 2 - tilesWidth) / 2; qreal yFrame = (height() / 2 - tilesHeight) / 2; // TODO - The last /2 makes it HALF what it should be, but it gets doubled // somehow before the view is painted. Why? Apparently it is because // the background is painted independently by the VIEW, rather than // being an item in the scene and filling the scene completely. So // the whole scene is just the rectangle that contains the tiles. // NOTE - scene()->itemsBoundingRect() returns the correct doubled offset. for (int i = 0; i < gameItems.size(); ++i) { GameItem * gameItem = gameItems.at(i); // Get rasterized positions of the item. int x = gameItem->getGridPosX(); int y = gameItem->getGridPosY(); int z = gameItem->getGridPosZ(); // Set the position of the item on the scene. gameItem->setPos( xFrame + tileWidth * x / 2 + z * angleXFactor * (m_tiles->levelOffsetX() / 2), yFrame + tileHeight * y / 2 + z * angleYFactor * (m_tiles->levelOffsetY() / 2)); } } void GameView::updateItemsOrder() { int zCount = 0; int xStart = 0; int xEnd = 0; int xCounter = 0; int yStart = 0; int yEnd = 0; int yCounter = 0; switch (m_angle) { case NW: xStart = m_gameData->m_width - 1; xEnd = -1; xCounter = -1; yStart = 0; yEnd = m_gameData->m_height; yCounter = 1; break; case NE: xStart = 0; xEnd = m_gameData->m_width; xCounter = 1; yStart = 0; yEnd = m_gameData->m_height; yCounter = 1; break; case SE: xStart = 0; xEnd = m_gameData->m_width; xCounter = 1; yStart = m_gameData->m_height - 1; yEnd = -1; yCounter = -1; break; case SW: xStart = m_gameData->m_width - 1; xEnd = -1; xCounter = -1; yStart = m_gameData->m_height - 1; yEnd = -1; yCounter = -1; break; } GameScene * gameScene = scene(); for (int z = 0; z < m_gameData->m_depth; ++z) { for (int y = yStart; y != yEnd; y = y + yCounter) { orderLine(gameScene->getItemOnGridPos(xStart, y, z), xStart, xEnd, xCounter, y, yCounter, z, zCount); } } updateItemsPosition(items()); } void GameView::orderLine(GameItem * startItem, int xStart, int xEnd, int xCounter, int y, int yCounter, int z, int & zCount) { GameScene * gameScene = scene(); GameItem * gameItem = startItem; for (int i = xStart; i != xEnd; i = i + xCounter) { if (gameItem == nullptr) { if ((gameItem = gameScene->getItemOnGridPos(i, y, z)) == nullptr) { continue; } } gameItem->setZValue(zCount); ++zCount; gameItem = gameScene->getItemOnGridPos(i + 2 * xCounter, y - 1 * yCounter, z); if (gameItem != nullptr) { orderLine(gameItem, i + 2 * xCounter, xEnd, xCounter, y - 1 * yCounter, yCounter, z, zCount); gameItem = nullptr; } } } bool GameView::setTilesetPath(QString const & tilesetPath) { *m_tilesetPath = tilesetPath; if (m_tiles->loadTileset(tilesetPath)) { if (m_tiles->loadGraphics()) { resizeTileset(size()); return true; } } // Tileset or graphics could not be loaded, try default if (m_tiles->loadDefault()) { if (m_tiles->loadGraphics()) { resizeTileset(size()); *m_tilesetPath = m_tiles->path(); } } return false; } bool GameView::setBackgroundPath(QString const & backgroundPath) { qCDebug(KMAHJONGG_LOG) << "Set a new Background: " << backgroundPath; *m_backgroundPath = backgroundPath; if (m_background->load(backgroundPath, width(), height())) { if (m_background->loadGraphics()) { // Update the new background. updateBackground(); return true; } } qCDebug(KMAHJONGG_LOG) << "Loading the background failed. Try to load the default background."; // Try default if (m_background->loadDefault()) { if (m_background->loadGraphics()) { // Update the new background. updateBackground(); *m_backgroundPath = m_background->path(); } } return false; } void GameView::setAngle(TileViewAngle angle) { m_angle = angle; updateItemsImages(items()); updateItemsOrder(); } TileViewAngle GameView::getAngle() const { return m_angle; } void GameView::angleSwitchCCW() { switch (m_angle) { case SW: m_angle = NW; break; case NW: m_angle = NE; break; case NE: m_angle = SE; break; case SE: m_angle = SW; break; } updateItemsImages(items()); updateItemsOrder(); } void GameView::angleSwitchCW() { switch (m_angle) { case SW: m_angle = SE; break; case SE: m_angle = NE; break; case NE: m_angle = NW; break; case NW: m_angle = SW; break; } updateItemsImages(items()); updateItemsOrder(); } QList GameView::items() const { QList originalList = QGraphicsView::items(); QList tmpList; for (int i = 0; i < originalList.size(); ++i) { tmpList.append(dynamic_cast(originalList.at(i))); } return tmpList; } void GameView::mousePressEvent(QMouseEvent * pMouseEvent) { // If a move list animation is running start a new game. if (checkMoveListAnimationActive(true)) { emit demoOrMoveListAnimationOver(false); return; } // No mouse events when the demo mode is active. if (checkDemoAnimationActive(true)) { emit demoOrMoveListAnimationOver(false); return; } // If any help mode is active, ... stop it. checkHelpAnimationActive(true); // Then go on with the press event. QGraphicsView::mousePressEvent(pMouseEvent); } void GameView::resizeEvent(QResizeEvent * event) { if (event->spontaneous() || m_gameData == 0) { return; } resizeTileset(event->size()); m_background->sizeChanged(width(), height()); updateBackground(); setSceneRect(0, 0, width(), height()); } void GameView::resizeTileset(const QSize & size) { if (m_gameData == 0) { return; } QSize newtiles = m_tiles->preferredTileSize(size, m_gameData->m_width / 2, m_gameData->m_height / 2); foreach (GameItem * item, items()) { item->prepareForGeometryChange(); } m_tiles->reloadTileset(newtiles); updateItemsImages(items()); updateItemsPosition(items()); } void GameView::updateItemsImages(const QList &gameItems) { for (int i = 0; i < gameItems.size(); ++i) { GameItem * gameItem = gameItems.at(i); QPixmap selPix; QPixmap unselPix; QPixmap facePix; USHORT faceId = (m_gameData->BoardData(gameItem->getGridPosZ(), gameItem->getGridPosY(), gameItem->getGridPosX()) - TILE_OFFSET); gameItem->setFaceId(faceId); facePix = m_tiles->tileface(faceId); selPix = m_tiles->selectedTile(m_angle); unselPix = m_tiles->unselectedTile(m_angle); // Set the background pictures to the item. int shadowWidth = selPix.width() - m_tiles->levelOffsetX() - facePix.width(); int shadowHeight = selPix.height() - m_tiles->levelOffsetY() - facePix.height(); gameItem->setAngle(m_angle, &selPix, &unselPix, shadowWidth, shadowHeight); gameItem->setFace(&facePix); } // Repaint the view. update(); } void GameView::setStatusText(QString const & text) { emit statusTextChanged(text, m_gameNumber); } void GameView::updateBackground() { // qCDebug(KMAHJONGG_LOG) << "Update the background"; // TODO - The background should be a scene-item? See updateItemsPosition(). QBrush brush(m_background->getBackground()); setBackgroundBrush(brush); } void GameView::setGameData(GameData * gameData) { m_gameData = gameData; addItemsFromBoardLayout(); populateItemNumber(); } GameData * GameView::getGameData() const { return m_gameData; } QString GameView::getTilesetPath() const { return *m_tilesetPath; } QString GameView::getBackgroundPath() const { return *m_backgroundPath; } void GameView::setMatch(bool match) { m_match = match; } bool GameView::getMatch() const { return m_match; } diff --git a/src/gameview.h b/src/gameview.h index 324e784..659e356 100644 --- a/src/gameview.h +++ b/src/gameview.h @@ -1,420 +1,422 @@ /* Copyright (C) 2012 Christian Krippendorf * * Kmahjongg 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. */ #ifndef GAMEVIEW_H #define GAMEVIEW_H // Qt #include // KMahjongg #include "kmtypes.h" static const int ANIMATION_SPEED = 200; // Forward declaration... class GameScene; class GameData; class GameItem; +class GameBackground; class SelectionAnimation; class MoveListAnimation; class DemoAnimation; class KMahjonggTileset; class KMahjonggBackground; class QMouseEvent; /** * The Mahjongg board where the tiles (GameItems) will be painted. * * @author Christian Krippendorf */ class GameView : public QGraphicsView { Q_OBJECT public: /** * Constructor * * @param gameScene The related GameScene object. * @param gameData The related GameData object. * @param parent The parent widget. */ GameView(GameScene * gameScene, GameData * gameData, QWidget * parent = nullptr); ~GameView() override; /** * Items where added to the scene and should now be layouted. * * @param gameItems The items of which the positions should be updated. */ void updateItemsPosition(const QList &gameItems); /** * Updates the whole widget. * * @param showTiles True if the tiles should be displayed, else false. */ void updateWidget(bool showTiles); /** * Override from QGraphcisView. */ virtual QList items() const; /** * Override from QGraphicsView. */ GameScene * scene() const; /** * Set the GameData object. * * @param gameData The game data object. */ void setGameData(GameData * gameData); /** * Get the GameData object that is actually set. * * @return The actual GameData object. */ GameData * getGameData() const; /** * Set the angle of the view. * * @param angle The angle of to set up. */ void setAngle(TileViewAngle angle); /** * Get the angle of the view. * * @return The angle of the view. */ TileViewAngle getAngle() const; /** * Test for active help animation and maybe close. * * @param stop Stop the help animation if running. * @return Return true if the help animation was running else false. */ bool checkHelpAnimationActive(bool stop = false); /** * Test for active demo animation and maybe close. * * @param stop Stop the demo animation if running. * @return Return true if the demo animation was running else false. */ bool checkDemoAnimationActive(bool stop = false); /** * Test for active move list animation and maybe close. * * @param stop Stop the move list animation if running. * @return Return true if the move list animation was running else false. */ bool checkMoveListAnimationActive(bool stop = false); /** * Set the match variable. If set to true, the matching items to the selected will be animated. * * @param match The match value to set up. */ void setMatch(bool match); /** * Get the match value. * * @return True when matching items of the eselected one will be displayed, else false. */ bool getMatch() const; /** * Gets the tilesetpath that is actually set. * * @return The tilesetpath as a string. */ QString getTilesetPath() const; /** * Gets the background path that is actually set. * * @return The background path as a string. */ QString getBackgroundPath() const; /** * Sets the tileset path and tries to load it. * * @param tilesetPath The path to the tileset. * @return True if setting and therfore loading success, else false. */ bool setTilesetPath(QString const & tilesetPath); /** * Sets the background path and tries to load it. * * @param backgroundPath The path to the background. * @return True if setting and therfore loading success, else false. */ bool setBackgroundPath(QString const & backgroundPath); /** * Undo the last move. * * @return True if successful, else false. */ bool undo(); /** * Redo the last undo. * * @return True if successful, else false. */ bool redo(); /** * Test if undo is allowed. * * @return True if allowed, else false. */ bool checkUndoAllowed(); /** * Test if redo is allowed. * * @return True if allowed, else false. */ bool checkRedoAllowed(); /** * Get the game number. * * @return The game number or -1 if no game number set. */ long getGameNumber() const; /** * Set the game number. * * @param gameNumber Game number. */ void setGameNumber(long gameNumber); /** * Search for a valid move silently or with an information text. * * @param silent False if a message should appears when no legal moves exist, else true. * Default ist false! * @return True if a legal move exist, else false. */ bool validMovesAvailable(bool silent = false); /** * Hide/show tiles when game is paused/unpaused. */ void pause(bool isPaused); /** * Get whether a game was generated. * * @return True if game was generated, or false. */ bool gameGenerated(); public slots: /** * Add a new item with teh given position and update imgages, position and order. */ void addItemAndUpdate(POSITION & stItemPos); /** * Remove the given item. * * @param stItemPos The item position. */ void removeItem(POSITION & stItemPos); /** * Starts the demo animation. */ void startDemo(); /** * Switch the view angle to the next right around. */ void angleSwitchCCW(); /** * Switch the view angle to the next left around. */ void angleSwitchCW(); /** * Create a new game. * * @param gameNumber The game number to create or -1 for a random number. */ void createNewGame(long gameNumber = -1); /** * Shuffle the position of items. */ void shuffle(); /** * Give a hint for a valid move. */ void helpMove(); /** * Give a hint to the matching tiles. * * @param gameItem The item we search matching tiles for. */ void helpMatch(const GameItem * const gameItem); /** * Start the move list animation. */ void startMoveListAnimation(); /** * Clear the selection. */ void clearSelectedTile(); protected: /** * Override from QGraphicsView. */ void resizeEvent(QResizeEvent * event) override; /** * Override from QGraphicsView. */ void mousePressEvent(QMouseEvent * mouseEvent) override; signals: /** * Emits when a new game was calculated. */ void newGameCalculated(); /** * Emits when the status text changed. * * @param text The new status text. * @param gameNumber The actual game number. */ void statusTextChanged(const QString & text, long gameNumber); /** * Emits when the number of the items changed or could change. * * @param maxItemNum The max tile number. * @param itemNum The item number that are still there. * @param moveCount Number of moves. */ void itemNumberChanged(int maxItemNum, int itemNum, int moveCount); /** * Emits when the game is over. * * @param removedItems The number of the removed items. * @param cheatsUsed The number of the cheats that are used. */ void gameOver(unsigned short removedItems, unsigned short cheatsUsed); /** * Emits when demo is played out and lost or stopped by a mouse click, or * the MoveListAnimation is stopped by a mouse click. * * @param demoGameLost True if demo game is played out and lost. */ void demoOrMoveListAnimationOver(bool demoGameLost); /** * Emits when no more moves are available. */ void noMovesAvailable(); private slots: /** * Add a new item with the given position. * * @param stItemPos The position for the new item. * @param updateImage True for updating the images else false. * @param updateOrder True for updating the order else false. * @param updatePosition True for updating the position else false. */ void addItem(POSITION & stItemPos, bool updateImage = false, bool updateOrder = false, bool updatePosition = false); /** * Add a new item. * * @param gameItem The new game item object. * @param updateImage True for updating the images else false. * @param updateOrder True for updating the order else false. * @param updatePosition True for updating the position else false. */ void addItem(GameItem * gameItem, bool updateImage = false, bool updateOrder = false, bool updatePosition = false); /** * When the game is over by the demo mode. * * @param won True if the computer won in demo mode, else false. */ void demoGameOver(bool won); /** * Change the selected state of the given item. * * @param stItemPos The position of the item. * @param selected The selection state to set. */ void changeItemSelectedState(POSITION & stItemPos, bool selected); /** * Gets called when a pair was selected. */ void selectionChanged(); private: /** * Updates the images of the items. * * @param gameItem The items of which the images should be updated. */ void updateItemsImages(const QList &gameItems); /** * Updates the order of the items. */ void updateItemsOrder(); /** * Populates the number of the items, by emit a signal: itemNumberChanged(...). */ void populateItemNumber(); /** * Sets the status text. * * @param text The new status text. */ void setStatusText(const QString & text); /** * Resize the tileset to the given size. * * @param size The new size of the tileset. */ void resizeTileset(const QSize & size); /** * Updates the background by creating a new QPalette object. */ void updateBackground(); /** * Add all the items from the board layout to the scene object. */ void addItemsFromBoardLayout(); /** * Order the line starting by the item. * * @param startItem The item where the line starts. * @param xStart The x position of the item. * @param y The y position of the item. * @param z The z position of the item. * @param zCount The z count variable for ther order. */ void orderLine(GameItem * startItem, int xStart, int xEnd, int xCounter, int y, int yCounter, int z, int & zCount); unsigned short m_cheatsUsed; long m_gameNumber; bool m_gamePaused; bool m_match; bool m_gameGenerated; GameData * m_gameData; GameItem * m_selectedItem; + GameBackground *m_gameBackground; QString * m_tilesetPath; QString * m_backgroundPath; SelectionAnimation * m_helpAnimation; MoveListAnimation * m_moveListAnimation; DemoAnimation * m_demoAnimation; KMahjonggTileset * m_tiles; KMahjonggBackground * m_background; TileViewAngle m_angle; // Needed for disconnecting connection QMetaObject::Connection m_selectionChangedConnect; }; #endif // GAMEVIEW_H