Level.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Level.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Text;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Input.Touch;
  15. using Microsoft.Xna.Framework.Graphics;
  16. using System.Xml.Linq;
  17. #endregion
  18. namespace MemoryMadness
  19. {
  20. class Level : DrawableGameComponent
  21. {
  22. #region Fields/Properties
  23. public int levelNumber;
  24. LinkedList<ButtonColors[]> sequence;
  25. LinkedListNode<ButtonColors[]> currentSequenceItem;
  26. public LevelState CurrentState;
  27. public bool IsActive;
  28. /// <summary>
  29. /// The amount of moves correctly performed by the user so far
  30. /// </summary>
  31. public int MovesPerformed { get; set; }
  32. // Sequence demonstration delays are multiplied by this each level
  33. const float DifficultyFactor = 0.75f;
  34. // Define the delay between flashes when the current set of moves is
  35. // demonstrated to the player
  36. TimeSpan delayFlashOn = TimeSpan.FromSeconds(1);
  37. TimeSpan delayFlashOff = TimeSpan.FromSeconds(0.5);
  38. // Define the allowed delay between two user inputs
  39. TimeSpan delayBetweenInputs = TimeSpan.FromSeconds(5);
  40. // Define the delay per move which will be used to calculate the overall time
  41. // the player has to input the sample. For example, if this delay is 4 and the
  42. // current level has 5 steps, the user will have 20 seconds overall to complete
  43. // the level.
  44. readonly TimeSpan DelayOverallPerInput = TimeSpan.FromSeconds(4);
  45. // The display period for the user's own input (feedback)
  46. readonly TimeSpan InputFlashDuration = TimeSpan.FromSeconds(0.75);
  47. TimeSpan delayPeriod;
  48. TimeSpan inputFlashPeriod;
  49. TimeSpan elapsedPatternInput;
  50. TimeSpan overallAllowedInputPeriod;
  51. bool flashOn;
  52. bool drawUserInput;
  53. ButtonColors?[] currentTouchSampleColors = new ButtonColors?[4];
  54. // Define spheres covering the various buttons
  55. BoundingSphere redShpere;
  56. BoundingSphere blueShpere;
  57. BoundingSphere greenShpere;
  58. BoundingSphere yellowShpere;
  59. // Rendering members
  60. SpriteBatch spriteBatch;
  61. Texture2D buttonsTexture;
  62. #endregion
  63. #region Initializaton
  64. public Level(Game game, SpriteBatch spriteBatch, int levelNumber,
  65. int movesPerformed, Texture2D buttonsTexture)
  66. : base(game)
  67. {
  68. this.levelNumber = levelNumber;
  69. this.spriteBatch = spriteBatch;
  70. CurrentState = LevelState.NotReady;
  71. this.buttonsTexture = buttonsTexture;
  72. MovesPerformed = movesPerformed;
  73. }
  74. public Level(Game game, SpriteBatch spriteBatch, int levelNumber,
  75. Texture2D buttonsTexture)
  76. : this(game, spriteBatch, levelNumber, 0, buttonsTexture)
  77. {
  78. }
  79. public override void Initialize()
  80. {
  81. //Update delays to match level difficulty
  82. UpdateDelays();
  83. // Define button bounding spheres
  84. DefineBoundingSpheres();
  85. // Load sequences for current level from definitions XML
  86. LoadLevelSequences();
  87. }
  88. #endregion
  89. #region Update and Render
  90. public override void Update(GameTime gameTime)
  91. {
  92. if (!IsActive)
  93. {
  94. base.Update(gameTime);
  95. return;
  96. }
  97. switch (CurrentState)
  98. {
  99. case LevelState.NotReady:
  100. // Nothing to update in this state
  101. break;
  102. case LevelState.Ready:
  103. // Wait for a while before demonstrating the level's move set
  104. delayPeriod += gameTime.ElapsedGameTime;
  105. if (delayPeriod >= delayFlashOn)
  106. {
  107. // Initiate flashing sequence
  108. currentSequenceItem = sequence.First;
  109. PlaySequenceStepSound();
  110. CurrentState = LevelState.Flashing;
  111. delayPeriod = TimeSpan.Zero;
  112. flashOn = true;
  113. }
  114. break;
  115. case LevelState.Flashing:
  116. // Display the level's move set. When done, start accepting
  117. // user input
  118. delayPeriod += gameTime.ElapsedGameTime;
  119. if ((delayPeriod >= delayFlashOn) && (flashOn))
  120. {
  121. delayPeriod = TimeSpan.Zero;
  122. flashOn = false;
  123. }
  124. if ((delayPeriod >= delayFlashOff) && (!flashOn))
  125. {
  126. delayPeriod = TimeSpan.Zero;
  127. currentSequenceItem = currentSequenceItem.Next;
  128. PlaySequenceStepSound();
  129. flashOn = true;
  130. }
  131. if (currentSequenceItem == null)
  132. {
  133. InitializeUserInputStage();
  134. }
  135. break;
  136. case LevelState.Started:
  137. case LevelState.InProcess:
  138. delayPeriod += gameTime.ElapsedGameTime;
  139. inputFlashPeriod += gameTime.ElapsedGameTime;
  140. elapsedPatternInput += gameTime.ElapsedGameTime;
  141. if ((delayPeriod >= delayBetweenInputs) ||
  142. (elapsedPatternInput >= overallAllowedInputPeriod))
  143. {
  144. // The user was not quick enough
  145. inputFlashPeriod = TimeSpan.Zero;
  146. CurrentState = LevelState.FinishedFail;
  147. }
  148. if (inputFlashPeriod >= InputFlashDuration)
  149. {
  150. drawUserInput = false;
  151. }
  152. break;
  153. case LevelState.Fault:
  154. inputFlashPeriod += gameTime.ElapsedGameTime;
  155. if (inputFlashPeriod >= InputFlashDuration)
  156. {
  157. drawUserInput = false;
  158. CurrentState = LevelState.FinishedFail;
  159. }
  160. break;
  161. case LevelState.Success:
  162. inputFlashPeriod += gameTime.ElapsedGameTime;
  163. if (inputFlashPeriod >= InputFlashDuration)
  164. {
  165. drawUserInput = false;
  166. CurrentState = LevelState.FinishedOk;
  167. }
  168. break;
  169. case LevelState.FinishedOk:
  170. // Gameplay screen will advance the level
  171. break;
  172. case LevelState.FinishedFail:
  173. // Gameplay screen will reset the level
  174. break;
  175. default:
  176. break;
  177. }
  178. base.Update(gameTime);
  179. }
  180. public override void Draw(GameTime gameTime)
  181. {
  182. if (IsActive)
  183. {
  184. spriteBatch.Begin();
  185. Rectangle redButtonRectangle = Settings.RedButtonDim;
  186. Rectangle greenButtonRectangle = Settings.GreenButtonDim;
  187. Rectangle blueButtonRectangle = Settings.BlueButtonDim;
  188. Rectangle yellowButtonRectangle = Settings.YellowButtonDim;
  189. // Draw the darkened buttons
  190. DrawDarkenedButtons(redButtonRectangle, greenButtonRectangle,
  191. blueButtonRectangle, yellowButtonRectangle);
  192. switch (CurrentState)
  193. {
  194. case LevelState.NotReady:
  195. case LevelState.Ready:
  196. // Nothing extra to draw
  197. break;
  198. case LevelState.Flashing:
  199. if ((currentSequenceItem != null) && (flashOn))
  200. {
  201. ButtonColors[] toDraw = currentSequenceItem.Value;
  202. DrawLitButtons(toDraw);
  203. }
  204. break;
  205. case LevelState.Started:
  206. case LevelState.InProcess:
  207. case LevelState.Fault:
  208. case LevelState.Success:
  209. if (drawUserInput)
  210. {
  211. List<ButtonColors> toDraw =
  212. new List<ButtonColors>(currentTouchSampleColors.Length);
  213. foreach (var touchColor in currentTouchSampleColors)
  214. {
  215. if (touchColor.HasValue)
  216. {
  217. toDraw.Add(touchColor.Value);
  218. }
  219. }
  220. DrawLitButtons(toDraw.ToArray());
  221. }
  222. break;
  223. case LevelState.FinishedOk:
  224. break;
  225. case LevelState.FinishedFail:
  226. break;
  227. default:
  228. break;
  229. }
  230. spriteBatch.End();
  231. }
  232. base.Draw(gameTime);
  233. }
  234. #endregion
  235. #region Public functionality
  236. /// <summary>
  237. /// Handle user presses.
  238. /// </summary>
  239. /// <param name="touchPoints">The locations touched by the user. The list
  240. /// is expected to contain at least one member.</param>
  241. public void RegisterTouch(List<TouchLocation> touchPoints)
  242. {
  243. if ((CurrentState == LevelState.Started ||
  244. CurrentState == LevelState.InProcess))
  245. {
  246. ButtonColors[] stepColors = sequence.First.Value;
  247. bool validTouchRegistered = false;
  248. if (touchPoints.Count > 0)
  249. {
  250. // Reset current touch sample
  251. for (int i = 0; i < Settings.ButtonAmount; i++)
  252. {
  253. currentTouchSampleColors[i] = null;
  254. }
  255. // Go over the touch points and populate the current touch sample
  256. for (int i = 0; i < touchPoints.Count; i++)
  257. {
  258. var gestureBox = new BoundingBox(
  259. new Vector3(touchPoints[i].Position.X - 5,
  260. touchPoints[i].Position.Y - 5, 0),
  261. new Vector3(touchPoints[i].Position.X + 10,
  262. touchPoints[i].Position.Y + 10, 0));
  263. if (redShpere.Intersects(gestureBox))
  264. {
  265. currentTouchSampleColors[i] = ButtonColors.Red;
  266. AudioManager.PlaySound("red");
  267. }
  268. else if (yellowShpere.Intersects(gestureBox))
  269. {
  270. currentTouchSampleColors[i] = ButtonColors.Yellow;
  271. AudioManager.PlaySound("yellow");
  272. }
  273. else if (blueShpere.Intersects(gestureBox))
  274. {
  275. currentTouchSampleColors[i] = ButtonColors.Blue;
  276. AudioManager.PlaySound("blue");
  277. }
  278. else if (greenShpere.Intersects(gestureBox))
  279. {
  280. currentTouchSampleColors[i] = ButtonColors.Green;
  281. AudioManager.PlaySound("green");
  282. }
  283. CurrentState = LevelState.InProcess;
  284. }
  285. List<ButtonColors> colorsHit =
  286. new List<ButtonColors>(currentTouchSampleColors.Length);
  287. // Check if the user pressed at least one of the colored buttons
  288. foreach (var hitColor in currentTouchSampleColors)
  289. {
  290. if (hitColor.HasValue)
  291. {
  292. validTouchRegistered = true;
  293. colorsHit.Add(hitColor.Value);
  294. }
  295. }
  296. // Find the buttons which the user failed to touch
  297. List<ButtonColors> missedColors =
  298. new List<ButtonColors>(stepColors.Length);
  299. foreach (var stepColor in stepColors)
  300. {
  301. if (!colorsHit.Contains(stepColor))
  302. {
  303. missedColors.Add(stepColor);
  304. }
  305. }
  306. // If the user failed to perform the current move, fail the level
  307. // Do nothing if no buttons were touched
  308. if (((missedColors.Count > 0) ||
  309. (touchPoints.Count != stepColors.Length)) && validTouchRegistered)
  310. CurrentState = LevelState.Fault;
  311. if (validTouchRegistered)
  312. {
  313. // Show user pressed buttons, reset timeout period
  314. // for button flash
  315. drawUserInput = true;
  316. inputFlashPeriod = TimeSpan.Zero;
  317. MovesPerformed++;
  318. sequence.Remove(stepColors);
  319. if ((sequence.Count == 0) && (CurrentState != LevelState.Fault))
  320. {
  321. CurrentState = LevelState.Success;
  322. }
  323. }
  324. }
  325. }
  326. }
  327. #endregion
  328. #region Private Functionality
  329. /// <summary>
  330. /// Load sequences for the current level from definitions XML
  331. /// </summary>
  332. private void LoadLevelSequences()
  333. {
  334. XDocument doc = XDocument.Load(@"Content\Gameplay\LevelDefinitions.xml");
  335. var definitions = doc.Document.Descendants(XName.Get("Level"));
  336. XElement levelDefinition = null;
  337. foreach (var definition in definitions)
  338. {
  339. if (int.Parse(
  340. definition.Attribute(XName.Get("Number")).Value) == levelNumber)
  341. {
  342. levelDefinition = definition;
  343. break;
  344. }
  345. }
  346. int skipMoves = 0; // Used to skip moves if we are resuming a level mid-play
  347. // If definitions are found, create sequences
  348. if (null != levelDefinition)
  349. {
  350. sequence = new LinkedList<ButtonColors[]>();
  351. foreach (var pattern in
  352. levelDefinition.Descendants(XName.Get("Pattern")))
  353. {
  354. if (skipMoves < MovesPerformed)
  355. {
  356. skipMoves++;
  357. continue;
  358. }
  359. string[] values = pattern.Value.Split(',');
  360. ButtonColors[] colors = new ButtonColors[values.Length];
  361. // Add each color to a sequence
  362. for (int i = 0; i < values.Length; i++)
  363. {
  364. colors[i] = (ButtonColors)Enum.Parse(
  365. typeof(ButtonColors), values[i], true);
  366. }
  367. // Add each sequence to the sequence list
  368. sequence.AddLast(colors);
  369. }
  370. if (MovesPerformed == 0)
  371. {
  372. CurrentState = LevelState.Ready;
  373. delayPeriod = TimeSpan.Zero;
  374. }
  375. else
  376. {
  377. InitializeUserInputStage();
  378. }
  379. }
  380. }
  381. /// <summary>
  382. /// Define button bounding spheres
  383. /// </summary>
  384. private void DefineBoundingSpheres()
  385. {
  386. redShpere = new BoundingSphere(
  387. new Vector3(
  388. Settings.RedButtonPosition.X + Settings.ButtonSize.X / 2,
  389. Settings.RedButtonPosition.Y + Settings.ButtonSize.Y / 2, 0),
  390. Settings.ButtonSize.X / 2);
  391. blueShpere = new BoundingSphere(
  392. new Vector3(
  393. Settings.BlueButtonPosition.X + Settings.ButtonSize.X / 2,
  394. Settings.BlueButtonPosition.Y + Settings.ButtonSize.Y / 2, 0),
  395. Settings.ButtonSize.X / 2);
  396. greenShpere = new BoundingSphere(
  397. new Vector3(
  398. Settings.GreenButtonPosition.X + Settings.ButtonSize.X / 2,
  399. Settings.GreenButtonPosition.Y + Settings.ButtonSize.Y / 2, 0),
  400. Settings.ButtonSize.X / 2);
  401. yellowShpere = new BoundingSphere(
  402. new Vector3(
  403. Settings.YellowButtonPosition.X + Settings.ButtonSize.X / 2,
  404. Settings.YellowButtonPosition.Y + Settings.ButtonSize.Y / 2, 0),
  405. Settings.ButtonSize.X / 2);
  406. }
  407. /// <summary>
  408. /// Update delays to match level difficulty
  409. /// </summary>
  410. private void UpdateDelays()
  411. {
  412. delayFlashOn = TimeSpan.FromTicks(
  413. (long)(delayFlashOn.Ticks *
  414. Math.Pow(DifficultyFactor, levelNumber - 1)));
  415. delayFlashOff = TimeSpan.FromTicks(
  416. (long)(delayFlashOff.Ticks *
  417. Math.Pow(DifficultyFactor, levelNumber - 1)));
  418. }
  419. /// <summary>
  420. /// Sets various members to allow the user to supply input.
  421. /// </summary>
  422. private void InitializeUserInputStage()
  423. {
  424. elapsedPatternInput = TimeSpan.Zero;
  425. overallAllowedInputPeriod = TimeSpan.Zero;
  426. CurrentState = LevelState.Started;
  427. drawUserInput = false;
  428. // Calculate total allowed timeout period for the entire level
  429. overallAllowedInputPeriod = TimeSpan.FromSeconds(
  430. DelayOverallPerInput.TotalSeconds * sequence.Count);
  431. }
  432. /// <summary>
  433. /// Draws the set of lit buttons according to the colors specified.
  434. /// </summary>
  435. /// <param name="toDraw">The array of colors representing the lit
  436. /// buttons.</param>
  437. private void DrawLitButtons(ButtonColors[] toDraw)
  438. {
  439. Vector2 position = Vector2.Zero;
  440. Rectangle rectangle = Rectangle.Empty;
  441. for (int i = 0; i < toDraw.Length; i++)
  442. {
  443. switch (toDraw[i])
  444. {
  445. case ButtonColors.Red:
  446. position = Settings.RedButtonPosition;
  447. rectangle = Settings.RedButtonLit;
  448. break;
  449. case ButtonColors.Yellow:
  450. position = Settings.YellowButtonPosition;
  451. rectangle = Settings.YellowButtonLit;
  452. break;
  453. case ButtonColors.Blue:
  454. position = Settings.BlueButtonPosition;
  455. rectangle = Settings.BlueButtonLit;
  456. break;
  457. case ButtonColors.Green:
  458. position = Settings.GreenButtonPosition;
  459. rectangle = Settings.GreenButtonLit;
  460. break;
  461. }
  462. spriteBatch.Draw(buttonsTexture, position, rectangle, Color.White);
  463. }
  464. }
  465. /// <summary>
  466. /// Draw the darkened buttons
  467. /// </summary>
  468. /// <param name="redButtonRectangle">Red button rectangle
  469. /// in source texture.</param>
  470. /// <param name="greenButtonRectangle">Green button rectangle
  471. /// in source texture.</param>
  472. /// <param name="blueButtonRectangle">Blue button rectangle
  473. /// in source texture.</param>
  474. /// <param name="yellowButtonRectangle">Yellow button rectangle
  475. /// in source texture.</param>
  476. private void DrawDarkenedButtons(Rectangle redButtonRectangle,
  477. Rectangle greenButtonRectangle, Rectangle blueButtonRectangle,
  478. Rectangle yellowButtonRectangle)
  479. {
  480. spriteBatch.Draw(buttonsTexture, Settings.RedButtonPosition,
  481. redButtonRectangle, Color.White);
  482. spriteBatch.Draw(buttonsTexture, Settings.GreenButtonPosition,
  483. greenButtonRectangle, Color.White);
  484. spriteBatch.Draw(buttonsTexture, Settings.BlueButtonPosition,
  485. blueButtonRectangle, Color.White);
  486. spriteBatch.Draw(buttonsTexture, Settings.YellowButtonPosition,
  487. yellowButtonRectangle, Color.White);
  488. }
  489. /// <summary>
  490. /// Plays a sound appropriate to the current color flashed by the computer
  491. /// </summary>
  492. private void PlaySequenceStepSound()
  493. {
  494. if (currentSequenceItem == null)
  495. {
  496. return;
  497. }
  498. for (int i = 0; i < currentSequenceItem.Value.Length; ++i)
  499. {
  500. switch (currentSequenceItem.Value[i])
  501. {
  502. case ButtonColors.Red:
  503. AudioManager.PlaySound("red");
  504. break;
  505. case ButtonColors.Yellow:
  506. AudioManager.PlaySound("yellow");
  507. break;
  508. case ButtonColors.Blue:
  509. AudioManager.PlaySound("blue");
  510. break;
  511. case ButtonColors.Green:
  512. AudioManager.PlaySound("green");
  513. break;
  514. default:
  515. break;
  516. }
  517. }
  518. }
  519. #endregion
  520. }
  521. }