//#define DEBUGGRAPHICS using System; using System.Collections.Generic; using System.Text; using FDS.Fathom; using Microsoft.Xna.Framework; using FDS.Fathom.Graphics; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Vectris { public enum GridState { Play, Explosion, Moving, Lose } public class GameGrid : DrawableFathomComponent { private string[] messages = new string[] { "Damn!", "Nice!", "Awesome!", "Rad!", "Whoa!!", "Crazy!!", "WTF!!!" }; private int rows = 9; private int columns = 19; private float gridWidth; private float gridHeight; private float gridX; private float gridY; private float blockWidth; private float blockHeight; private float timeToDrop = 1; private float timeToDropElapsed = 0; private float moveSideTime = 0.150f; private float rotateTime = 0.150f; private float explosionTime = 0.250f; private float movingTime = 0.250f; private float moveSideElapsed; private float rotateElapsed; private float explosionElapsed; private float movingElapsed; private float particleCount = 1; private VectorParticleSystem particleSys; private ExplosionParticleSystem explosionParticles; private ExplosionSmokeParticleSystem explosionSmokeParticles; private LineBatch lineBatch; private BaseShape currentShape; private List blocks; private List> rowsToBreak; private List> rowsToMove; private GridState gridState; private List breakMessages; public List Blocks { get { return blocks; } } public BaseShape CurrentShape { get { return currentShape; } set { currentShape = value; } } public float BlockWidth { get { return blockWidth; } } public float BlockHeight { get { return blockHeight; } } public float X { get { return gridX; } } public float Y { get { return gridY; } } public float Width { get { return gridWidth; } } public float Height { get { return gridHeight; } } public GameGrid(Game game) : base(game) { } private SpriteFont messageFont; protected override void LoadContent() { base.LoadContent(); gridState = GridState.Play; rowsToMove = new List>(); blocks = new List(); breakMessages = new List(); messageFont = Engine.Content.Load("Content/Fonts/MessageFont"); moveSideElapsed = moveSideTime; rotateElapsed = rotateTime; explosionElapsed = explosionTime; movingElapsed = movingTime; blockWidth = 38; blockHeight = 38; gridX = Engine.Width * 0.30f; gridY = Engine.Height * 0.025f; gridWidth = blockWidth * rows; gridHeight = blockHeight * columns; explosionParticles = new ExplosionParticleSystem(Game, 10); explosionSmokeParticles = new ExplosionSmokeParticleSystem(Game, 10); lineBatch = new LineBatch(GraphicsDevice); particleSys = new VectorParticleSystem(Game); Game.Components.Add(particleSys); Game.Components.Add(explosionParticles); Game.Components.Add(explosionSmokeParticles); } public override void Update(GameTime gameTime) { base.Update(gameTime); float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; timeToDropElapsed += elapsed; if (gridState != GridState.Lose && timeToDropElapsed >= timeToDrop) { timeToDropElapsed = 0; if (CheckCollision(CurrentShape, new Vector3(0, BlockHeight, 0), Quaternion.Identity)) { SnapToGrid(CurrentShape, new Vector3(0, BlockHeight, 0)); NewShape(); CheckLines(); if (CheckCollision(CurrentShape, Vector3.Zero, Quaternion.Identity)) { gridState = GridState.Lose; } } else { CurrentShape.Position += new Vector3(0, BlockHeight, 0); } } List toRemove = new List(); for (int i = 0; i < breakMessages.Count; i++) { breakMessages[i].LifeTime -= elapsed; if (breakMessages[i].LifeTime <= 0) { toRemove.Add(breakMessages[i]); } } for (int i = 0; i < toRemove.Count; i++) { breakMessages.Remove(toRemove[i]); } if (gridState == GridState.Lose) { UpdateLoseState(gameTime); } else if (gridState == GridState.Explosion) { UpdateExplosionState(gameTime); } else if (gridState == GridState.Moving) { UpdateMovingState(gameTime); } else { UpdateGameplayState(gameTime); } } private void UpdateExplosionState(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; i < rowsToBreak.Count; i++) { for (int a = 0; a < rowsToBreak[i].Count; a++) { float minX = rowsToBreak[i][a].Vertices[0].X; float maxX = rowsToBreak[i][a].Vertices[2].X; float minY = rowsToBreak[i][a].Vertices[1].Y; float maxY = rowsToBreak[i][a].Vertices[3].Y; Vector2 explosionOrigin = new Vector2(minX + ((maxX - minX) / 2), minY + ((maxY - minY) / 2)); explosionParticles.AddParticles(explosionOrigin); explosionSmokeParticles.AddParticles(explosionOrigin); for (int j = 0; j < particleCount; j++) { explosionOrigin = new Vector2(Utility.RandomFloatMinMax(minX, maxX), Utility.RandomFloatMinMax(minY, maxY)); float xSpeed = Utility.RandomMinMax(-100, 100); float ySpeed = Utility.RandomMinMax(-100, 100); float rotation = Utility.RandomFloatMinMax(0, MathHelper.Pi); float lifeTime = Utility.RandomMinMax(1, 4); float rotationSpeed = Utility.RandomFloatMinMax(0, MathHelper.Pi * 3); VertexPositionColor[] vertices = new VertexPositionColor[] { new VertexPositionColor(new Vector3(Utility.RandomMinMax(-3, -1), 0, 0), rowsToBreak[i][a].Color), new VertexPositionColor(new Vector3(Utility.RandomMinMax(1, 3), 0, 0), rowsToBreak[i][a].Color) }; particleSys.Add(explosionOrigin, new Vector2(xSpeed, ySpeed), lifeTime, rotation, rotationSpeed, vertices); } Blocks.Remove(rowsToBreak[i][a]); } } explosionElapsed -= elapsed; if (explosionElapsed <= 0) { explosionElapsed = explosionTime; gridState = GridState.Moving; } } private void UpdateMovingState(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; i < rowsToMove.Count; i++) { for (int j = 0; j < rowsToMove[i].Count; j++) { for (int k = 0; k < rowsToMove[i][j].Vertices.Length; k++) { rowsToMove[i][j].Vertices[k] += new Vector2(0, blockHeight); } } } rowsToMove.Clear(); movingElapsed = movingTime; gridState = GridState.Play; } private void UpdateGameplayState(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; moveSideElapsed += elapsed; rotateElapsed += elapsed; if (InputState.KeyboardKeyPressed(Keys.A) && moveSideElapsed > moveSideTime) { moveSideElapsed = 0; if (!CheckCollision(CurrentShape, new Vector3(-BlockWidth, 0, 0), Quaternion.Identity)) { CurrentShape.Position -= new Vector3(BlockWidth, 0, 0); } } if (InputState.KeyboardKeyPressed(Keys.D) && moveSideElapsed > moveSideTime) { moveSideElapsed = 0; if (!CheckCollision(CurrentShape, new Vector3(BlockWidth, 0, 0), Quaternion.Identity)) { CurrentShape.Position += new Vector3(BlockWidth, 0, 0); } } if (InputState.KeyboardKeyPressed(Keys.S)) { timeToDropElapsed = 0; if (moveSideElapsed > moveSideTime) { moveSideElapsed = 0; if (CheckCollision(CurrentShape, new Vector3(0, BlockHeight, 0), Quaternion.Identity)) { SnapToGrid(CurrentShape, new Vector3(0, BlockHeight, 0)); NewShape(); CheckLines(); if (CheckCollision(CurrentShape, Vector3.Zero, Quaternion.Identity)) { gridState = GridState.Lose; } } else { CurrentShape.Position += new Vector3(0, BlockHeight, 0); } } } if (InputState.KeyboardKeyJustPressed(Keys.Q) && rotateElapsed > rotateTime) { rotateElapsed = 0; if (!CheckCollision(CurrentShape, Vector3.Zero, Quaternion.CreateFromYawPitchRoll(0, 0, -MathHelper.PiOver2))) { CurrentShape.Rotation *= Quaternion.CreateFromYawPitchRoll(0, 0, -MathHelper.PiOver2); } } if (InputState.KeyboardKeyJustPressed(Keys.E) && rotateElapsed > rotateTime) { rotateElapsed = 0; if (!CheckCollision(CurrentShape, Vector3.Zero, Quaternion.CreateFromYawPitchRoll(0, 0, MathHelper.PiOver2))) { CurrentShape.Rotation *= Quaternion.CreateFromYawPitchRoll(0, 0, MathHelper.PiOver2); } } if (InputState.KeyboardKeyJustPressed(Keys.Space)) { SnapToGrid(CurrentShape, new Vector3(0, gridY + gridHeight, 0)); NewShape(); CheckLines(); if (CheckCollision(CurrentShape, Vector3.Zero, Quaternion.Identity)) { gridState = GridState.Lose; } } if (CurrentShape != null) { CurrentShape.Update(gameTime); } } private void UpdateLoseState(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; int count = Blocks.Count; for (int i = 0; i < count; i++) { Block b = Blocks[i]; b.Color = Utility.RandomColor(); Blocks[i] = b; } if (Utility.RandomDouble() > 0.96) { explosionParticles.AddParticles(new Vector2(Utility.Random(Engine.Width), Utility.Random(Engine.Height))); } } public override void Draw(GameTime gameTime) { base.Draw(gameTime); Matrix proj = Matrix.CreateOrthographicOffCenter(0, Engine.Width, Engine.Height, 0, 0, 1); Renderer.PrimitiveBatch.BasicEffect.Projection = proj; Renderer.PrimitiveBatch.BasicEffect.World = Matrix.Identity; Renderer.PrimitiveBatch.Begin(PrimitiveType.LineList); for (int x = 0; x < rows; x++) { Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + x * blockWidth, gridY), new Color(0x10, 0x10, 0x10)); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + x * blockWidth, gridY + gridHeight), new Color(0x10, 0x10, 0x10)); } for (int y = 0; y < columns; y++) { Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX, gridY + y * blockHeight), new Color(0x10, 0x10, 0x10)); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + gridWidth, gridY + y * blockHeight), new Color(0x10, 0x10, 0x10)); } Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX, gridY), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX, gridY + gridHeight), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX, gridY), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + gridWidth, gridY), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + gridWidth, gridY), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + gridWidth, gridY + gridHeight), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX, gridY + gridHeight), Color.Blue); Renderer.PrimitiveBatch.AddVertex(new Vector2(gridX + gridWidth, gridY + gridHeight), Color.Blue); Renderer.PrimitiveBatch.End(); Renderer.PrimitiveBatch.Begin(PrimitiveType.LineList); if (CurrentShape != null && CurrentShape.Blocks != null) { Renderer.PrimitiveBatch.BasicEffect.World = CurrentShape.World; Renderer.PrimitiveBatch.BasicEffect.CommitChanges(); for (int i = 0; i < CurrentShape.Blocks.Length; i++) { Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[0], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[1], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[0], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[2], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[2], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[3], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[1], CurrentShape.Blocks[i].Color); Renderer.PrimitiveBatch.AddVertex(CurrentShape.Blocks[i].Vertices[3], CurrentShape.Blocks[i].Color); } } Renderer.PrimitiveBatch.End(); Renderer.PrimitiveBatch.Begin(PrimitiveType.LineList); for (int i = 0; i < Blocks.Count; i++) { Block block = Blocks[i]; Renderer.PrimitiveBatch.BasicEffect.World = Matrix.Identity; Renderer.PrimitiveBatch.BasicEffect.CommitChanges(); Renderer.PrimitiveBatch.AddVertex(block.Vertices[0], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[1], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[0], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[2], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[2], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[3], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[1], block.Color); Renderer.PrimitiveBatch.AddVertex(block.Vertices[3], block.Color); } Renderer.PrimitiveBatch.End(); Renderer.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.None); for (int i = 0; i < breakMessages.Count; i++) { Vector2 position = new Vector2(breakMessages[i].Position.X, breakMessages[i].Position.Y) + new Vector2(Utility.RandomFloatMinMax(1, 3), Utility.RandomFloatMinMax(1, 3)); Renderer.SpriteBatch.DrawString(messageFont, breakMessages[i].Message, position, Utility.RandomColor()); } Renderer.SpriteBatch.End(); #if DEBUGGRAPHICS Renderer.PrimitiveBatch.BasicEffect.World = Matrix.Identity; Renderer.PrimitiveBatch.Begin(PrimitiveType.LineList); GraphicsDevice.RenderState.PointSize = 4f; for (int i = 0; i < Blocks.Count; i++) { Block block = Blocks[i]; OrientedBoundingBox bb = block.BoundingBox; VertexPositionColor[] verts = new VertexPositionColor[] { new VertexPositionColor(new Vector3(new Vector2(bb.Min.X, bb.Min.Y), 0), Color.White), new VertexPositionColor(new Vector3(new Vector2(bb.Max.X, bb.Max.Y), 0), Color.White) }; GraphicsDevice.DrawUserPrimitives(PrimitiveType.PointList, verts, 0, 2); } for (int i = 0; i < columns; i++) { float row = Y + (blockHeight * i) + (blockHeight / 2); Vector2 start = new Vector2(X, row); Vector2 end = new Vector2(X + Width, row); OrientedBoundingBox bb = new OrientedBoundingBox(new Vector3(start, 0), new Vector3(end, 0)); Renderer.PrimitiveBatch.AddVertex(start, Color.White); Renderer.PrimitiveBatch.AddVertex(end, Color.White); } GraphicsDevice.RenderState.PointSize = 1f; Renderer.PrimitiveBatch.End(); #endif } public void AddShapeToGrid() { if (CurrentShape != null) { for (int i = 0; i < CurrentShape.Blocks.Length; i++) { Block b = new Block(); b.Vertices = new Vector2[CurrentShape.Blocks.Length]; for (int a = 0; a < b.Vertices.Length; a++) { b.Vertices[a] = Vector2.Transform(CurrentShape.Blocks[i].Vertices[a], CurrentShape.World); } b.Visible = CurrentShape.Visible; b.Color = CurrentShape.Blocks[i].Color; Blocks.Add(b); } CurrentShape = null; } } public void NewShape() { AddShapeToGrid(); BaseShape shape = null; switch (Utility.Random(7)) { case 0: shape = new LineShape(Game); break; case 1: shape = new LShape(Game); break; case 2: shape = new BackwardLShape(Game); break; case 3: shape = new TShape(Game); break; case 4: shape = new SShape(Game); break; case 5: shape = new ZShape(Game); break; default: shape = new BlockShape(Game); break; } shape.GameGrid = this; shape.Position = shape.StartPosition; shape.Initialize(); CurrentShape = shape; } private void CheckLines() { rowsToBreak = new List>(); particleSys.BasicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, Engine.Width, Engine.Height, 0, 0, 1); for (int i = columns - 1; i >= 0; i--) { float row = Y + (blockHeight * i) + (blockHeight / 2); Vector3 start = new Vector3(X, row, 0); Vector3 end = new Vector3(X + Width, row, 0); List blocks = new List(); OrientedBoundingBox bb = new OrientedBoundingBox(start, end); bb.Transforms = Matrix.Identity; for (int a = 0; a < Blocks.Count; a++) { if (bb.Intersects(Blocks[a].BoundingBox)) { blocks.Add(Blocks[a]); } } if (blocks.Count == rows) { BreakMessage message = new BreakMessage(); message.Message = messages[Utility.Random(messages.Length)]; message.LifeTime = 1; message.Position = new Vector3(Utility.RandomFloatMinMax(start.X, end.X), Utility.RandomFloatMinMax(start.Y - blockHeight, end.Y - blockHeight), 0); breakMessages.Add(message); rowsToBreak.Add(blocks); for (int j = i - 1; j >= 0; j--) { rowsToMove.Add(new List()); row = Y + (blockHeight * j) + (blockHeight / 2); start = new Vector3(X, row, 0); end = new Vector3(X + Width, row, 0); bb = new OrientedBoundingBox(start, end); bb.Transforms = Matrix.Identity; for (int k = 0; k < Blocks.Count; k++) { Block b = Blocks[k]; if (bb.Intersects(b.BoundingBox)) { rowsToMove[rowsToMove.Count - 1].Add(b); } } } } } if (rowsToBreak.Count > 0) { gridState = GridState.Explosion; } } public bool CheckCollision(BaseShape moveShape, Vector3 deltaMovement, Quaternion deltaRotation) { bool collision = false; List collisionObjects = Blocks; Vector3 position = moveShape.Position + deltaMovement; Quaternion rotation = moveShape.Rotation * deltaRotation; Matrix world = Matrix.CreateScale(moveShape.Scale) * Matrix.CreateFromQuaternion(rotation) * Matrix.CreateTranslation(position); if (collisionObjects == null) { return false; } for (int k = 0; k < moveShape.Blocks.Length && !collision; k++) { for (int i = 0; i < moveShape.Blocks[k].Vertices.Length && !collision; i++) { Vector2 vector = Vector2.Transform(moveShape.Blocks[k].Vertices[i], world); if (vector.X <= X - 1 || vector.X >= X + Width + 1 || vector.Y <= Y - 1 || vector.Y >= Y + Height + 1) { collision = true; } } } for (int i = 0; i < collisionObjects.Count && !collision; i++) { Block blockA = collisionObjects[i]; if (!blockA.Visible) { continue; } OrientedBoundingBox bba = blockA.BoundingBox; bba.Transforms = Matrix.Identity; for (int k = 0; k < moveShape.Blocks.Length && !collision; k++) { Block blockB = moveShape.Blocks[k]; if (!blockB.Visible) { continue; } OrientedBoundingBox bbb = blockB.BoundingBox; bbb.Transforms = world; collision = bba.Intersects(bbb); } } return collision; } internal void SnapToGrid(BaseShape shape, Vector3 collisionDelta) { int y = -1; shape.Position += collisionDelta; while (CheckCollision(shape, new Vector3(0, y, 0), Quaternion.Identity)) { y--; } shape.Position += new Vector3(0, y, 0); float delta = (shape.Position.Y - shape.StartPosition.Y) % blockHeight; shape.Position += new Vector3(0, -delta, 0); } } }