I reworked the Game I did in FLTK some while ago: Hunt the Wumpus GUI (FLTK)
This time I used C++ with the Qt Framework. The result looks like this:
If you want to try out the game. The full code can be found in my Git Hub: https://github.com/sandro4912/hunt-the-wumpus
One more hint not find in the Instructions. If the main game is started you can type "show" to show all the hazards in the neighbour rooms. Or "hide" to hide them. I have a basic keylogger looking for these words to be typed to debug/cheat.
Since the codebase is to big to post it here, I want to focus on some important parts of the code.
- Room: represents a single Room in the Dungeon.
- DungeonView: The view which displays the dungeon. It manages dragging the player to annother room and shooting the arrows.
- Dungeon: Manages the Scene and the DungeonView and the rooms
- roomutility: helper functions to create the dungeon / connect the rooms.
Let me know what you think of the code. Is it easy to read / understand? Are there any bad practices?
Feel free to also check the other classes in the repo. If there's anything strange, let me know.
room.h
#ifndef ROOM_H
#define ROOM_H
#include <QGraphicsWidget>
#include <array>
class QAction;
class Room : public QGraphicsWidget
{
Q_OBJECT
public:
explicit Room(QGraphicsItem *parent = nullptr);
void emitNeigbourHazards();
[[nodiscard]] bool isEmpty() const;
void setWumpus(bool value);
[[nodiscard]] bool hasWumpus() const;
void setPit(bool value);
[[nodiscard]] bool hasPit() const;
void setBat(bool value);
[[nodiscard]] bool hasBat() const;
void setPlayer(bool value);
[[nodiscard]] bool hasPlayer() const;
void setPlayerWasHere(bool value);
[[nodiscard]] bool playerWasHere() const;
void setTarget(bool value);
[[nodiscard]] bool isTarget() const;
void addNeighbour(Room *neighbour);
[[nodiscard]] QVector<Room *> neighbours() const;
void showContent(bool value);
void clear();
[[nodiscard]] QRectF boundingRect() const override;
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
enum { Type = UserType + 1 };
[[nodiscard]] int type() const override;
signals:
void wumpusNear();
void batNear();
void pitNear();
void playerDiedFromWumpus();
void playerDiedFromPit();
void playerDraggedByBat();
void entered();
public slots:
void enter();
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override;
void dropEvent(QGraphicsSceneDragDropEvent *event) override;
private slots:
void toggleGuessWumpus();
void toggleGuessPit();
void toggleGuessBat();
private:
bool isNeighbour(Room *room) const;
bool minDistanceReached(QGraphicsSceneMouseEvent *event,
Qt::MouseButton button);
void executePlayerDrag(QGraphicsSceneMouseEvent *event);
bool isPlayerDrag(QGraphicsSceneDragDropEvent *event);
void drawRoom(QPainter *painter, const QImage &roomImage);
void drawWumpus(QPainter *painter, const QImage &wumpusImage);
void drawBat(QPainter *painter, const QImage &batImage);
void drawPit(QPainter *painter, const QImage &pitImage);
void drawPlayer(QPainter *painter);
[[nodiscard]] QImage roomImage() const;
[[nodiscard]] QImage roomVisitedImage() const;
[[nodiscard]] QImage roomTargetImage() const;
[[nodiscard]] QImage batImage() const;
[[nodiscard]] QImage batConfirmedImage() const;
[[nodiscard]] QImage pitImage() const;
[[nodiscard]] QImage pitConfirmedImage() const;
[[nodiscard]] QImage wumpusImage() const;
[[nodiscard]] QImage wumpusConfirmedImage() const;
[[nodiscard]] QImage playerImage() const;
[[nodiscard]] QImage playerDraggedImage() const;
bool mBatConfirmed{ false };
bool mHasWumpus{ false };
bool mHasPit{ false };
bool mHasBat{ false };
bool mHasPlayer{ false };
bool mPlayerWasHere{ false };
bool mGuessWumpus{ false };
bool mGuessBat{ false };
bool mGuessPit{ false };
bool mShowContent{ false };
bool mIsTarget{ false };
QAction *mGuessWumpusAction;
QAction *mGuessBatAction;
QAction *mGuessPitAction;
QVector<Room *> mNeighbours;
};
#endif // ROOM_H
room.cpp
#include "room.h"
#include "dragplayermimedata.h"
#include <QApplication>
#include <QAction>
#include <QCursor>
#include <QDrag>
#include <QMimeData>
#include <QGraphicsWidget>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QMenuBar>
#include <QMouseEvent>
#include <QPainter>
Room::Room(QGraphicsItem *parent)
: QGraphicsWidget(parent)
{
Q_ASSERT(roomImage().size() == roomVisitedImage().size());
Q_ASSERT(roomVisitedImage().size() == roomTargetImage().size());
Q_ASSERT(roomImage().height() ==
pitImage().height() + batImage().height());
Q_ASSERT(roomImage().height() ==
pitImage().height() + playerImage().height());
Q_ASSERT(roomImage().height() ==
pitImage().height() + wumpusImage().height());
Q_ASSERT(roomImage().width() ==
batImage().width() + playerImage().width() +
wumpusImage().width());
Q_ASSERT(batImage().size() == batConfirmedImage().size());
Q_ASSERT(pitImage().size() == pitConfirmedImage().size());
Q_ASSERT(wumpusImage().size() == wumpusConfirmedImage().size());
setAcceptDrops(true);
mGuessWumpusAction = new QAction{tr("Has &Wumpus")};
mGuessWumpusAction->setCheckable(true);
connect(mGuessWumpusAction, &QAction::triggered,
this, &Room::toggleGuessWumpus);
mGuessBatAction = new QAction{tr("Has &Bat")};
mGuessBatAction->setCheckable(true);
connect(mGuessBatAction, &QAction::triggered, this, &Room::toggleGuessBat);
mGuessPitAction = new QAction{tr("Has &Pit")};
mGuessPitAction->setCheckable(true);
connect(mGuessPitAction, &QAction::triggered, this, &Room::toggleGuessPit);
addAction(mGuessWumpusAction);
addAction(mGuessBatAction);
addAction(mGuessPitAction);
resize(boundingRect().size());
setVisible(false);
}
void Room::emitNeigbourHazards()
{
auto batEmitted = false;
auto wumpusEmitted = false;
auto pitEmitted = false;
for(const auto& neigbour : mNeighbours) {
if(neigbour == nullptr) {
continue;
}
if (!batEmitted && neigbour->hasBat()) {
emit batNear();
batEmitted = true;
}
if (!wumpusEmitted && neigbour->hasWumpus()) {
emit wumpusNear();
wumpusEmitted = true;
}
if (!pitEmitted && neigbour->hasPit()) {
emit pitNear();
pitEmitted = true;
}
}
}
bool Room::isEmpty() const
{
return !hasPlayer() && !hasBat()&& !hasPit() && !hasWumpus();
}
void Room::setWumpus(bool value)
{
if(value && hasPlayer()) {
emit playerDiedFromWumpus();
}
mHasWumpus = value;
update();
}
bool Room::hasWumpus() const
{
return mHasWumpus;
}
void Room::setPit(bool value)
{
mHasPit = value;
update();
}
bool Room::hasPit() const
{
return mHasPit;
}
void Room::setBat(bool value)
{
mHasBat = value;
update();
}
bool Room::hasBat() const
{
return mHasBat;
}
void Room::setPlayer(bool value)
{
mHasPlayer = value;
if (value) {
mPlayerWasHere = true;
}
update();
}
bool Room::hasPlayer() const
{
return mHasPlayer;
}
void Room::setPlayerWasHere(bool value)
{
mPlayerWasHere = value;
update();
}
bool Room::playerWasHere() const
{
return mPlayerWasHere;
}
void Room::setTarget(bool value)
{
mIsTarget = value;
update();
}
bool Room::isTarget() const
{
return mIsTarget;
}
void Room::addNeighbour(Room *neighbour)
{
mNeighbours.push_back(neighbour);
}
QVector<Room *> Room::neighbours() const
{
return mNeighbours;
}
void Room::showContent(bool value)
{
mShowContent = value;
update();
}
void Room::clear()
{
mBatConfirmed = false;
mHasWumpus = false;
mHasPit = false;
mHasBat = false;
mHasPlayer = false;
mPlayerWasHere = false;
mGuessWumpus = false;
mGuessBat = false;
mGuessPit = false;
mShowContent = false;
mIsTarget = false;
}
QRectF Room::boundingRect() const
{
return QRectF{ 0, 0,
static_cast<qreal>(roomImage().width()),
static_cast<qreal>(roomImage().height())};
}
void Room::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
if (isTarget()) {
drawRoom(painter, roomTargetImage());
}
else if (playerWasHere()) {
drawRoom(painter, roomVisitedImage());
}
else {
drawRoom(painter, roomImage());
}
if (hasPlayer()) {
drawPlayer(painter);
}
if (mShowContent) {
if (hasWumpus()) {
drawWumpus(painter, wumpusConfirmedImage());
}
if (hasBat()) {
drawBat(painter, batConfirmedImage());
}
if (hasPit()) {
drawPit(painter, pitConfirmedImage());
}
}
else {
if (mGuessWumpus) {
drawWumpus(painter, wumpusImage());
}
if (mBatConfirmed) {
drawBat(painter, batConfirmedImage());
}
else if (mGuessBat) {
drawBat(painter, batImage());
}
if (mGuessPit) {
drawPit(painter, pitImage());
}
}
}
int Room::type() const
{
return Type;
}
void Room::enter()
{
if (mHasWumpus) {
emit playerDiedFromWumpus();
return;
}
if (mHasPit) {
emit playerDiedFromPit();
return;
}
if (mHasBat) {
emit playerDraggedByBat();
mBatConfirmed = true;
mGuessBatAction->setChecked(true);
mGuessBatAction->setEnabled(false);
return;
}
setVisible(true);
for(auto &neighbour : mNeighbours) {
neighbour->setVisible(true);
neighbour->update();
}
emitNeigbourHazards();
emit entered();
setPlayer(true);
}
void Room::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
if (hasPlayer()) {
return;
}
QMenu menu;
menu.addAction(mGuessWumpusAction);
menu.addAction(mGuessBatAction);
menu.addAction(mGuessPitAction);
menu.exec(event->screenPos());
}
void Room::mousePressEvent(QGraphicsSceneMouseEvent *event)
// needs to be overriden to toggle mouseMoveEvent
{
Q_UNUSED(event)
}
void Room::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(!hasPlayer()) {
return;
}
if (minDistanceReached(event, Qt::LeftButton)) {
executePlayerDrag(event);
}
}
void Room::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
// needs to be overriden to toggle mouseMoveEvent
{
Q_UNUSED(event)
}
void Room::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
const auto dragPlayerData =
qobject_cast<const DragPlayerMimeData *>(event->mimeData());
if(dragPlayerData && isNeighbour(dragPlayerData->room()) &&
dragPlayerData->imageData() == playerDraggedImage()) {
event->setAccepted(true);
}
else {
event->setAccepted(false);
}
}
void Room::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
if (isPlayerDrag(event)) {
setPlayer(false);
update();
}
}
void Room::dropEvent(QGraphicsSceneDragDropEvent *event)
{
const auto roomData =
qobject_cast<const DragPlayerMimeData *>(event->mimeData());
if(roomData && isNeighbour(roomData->room()) &&
roomData->imageData() == playerDraggedImage()) {
enter();
update();
event->setAccepted(true);
}
else {
event->setAccepted(false);
}
}
void Room::toggleGuessWumpus()
{
if(mGuessWumpus) {
mGuessWumpus = false;
mGuessWumpusAction->setChecked(false);
}
else {
mGuessWumpus = true;
mGuessWumpusAction->setChecked(true);
}
update();
}
void Room::toggleGuessPit()
{
if(mGuessPit) {
mGuessPit = false;
mGuessPitAction->setChecked(false);
}
else {
mGuessPit = true;
mGuessPitAction->setChecked(true);
}
update();
}
void Room::toggleGuessBat()
{
if(mGuessBat) {
mGuessBat = false;
mGuessBatAction->setChecked(false);
}
else {
mGuessBat = true;
mGuessBatAction->setChecked(true);
}
update();
}
bool Room::isNeighbour(Room *room) const
{
if (room == nullptr) {
return false;
}
for(const auto &neighbour : mNeighbours) {
if (room == neighbour) {
return true;
}
}
return false;
}
bool Room::minDistanceReached(QGraphicsSceneMouseEvent *event,
Qt::MouseButton button)
{
return QLineF(event->screenPos(),
event->buttonDownScreenPos(button))
.length() >= QApplication::startDragDistance();
}
void Room::executePlayerDrag(QGraphicsSceneMouseEvent *event)
{
setPlayer(false);
auto drag = new QDrag(event->widget());
auto mime = new DragPlayerMimeData;
mime->setRoom(this);
drag->setMimeData(mime);
mime->setImageData(playerDraggedImage());
drag->setPixmap(QPixmap::fromImage(playerDraggedImage()));
drag->setHotSpot(QPoint(0, 0));
auto result = drag->exec();
if (result == Qt::IgnoreAction) {
setPlayer(true);
}
}
bool Room::isPlayerDrag(QGraphicsSceneDragDropEvent *event)
{
return qobject_cast<const DragPlayerMimeData *>(event->mimeData()) &&
event->mimeData()->imageData() == playerDraggedImage();
}
void Room::drawRoom(QPainter *painter, const QImage &roomImage)
{
painter->drawImage(boundingRect(), roomImage);
}
void Room::drawWumpus(QPainter *painter, const QImage &wumpusImage)
{
auto xOffset = roomImage().width() - wumpusImage.width();
painter->drawImage(
QPointF{boundingRect().x() + xOffset, boundingRect().y()},
wumpusImage);
}
void Room::drawBat(QPainter *painter, const QImage &batImage)
{
painter->drawImage(
QPointF{boundingRect().x(), boundingRect().y()}, batImage);
}
void Room::drawPit(QPainter *painter, const QImage &pitImage)
{
auto yOffset = roomImage().height() - pitImage.height();
painter->drawImage(
QPointF{boundingRect().x(), boundingRect().y() + yOffset},
pitImage);
}
void Room::drawPlayer(QPainter *painter)
{
auto xOffset = roomImage().width() - wumpusImage().width() -
playerImage().width();
painter->drawImage(
QPointF{boundingRect().x() + xOffset, boundingRect().y()},
playerImage());
}
QImage Room::roomImage() const
{
return QImage{":/ressources/room.png"};
}
QImage Room::roomVisitedImage() const
{
return QImage{":/ressources/room_visited.png"};
}
QImage Room::roomTargetImage() const
{
return QImage{":/ressources/room_target.png"};
}
QImage Room::batImage() const
{
return QImage{":/ressources/bat.png"};
}
QImage Room::batConfirmedImage() const
{
return QImage{":/ressources/bat_confirmed.png"};
}
QImage Room::pitImage() const
{
return QImage{":/ressources/pit.png"};
}
QImage Room::pitConfirmedImage() const
{
return QImage{":/ressources/pit_confirmed.png"};
}
QImage Room::wumpusImage() const
{
return QImage{":/ressources/wumpus.png"};
}
QImage Room::wumpusConfirmedImage() const
{
return QImage{":/ressources/wumpus_confirmed.png"};
}
QImage Room::playerImage() const
{
return QImage{":/ressources/player.png"};
}
QImage Room::playerDraggedImage() const
{
return QImage{":/ressources/player_dragged.png"};
}
dungeonview.h
#ifndef DUNGEONVIEW_H
#define DUNGEONVIEW_H
#include <QGraphicsView>
class Room;
class DungeonView : public QGraphicsView
{
Q_OBJECT
public:
explicit DungeonView(QWidget *parent = nullptr);
signals:
void shootHitWumpus();
void shootMissedWumpus();
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
void startShootArrowMode(Room *room);
void stopShootArrowMode(QMouseEvent *event);
void shootArrow(QMouseEvent *event);
void startDragPlayerMode(QMouseEvent *event);
void stopDragPlayerMode(QMouseEvent *event);
[[nodiscard]] bool maxArrowRangeReached() const;
bool isMarked(Room *room) const;
bool isNeigbourOfLastMarkedRoom(Room *room) const;
bool leftRoom(Room *current, Room* last);
bool enteredRoom(Room *current, Room* last);
static constexpr auto mArrowRoomRange{ 3 };
bool mShootArrowSelectOn{ false };
Room *mlastRoom{ nullptr };
QVector<Room *> mMarkedRooms;
};
#endif // DUNGEONVIEW_H
dungeonview.cpp
#include "dungeonview.h"
#include "room.h"
#include <QDebug>
#include <QMouseEvent>
#include <algorithm>
DungeonView::DungeonView(QWidget *parent)
:QGraphicsView{ parent }
{
setMouseTracking(true);
}
void DungeonView::mousePressEvent(QMouseEvent *event)
{
if (auto room = qgraphicsitem_cast<Room *>(itemAt(event->pos()));
room && room->hasPlayer()) {
if (event->button() == Qt::RightButton) {
startShootArrowMode(room);
}
else if (event->button() == Qt::LeftButton) {
startDragPlayerMode(event);
}
}
}
void DungeonView::mouseReleaseEvent(QMouseEvent *event)
{
if (mShootArrowSelectOn && event->button() == Qt::RightButton) {
stopShootArrowMode(event);
}
else if (auto room = qgraphicsitem_cast<Room *>(itemAt(event->pos())) ;
room && room->hasPlayer() &&
event->button() == Qt::LeftButton) {
stopDragPlayerMode(event);
}
}
void DungeonView::mouseMoveEvent(QMouseEvent *event)
{
auto room = qgraphicsitem_cast<Room *>(itemAt(event->pos()));
if(room && !mShootArrowSelectOn) {
QGraphicsView::mouseMoveEvent(event);
}
if(room == mlastRoom) {
return;
}
if(!mShootArrowSelectOn && leftRoom(room, mlastRoom)) {
setCursor(Qt::ArrowCursor);
}
else if (mShootArrowSelectOn && !maxArrowRangeReached() &&
!isMarked(room) && isNeigbourOfLastMarkedRoom(room)) {
room->setTarget(true);
mMarkedRooms.push_back(room);
}
else if (enteredRoom(room, mlastRoom) && room->hasPlayer()) {
setCursor(Qt::OpenHandCursor);
}
mlastRoom = room;
}
void DungeonView::startShootArrowMode(Room *room)
{
mShootArrowSelectOn = true;
room->setTarget(true);
mlastRoom = room;
mMarkedRooms.push_back(room);
setCursor(Qt::CrossCursor);
}
void DungeonView::stopShootArrowMode(QMouseEvent *event)
{
for (auto &room : mMarkedRooms) {
room->setTarget(false);
}
if (mMarkedRooms.size() > 1) {
shootArrow(event);
}
mMarkedRooms.clear();
mlastRoom = nullptr;
mShootArrowSelectOn = false;
setCursor(Qt::ArrowCursor);
}
void DungeonView::shootArrow(QMouseEvent *event)
{
auto room = qgraphicsitem_cast<Room *>(itemAt(event->pos()));
if(room && room == mMarkedRooms.back()) {
auto hitWumpus = false;
for (auto i = 1; i < mMarkedRooms.size(); ++i) {
if(room->hasWumpus()) {
emit shootHitWumpus();
hitWumpus = true;
break;
}
}
if (!hitWumpus) {
emit shootMissedWumpus();
}
}
}
void DungeonView::startDragPlayerMode(QMouseEvent *event)
{
setCursor(Qt::ClosedHandCursor);
QGraphicsView::mousePressEvent(event);
}
void DungeonView::stopDragPlayerMode(QMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
QGraphicsView::mousePressEvent(event);
}
bool DungeonView::maxArrowRangeReached() const
{
return mMarkedRooms.size() > mArrowRoomRange;
}
bool DungeonView::isMarked(Room *room) const
{
if(mMarkedRooms.empty()) {
return false;
}
return std::find(mMarkedRooms.begin(),
mMarkedRooms.end(), room) != mMarkedRooms.end();
}
bool DungeonView::isNeigbourOfLastMarkedRoom(Room *room) const
{
if(mMarkedRooms.empty()) {
return false;
}
auto lastRoomNeigbours = mMarkedRooms.back()->neighbours();
return std::find(lastRoomNeigbours.begin(),
lastRoomNeigbours.end(), room) != lastRoomNeigbours.end();
}
bool DungeonView::leftRoom(Room *current, Room *last)
{
return (current == nullptr && last != nullptr);
}
bool DungeonView::enteredRoom(Room *current, Room *last)
{
return (current != nullptr && last == nullptr);
}
dungeon.h
#ifndef DUNGEON_H
#define DUNGEON_H
#include <QWidget>
class Room;
class QGraphicsScene;
class QGraphicsLineItem;
class DungeonView;
class Dungeon : public QWidget
{
Q_OBJECT
public:
explicit Dungeon(QWidget *parent = nullptr);
void showHazards(bool value);
void enter();
void reset();
[[nodiscard]] int remainingArrows() const;
signals:
void wumpusNear();
void batNear();
void pitNear();
void playerDraggedByBat();
void playerDiedFromWumpus();
void playerDiedFromPit();
void playerIsOutOfArrows();
void playerKilledWumpus();
void enteredRoom();
void arrowShot();
private slots:
void moveWumpusIfWakeUp();
void showLinesToNeigboursOfRoom();
void decreaseArrows();
private:
void scaleViewToSize();
void emptyRooms();
void hideDungeon();
void createRooms();
void connectToRooms();
void addRoomsToScene();
void addLinesToScene();
void addLineToNeigbours(const Room *room);
bool lineExistsInScene(const QLineF &line);
QGraphicsLineItem *findLineInScene(const QLineF &line);
static constexpr auto mCountOfRooms = 20;
static constexpr auto mCountOfPits = 2;
static constexpr auto mCountOfBats = 2;
static constexpr auto mCountOfArrows = 5;
int mRemainingArrows{ mCountOfArrows };
QGraphicsScene *mGraphicsScene;
DungeonView *mDungeonView;
QVector<Room *> mRooms;
};
#endif // DUNGEON_H
dungeon.cpp
#include "dungeon.h"
#include "dungeonview.h"
#include "room.h"
#include "roomutility.h"
#include <QGuiApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QRandomGenerator>
#include <QScreen>
#include <QVBoxLayout>
#include <QDebug>
#include <algorithm>
Dungeon::Dungeon(QWidget *parent)
: QWidget(parent),
mGraphicsScene{ new QGraphicsScene },
mDungeonView{ new DungeonView }
{
createRooms();
connectRoomsAsDodekaeder(mRooms);
setPositionOfRooms(mRooms);
addRoomsToScene();
addLinesToScene();
connectToRooms();
populateRoomsRandom(mRooms, mCountOfPits, mCountOfBats);
mDungeonView->setScene(mGraphicsScene);
mDungeonView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mDungeonView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
connect(mDungeonView, &DungeonView::shootHitWumpus,
this, &Dungeon::playerKilledWumpus);
connect(mDungeonView, &DungeonView::shootMissedWumpus,
this, &Dungeon::moveWumpusIfWakeUp);
connect(mDungeonView, &DungeonView::shootHitWumpus,
this, &Dungeon::decreaseArrows);
connect(mDungeonView, &DungeonView::shootMissedWumpus,
this, &Dungeon::decreaseArrows);
connect(mDungeonView, &DungeonView::shootHitWumpus,
this, &Dungeon::arrowShot);
connect(mDungeonView, &DungeonView::shootMissedWumpus,
this, &Dungeon::arrowShot);
auto layout = new QVBoxLayout;
layout->addWidget(mDungeonView);
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
setLayout(layout);
scaleViewToSize();
setFixedSize(size());
}
void Dungeon::showHazards(bool value)
{
for(auto &room : mRooms) {
room->showContent(value);
}
}
void Dungeon::enter()
{
for (;;) {
auto idx = QRandomGenerator::global()->bounded(0, mRooms.size() - 1);
if(mRooms[idx]->isEmpty()) {
mRooms[idx]->enter();
break;
}
}
}
void Dungeon::reset()
{
emptyRooms();
hideDungeon();
mRemainingArrows = mCountOfArrows;
populateRoomsRandom(mRooms, mCountOfPits, mCountOfBats);
}
int Dungeon::remainingArrows() const
{
return mRemainingArrows;
}
void Dungeon::moveWumpusIfWakeUp()
{
auto direction = QRandomGenerator::global()->bounded(0, 3);
if (direction == 3) { // 25% chance that wumpus won't move
return;
}
auto wumpusRoomIt{ std::find_if(mRooms.begin(), mRooms.end(),
[](Room *room) { return room->hasWumpus(); }) };
(*wumpusRoomIt)->setWumpus(false);
auto newRoom = (*wumpusRoomIt)->neighbours()[direction];
newRoom->setWumpus(true);
auto playerRoomIt{ std::find_if(mRooms.begin(), mRooms.end(),
[](Room *room) { return room->hasPlayer(); }) };
(*playerRoomIt)->emitNeigbourHazards();
}
void Dungeon::showLinesToNeigboursOfRoom()
{
auto room = qobject_cast<Room *>(sender());
auto startPoint = room->mapToScene(room->rect().center());
auto neighbours = room->neighbours();
for (const auto &neighbour : neighbours) {
auto endPoint = neighbour->mapToScene(neighbour->rect().center());
QLineF line{ startPoint, endPoint };
auto lineItem = findLineInScene(line);
if (lineItem) {
lineItem->show();
}
}
}
void Dungeon::decreaseArrows()
{
--mRemainingArrows;
if (mRemainingArrows == 0) {
emit playerIsOutOfArrows();
}
}
void Dungeon::scaleViewToSize()
{
mDungeonView->fitInView(mDungeonView->scene()->sceneRect(),
Qt::KeepAspectRatio);
}
void Dungeon::emptyRooms()
{
for(auto room : mRooms) {
room->clear();
}
}
void Dungeon::hideDungeon()
{
auto items = mGraphicsScene->items();
for (auto &item : items) {
item->hide();
}
}
void Dungeon::createRooms()
{
mRooms.reserve(mCountOfRooms);
for(int i = 0; i< mCountOfRooms; ++i) {
mRooms.push_back(new Room);
}
}
void Dungeon::connectToRooms()
{
for(const auto &room : mRooms) {
connect(room, &Room::entered,
this, &Dungeon::showLinesToNeigboursOfRoom);
connect(room, &Room::entered,
this, &Dungeon::enteredRoom);
connect(room, &Room::wumpusNear,
this, &Dungeon::wumpusNear);
connect(room, &Room::batNear,
this, &Dungeon::batNear);
connect(room, &Room::pitNear,
this, &Dungeon::pitNear);
connect(room, &Room::playerDiedFromWumpus,
this, &Dungeon::playerDiedFromWumpus);
connect(room, &Room::playerDiedFromPit,
this, &Dungeon::playerDiedFromPit);
connect(room, &Room::playerDraggedByBat,
this, &Dungeon::playerDraggedByBat);
connect(room, &Room::playerDraggedByBat,
this, &Dungeon::enter);
}
}
void Dungeon::addRoomsToScene()
{
for(const auto &room : mRooms) {
mGraphicsScene->addItem(room);
}
}
void Dungeon::addLinesToScene()
{
for (const auto& room : mRooms) {
addLineToNeigbours(room);
}
}
void Dungeon::addLineToNeigbours(const Room *room)
{
auto startPoint = room->mapToScene(room->rect().center());
constexpr auto lineWidth = 10;
QPen pen;
pen.setBrush(Qt::black);
pen.setWidth(lineWidth);
auto neighbours = room->neighbours();
for (const auto &neighbour : neighbours) {
auto endPoint = neighbour->mapToScene(neighbour->rect().center());
QLineF line{ startPoint, endPoint };
if (lineExistsInScene(line)) {
continue;
}
auto lineItem = mGraphicsScene->addLine(line, pen);
lineItem->setZValue(-1);
lineItem->hide();
}
mGraphicsScene->update();
}
bool Dungeon::lineExistsInScene(const QLineF &line)
{
return findLineInScene(line) != nullptr;
}
QGraphicsLineItem *Dungeon::findLineInScene(const QLineF &line)
{
auto items = mGraphicsScene->items();
for(const auto &item : items) {
auto existingLineItem = qgraphicsitem_cast<QGraphicsLineItem *>(item);
if(existingLineItem) {
auto p1 = existingLineItem->line().p1();
auto p2 = existingLineItem->line().p2();
auto newP1 = line.p1();
auto newP2 = line.p2();
if((p1 == newP1 && p2 == newP2) || (p1 == newP2 && p2 == newP1)) {
return existingLineItem;
}
}
}
return nullptr;
}
roomutility.h
#ifndef ROOMUTILITY_H
#define ROOMUTILITY_H
#include <QVector>
class Room;
void connectRoomsAsDodekaeder(QVector<Room *> &rooms);
void setNeigbours(QVector<Room *> &rooms,
int roomNo,
int roomNoNeigbourOne,
int roomNoNeigbourTwo,
int roomNoNeigbourThree);
void populateRoomsRandom(
QVector<Room *> &rooms, int countOfPits, int countOfBats);
void setPositionOfRooms(QVector<Room *> &rooms);
Room *randomRoom(const QVector<Room *> &rooms);
#endif // ROOMUTILITY_H
roomutility.cpp
#include "roomutility.h"
#include "room.h"
#include <QRandomGenerator>
static constexpr auto countOfRooms = 20;
void connectRoomsAsDodekaeder(QVector<Room *> &rooms)
{
Q_ASSERT(rooms.size() == countOfRooms);
setNeigbours(rooms, 0, 1, 4, 19);
setNeigbours(rooms, 1, 0 , 2, 17);
setNeigbours(rooms, 2, 1 , 3, 15);
setNeigbours(rooms, 3, 2 , 4, 13);
setNeigbours(rooms, 4, 0 , 3, 5);
setNeigbours(rooms, 5, 4 , 6, 12);
setNeigbours(rooms, 6, 5 , 7, 19);
setNeigbours(rooms, 7, 6 , 8, 11);
setNeigbours(rooms, 8, 7 , 9, 18);
setNeigbours(rooms, 9, 8 , 10, 16);
setNeigbours(rooms, 10, 9 , 11, 14);
setNeigbours(rooms, 11, 7 , 10, 12);
setNeigbours(rooms, 12, 5 , 11, 13);
setNeigbours(rooms, 13, 3 , 12, 14);
setNeigbours(rooms, 14, 10 , 13, 15);
setNeigbours(rooms, 15, 2 , 14, 16);
setNeigbours(rooms, 16, 9 , 15, 17);
setNeigbours(rooms, 17, 1 , 16, 18);
setNeigbours(rooms, 18, 8 , 17, 19);
setNeigbours(rooms, 19, 0 , 6, 18);
}
void setNeigbours(QVector<Room *> &rooms,
int roomNo,
int roomNoNeigbourOne,
int roomNoNeigbourTwo,
int roomNoNeigbourThree)
{
Q_ASSERT(roomNo >= 0 && roomNo < rooms.size());
Q_ASSERT(roomNoNeigbourOne >= 0 && roomNoNeigbourOne < rooms.size());
Q_ASSERT(roomNoNeigbourTwo >= 0 && roomNoNeigbourTwo < rooms.size());
Q_ASSERT(roomNoNeigbourThree >= 0 && roomNoNeigbourThree < rooms.size());
Q_ASSERT(roomNo != roomNoNeigbourOne);
Q_ASSERT(roomNoNeigbourOne != roomNoNeigbourTwo);
Q_ASSERT(roomNoNeigbourTwo != roomNoNeigbourThree);
Q_ASSERT(roomNoNeigbourThree != roomNo);
rooms[roomNo]->addNeighbour(rooms[roomNoNeigbourOne]);
rooms[roomNo]->addNeighbour(rooms[roomNoNeigbourTwo]);
rooms[roomNo]->addNeighbour(rooms[roomNoNeigbourThree]);
}
void populateRoomsRandom(
QVector<Room *> &rooms, int countOfPits, int countOfBats)
{
constexpr auto countOfWumpus = 1;
Q_ASSERT(countOfPits >= 0);
Q_ASSERT(countOfBats >= 0);
Q_ASSERT(countOfPits + countOfBats + countOfWumpus < rooms.size());
auto room = randomRoom(rooms);
room->setWumpus(true);
for (auto i = 0; i < countOfPits; ++i) {
for (;;) {
auto room = randomRoom(rooms);
if (room->isEmpty()) {
room->setPit(true);
break;
}
}
}
for (auto i = 0; i < countOfBats; ++i) {
for (;;) {
auto room = randomRoom(rooms);
if (room->isEmpty()) {
room->setBat(true);
break;
}
}
}
}
void setPositionOfRooms(QVector<Room *> &rooms)
{
const auto roomWidth = static_cast<int>(rooms[0]->size().width());
const auto roomHeight = static_cast<int>(rooms[0]->size().height());
QVector<QPoint> points
{
QPoint{ 400 - roomWidth / 2, 450 - roomHeight / 2},
QPoint{ 600 - roomWidth / 2, 450 - roomHeight / 2},
QPoint{ 600 - roomWidth / 2, 550 - roomHeight / 2},
QPoint{ 500 - roomWidth / 2, 600 - roomHeight / 2},
QPoint{ 400 - roomWidth / 2, 550 - roomHeight / 2},
QPoint{ 250 - roomWidth / 2, 600 - roomHeight / 2},
QPoint{ 250 - roomWidth / 2, 450 - roomHeight / 2},
QPoint{ 100 - roomWidth / 2, 300 - roomHeight / 2},
QPoint{ 500 - roomWidth / 2, 75 - roomHeight / 2},
QPoint{ 900 - roomWidth / 2, 300 - roomHeight / 2},
QPoint{ 900 - roomWidth / 2, 900 - roomHeight / 2},
QPoint{ 100 - roomWidth / 2, 900 - roomHeight / 2},
QPoint{ 300 - roomWidth / 2, 750 - roomHeight / 2},
QPoint{ 500 - roomWidth / 2, 750 - roomHeight / 2},
QPoint{ 750 - roomWidth / 2, 750 - roomHeight / 2},
QPoint{ 750 - roomWidth / 2, 600 - roomHeight / 2},
QPoint{ 800 - roomWidth / 2, 450 - roomHeight / 2},
QPoint{ 700 - roomWidth / 2, 300 - roomHeight / 2},
QPoint{ 500 - roomWidth / 2, 200 - roomHeight / 2},
QPoint{ 350 - roomWidth / 2, 300 - roomHeight / 2},
};
Q_ASSERT(points.size() == rooms.size());
for(auto i = 0; i<rooms.size(); ++i) {
rooms[i]->setPos(points[i]);
}
}
Room *randomRoom(const QVector<Room *> &rooms)
{
auto idx = QRandomGenerator::global()->bounded(0, rooms.size() - 1);
return rooms[idx];
}