GameBoard.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Font.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #define MOUSE //uncomment to enable mouse - see mouse tutorial
  10. #region Using Statements
  11. using System;
  12. using System.IO;
  13. using System.Xml.Serialization;
  14. using Microsoft.Xna.Framework;
  15. using Microsoft.Xna.Framework.Graphics;
  16. using Microsoft.Xna.Framework.Input;
  17. using System.Text;
  18. using System.Collections.Generic;
  19. #endregion
  20. namespace Marblets
  21. {
  22. /// <summary>
  23. /// The current animation state of the board and of individual marbles
  24. /// </summary>
  25. public enum Animation
  26. {
  27. /// <summary>
  28. /// No animations happening
  29. /// </summary>
  30. None,
  31. /// <summary>
  32. /// Some marbles are currently breaking
  33. /// </summary>
  34. Breaking,
  35. /// <summary>
  36. /// Breaking marbles are all gone
  37. /// </summary>
  38. Gone,
  39. }
  40. /// <summary>
  41. /// The game board holds the pieces, the cursor and handles most of the game logic
  42. /// </summary>
  43. public class GameBoard : DrawableGameComponent
  44. {
  45. /// <summary>
  46. /// Number if marbles in the x direction
  47. /// </summary>
  48. public const int Width = 7;
  49. /// <summary>
  50. /// Number of marbles in the y direction
  51. /// </summary>
  52. public const int Height = 9;
  53. private const int LeftEdge = 2;
  54. private const int TopEdge = 45;
  55. /// <summary>
  56. /// Marbles stores the set of marbles used on the game board
  57. /// </summary>
  58. internal Marble[,] Marbles = new Marble[Width, Height];
  59. /// <summary>
  60. /// Animation state stores what the game board is currently doing at any moment
  61. /// in time
  62. /// </summary>
  63. internal Animation AnimationState = Animation.None;
  64. private bool gameOver;
  65. /// <summary>
  66. /// Cursor position and texture
  67. /// </summary>
  68. private int cursorX;
  69. private int cursorY;
  70. private Texture2D marbleCursorTexture;
  71. /// <summary>
  72. /// Score information
  73. /// </summary>
  74. private Vector2 scoreOffset = new Vector2(0, -Marble.Height * 1.5f);
  75. private GameTime scoreFadeStart;
  76. private float scoreFadeFactor;
  77. private const float scoreFadeDistance = Marble.Height * 2;
  78. private int totalSelected;
  79. #if MOUSE
  80. private int lastMouseX;
  81. private int lastMouseY;
  82. private bool buttonDown;
  83. #endif
  84. /// <summary>
  85. /// Create a new game board
  86. /// </summary>
  87. /// <param name="game"></param>
  88. public GameBoard(Game game)
  89. : base(game)
  90. {
  91. Marble.Initialize();
  92. #if MOUSE
  93. MouseState mouse = Mouse.GetState();
  94. lastMouseX = mouse.X;
  95. lastMouseY = mouse.Y;
  96. #endif
  97. }
  98. /// <summary>
  99. /// Load graphics content.
  100. /// </summary>
  101. protected override void LoadContent()
  102. {
  103. marbleCursorTexture =
  104. MarbletsGame.Content.Load<Texture2D>("Textures/marble_cursor");
  105. Marble.LoadContent();
  106. base.LoadContent();
  107. }
  108. /// <summary>
  109. /// Checks to see if the game is over yet
  110. /// </summary>
  111. public bool GameOver
  112. {
  113. get
  114. {
  115. if(!gameOver && IsGameOver())
  116. {
  117. gameOver = true;
  118. }
  119. return gameOver;
  120. }
  121. }
  122. /// <summary>
  123. /// Called when the GameComponent needs to be updated.
  124. /// Game Board update performs animation on the marbles, checks for cursor
  125. /// movement. Most of the game logic is here or called from here
  126. /// </summary>
  127. /// <param name="gameTime">Current game time</param>
  128. public override void Update(GameTime gameTime)
  129. {
  130. if(gameTime == null)
  131. return;
  132. base.Update(gameTime);
  133. if(!GameOver)
  134. {
  135. Marble.UpdateStatic(gameTime);
  136. foreach(Marble marble in Marbles)
  137. {
  138. if(marble != null)
  139. {
  140. marble.Update(gameTime);
  141. }
  142. }
  143. switch(AnimationState)
  144. {
  145. case Animation.Breaking:
  146. {
  147. //Fade the score
  148. scoreFadeFactor = (float)((gameTime.TotalGameTime -
  149. scoreFadeStart.TotalGameTime).TotalSeconds /
  150. Marble.BreakTime);
  151. //Wait until all marbles are broken.
  152. bool stillBreaking = false;
  153. foreach(Marble marble in Marbles)
  154. {
  155. if(marble != null)
  156. {
  157. if(marble.Animation == Animation.Breaking)
  158. {
  159. stillBreaking = true;
  160. break;
  161. }
  162. }
  163. }
  164. //Done breaking - do the 'fall' and 'slide'
  165. if(!stillBreaking)
  166. {
  167. FallAndSlide();
  168. totalSelected = 0;
  169. FindCursorPosition();
  170. FindSelectedMarbles();
  171. Sound.Play(SoundEntry.LandMarbles);
  172. AnimationState = Animation.None;
  173. }
  174. break;
  175. }
  176. case Animation.None:
  177. {
  178. moveCursor();
  179. BreakMarbles(gameTime);
  180. break;
  181. }
  182. }
  183. }
  184. }
  185. private void FallAndSlide()
  186. {
  187. //Fall
  188. for(int x = 0; x < Width; x++)
  189. {
  190. int moveDistance = 0;
  191. for(int y = Height - 1; y >= 0; y--)
  192. {
  193. //Remember the current marble
  194. Marble currentMarble = Marbles[x, y];
  195. if(moveDistance > 0)
  196. {
  197. Marbles[x, y + moveDistance] = Marbles[x, y];
  198. Marbles[x, y] = null;
  199. if(Marbles[x, y + moveDistance] != null)
  200. {
  201. Marbles[x, y + moveDistance].Position =
  202. BoardToScreen(x, y + moveDistance);
  203. }
  204. }
  205. if(currentMarble != null && currentMarble.Selected)
  206. {
  207. moveDistance++;
  208. }
  209. }
  210. //Tidy up any marbles that didn't have anything above them
  211. for(int y = 0; y < moveDistance; y++)
  212. {
  213. Marbles[x, y] = null;
  214. }
  215. }
  216. //Slide
  217. for(int y = 0; y < Height; y++)
  218. {
  219. int moveDistance = 0;
  220. for(int x = Width - 1; x >= 0; x--)
  221. {
  222. //Remember the current marble
  223. Marble currentMarble = Marbles[x, y];
  224. if(moveDistance > 0)
  225. {
  226. Marbles[x + moveDistance, y] = Marbles[x, y];
  227. Marbles[x, y] = null;
  228. if(Marbles[x + moveDistance, y] != null)
  229. {
  230. Marbles[x + moveDistance, y].Position =
  231. BoardToScreen(x + moveDistance, y);
  232. }
  233. }
  234. if(currentMarble == null)
  235. {
  236. moveDistance++;
  237. }
  238. }
  239. //Tidy up any marbles that didn't have anything to their left
  240. for(int x = 0; x < moveDistance; x++)
  241. {
  242. Marbles[x, y] = null;
  243. }
  244. }
  245. }
  246. /// <summary>
  247. /// Converts a board grid position into a pixel screen position
  248. /// in 1280x720 space
  249. /// </summary>
  250. /// <param name="x">0-number of marbles wide</param>
  251. /// <param name="y">0-number of marbles high</param>
  252. /// <returns></returns>
  253. protected static Vector2 BoardToScreen(int x, int y)
  254. {
  255. return new Vector2(LeftEdge + (float)x * Marble.Width,
  256. TopEdge + (float)y * Marble.Height);
  257. }
  258. private void FindCursorPosition()
  259. {
  260. //If the cursor is on a marble then leave it there
  261. if(Marbles[cursorX, cursorY] != null) return;
  262. //We search the squares in this order....
  263. // X 3 8 15
  264. // 1 2 7 14
  265. // 4 5 6 13
  266. // 9 10 11 12
  267. //Otherwise move to the closes marble down and to the right.
  268. for(int distance = 1;
  269. distance < Math.Max(Width - cursorX, Height - cursorY) - 1;
  270. distance++)
  271. {
  272. //Search below and across
  273. if(cursorY + distance < Height)
  274. {
  275. for(int x = cursorX;
  276. x < Math.Min(cursorX + distance+1, Width); x++)
  277. {
  278. if(Marbles[x, cursorY + distance] != null)
  279. {
  280. cursorX = x;
  281. cursorY = cursorY + distance;
  282. return;
  283. }
  284. }
  285. }
  286. //Search to the right
  287. if(cursorX + distance < Width)
  288. {
  289. for(int y = Math.Min(cursorY + distance, Height) -1; y >= cursorY;
  290. y--)
  291. {
  292. if(Marbles[cursorX + distance, y] != null)
  293. {
  294. cursorX = cursorX + distance;
  295. cursorY = y;
  296. return;
  297. }
  298. }
  299. }
  300. }
  301. //If we exit the loop then there is no place to go so they must have cleared
  302. //the board in which case its game over anyway
  303. return;
  304. }
  305. private void BreakMarbles(GameTime time)
  306. {
  307. #if MOUSE
  308. bool buttonClicked=false;
  309. MouseState mouse = Mouse.GetState();
  310. if (mouse.LeftButton == ButtonState.Pressed)
  311. {
  312. buttonDown = true;
  313. }
  314. if (buttonDown && mouse.LeftButton == ButtonState.Released)
  315. {
  316. buttonDown = false;
  317. buttonClicked = true;
  318. }
  319. #endif
  320. //break if possible
  321. bool burstPressed = InputHelper.GamePads[PlayerIndex.One].APressed
  322. #if MOUSE
  323. || (Game.IsMouseVisible && buttonClicked)
  324. #endif
  325. ;
  326. if(totalSelected > 0 && burstPressed)
  327. {
  328. if(totalSelected > 10)
  329. {
  330. Sound.Play(SoundEntry.ClearBonus);
  331. }
  332. else
  333. {
  334. Sound.Play(SoundEntry.ClearMarbles);
  335. }
  336. MarbletsGame.Score += Score(totalSelected);
  337. AnimationState = Animation.Breaking;
  338. foreach(Marble marble in Marbles)
  339. {
  340. if(marble != null && marble.Selected)
  341. {
  342. marble.Break(time);
  343. }
  344. }
  345. //Setup the score fading
  346. scoreFadeStart = time;
  347. }
  348. else if(burstPressed)
  349. {
  350. //not enough marbles
  351. Sound.Play(SoundEntry.ClearIllegal);
  352. }
  353. }
  354. private void moveCursor()
  355. {
  356. //Move the cursor
  357. bool cursorMovedWithMouse = false;
  358. #if MOUSE
  359. //If user moves the mouse then show it
  360. MouseState mouse = Mouse.GetState();
  361. if (mouse.X != lastMouseX && mouse.Y != lastMouseY)
  362. {
  363. //TODO: Doesn't seem to actually show the mouse until you move outside
  364. //the window and back in.
  365. Game.IsMouseVisible = true;
  366. }
  367. //if the mouse is visible then track the cursor with the mouse
  368. if (Game.IsMouseVisible)
  369. {
  370. //Find which marble is under the cursor
  371. cursorMovedWithMouse = moveCursorWithMouse(mouse.X, mouse.Y);
  372. lastMouseX = mouse.X;
  373. lastMouseY = mouse.Y;
  374. }
  375. #endif
  376. //Move the cursor with keyboard/gamepad
  377. bool cursorMovedWithGamePad = false;
  378. if(InputHelper.GamePads[PlayerIndex.One].LeftPressed)
  379. {
  380. cursorMovedWithGamePad = FindNextMarbleX(-1);
  381. }
  382. if(InputHelper.GamePads[PlayerIndex.One].RightPressed)
  383. {
  384. cursorMovedWithGamePad = FindNextMarbleX(1);
  385. }
  386. if(InputHelper.GamePads[PlayerIndex.One].UpPressed)
  387. {
  388. cursorMovedWithGamePad = FindNextMarbleY(-1);
  389. }
  390. if(InputHelper.GamePads[PlayerIndex.One].DownPressed)
  391. {
  392. cursorMovedWithGamePad = FindNextMarbleY(1);
  393. }
  394. //if anything moved we play the move sound
  395. if(cursorMovedWithGamePad || cursorMovedWithMouse)
  396. {
  397. Sound.Play(SoundEntry.Navigate);
  398. FindSelectedMarbles();
  399. }
  400. #if MOUSE
  401. //If the user goes back to moving with the keyboard/gamepad
  402. //then hide the mouse
  403. if (cursorMovedWithGamePad)
  404. {
  405. Game.IsMouseVisible = false;
  406. }
  407. #endif
  408. }
  409. #if MOUSE
  410. private bool moveCursorWithMouse(int x, int y)
  411. {
  412. bool clickedOnMarble = false;
  413. double scale = (float)GraphicsDevice.Viewport.Height / 480.0;
  414. double offset = (int)((GraphicsDevice.Viewport.Width - 320 * scale) / 2);
  415. int boardX = (int)((((x-offset)/scale) - LeftEdge) / Marble.Width);
  416. int boardY = (int)(((y/scale) - TopEdge ) / Marble.Height);
  417. if (boardX >=0 && boardX < Width && boardY>=0 && boardY < Height &&
  418. Marbles[boardX, boardY] != null)
  419. {
  420. //Don't actually move and play the sound if the mouse is moving within
  421. //the marble
  422. if (cursorX != boardX || cursorY != boardY)
  423. {
  424. cursorX = boardX;
  425. cursorY = boardY;
  426. clickedOnMarble = true;
  427. }
  428. }
  429. return clickedOnMarble;
  430. }
  431. #endif
  432. private bool FindNextMarbleX(int direction)
  433. {
  434. bool cursorMoved = false;
  435. for(int move = 0; move < GameBoard.Width; move++)
  436. {
  437. //Wrap at the edge
  438. if(cursorX == 0 && direction == -1)
  439. {
  440. cursorX = GameBoard.Width;
  441. }
  442. else if(cursorX == (GameBoard.Width - 1) && direction == 1)
  443. {
  444. cursorX = -1;
  445. }
  446. //Move
  447. cursorX += direction;
  448. //Stop when we find a marble
  449. if(Marbles[cursorX, cursorY] != null)
  450. {
  451. cursorMoved = true;
  452. break;
  453. }
  454. }
  455. return cursorMoved;
  456. }
  457. private bool FindNextMarbleY(int direction)
  458. {
  459. bool cursorMoved = false;
  460. for(int move = 0; move < GameBoard.Height; move++)
  461. {
  462. //Wrap at the edge
  463. if(cursorY == 0 && direction == -1)
  464. {
  465. cursorY = GameBoard.Height;
  466. }
  467. else if(cursorY == (GameBoard.Height -1) && direction == 1)
  468. {
  469. cursorY = -1;
  470. }
  471. //Move
  472. cursorY += direction;
  473. //Stop when we find a marble
  474. if(Marbles[cursorX, cursorY] != null)
  475. {
  476. cursorMoved = true;
  477. break;
  478. }
  479. }
  480. return cursorMoved;
  481. }
  482. private void FindSelectedMarbles()
  483. {
  484. //Update the selected portions of the board
  485. foreach(Marble marble in Marbles)
  486. {
  487. if(marble != null)
  488. {
  489. marble.Selected = false;
  490. }
  491. }
  492. totalSelected = 0;
  493. SelectMarble(cursorX, cursorY);
  494. }
  495. private static int Score(int totalSelected)
  496. {
  497. return totalSelected * (totalSelected - 1);
  498. }
  499. //Select all marbles with the same color as this one
  500. private void SelectMarble(int x, int y)
  501. {
  502. if(Marbles[x, y] != null)
  503. {
  504. Marbles[x, y].Selected = true;
  505. totalSelected++;
  506. SelectSurrounding(Marbles[x, y].Color, x, y);
  507. //If this is the only one then don't select it
  508. if(totalSelected == 1)
  509. {
  510. Marbles[x, y].Selected = false;
  511. totalSelected--;
  512. }
  513. }
  514. }
  515. private void SelectSurrounding(Color color, int x, int y)
  516. {
  517. if(x < Width - 1) MatchColor(color, x + 1, y);
  518. if(x > 0) MatchColor(color, x - 1, y);
  519. if(y < Height - 1) MatchColor(color, x, y + 1);
  520. if(y > 0) MatchColor(color, x, y - 1);
  521. }
  522. private void MatchColor(Color color, int x, int y)
  523. {
  524. //If we are already selected then early out - we've been here
  525. if(Marbles[x, y] != null && !Marbles[x, y].Selected)
  526. {
  527. //If the color matches
  528. if(Marbles[x, y].Color == color)
  529. {
  530. totalSelected++;
  531. Marbles[x, y].Selected = true;
  532. SelectSurrounding(color, x, y);
  533. }
  534. }
  535. }
  536. /// <summary>
  537. /// Draws the game board and all child objects
  538. /// </summary>
  539. /// <param name="spriteBatch">a sprite batch to use to draw any sprites</param>
  540. public void Draw(RelativeSpriteBatch spriteBatch)
  541. {
  542. if(spriteBatch == null)
  543. return;
  544. foreach(Marble marble in Marbles)
  545. {
  546. if(marble != null)
  547. {
  548. marble.Draw(spriteBatch);
  549. }
  550. }
  551. if(!GameOver)
  552. {
  553. //Draw the cursor
  554. spriteBatch.Draw(marbleCursorTexture, BoardToScreen(cursorX, cursorY),
  555. Color.White);
  556. }
  557. if(!GameOver)
  558. {
  559. if(totalSelected > 0)
  560. {
  561. Vector2 position = BoardToScreen(cursorX, cursorY) + scoreOffset;
  562. Color color = Color.White;
  563. if(AnimationState == Animation.Breaking)
  564. {
  565. //Slide the score up and fade it
  566. position -= new Vector2(0, scoreFadeFactor * scoreFadeDistance);
  567. color = Color.White * (1f - scoreFadeFactor);
  568. }
  569. Font.Draw(spriteBatch, FontStyle.Large, position,
  570. Score(totalSelected), color);
  571. }
  572. }
  573. }
  574. /// <summary>
  575. /// Returns the state of the board as a string - useful for debugging
  576. /// </summary>
  577. /// <returns>String representation of the board</returns>
  578. public override string ToString()
  579. {
  580. StringBuilder board = new StringBuilder();
  581. for(int y = 0; y < Height; y++)
  582. {
  583. for(int x = 0; x < Width; x++)
  584. {
  585. if(Marbles[x, y] == null)
  586. board.Append("--null-- ");
  587. else
  588. board.Append(Marbles[x, y].Color.ToString() + " ");
  589. }
  590. board.AppendLine();
  591. }
  592. board.AppendLine();
  593. return board.ToString();
  594. }
  595. /// <summary>
  596. /// Check to see if the game is over or not. This is a function rather than a
  597. /// property to indicate that it does significant
  598. /// work rather than being a cheap property get
  599. /// </summary>
  600. /// <returns>true is the game is over</returns>
  601. private bool IsGameOver()
  602. {
  603. //Check all squares on board for pairs either to the right or below.
  604. //This will catch any possibilities
  605. for(int y = 0; y < Height; y++)
  606. {
  607. for(int x = 0; x < Width; x++)
  608. {
  609. if(Marbles[x, y] != null)
  610. {
  611. if((x < Width-1 && Marbles[x + 1, y] != null &&
  612. Marbles[x, y].Color == Marbles[x + 1, y].Color) ||
  613. (y < Height-1 && Marbles[x, y + 1] != null &&
  614. Marbles[x, y].Color == Marbles[x, y + 1].Color))
  615. {
  616. return false;
  617. }
  618. }
  619. }
  620. }
  621. //Game is over.
  622. SaveHighScores();
  623. return true;
  624. }
  625. /// <summary>
  626. /// Save the high scores to the drive.
  627. /// </summary>
  628. private static void SaveHighScores()
  629. {
  630. //Insert the score into the high score table
  631. for(int i = 0; i < 5; i++)
  632. {
  633. if(MarbletsGame.Score > MarbletsGame.HighScores[i])
  634. {
  635. //Insert new score
  636. MarbletsGame.HighScores.Insert(i, MarbletsGame.Score);
  637. //And remove the lowest from the bottom
  638. MarbletsGame.HighScores.RemoveAt(5);
  639. break;
  640. }
  641. }
  642. }
  643. /// <summary>
  644. /// Initializes the board
  645. /// </summary>
  646. public void NewGame()
  647. {
  648. gameOver = false;
  649. //Fill board
  650. for(int x = 0; x < Width; x++)
  651. {
  652. for(int y = 0; y < Height; y++)
  653. {
  654. Marbles[x, y] = new Marble();
  655. Marbles[x, y].Position = BoardToScreen(x, y);
  656. }
  657. }
  658. cursorX = 0;
  659. cursorY = 0;
  660. //The cursor might be on a matching color set
  661. FindSelectedMarbles();
  662. }
  663. }
  664. }