summaryrefslogtreecommitdiffstats
path: root/examples/statemachine/tankgame
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eblomfel@trolltech.com>2009-05-12 14:40:44 (GMT)
committerEskil Abrahamsen Blomfeldt <eblomfel@trolltech.com>2009-05-12 14:42:26 (GMT)
commitd26a56e2d94bea2b5a1135b8336bbeefebf3ba1c (patch)
treec4a6497357266aa6f8bca69aef985a0f4ac5779d /examples/statemachine/tankgame
parentcb516fd65149991a2105c545192a52f26a6ab67d (diff)
downloadQt-d26a56e2d94bea2b5a1135b8336bbeefebf3ba1c.zip
Qt-d26a56e2d94bea2b5a1135b8336bbeefebf3ba1c.tar.gz
Qt-d26a56e2d94bea2b5a1135b8336bbeefebf3ba1c.tar.bz2
Change name of "errorstate" example to "tankgame"
The error state is not a big enough part of the example to justify naming it after it.
Diffstat (limited to 'examples/statemachine/tankgame')
-rw-r--r--examples/statemachine/tankgame/gameitem.cpp88
-rw-r--r--examples/statemachine/tankgame/gameitem.h23
-rw-r--r--examples/statemachine/tankgame/main.cpp12
-rw-r--r--examples/statemachine/tankgame/mainwindow.cpp264
-rw-r--r--examples/statemachine/tankgame/mainwindow.h46
-rw-r--r--examples/statemachine/tankgame/plugin.h17
-rw-r--r--examples/statemachine/tankgame/rocketitem.cpp60
-rw-r--r--examples/statemachine/tankgame/rocketitem.h26
-rw-r--r--examples/statemachine/tankgame/tankgame.pro13
-rw-r--r--examples/statemachine/tankgame/tankitem.cpp262
-rw-r--r--examples/statemachine/tankgame/tankitem.h67
11 files changed, 878 insertions, 0 deletions
diff --git a/examples/statemachine/tankgame/gameitem.cpp b/examples/statemachine/tankgame/gameitem.cpp
new file mode 100644
index 0000000..1a2af71
--- /dev/null
+++ b/examples/statemachine/tankgame/gameitem.cpp
@@ -0,0 +1,88 @@
+#include "gameitem.h"
+
+#include <QGraphicsScene>
+#include <QDebug>
+
+GameItem::GameItem(QObject *parent) : QObject(parent)
+{
+}
+
+QPointF GameItem::tryMove(const QPointF &requestedPosition, QLineF *collidedLine,
+ QGraphicsItem **collidedItem) 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())));
+
+ QList<QGraphicsItem *> itemsInRect = scene()->items(boundingRectPath, Qt::IntersectsItemBoundingRect);
+
+ QPointF nextPoint = requestedPosition;
+ QRectF sceneRect = scene()->sceneRect();
+
+ 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();
+
+ if (collidedLine != 0)
+ *collidedLine = rectLine;
+
+ if (collidedItem != 0)
+ *collidedItem = item;
+ }
+ }
+ }
+
+
+ // Don't go outside of map
+ if (nextPoint.x() < sceneRect.left()) {
+ nextPoint.rx() = sceneRect.left();
+ if (collidedLine != 0)
+ *collidedLine = QLineF(scene()->sceneRect().topLeft(), scene()->sceneRect().bottomLeft());
+ }
+
+ if (nextPoint.x() > sceneRect.right()) {
+ nextPoint.rx() = sceneRect.right();
+ if (collidedLine != 0)
+ *collidedLine = QLineF(scene()->sceneRect().topRight(), scene()->sceneRect().bottomRight());
+ }
+
+ if (nextPoint.y() < sceneRect.top()) {
+ nextPoint.ry() = sceneRect.top();
+ if (collidedLine != 0)
+ *collidedLine = QLineF(scene()->sceneRect().topLeft(), scene()->sceneRect().topRight());
+ }
+
+ if (nextPoint.y() > sceneRect.bottom()) {
+ nextPoint.ry() = sceneRect.bottom();
+ if (collidedLine != 0)
+ *collidedLine = QLineF(scene()->sceneRect().bottomLeft(), scene()->sceneRect().bottomRight());
+ }
+
+ return nextPoint;
+}
+
diff --git a/examples/statemachine/tankgame/gameitem.h b/examples/statemachine/tankgame/gameitem.h
new file mode 100644
index 0000000..43b8785
--- /dev/null
+++ b/examples/statemachine/tankgame/gameitem.h
@@ -0,0 +1,23 @@
+#ifndef GAMEITEM_H
+#define GAMEITEM_H
+
+#include <QGraphicsItem>
+
+class QLineF;
+class GameItem: public QObject, public QGraphicsItem
+{
+ Q_OBJECT
+public:
+ enum { Type = UserType + 1 };
+ int type() const { return Type; }
+
+ GameItem(QObject *parent = 0);
+
+ virtual void idle(qreal elapsed) = 0;
+
+protected:
+ QPointF tryMove(const QPointF &requestedPosition, QLineF *collidedLine = 0,
+ QGraphicsItem **collidedItem = 0) const;
+};
+
+#endif
diff --git a/examples/statemachine/tankgame/main.cpp b/examples/statemachine/tankgame/main.cpp
new file mode 100644
index 0000000..26fc1bb
--- /dev/null
+++ b/examples/statemachine/tankgame/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/tankgame/mainwindow.cpp b/examples/statemachine/tankgame/mainwindow.cpp
new file mode 100644
index 0000000..3bc9bbe
--- /dev/null
+++ b/examples/statemachine/tankgame/mainwindow.cpp
@@ -0,0 +1,264 @@
+#include "mainwindow.h"
+#include "tankitem.h"
+#include "rocketitem.h"
+#include "plugin.h"
+
+#include <QStateMachine>
+#include <QGraphicsView>
+#include <QAction>
+#include <QMenuBar>
+#include <QState>
+#include <QHistoryState>
+#include <QTimer>
+#include <QFileDialog>
+#include <QPluginLoader>
+#include <QApplication>
+#include <QInputDialog>
+#include <QMessageBox>
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent), m_scene(0), m_machine(0), m_runningState(0), m_started(false)
+{
+ 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);
+ view->setRenderHints(QPainter::Antialiasing);
+ 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, -10.0),
+ sceneRect.topLeft() + QPointF(sceneRect.width() / 2.0 + 5.0, 100.0)));
+ addWall(QRectF(sceneRect.bottomLeft() + QPointF(sceneRect.width() / 2.0 - 5.0, 10.0),
+ sceneRect.bottomLeft() + QPointF(sceneRect.width() / 2.0 + 5.0, -100.0)));
+ addWall(QRectF(sceneRect.topLeft() + QPointF(-10.0, sceneRect.height() / 2.0 - 5.0),
+ sceneRect.topLeft() + QPointF(100.0, sceneRect.height() / 2.0 + 5.0)));
+ addWall(QRectF(sceneRect.topRight() + QPointF(10.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");
+ runGameAction->setObjectName("runGameAction");
+ QAction *stopGameAction = menuBar()->addAction("&Stop game");
+ menuBar()->addSeparator();
+ QAction *quitAction = menuBar()->addAction("&Quit");
+
+ connect(addTankAction, SIGNAL(triggered()), this, SLOT(addTank()));
+ connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
+
+ m_machine = new QStateMachine(this);
+ QState *stoppedState = new QState(m_machine->rootState());
+ stoppedState->setObjectName("stoppedState");
+ stoppedState->assignProperty(runGameAction, "enabled", true);
+ stoppedState->assignProperty(stopGameAction, "enabled", false);
+ stoppedState->assignProperty(this, "started", false);
+ m_machine->setInitialState(stoppedState);
+
+ QState *spawnsAvailable = new QState(stoppedState);
+ spawnsAvailable->setObjectName("spawnsAvailable");
+ spawnsAvailable->assignProperty(addTankAction, "enabled", true);
+
+ QState *noSpawnsAvailable = new QState(stoppedState);
+ noSpawnsAvailable->setObjectName("noSpawnsAvailable");
+ noSpawnsAvailable->assignProperty(addTankAction, "enabled", false);
+
+ spawnsAvailable->addTransition(this, SIGNAL(mapFull()), noSpawnsAvailable);
+
+ QHistoryState *hs = new QHistoryState(stoppedState);
+ hs->setDefaultState(spawnsAvailable);
+
+ stoppedState->setInitialState(hs);
+
+//! [0]
+ m_runningState = new QState(QState::ParallelStates, m_machine->rootState());
+//! [0]
+ m_runningState->setObjectName("runningState");
+ 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(stopGameAction, SIGNAL(triggered()), stoppedState);
+
+ 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_machine->start();
+ m_time.start();
+}
+
+void MainWindow::runStep()
+{
+ if (!m_started) {
+ m_time.restart();
+ m_started = true;
+ } else {
+ int elapsed = m_time.elapsed();
+ if (elapsed > 0) {
+ m_time.restart();
+ qreal elapsedSecs = elapsed / 1000.0;
+ QList<QGraphicsItem *> items = m_scene->items();
+ foreach (QGraphicsItem *item, items) {
+ if (GameItem *gameItem = qgraphicsitem_cast<GameItem *>(item))
+ gameItem->idle(elapsedSecs);
+ }
+ }
+ }
+}
+
+void MainWindow::addRocket()
+{
+ TankItem *tankItem = qobject_cast<TankItem *>(sender());
+ if (tankItem != 0) {
+ RocketItem *rocketItem = new RocketItem;
+
+ QPointF s = tankItem->mapToScene(QPointF(tankItem->boundingRect().right() + 10.0,
+ tankItem->boundingRect().center().y()));
+ rocketItem->setPos(s);
+ rocketItem->setDirection(tankItem->direction());
+ m_scene->addItem(rocketItem);
+ }
+}
+
+void MainWindow::addTank()
+{
+ Q_ASSERT(!m_spawns.isEmpty());
+
+ QDir pluginsDir(qApp->applicationDirPath());
+#if defined(Q_OS_WIN)
+ if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
+ pluginsDir.cdUp();
+#elif defined(Q_OS_MAC)
+ if (pluginsDir.dirName() == "MacOS") {
+ pluginsDir.cdUp();
+ pluginsDir.cdUp();
+ pluginsDir.cdUp();
+ }
+#endif
+
+ pluginsDir.cd("plugins");
+
+ QStringList itemNames;
+ QList<Plugin *> items;
+ foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
+ QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
+ QObject *possiblePlugin = loader.instance();
+ if (Plugin *plugin = qobject_cast<Plugin *>(possiblePlugin)) {
+ QString objectName = possiblePlugin->objectName();
+ if (objectName.isEmpty())
+ objectName = fileName;
+
+ itemNames.append(objectName);
+ items.append(plugin);
+ }
+ }
+
+ if (items.isEmpty()) {
+ QMessageBox::information(this, "No tank types found", "Please build the errorstateplugins directory");
+ return;
+ }
+
+ bool ok;
+//! [1]
+ QString selectedName = QInputDialog::getItem(this, "Select a tank type", "Tank types",
+ itemNames, 0, false, &ok);
+//! [1]
+
+ if (ok && !selectedName.isEmpty()) {
+ int idx = itemNames.indexOf(selectedName);
+ if (Plugin *plugin = idx >= 0 ? items.at(idx) : 0) {
+ TankItem *tankItem = m_spawns.takeLast();
+ m_scene->addItem(tankItem);
+ connect(tankItem, SIGNAL(cannonFired()), this, SLOT(addRocket()));
+ if (m_spawns.isEmpty())
+ emit mapFull();
+
+ QState *region = new QState(m_runningState);
+//! [2]
+ QState *pluginState = plugin->create(region, tankItem);
+//! [2]
+ region->setInitialState(pluginState);
+
+
+ // If the plugin has an error it is disabled
+ QState *errorState = new QState(region);
+ errorState->assignProperty(tankItem, "enabled", false);
+ pluginState->setErrorState(errorState);
+ }
+ }
+}
+
diff --git a/examples/statemachine/tankgame/mainwindow.h b/examples/statemachine/tankgame/mainwindow.h
new file mode 100644
index 0000000..622dabe
--- /dev/null
+++ b/examples/statemachine/tankgame/mainwindow.h
@@ -0,0 +1,46 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QTime>
+
+class QGraphicsScene;
+class QStateMachine;
+class QState;
+class TankItem;
+class MainWindow: public QMainWindow
+{
+ Q_OBJECT
+ Q_PROPERTY(bool started READ started WRITE setStarted)
+public:
+ MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+ void setStarted(bool b) { m_started = b; }
+ bool started() const { return m_started; }
+
+public slots:
+ void addTank();
+ void addRocket();
+ void runStep();
+
+signals:
+ 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;
+
+ bool m_started : 1;
+};
+
+#endif
+
diff --git a/examples/statemachine/tankgame/plugin.h b/examples/statemachine/tankgame/plugin.h
new file mode 100644
index 0000000..2b48d43
--- /dev/null
+++ b/examples/statemachine/tankgame/plugin.h
@@ -0,0 +1,17 @@
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
+#include <QtPlugin>
+
+class QState;
+class Plugin
+{
+public:
+ virtual ~Plugin() {}
+
+ virtual QState *create(QState *parentState, QObject *tank) = 0;
+};
+
+Q_DECLARE_INTERFACE(Plugin, "TankPlugin")
+
+#endif
diff --git a/examples/statemachine/tankgame/rocketitem.cpp b/examples/statemachine/tankgame/rocketitem.cpp
new file mode 100644
index 0000000..c324980
--- /dev/null
+++ b/examples/statemachine/tankgame/rocketitem.cpp
@@ -0,0 +1,60 @@
+#include "rocketitem.h"
+#include "tankitem.h"
+
+#include <QPainter>
+#include <QGraphicsScene>
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+RocketItem::RocketItem(QObject *parent)
+ : GameItem(parent), m_direction(0.0), m_distance(300.0)
+{
+}
+
+QRectF RocketItem::boundingRect() const
+{
+ return QRectF(-1.0, -1.0, 2.0, 2.0);
+}
+
+void RocketItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ painter->setBrush(Qt::black);
+ painter->drawEllipse(boundingRect());
+}
+
+void RocketItem::idle(qreal elapsed)
+{
+ qreal dist = elapsed * speed();
+
+ m_distance -= dist;
+ if (m_distance < 0.0) {
+ scene()->removeItem(this);
+ delete this;
+ return;
+ }
+
+ qreal a = m_direction * M_PI / 180.0;
+
+ qreal yd = dist * sin(a);
+ qreal xd = dist * sin(M_PI / 2.0 - a);
+
+ QPointF requestedPosition = pos() + QPointF(xd, yd);
+ QGraphicsItem *collidedItem = 0;
+ QPointF nextPosition = tryMove(requestedPosition, 0, &collidedItem);
+ if (requestedPosition == nextPosition) {
+ setPos(nextPosition);
+ } else {
+ if (GameItem *gameItem = qgraphicsitem_cast<GameItem *>(collidedItem)) {
+ TankItem *tankItem = qobject_cast<TankItem *>(gameItem);
+ if (tankItem != 0)
+ tankItem->hitByRocket();
+ }
+
+ scene()->removeItem(this);
+ delete this;
+ }
+}
diff --git a/examples/statemachine/tankgame/rocketitem.h b/examples/statemachine/tankgame/rocketitem.h
new file mode 100644
index 0000000..189a1dd
--- /dev/null
+++ b/examples/statemachine/tankgame/rocketitem.h
@@ -0,0 +1,26 @@
+#ifndef ROCKETITEM_H
+#define ROCKETITEM_H
+
+#include "gameitem.h"
+
+class RocketItem: public GameItem
+{
+ Q_OBJECT
+public:
+ RocketItem(QObject *parent = 0);
+
+ virtual void idle(qreal elapsed);
+ qreal speed() const { return 100.0; }
+ void setDirection(qreal direction) { m_direction = direction; }
+
+protected:
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ QRectF boundingRect() const;
+
+private:
+ qreal m_direction;
+ qreal m_distance;
+};
+
+
+#endif
diff --git a/examples/statemachine/tankgame/tankgame.pro b/examples/statemachine/tankgame/tankgame.pro
new file mode 100644
index 0000000..f7b0760
--- /dev/null
+++ b/examples/statemachine/tankgame/tankgame.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/tankgame/. .
+
+# Input
+HEADERS += mainwindow.h plugin.h tankitem.h rocketitem.h gameitem.h
+SOURCES += main.cpp mainwindow.cpp tankitem.cpp rocketitem.cpp gameitem.cpp
+CONFIG += console
diff --git a/examples/statemachine/tankgame/tankitem.cpp b/examples/statemachine/tankgame/tankitem.cpp
new file mode 100644
index 0000000..5506a7e
--- /dev/null
+++ b/examples/statemachine/tankgame/tankitem.cpp
@@ -0,0 +1,262 @@
+#include "tankitem.h"
+
+#include <QPainter>
+#include <QGraphicsScene>
+#include <QDebug>
+
+#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)
+ {
+ m_reverse = m_distance < 0.0;
+ }
+
+ bool apply(qreal timeDelta)
+ {
+ qreal dist = timeDelta * item()->speed() * (m_reverse ? -1.0 : 1.0);
+
+ bool done = false;
+ if (qAbs(m_distance) < qAbs(dist)) {
+ done = true;
+ dist = m_distance;
+ }
+ m_distance -= dist;
+
+ 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 !done;
+ }
+
+private:
+ qreal m_distance;
+ bool m_reverse;
+};
+
+class TurnAction: public Action
+{
+public:
+ TurnAction(TankItem *item, qreal distance)
+ : Action(item), m_distance(distance)
+ {
+ m_reverse = m_distance < 0.0;
+ }
+
+ bool apply(qreal timeDelta)
+ {
+ qreal dist = timeDelta * item()->angularSpeed() * (m_reverse ? -1.0 : 1.0);
+ bool done = false;
+ if (qAbs(m_distance) < qAbs(dist)) {
+ done = true;
+ dist = m_distance;
+ }
+ m_distance -= dist;
+
+ item()->setDirection(item()->direction() + dist);
+ return !done;
+ }
+
+private:
+ qreal m_distance;
+ bool m_reverse;
+};
+
+TankItem::TankItem(QObject *parent)
+ : GameItem(parent), m_currentAction(0), m_currentDirection(0.0), m_enabled(true)
+{
+ connect(this, SIGNAL(cannonFired()), this, SIGNAL(actionCompleted()));
+}
+
+void TankItem::idle(qreal elapsed)
+{
+ if (m_enabled) {
+ if (m_currentAction != 0) {
+ if (!m_currentAction->apply(elapsed)) {
+ setAction(0);
+ emit actionCompleted();
+ }
+
+ QGraphicsItem *item = 0;
+ qreal distance = distanceToObstacle(&item);
+ if (TankItem *tankItem = qgraphicsitem_cast<TankItem *>(item))
+ emit tankSpotted(tankItem->direction(), distance);
+ }
+ }
+}
+
+void TankItem::hitByRocket()
+{
+ deleteLater();
+}
+
+void TankItem::setAction(Action *newAction)
+{
+ if (m_currentAction != 0)
+ delete m_currentAction;
+
+ m_currentAction = newAction;
+}
+
+void TankItem::fireCannon()
+{
+ emit cannonFired();
+}
+
+void TankItem::moveForwards(qreal length)
+{
+ setAction(new MoveAction(this, length));
+}
+
+void TankItem::moveBackwards(qreal length)
+{
+ setAction(new MoveAction(this, -length));
+}
+
+void TankItem::turn(qreal degrees)
+{
+ setAction(new TurnAction(this, degrees));
+}
+
+void TankItem::turnTo(qreal degrees)
+{
+ setAction(new TurnAction(this, degrees - direction()));
+}
+
+void TankItem::stop()
+{
+ setAction(0);
+}
+
+QVariant TankItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
+{
+ if (change == ItemPositionChange && scene()) {
+ QPointF requestedPosition = value.toPointF();
+ QLineF collidedLine;
+ QPointF nextPoint = tryMove(requestedPosition, &collidedLine);
+ if (nextPoint != requestedPosition)
+ emit collision(collidedLine);
+ return nextPoint;
+ } 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);
+
+ if (!m_enabled) {
+ painter->setPen(QPen(Qt::red, 5));
+
+ painter->drawEllipse(brect);
+
+ QPainterPath path;
+ path.addEllipse(brect);
+ painter->setClipPath(path);
+ painter->drawLine(brect.topRight(), brect.bottomLeft());
+ }
+}
+
+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)
+{
+ int fullRotations = int(newDirection) / 360;
+ newDirection -= fullRotations * 360.0;
+
+ qreal diff = newDirection - m_currentDirection;
+ m_currentDirection = newDirection;
+ rotate(diff);
+}
+
+qreal TankItem::distanceToObstacle(QGraphicsItem **obstacle) const
+{
+ qreal dist = sqrt(pow(scene()->sceneRect().width(), 2) + pow(scene()->sceneRect().height(), 2));
+
+ qreal a = m_currentDirection * M_PI / 180.0;
+
+ qreal yd = dist * sin(a);
+ qreal xd = dist * sin(M_PI / 2.0 - a);
+
+ QPointF requestedPosition = pos() + QPointF(xd, yd);
+ QGraphicsItem *collidedItem = 0;
+ QPointF nextPosition = tryMove(requestedPosition, 0, &collidedItem);
+ if (collidedItem != 0) {
+ if (obstacle != 0)
+ *obstacle = collidedItem;
+
+ QPointF d = nextPosition - pos();
+ return sqrt(pow(d.x(), 2) + pow(d.y(), 2));
+ } else {
+ return 0.0;
+ }
+}
+
+qreal TankItem::distanceToObstacle() const
+{
+ return distanceToObstacle(0);
+}
+
+
+
diff --git a/examples/statemachine/tankgame/tankitem.h b/examples/statemachine/tankgame/tankitem.h
new file mode 100644
index 0000000..66f05aa
--- /dev/null
+++ b/examples/statemachine/tankgame/tankitem.h
@@ -0,0 +1,67 @@
+#ifndef TANKITEM_H
+#define TANKITEM_H
+
+#include "gameitem.h"
+
+#include <QColor>
+
+class Action;
+class TankItem: public GameItem
+{
+ Q_OBJECT
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled)
+ Q_PROPERTY(qreal direction READ direction WRITE turnTo)
+ Q_PROPERTY(qreal distanceToObstacle READ distanceToObstacle)
+public:
+ TankItem(QObject *parent = 0);
+
+ 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 90.0; }
+ qreal angularSpeed() const { return 90.0; }
+
+ QRectF boundingRect() const;
+
+ void hitByRocket();
+
+ void setEnabled(bool b) { m_enabled = b; }
+ bool enabled() const { return m_enabled; }
+
+ qreal direction() const;
+ qreal distanceToObstacle() const;
+ qreal distanceToObstacle(QGraphicsItem **item) const;
+
+//! [0]
+signals:
+ void tankSpotted(qreal direction, qreal distance);
+ void collision(const QLineF &collidedLine);
+ void actionCompleted();
+ void cannonFired();
+
+public slots:
+ void moveForwards(qreal length = 10.0);
+ void moveBackwards(qreal length = 10.0);
+ void turn(qreal degrees = 30.0);
+ void turnTo(qreal degrees = 0.0);
+ void stop();
+ void fireCannon();
+//! [0]
+
+protected:
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value);
+
+private:
+ void setAction(Action *newAction);
+
+ Action *m_currentAction;
+ qreal m_currentDirection;
+ QColor m_color;
+ bool m_enabled;
+};
+
+#endif