#region File Description
//-----------------------------------------------------------------------------
// Font.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#define MOUSE //uncomment to enable mouse - see mouse tutorial
#region Using Statements
using System;
using System.IO;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Text;
using System.Collections.Generic;
#endregion
namespace Marblets
{
///
/// The current animation state of the board and of individual marbles
///
public enum Animation
{
///
/// No animations happening
///
None,
///
/// Some marbles are currently breaking
///
Breaking,
///
/// Breaking marbles are all gone
///
Gone,
}
///
/// The game board holds the pieces, the cursor and handles most of the game logic
///
public class GameBoard : DrawableGameComponent
{
///
/// Number if marbles in the x direction
///
public const int Width = 7;
///
/// Number of marbles in the y direction
///
public const int Height = 9;
private const int LeftEdge = 2;
private const int TopEdge = 45;
///
/// Marbles stores the set of marbles used on the game board
///
internal Marble[,] Marbles = new Marble[Width, Height];
///
/// Animation state stores what the game board is currently doing at any moment
/// in time
///
internal Animation AnimationState = Animation.None;
private bool gameOver;
///
/// Cursor position and texture
///
private int cursorX;
private int cursorY;
private Texture2D marbleCursorTexture;
///
/// Score information
///
private Vector2 scoreOffset = new Vector2(0, -Marble.Height * 1.5f);
private GameTime scoreFadeStart;
private float scoreFadeFactor;
private const float scoreFadeDistance = Marble.Height * 2;
private int totalSelected;
#if MOUSE
private int lastMouseX;
private int lastMouseY;
private bool buttonDown;
#endif
///
/// Create a new game board
///
///
public GameBoard(Game game)
: base(game)
{
Marble.Initialize();
#if MOUSE
MouseState mouse = Mouse.GetState();
lastMouseX = mouse.X;
lastMouseY = mouse.Y;
#endif
}
///
/// Load graphics content.
///
protected override void LoadContent()
{
marbleCursorTexture =
MarbletsGame.Content.Load("Textures/marble_cursor");
Marble.LoadContent();
base.LoadContent();
}
///
/// Checks to see if the game is over yet
///
public bool GameOver
{
get
{
if(!gameOver && IsGameOver())
{
gameOver = true;
}
return gameOver;
}
}
///
/// Called when the GameComponent needs to be updated.
/// Game Board update performs animation on the marbles, checks for cursor
/// movement. Most of the game logic is here or called from here
///
/// Current game time
public override void Update(GameTime gameTime)
{
if(gameTime == null)
return;
base.Update(gameTime);
if(!GameOver)
{
Marble.UpdateStatic(gameTime);
foreach(Marble marble in Marbles)
{
if(marble != null)
{
marble.Update(gameTime);
}
}
switch(AnimationState)
{
case Animation.Breaking:
{
//Fade the score
scoreFadeFactor = (float)((gameTime.TotalGameTime -
scoreFadeStart.TotalGameTime).TotalSeconds /
Marble.BreakTime);
//Wait until all marbles are broken.
bool stillBreaking = false;
foreach(Marble marble in Marbles)
{
if(marble != null)
{
if(marble.Animation == Animation.Breaking)
{
stillBreaking = true;
break;
}
}
}
//Done breaking - do the 'fall' and 'slide'
if(!stillBreaking)
{
FallAndSlide();
totalSelected = 0;
FindCursorPosition();
FindSelectedMarbles();
Sound.Play(SoundEntry.LandMarbles);
AnimationState = Animation.None;
}
break;
}
case Animation.None:
{
moveCursor();
BreakMarbles(gameTime);
break;
}
}
}
}
private void FallAndSlide()
{
//Fall
for(int x = 0; x < Width; x++)
{
int moveDistance = 0;
for(int y = Height - 1; y >= 0; y--)
{
//Remember the current marble
Marble currentMarble = Marbles[x, y];
if(moveDistance > 0)
{
Marbles[x, y + moveDistance] = Marbles[x, y];
Marbles[x, y] = null;
if(Marbles[x, y + moveDistance] != null)
{
Marbles[x, y + moveDistance].Position =
BoardToScreen(x, y + moveDistance);
}
}
if(currentMarble != null && currentMarble.Selected)
{
moveDistance++;
}
}
//Tidy up any marbles that didn't have anything above them
for(int y = 0; y < moveDistance; y++)
{
Marbles[x, y] = null;
}
}
//Slide
for(int y = 0; y < Height; y++)
{
int moveDistance = 0;
for(int x = Width - 1; x >= 0; x--)
{
//Remember the current marble
Marble currentMarble = Marbles[x, y];
if(moveDistance > 0)
{
Marbles[x + moveDistance, y] = Marbles[x, y];
Marbles[x, y] = null;
if(Marbles[x + moveDistance, y] != null)
{
Marbles[x + moveDistance, y].Position =
BoardToScreen(x + moveDistance, y);
}
}
if(currentMarble == null)
{
moveDistance++;
}
}
//Tidy up any marbles that didn't have anything to their left
for(int x = 0; x < moveDistance; x++)
{
Marbles[x, y] = null;
}
}
}
///
/// Converts a board grid position into a pixel screen position
/// in 1280x720 space
///
/// 0-number of marbles wide
/// 0-number of marbles high
///
protected static Vector2 BoardToScreen(int x, int y)
{
return new Vector2(LeftEdge + (float)x * Marble.Width,
TopEdge + (float)y * Marble.Height);
}
private void FindCursorPosition()
{
//If the cursor is on a marble then leave it there
if(Marbles[cursorX, cursorY] != null) return;
//We search the squares in this order....
// X 3 8 15
// 1 2 7 14
// 4 5 6 13
// 9 10 11 12
//Otherwise move to the closes marble down and to the right.
for(int distance = 1;
distance < Math.Max(Width - cursorX, Height - cursorY) - 1;
distance++)
{
//Search below and across
if(cursorY + distance < Height)
{
for(int x = cursorX;
x < Math.Min(cursorX + distance+1, Width); x++)
{
if(Marbles[x, cursorY + distance] != null)
{
cursorX = x;
cursorY = cursorY + distance;
return;
}
}
}
//Search to the right
if(cursorX + distance < Width)
{
for(int y = Math.Min(cursorY + distance, Height) -1; y >= cursorY;
y--)
{
if(Marbles[cursorX + distance, y] != null)
{
cursorX = cursorX + distance;
cursorY = y;
return;
}
}
}
}
//If we exit the loop then there is no place to go so they must have cleared
//the board in which case its game over anyway
return;
}
private void BreakMarbles(GameTime time)
{
#if MOUSE
bool buttonClicked=false;
MouseState mouse = Mouse.GetState();
if (mouse.LeftButton == ButtonState.Pressed)
{
buttonDown = true;
}
if (buttonDown && mouse.LeftButton == ButtonState.Released)
{
buttonDown = false;
buttonClicked = true;
}
#endif
//break if possible
bool burstPressed = InputHelper.GamePads[PlayerIndex.One].APressed
#if MOUSE
|| (Game.IsMouseVisible && buttonClicked)
#endif
;
if(totalSelected > 0 && burstPressed)
{
if(totalSelected > 10)
{
Sound.Play(SoundEntry.ClearBonus);
}
else
{
Sound.Play(SoundEntry.ClearMarbles);
}
MarbletsGame.Score += Score(totalSelected);
AnimationState = Animation.Breaking;
foreach(Marble marble in Marbles)
{
if(marble != null && marble.Selected)
{
marble.Break(time);
}
}
//Setup the score fading
scoreFadeStart = time;
}
else if(burstPressed)
{
//not enough marbles
Sound.Play(SoundEntry.ClearIllegal);
}
}
private void moveCursor()
{
//Move the cursor
bool cursorMovedWithMouse = false;
#if MOUSE
//If user moves the mouse then show it
MouseState mouse = Mouse.GetState();
if (mouse.X != lastMouseX && mouse.Y != lastMouseY)
{
//TODO: Doesn't seem to actually show the mouse until you move outside
//the window and back in.
Game.IsMouseVisible = true;
}
//if the mouse is visible then track the cursor with the mouse
if (Game.IsMouseVisible)
{
//Find which marble is under the cursor
cursorMovedWithMouse = moveCursorWithMouse(mouse.X, mouse.Y);
lastMouseX = mouse.X;
lastMouseY = mouse.Y;
}
#endif
//Move the cursor with keyboard/gamepad
bool cursorMovedWithGamePad = false;
if(InputHelper.GamePads[PlayerIndex.One].LeftPressed)
{
cursorMovedWithGamePad = FindNextMarbleX(-1);
}
if(InputHelper.GamePads[PlayerIndex.One].RightPressed)
{
cursorMovedWithGamePad = FindNextMarbleX(1);
}
if(InputHelper.GamePads[PlayerIndex.One].UpPressed)
{
cursorMovedWithGamePad = FindNextMarbleY(-1);
}
if(InputHelper.GamePads[PlayerIndex.One].DownPressed)
{
cursorMovedWithGamePad = FindNextMarbleY(1);
}
//if anything moved we play the move sound
if(cursorMovedWithGamePad || cursorMovedWithMouse)
{
Sound.Play(SoundEntry.Navigate);
FindSelectedMarbles();
}
#if MOUSE
//If the user goes back to moving with the keyboard/gamepad
//then hide the mouse
if (cursorMovedWithGamePad)
{
Game.IsMouseVisible = false;
}
#endif
}
#if MOUSE
private bool moveCursorWithMouse(int x, int y)
{
bool clickedOnMarble = false;
double scale = (float)GraphicsDevice.Viewport.Height / 480.0;
double offset = (int)((GraphicsDevice.Viewport.Width - 320 * scale) / 2);
int boardX = (int)((((x-offset)/scale) - LeftEdge) / Marble.Width);
int boardY = (int)(((y/scale) - TopEdge ) / Marble.Height);
if (boardX >=0 && boardX < Width && boardY>=0 && boardY < Height &&
Marbles[boardX, boardY] != null)
{
//Don't actually move and play the sound if the mouse is moving within
//the marble
if (cursorX != boardX || cursorY != boardY)
{
cursorX = boardX;
cursorY = boardY;
clickedOnMarble = true;
}
}
return clickedOnMarble;
}
#endif
private bool FindNextMarbleX(int direction)
{
bool cursorMoved = false;
for(int move = 0; move < GameBoard.Width; move++)
{
//Wrap at the edge
if(cursorX == 0 && direction == -1)
{
cursorX = GameBoard.Width;
}
else if(cursorX == (GameBoard.Width - 1) && direction == 1)
{
cursorX = -1;
}
//Move
cursorX += direction;
//Stop when we find a marble
if(Marbles[cursorX, cursorY] != null)
{
cursorMoved = true;
break;
}
}
return cursorMoved;
}
private bool FindNextMarbleY(int direction)
{
bool cursorMoved = false;
for(int move = 0; move < GameBoard.Height; move++)
{
//Wrap at the edge
if(cursorY == 0 && direction == -1)
{
cursorY = GameBoard.Height;
}
else if(cursorY == (GameBoard.Height -1) && direction == 1)
{
cursorY = -1;
}
//Move
cursorY += direction;
//Stop when we find a marble
if(Marbles[cursorX, cursorY] != null)
{
cursorMoved = true;
break;
}
}
return cursorMoved;
}
private void FindSelectedMarbles()
{
//Update the selected portions of the board
foreach(Marble marble in Marbles)
{
if(marble != null)
{
marble.Selected = false;
}
}
totalSelected = 0;
SelectMarble(cursorX, cursorY);
}
private static int Score(int totalSelected)
{
return totalSelected * (totalSelected - 1);
}
//Select all marbles with the same color as this one
private void SelectMarble(int x, int y)
{
if(Marbles[x, y] != null)
{
Marbles[x, y].Selected = true;
totalSelected++;
SelectSurrounding(Marbles[x, y].Color, x, y);
//If this is the only one then don't select it
if(totalSelected == 1)
{
Marbles[x, y].Selected = false;
totalSelected--;
}
}
}
private void SelectSurrounding(Color color, int x, int y)
{
if(x < Width - 1) MatchColor(color, x + 1, y);
if(x > 0) MatchColor(color, x - 1, y);
if(y < Height - 1) MatchColor(color, x, y + 1);
if(y > 0) MatchColor(color, x, y - 1);
}
private void MatchColor(Color color, int x, int y)
{
//If we are already selected then early out - we've been here
if(Marbles[x, y] != null && !Marbles[x, y].Selected)
{
//If the color matches
if(Marbles[x, y].Color == color)
{
totalSelected++;
Marbles[x, y].Selected = true;
SelectSurrounding(color, x, y);
}
}
}
///
/// Draws the game board and all child objects
///
/// a sprite batch to use to draw any sprites
public void Draw(RelativeSpriteBatch spriteBatch)
{
if(spriteBatch == null)
return;
foreach(Marble marble in Marbles)
{
if(marble != null)
{
marble.Draw(spriteBatch);
}
}
if(!GameOver)
{
//Draw the cursor
spriteBatch.Draw(marbleCursorTexture, BoardToScreen(cursorX, cursorY),
Color.White);
}
if(!GameOver)
{
if(totalSelected > 0)
{
Vector2 position = BoardToScreen(cursorX, cursorY) + scoreOffset;
Color color = Color.White;
if(AnimationState == Animation.Breaking)
{
//Slide the score up and fade it
position -= new Vector2(0, scoreFadeFactor * scoreFadeDistance);
color = Color.White * (1f - scoreFadeFactor);
}
Font.Draw(spriteBatch, FontStyle.Large, position,
Score(totalSelected), color);
}
}
}
///
/// Returns the state of the board as a string - useful for debugging
///
/// String representation of the board
public override string ToString()
{
StringBuilder board = new StringBuilder();
for(int y = 0; y < Height; y++)
{
for(int x = 0; x < Width; x++)
{
if(Marbles[x, y] == null)
board.Append("--null-- ");
else
board.Append(Marbles[x, y].Color.ToString() + " ");
}
board.AppendLine();
}
board.AppendLine();
return board.ToString();
}
///
/// Check to see if the game is over or not. This is a function rather than a
/// property to indicate that it does significant
/// work rather than being a cheap property get
///
/// true is the game is over
private bool IsGameOver()
{
//Check all squares on board for pairs either to the right or below.
//This will catch any possibilities
for(int y = 0; y < Height; y++)
{
for(int x = 0; x < Width; x++)
{
if(Marbles[x, y] != null)
{
if((x < Width-1 && Marbles[x + 1, y] != null &&
Marbles[x, y].Color == Marbles[x + 1, y].Color) ||
(y < Height-1 && Marbles[x, y + 1] != null &&
Marbles[x, y].Color == Marbles[x, y + 1].Color))
{
return false;
}
}
}
}
//Game is over.
SaveHighScores();
return true;
}
///
/// Save the high scores to the drive.
///
private static void SaveHighScores()
{
//Insert the score into the high score table
for(int i = 0; i < 5; i++)
{
if(MarbletsGame.Score > MarbletsGame.HighScores[i])
{
//Insert new score
MarbletsGame.HighScores.Insert(i, MarbletsGame.Score);
//And remove the lowest from the bottom
MarbletsGame.HighScores.RemoveAt(5);
break;
}
}
}
///
/// Initializes the board
///
public void NewGame()
{
gameOver = false;
//Fill board
for(int x = 0; x < Width; x++)
{
for(int y = 0; y < Height; y++)
{
Marbles[x, y] = new Marble();
Marbles[x, y].Position = BoardToScreen(x, y);
}
}
cursorX = 0;
cursorY = 0;
//The cursor might be on a matching color set
FindSelectedMarbles();
}
}
}