level.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*
  2. * Copyright (c) 2011 Nokia Corporation.
  3. */
  4. #include <QFile>
  5. #include <QXmlStreamReader>
  6. #include <QVariant>
  7. #include <QPainter>
  8. #include <QEvent>
  9. #include <QDebug>
  10. #include <QAudioOutput>
  11. #include <QThread>
  12. #include <QtDeclarative/QDeclarativeContext>
  13. #include "level.h"
  14. Level::Level() : m_rowCount(0), m_columnCount(0)
  15. {
  16. /***********************************************************************************************
  17. *Setting up the Sound player object, this object will live in a separate thread to ensure that *
  18. *sound playback does not interfere with the game itself. *
  19. * *
  20. *Connection type set to Qt::QueuedConnection, this is the connection type required for objects *
  21. *living in different threads. This would also get handled by the default Qt::AutoConnection, *
  22. *but this would make the code less readable. *
  23. ***********************************************************************************************/
  24. m_player = new SoundPlayer;
  25. connect(this, SIGNAL(bgSoundRequest()), m_player, SLOT(startBGSound()), Qt::QueuedConnection);
  26. connect(this, SIGNAL(boingSoundRequest()) ,m_player, SLOT(playBoing()), Qt::QueuedConnection);
  27. connect(this, SIGNAL(groanSoundRequest()) ,m_player, SLOT(playGroan()), Qt::QueuedConnection);
  28. connect(this, SIGNAL(stopSounds()), m_player, SLOT(stopSounds()), Qt::QueuedConnection);
  29. connect(m_player, SIGNAL(soundsStopped()), this, SIGNAL(readyToQuit()), Qt::QueuedConnection);
  30. }
  31. Level::~Level()
  32. {
  33. /***********************************************************************************************
  34. *Because the SoundPlayer object lives in another thread, it can not be a child of the Level *
  35. *Object and it needs to be deleted by hand. *
  36. ***********************************************************************************************/
  37. delete m_player;
  38. }
  39. /***************************************************************************************************
  40. *All sound playback is handled by an object living in another thread, playback needs to be *
  41. *invoked by means of a signal and queued connection. *
  42. ***************************************************************************************************/
  43. void Level::startBGSound()
  44. {
  45. #ifndef NO_SOUND
  46. emit bgSoundRequest();
  47. #endif
  48. }
  49. void Level::playBoing()
  50. {
  51. #ifndef NO_SOUND
  52. emit boingSoundRequest();
  53. #endif
  54. }
  55. void Level::playGroan()
  56. {
  57. #ifndef NO_SOUND
  58. emit groanSoundRequest();
  59. #endif
  60. }
  61. /**************************************************************************************************/
  62. QString Level::item(int row, int column) const
  63. {
  64. /***********************************************************************************************
  65. *Verifying that the row and column are in bounds. *
  66. *Extracting the string representing the item in that row and column. *
  67. ***********************************************************************************************/
  68. if ( row < 0 || row >= m_rowCount || column < 0 || column >= m_columnCount )
  69. return QString();
  70. const QString & text = m_map.at(row);
  71. if ( column >= text.count())
  72. return QString(" ");
  73. return text.at(column);
  74. }
  75. bool Level::load(QString fileName)
  76. {
  77. /***********************************************************************************************
  78. *Clearing the levels old data. *
  79. ***********************************************************************************************/
  80. m_rowCount = m_columnCount = 0;
  81. m_map.clear();
  82. /***********************************************************************************************
  83. *Opening the level file in read only mode. *
  84. ***********************************************************************************************/
  85. if(fileName.isEmpty() || !QFile::exists(fileName))
  86. fileName = ":/level/level.xml";
  87. QFile file(fileName);
  88. if (!file.open(QFile::ReadOnly | QFile::Text)) {
  89. qDebug() << Q_FUNC_INFO << file.errorString();
  90. return false;
  91. }
  92. /***********************************************************************************************
  93. *Using a QXmlStreamReader to parse the level file. *
  94. ***********************************************************************************************/
  95. QXmlStreamReader xml(&file);
  96. if (xml.readNextStartElement()) {
  97. if (xml.name() == "level") {
  98. while (xml.readNextStartElement()) {
  99. if (xml.name() == "layer") {
  100. const QString text = xml.readElementText();
  101. m_map.append(text);
  102. ++m_rowCount;
  103. m_columnCount = qMax(m_columnCount, text.count());
  104. }
  105. }
  106. }
  107. }
  108. /***********************************************************************************************
  109. *Checking xml parsing errors *
  110. ***********************************************************************************************/
  111. if (xml.hasError()) {
  112. qDebug() << Q_FUNC_INFO << xml.errorString();
  113. return false;
  114. }
  115. return true;
  116. }
  117. bool Level::intersects(QDeclarativeItem *item1, QDeclarativeItem *item2)
  118. {
  119. /***********************************************************************************************
  120. *Using QRect to check if the two items intersect. *
  121. ***********************************************************************************************/
  122. if ( !(item1&&item2))
  123. return false;
  124. const QRect r1 = QRect(item1->x(),item1->y(),item1->width(),item1->height());
  125. const QRect r2 = QRect(item2->x(),item2->y(),item2->width(),item2->height());
  126. return r1.intersects(r2);
  127. }
  128. QVariant Level::collidingItems(QDeclarativeItem *item) const
  129. {
  130. if (!item)
  131. return QVariant();
  132. QList<QObject*> dataList;
  133. /***********************************************************************************************
  134. *Extracting the list of items that collide with the item. *
  135. *Casting the item pointer to a EllipseItem pointer, *
  136. *if successful clearing the ellipse items m_rect vector. *
  137. ***********************************************************************************************/
  138. QList<QGraphicsItem *> list = item->collidingItems();
  139. EllipseItem *el = qobject_cast<EllipseItem*>(item);
  140. if (el)
  141. el->m_rect.clear();
  142. for ( int i=0; i<list.count();++i ) {
  143. /*******************************************************************************************
  144. *Ignore the collision if the items happen to be in a parent-child relation. *
  145. *Casting the colliding item pointer to a QDeclarativeItem pointer. *
  146. *******************************************************************************************/
  147. if ( item->isAncestorOf(list.at(i)) || item->parentItem() != list.at(i)->parentItem())
  148. continue;
  149. QDeclarativeItem * collidedItem = qobject_cast<QDeclarativeItem*>(list.at(i));
  150. if (collidedItem) {
  151. /***************************************************************************************
  152. *Ignore the collision if the colliding item happens to be the background *
  153. *or one of the ui controls. *
  154. *If an EllipseItem append the collision rect to the items m_rect vector. *
  155. *Append the item pointer to the result list. *
  156. ***************************************************************************************/
  157. if ( collidedItem->objectName() == QLatin1String("arrow") ||
  158. collidedItem->objectName() == QLatin1String("background") ||
  159. collidedItem->objectName() == QLatin1String("quit"))
  160. continue;
  161. if (el)
  162. el->m_rect.append( intersected(item,collidedItem) );
  163. dataList << collidedItem;
  164. }
  165. }
  166. /***********************************************************************************************
  167. *Return a QVariant containing the colliding object pointers. *
  168. ***********************************************************************************************/
  169. return QVariant::fromValue(dataList);
  170. }
  171. QRect Level::intersected(QDeclarativeItem *item1, QDeclarativeItem *item2) const
  172. {
  173. if (!(item1&&item2))
  174. return QRect();
  175. /***********************************************************************************************
  176. *Extracting the items shapes. *
  177. *Translate the second items shapes position to be relative to the first item. *
  178. *Calculate the items intersection. *
  179. *Return the intersections bounding rectangle. *
  180. ***********************************************************************************************/
  181. QPainterPath p1 = item1->shape();
  182. QPainterPath p2 = item2->shape().translated(item2->pos()-item1->pos());
  183. QPainterPath intersection = p2.intersected(p1);
  184. const QRect bounder = intersection.boundingRect().toRect();
  185. return bounder;
  186. }
  187. SoundPlayer::SoundPlayer() :m_musicBGOutput(0),m_boingOutput(0),m_groanOutput(0),
  188. m_musicBGPlaying(false),m_boingPlaying(false),m_groanPlaying(false)
  189. {
  190. /***********************************************************************************************
  191. *Creating a dedicated QThread for the SoundPlayer to live in, and moving the object to the *
  192. *new thread. *
  193. ***********************************************************************************************/
  194. m_soundThread = new QThread;
  195. this->moveToThread(m_soundThread);
  196. m_soundThread->start();
  197. /***********************************************************************************************
  198. *Setting up the audio format *
  199. ***********************************************************************************************/
  200. m_format.setSampleRate(22050);
  201. m_format.setChannelCount(1);
  202. m_format.setSampleSize(16);
  203. m_format.setCodec("audio/pcm");
  204. m_format.setByteOrder(QAudioFormat::LittleEndian);
  205. m_format.setSampleType(QAudioFormat::SignedInt);
  206. /***********************************************************************************************
  207. *Setting a flag indicating if the sound backend supports the given format, *
  208. *and loading the sound files. *
  209. * *
  210. *The QAudioOutput object creation is differed till the first usage, this is because the Sound *
  211. *players constructor is called from the main thread, and constructing them here would prevent *
  212. *them form working properly. *
  213. ***********************************************************************************************/
  214. QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
  215. if (!info.isFormatSupported(m_format)) {
  216. m_soundSupport = false;
  217. qWarning()<<"raw audio format not supported by backend, cannot play audio.";
  218. return;
  219. } else {
  220. m_soundSupport = true;
  221. m_musicBGFile.setFileName(":/sounds/grieg4f.raw");
  222. m_boingtFile.setFileName(":/sounds/boink.raw");
  223. m_groanFile.setFileName(":/sounds/groan.raw");
  224. }
  225. }
  226. SoundPlayer::~SoundPlayer()
  227. {
  228. /***********************************************************************************************
  229. *Calling the sound thread to exit its event loop, waiting for the thread do finish and deleting*
  230. *the instance. *
  231. ***********************************************************************************************/
  232. m_soundThread->exit(0);
  233. m_soundThread->wait();
  234. delete m_soundThread;
  235. }
  236. /***************************************************************************************************
  237. *Sound playback slots, called by signals emitted by the Level object. *
  238. *The audio output objects get constructed the first time the slot is called. *
  239. ***************************************************************************************************/
  240. void SoundPlayer::startBGSound()
  241. {
  242. if(m_musicBGPlaying) return;
  243. if(!m_soundSupport) return;
  244. if (m_musicBGOutput) {
  245. m_musicBGOutput->start(&m_musicBGFile);
  246. m_musicBGPlaying = true;
  247. } else{
  248. if (m_musicBGFile.open(QIODevice::ReadOnly)) {
  249. m_musicBGOutput = new QAudioOutput(m_format,this);
  250. connect(m_musicBGOutput,SIGNAL(stateChanged(QAudio::State)),
  251. SLOT(outputStateChanged(QAudio::State)));
  252. m_musicBGOutput->start(&m_musicBGFile);
  253. m_musicBGPlaying = true;
  254. }
  255. }
  256. }
  257. void SoundPlayer::playBoing()
  258. {
  259. if(!m_soundSupport) return;
  260. if (m_boingOutput) {
  261. if(m_boingPlaying) return;
  262. m_boingOutput->start(&m_boingtFile);
  263. m_boingPlaying = true;
  264. } else{
  265. if (m_boingtFile.open(QIODevice::ReadOnly)) {
  266. m_boingOutput = new QAudioOutput(m_format,this);
  267. connect(m_boingOutput,SIGNAL(stateChanged(QAudio::State)),
  268. SLOT(outputStateChanged(QAudio::State)));
  269. m_boingOutput->start(&m_boingtFile);
  270. }
  271. }
  272. }
  273. void SoundPlayer::playGroan()
  274. {
  275. if(!m_soundSupport) return;
  276. if (m_groanOutput) {
  277. if(m_groanPlaying) return;
  278. m_groanOutput->start(&m_groanFile);
  279. m_groanPlaying = true;
  280. } else{
  281. if (m_groanFile.open(QIODevice::ReadOnly)) {
  282. m_groanOutput = new QAudioOutput(m_format,this);
  283. connect(m_groanOutput,SIGNAL(stateChanged(QAudio::State)),
  284. SLOT(outputStateChanged(QAudio::State)));
  285. m_groanOutput->start(&m_groanFile);
  286. }
  287. }
  288. }
  289. /**************************************************************************************************/
  290. void SoundPlayer::stopSounds()
  291. {
  292. /***********************************************************************************************
  293. *Stop all sound playback and emit the signal indicating it is now safe to exit the application *
  294. ***********************************************************************************************/
  295. if(m_soundSupport)
  296. {
  297. if(m_musicBGOutput)
  298. m_musicBGOutput->stop();
  299. if(m_boingOutput)
  300. m_boingOutput->stop();
  301. if(m_groanOutput)
  302. m_groanOutput->stop();
  303. }
  304. emit soundsStopped();
  305. }
  306. void SoundPlayer::outputStateChanged(QAudio::State state)
  307. {
  308. /***********************************************************************************************
  309. *If signal sender == m_musicBGOutput resume background music playback. *
  310. *If signal sender == m_boingOutput drop the boing playing flag and prepare for next playback. *
  311. *If signal sender == m_groanOutput drop the groan playing flag and prepare for next playback. *
  312. ***********************************************************************************************/
  313. if (state == QAudio::IdleState) {
  314. if (sender() == m_musicBGOutput ) {
  315. m_musicBGOutput->stop();
  316. m_musicBGFile.seek(0);
  317. m_musicBGOutput->start(&m_musicBGFile);
  318. } else if (sender() == m_boingOutput ) {
  319. m_boingPlaying = false;
  320. m_boingOutput->stop();
  321. m_boingtFile.seek(0);
  322. } else if (sender() == m_groanOutput ) {
  323. m_groanPlaying = false;
  324. m_groanOutput->stop();
  325. m_groanFile.seek(0);
  326. }
  327. }
  328. }
  329. EllipseItem::EllipseItem(QDeclarativeItem *parent)
  330. : QDeclarativeItem(parent), m_margin(0),m_burn(false)
  331. {
  332. /***********************************************************************************************
  333. *Set the items flag to indicate that the item has some contents. *
  334. ***********************************************************************************************/
  335. setFlag(QGraphicsItem::ItemHasNoContents, false);
  336. }
  337. QPainterPath EllipseItem::shape () const
  338. {
  339. /***********************************************************************************************
  340. *Return the items shape as an ellipse taking in to account the items size and margins. *
  341. ***********************************************************************************************/
  342. QPainterPath path;
  343. path.addEllipse( m_margin, m_margin,width()-m_margin*2,height()-m_margin*2);
  344. return path;
  345. }
  346. void EllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
  347. QWidget *widget)
  348. {
  349. Q_UNUSED(option)
  350. Q_UNUSED(widget)
  351. /***********************************************************************************************
  352. *Draw the items shape. *
  353. ***********************************************************************************************/
  354. painter->save();
  355. painter->setPen(Qt::red);
  356. if (m_burn)
  357. painter->setBrush(Qt::black);
  358. else
  359. painter->setBrush(Qt::green);
  360. painter->drawPath(shape());
  361. painter->restore();
  362. }
  363. MultiTouchItem::MultiTouchItem(QDeclarativeItem *parent) : QDeclarativeItem(parent)
  364. {
  365. setAcceptTouchEvents(true);
  366. setAcceptedMouseButtons(Qt::LeftButton);
  367. setFlag(QGraphicsItem::ItemHasNoContents, false);
  368. }
  369. void MultiTouchItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
  370. QWidget *widget)
  371. {
  372. Q_UNUSED(option)
  373. Q_UNUSED(widget)
  374. /***********************************************************************************************
  375. *Draw the items pixmap with 0.5 opacity. *
  376. ***********************************************************************************************/
  377. painter->save();
  378. painter->setOpacity(0.5);
  379. painter->drawPixmap(0,0,width(),height(), m_pixmap);
  380. painter->restore();
  381. }
  382. bool MultiTouchItem::sceneEvent(QEvent *event)
  383. {
  384. /***********************************************************************************************
  385. *Overloaded items scene event, to handle touch and mouse press events. *
  386. *Handle TouchBegin, TouchUpdate, TouchEnd and mouse press/release events or revert to *
  387. *the QDeclarativeItems default implementation. *
  388. ***********************************************************************************************/
  389. switch (event->type()) {
  390. case QEvent::TouchBegin:
  391. emit pressed();
  392. break;
  393. case QEvent::TouchUpdate:
  394. break;
  395. case QEvent::TouchEnd:
  396. emit released();
  397. break;
  398. case QEvent::GraphicsSceneMousePress:
  399. emit pressed();
  400. break;
  401. case QEvent::GraphicsSceneMouseRelease:
  402. emit released();
  403. break;
  404. default:
  405. return QDeclarativeItem::sceneEvent(event);
  406. }
  407. return true;
  408. }
  409. void MultiTouchItem::setSource(const QString &s)
  410. {
  411. /***********************************************************************************************
  412. *Set the items pixmap and schedule a redraw. *
  413. ***********************************************************************************************/
  414. m_source = s;
  415. m_pixmap = QPixmap(s);
  416. update();
  417. }