diff options
author | Eskil Abrahamsen Blomfeldt <eblomfel@trolltech.com> | 2009-04-24 15:35:35 (GMT) |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <eblomfel@trolltech.com> | 2009-04-24 15:41:43 (GMT) |
commit | c3290381a1dba7971dd70b7220a71e6feceb3f4c (patch) | |
tree | 136f68ab9da00b4571118f18875b3d53928eee46 | |
parent | dfe6974c1e7103ad46fa1162d7c9fd6a426894b7 (diff) | |
download | Qt-c3290381a1dba7971dd70b7220a71e6feceb3f4c.zip Qt-c3290381a1dba7971dd70b7220a71e6feceb3f4c.tar.gz Qt-c3290381a1dba7971dd70b7220a71e6feceb3f4c.tar.bz2 |
Unfinished tank AI game. The idea is that you plug in AIs for the tanks, and
one such plugin will have a run time error, so the game server needs to use
errorState for handling errors.
-rw-r--r-- | examples/statemachine/errorstate/errorstate.pro | 13 | ||||
-rw-r--r-- | examples/statemachine/errorstate/main.cpp | 12 | ||||
-rw-r--r-- | examples/statemachine/errorstate/mainwindow.cpp | 184 | ||||
-rw-r--r-- | examples/statemachine/errorstate/mainwindow.h | 41 | ||||
-rw-r--r-- | examples/statemachine/errorstate/plugin.h | 16 | ||||
-rw-r--r-- | examples/statemachine/errorstate/tank.h | 31 | ||||
-rw-r--r-- | examples/statemachine/errorstate/tankitem.cpp | 274 | ||||
-rw-r--r-- | examples/statemachine/errorstate/tankitem.h | 48 |
8 files changed, 619 insertions, 0 deletions
diff --git a/examples/statemachine/errorstate/errorstate.pro b/examples/statemachine/errorstate/errorstate.pro new file mode 100644 index 0000000..d937b02 --- /dev/null +++ b/examples/statemachine/errorstate/errorstate.pro @@ -0,0 +1,13 @@ +###################################################################### +# Automatically generated by qmake (2.01a) on 22. apr 14:11:33 2009 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += C:/dev/kinetic/examples/statemachine/errorstate/. . + +# Input +HEADERS += mainwindow.h plugin.h tank.h tankitem.h +SOURCES += main.cpp mainwindow.cpp tankitem.cpp +CONFIG += console diff --git a/examples/statemachine/errorstate/main.cpp b/examples/statemachine/errorstate/main.cpp new file mode 100644 index 0000000..26fc1bb --- /dev/null +++ b/examples/statemachine/errorstate/main.cpp @@ -0,0 +1,12 @@ +#include <QApplication> +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/examples/statemachine/errorstate/mainwindow.cpp b/examples/statemachine/errorstate/mainwindow.cpp new file mode 100644 index 0000000..598756f --- /dev/null +++ b/examples/statemachine/errorstate/mainwindow.cpp @@ -0,0 +1,184 @@ +#include "mainwindow.h" +#include "tankitem.h" +#include "plugin.h" + +#include <QStateMachine> +#include <QGraphicsView> +#include <QAction> +#include <QMenuBar> +#include <QState> +#include <QHistoryState> +#include <QTimer> +#include <QFileDialog> +#include <QPluginLoader> + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + init(); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::addWall(const QRectF &wall) +{ + QGraphicsRectItem *item = new QGraphicsRectItem; + item->setRect(wall); + item->setBrush(Qt::darkGreen); + item->setPen(QPen(Qt::black, 0)); + + m_scene->addItem(item); +} + +void MainWindow::init() +{ + setWindowTitle("Pluggable Tank Game"); + + QGraphicsView *view = new QGraphicsView(this); + setCentralWidget(view); + + m_scene = new QGraphicsScene(this); + view->setScene(m_scene); + + QRectF sceneRect = QRectF(-200.0, -200.0, 400.0, 400.0); + m_scene->setSceneRect(sceneRect); + + { + TankItem *item = new TankItem(this); + + item->setPos(m_scene->sceneRect().topLeft() + QPointF(15.0, 15.0)); + item->setDirection(45.0); + item->setColor(Qt::red); + + m_spawns.append(item); + } + + { + TankItem *item = new TankItem(this); + + item->setPos(m_scene->sceneRect().topRight() + QPointF(-15.0, 15.0)); + item->setDirection(135.0); + item->setColor(Qt::green); + + m_spawns.append(item); + } + + { + TankItem *item = new TankItem(this); + + item->setPos(m_scene->sceneRect().bottomRight() + QPointF(-15.0, -15.0)); + item->setDirection(225.0); + item->setColor(Qt::blue); + + m_spawns.append(item); + } + + { + TankItem *item = new TankItem(this); + + item->setPos(m_scene->sceneRect().bottomLeft() + QPointF(15.0, -15.0)); + item->setDirection(315.0); + item->setColor(Qt::yellow); + + m_spawns.append(item); + } + + QPointF centerOfMap = sceneRect.center(); + addWall(QRectF(centerOfMap + QPointF(-50.0, -60.0), centerOfMap + QPointF(50.0, -50.0))); + addWall(QRectF(centerOfMap - QPointF(-50.0, -60.0), centerOfMap - QPointF(50.0, -50.0))); + addWall(QRectF(centerOfMap + QPointF(-50.0, -50.0), centerOfMap + QPointF(-40.0, 50.0))); + addWall(QRectF(centerOfMap - QPointF(-50.0, -50.0), centerOfMap - QPointF(-40.0, 50.0))); + + addWall(QRectF(sceneRect.topLeft() + QPointF(sceneRect.width() / 2.0 - 5.0, 0.0), + sceneRect.topLeft() + QPointF(sceneRect.width() / 2.0 + 5.0, 100.0))); + addWall(QRectF(sceneRect.bottomLeft() + QPointF(sceneRect.width() / 2.0 - 5.0, 0.0), + sceneRect.bottomLeft() + QPointF(sceneRect.width() / 2.0 + 5.0, -100.0))); + addWall(QRectF(sceneRect.topLeft() + QPointF(0.0, sceneRect.height() / 2.0 - 5.0), + sceneRect.topLeft() + QPointF(100.0, sceneRect.height() / 2.0 + 5.0))); + addWall(QRectF(sceneRect.topRight() + QPointF(0.0, sceneRect.height() / 2.0 - 5.0), + sceneRect.topRight() + QPointF(-100.0, sceneRect.height() / 2.0 + 5.0))); + + + QAction *addTankAction = menuBar()->addAction("&Add tank"); + QAction *runGameAction = menuBar()->addAction("&Run game"); + QAction *stopGameAction = menuBar()->addAction("&Stop game"); + menuBar()->addSeparator(); + QAction *quitAction = menuBar()->addAction("&Quit"); + + connect(addTankAction, SIGNAL(triggered()), this, SLOT(addTank())); + connect(stopGameAction, SIGNAL(triggered()), this, SIGNAL(gameOver())); + connect(quitAction, SIGNAL(triggered()), this, SLOT(close())); + + m_machine = new QStateMachine(this); + m_machine->setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QState *stoppedState = new QState(m_machine->rootState()); + + stoppedState->assignProperty(runGameAction, "enabled", true); + stoppedState->assignProperty(stopGameAction, "enabled", false); + m_machine->setInitialState(stoppedState); + + QState *spawnsAvailable = new QState(stoppedState); + spawnsAvailable->assignProperty(addTankAction, "enabled", true); + + QState *noSpawnsAvailable = new QState(stoppedState); + noSpawnsAvailable->assignProperty(addTankAction, "enabled", false); + + spawnsAvailable->addTransition(this, SIGNAL(mapFull()), noSpawnsAvailable); + + QHistoryState *hs = stoppedState->addHistoryState(); + hs->setDefaultState(spawnsAvailable); + stoppedState->setInitialState(spawnsAvailable); + + m_runningState = new QState(QState::ParallelGroup, m_machine->rootState()); + m_runningState->assignProperty(addTankAction, "enabled", false); + m_runningState->assignProperty(runGameAction, "enabled", false); + m_runningState->assignProperty(stopGameAction, "enabled", true); + + stoppedState->addTransition(runGameAction, SIGNAL(triggered()), m_runningState); + m_runningState->addTransition(this, SIGNAL(gameOver()), stoppedState); + + m_machine->start(); + + QTimer *timer = new QTimer(this); + timer->setInterval(100); + connect(timer, SIGNAL(timeout()), this, SLOT(runStep())); + connect(m_runningState, SIGNAL(entered()), timer, SLOT(start())); + connect(m_runningState, SIGNAL(exited()), timer, SLOT(stop())); + + m_time.start(); +} + +void MainWindow::runStep() +{ + int elapsed = m_time.elapsed(); + if (elapsed > 0) { + m_time.restart(); + qreal elapsedSecs = elapsed / 1000.0; + QList<TankItem *> tankItems = qFindChildren<TankItem *>(this); + foreach (TankItem *tankItem, tankItems) + tankItem->idle(elapsedSecs); + } +} + +void MainWindow::addTank() +{ + Q_ASSERT(!m_spawns.isEmpty()); + + QString fileName = QFileDialog::getOpenFileName(this, "Select plugin file", + "plugins/", "*.dll"); + QPluginLoader loader(fileName); + + Plugin *plugin = qobject_cast<Plugin *>(loader.instance()); + if (plugin != 0) { + TankItem *tankItem = m_spawns.takeLast(); + m_scene->addItem(tankItem); + if (m_spawns.isEmpty()) + emit mapFull(); + + plugin->create(m_runningState, tankItem); + } +} + diff --git a/examples/statemachine/errorstate/mainwindow.h b/examples/statemachine/errorstate/mainwindow.h new file mode 100644 index 0000000..2bd574d --- /dev/null +++ b/examples/statemachine/errorstate/mainwindow.h @@ -0,0 +1,41 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QTime> + +class QGraphicsScene; +class QStateMachine; +class QState; +class TankItem; +class MainWindow: public QMainWindow +{ + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +public slots: + void addTank(); + void runStep(); + +signals: + void gameOver(); + void mapFull(); + +private: + void init(); + void addWall(const QRectF &wall); + + QGraphicsScene *m_scene; + + QStateMachine *m_machine; + QState *m_runningState; + + QList<TankItem *> m_spawns; + QTime m_time; + +}; + +#endif + diff --git a/examples/statemachine/errorstate/plugin.h b/examples/statemachine/errorstate/plugin.h new file mode 100644 index 0000000..1ac7e0f --- /dev/null +++ b/examples/statemachine/errorstate/plugin.h @@ -0,0 +1,16 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + +class QState; +class Tank; +class Plugin +{ +public: + virtual ~Plugin() {} + + virtual QState *create(QState *parentState, Tank *tank) = 0; +}; + +Q_DECLARE_INTERFACE(Plugin, "TankPlugin") + +#endif diff --git a/examples/statemachine/errorstate/tank.h b/examples/statemachine/errorstate/tank.h new file mode 100644 index 0000000..9def6df --- /dev/null +++ b/examples/statemachine/errorstate/tank.h @@ -0,0 +1,31 @@ +#ifndef TANK_H +#define TANK_H + +#include <QObject> +#include <QLineF> + +class Tank: public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal direction READ direction) + Q_PROPERTY(qreal distanceToObstacle READ distanceToObstacle) +public: + Tank(QObject *parent = 0) : QObject(parent) {} + + virtual qreal direction() const = 0; + virtual qreal distanceToObstacle() const = 0; + +signals: + void collision(const QLineF &collidedLine) const; + void actionCompleted(); + void tankSpotted(qreal otherTankDirection, qreal distance); + +public slots: + virtual void moveForwards(qreal length) = 0; + virtual void moveBackwards(qreal length) = 0; + virtual void turn(qreal newDirection) = 0; + virtual void stop() = 0; + virtual void fireCannon(qreal distance) = 0; +}; + +#endif diff --git a/examples/statemachine/errorstate/tankitem.cpp b/examples/statemachine/errorstate/tankitem.cpp new file mode 100644 index 0000000..21d4a25 --- /dev/null +++ b/examples/statemachine/errorstate/tankitem.cpp @@ -0,0 +1,274 @@ +#include "tankitem.h" + +#include <QPainter> +#include <QGraphicsScene> + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +class Action +{ +public: + Action(TankItem *item) : m_item(item) + { + } + + TankItem *item() const { return m_item; } + void setItem(TankItem *item) { m_item = item; } + + virtual bool apply(qreal timeDelta) = 0; + +private: + TankItem *m_item; +}; + +class MoveAction: public Action +{ +public: + MoveAction(TankItem *item, qreal distance) + : Action(item), m_distance(distance) + { + } + + bool apply(qreal timeDelta) + { + qreal dist = timeDelta * item()->speed(); + m_distance -= dist; + if (qFuzzyCompare(m_distance, 0.0)) + return false; + + qreal a = item()->direction() * M_PI / 180.0; + + qreal yd = dist * sin(a); + qreal xd = dist * sin(M_PI / 2.0 - a); + + item()->setPos(item()->pos() + QPointF(xd, yd)); + return true; + } + +private: + qreal m_distance; +}; + +class TurnAction: public Action +{ +public: + TurnAction(TankItem *item, qreal distance) + : Action(item), m_distance(distance) + { + } + + bool apply(qreal timeDelta) + { + qreal dist = timeDelta * item()->angularSpeed(); + m_distance -= dist; + if (qFuzzyCompare(m_distance, 0.0)) + return false; + + item()->setDirection(item()->direction() + dist); + return true; + } + +private: + qreal m_distance; +}; + +class FireCannonAction: public Action +{ +public: + FireCannonAction(TankItem *item, qreal distance) + : Action(item), m_distance(distance) + { + } + + bool apply(qreal ) + { + return false; + } + +private: + qreal m_distance; +}; + +TankItem::TankItem(QObject *parent) : Tank(parent), m_currentAction(0), m_currentDirection(0.0) +{ +} + +void TankItem::idle(qreal elapsed) +{ + if (m_currentAction != 0) { + if (!m_currentAction->apply(elapsed)) { + setAction(0); + emit actionCompleted(); + } + } +} + +void TankItem::setAction(Action *newAction) +{ + if (m_currentAction != 0) + delete m_currentAction; + + m_currentAction = newAction; +} + +void TankItem::moveForwards(qreal length) +{ + setAction(new MoveAction(this, length)); +} + +void TankItem::moveBackwards(qreal length) +{ + setAction(new MoveAction(this, -length)); +} + +void TankItem::turn(qreal newDirection) +{ + setAction(new TurnAction(this, m_currentDirection - newDirection)); +} + +void TankItem::stop() +{ + setAction(0); +} + +void TankItem::fireCannon(qreal distance) +{ + setAction(new FireCannonAction(this, distance)); +} + +QPointF TankItem::tryMove(const QPointF &requestedPosition) const +{ + QLineF movementPath(pos(), requestedPosition); + + qreal cannonLength = 0.0; + { + QPointF p1 = boundingRect().center(); + QPointF p2 = QPointF(boundingRect().right() + 10.0, p1.y()); + + cannonLength = QLineF(mapToScene(p1), mapToScene(p2)).length(); + } + + movementPath.setLength(movementPath.length() + cannonLength); + + QRectF boundingRectPath(QPointF(qMin(movementPath.x1(), movementPath.x2()), qMin(movementPath.y1(), movementPath.y2())), + QPointF(qMax(movementPath.x1(), movementPath.x2()), qMax(movementPath.y1(), movementPath.y2()))); + + m_brp = mapFromScene(boundingRectPath); + + QList<QGraphicsItem *> itemsInRect = scene()->items(boundingRectPath, Qt::IntersectsItemBoundingRect); + + QPointF nextPoint = requestedPosition; + QRectF sceneRect = scene()->sceneRect(); + + QLineF collidedLine; + foreach (QGraphicsItem *item, itemsInRect) { + if (item == static_cast<const QGraphicsItem *>(this)) + continue; + + QPolygonF mappedBoundingRect = item->mapToScene(item->boundingRect()); + for (int i=0; i<mappedBoundingRect.size(); ++i) { + QPointF p1 = mappedBoundingRect.at(i == 0 ? mappedBoundingRect.size()-1 : i-1); + QPointF p2 = mappedBoundingRect.at(i); + + QLineF rectLine(p1, p2); + QPointF intersectionPoint; + QLineF::IntersectType intersectType = movementPath.intersect(rectLine, &intersectionPoint); + + if (intersectType == QLineF::BoundedIntersection) { + movementPath.setP2(intersectionPoint); + movementPath.setLength(movementPath.length() - cannonLength); + nextPoint = movementPath.p2(); + collidedLine = rectLine; + } + } + } + + // Don't go outside of map + if (nextPoint.x() < sceneRect.left()) + nextPoint.rx() = sceneRect.left(); + if (nextPoint.x() > sceneRect.right()) + nextPoint.rx() = sceneRect.right(); + if (nextPoint.y() < sceneRect.top()) + nextPoint.ry() = sceneRect.top(); + if (nextPoint.y() > sceneRect.bottom()) + nextPoint.ry() = sceneRect.bottom(); + + if (nextPoint != requestedPosition) { + emit collision(collidedLine); + } + + return nextPoint; +} + +QVariant TankItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && scene()) + return tryMove(value.toPointF()); + else + return QGraphicsItem::itemChange(change, value); +} + + +void TankItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + QRectF brect = boundingRect(); + + painter->setBrush(m_color); + painter->setPen(Qt::black); + + // body + painter->drawRect(brect.adjusted(0.0, 4.0, -2.0, -4.0)); + + // cannon + QRectF cannonBase = brect.adjusted(10.0, 6.0, -12.0, -6.0); + painter->drawEllipse(cannonBase); + + painter->drawRect(QRectF(QPointF(cannonBase.center().x(), cannonBase.center().y() - 2.0), + QPointF(brect.right(), cannonBase.center().y() + 2.0))); + + // left track + painter->setBrush(QBrush(Qt::black, Qt::VerPattern)); + QRectF leftTrackRect = QRectF(brect.topLeft(), QPointF(brect.right() - 2.0, brect.top() + 4.0)); + painter->fillRect(leftTrackRect, Qt::darkYellow); + painter->drawRect(leftTrackRect); + + // right track + QRectF rightTrackRect = QRectF(QPointF(brect.left(), brect.bottom() - 4.0), + QPointF(brect.right() - 2.0, brect.bottom())); + painter->fillRect(rightTrackRect, Qt::darkYellow); + painter->drawRect(rightTrackRect); + + painter->setBrush(QColor::fromRgb(255, 0, 0, 128)); + painter->drawPolygon(m_brp); + +} + +QRectF TankItem::boundingRect() const +{ + return QRectF(-20.0, -10.0, 40.0, 20.0); +} + +qreal TankItem::direction() const +{ + return m_currentDirection; +} + +void TankItem::setDirection(qreal newDirection) +{ + m_currentDirection = newDirection; + rotate(newDirection); +} + +qreal TankItem::distanceToObstacle() const +{ + // ### + + return 0.0; +} + + + diff --git a/examples/statemachine/errorstate/tankitem.h b/examples/statemachine/errorstate/tankitem.h new file mode 100644 index 0000000..df13689 --- /dev/null +++ b/examples/statemachine/errorstate/tankitem.h @@ -0,0 +1,48 @@ +#ifndef TANKITEM_H +#define TANKITEM_H + +#include "tank.h" + +#include <QGraphicsItem> +#include <QColor> + +class Action; +class TankItem: public Tank, public QGraphicsItem +{ + Q_OBJECT +public: + TankItem(QObject *parent = 0); + + virtual void moveForwards(qreal length); + virtual void moveBackwards(qreal length); + virtual void turn(qreal newDirection); + virtual void stop(); + virtual void fireCannon(qreal distance); + virtual qreal direction() const; + virtual qreal distanceToObstacle() const; + + void setColor(const QColor &color) { m_color = color; } + QColor color() const { return m_color; } + + void idle(qreal elapsed); + void setDirection(qreal newDirection); + + qreal speed() const { return 20.0; } + qreal angularSpeed() const { return 1.0; } + +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const; + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); + +private: + QPointF tryMove(const QPointF &requestedPosition) const; + void setAction(Action *newAction); + + Action *m_currentAction; + qreal m_currentDirection; + QColor m_color; + mutable QPolygonF m_brp; +}; + +#endif |