/// <summary> /// Determine whether the two shapes are close enough to warrant checking intersection /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> public static bool DistanceTest(ShapeBase a, ShapeBase b) { return(Math.Sqrt(Math.Pow(a.m_pLocation.X - b.m_pLocation.X, 2) + Math.Pow(a.m_pLocation.Y - b.m_pLocation.Y, 2)) < a.m_fMaxRadius + b.m_fMaxRadius); }
/// <summary> /// Tick does one iteration of the game mechanics and rendering /// </summary> /// <param name="graphics"></param> public void Tick(Graphics graphics) { m_StarList.ForEach(q => q.Render(graphics)); switch (m_CurrentState) { //Menu state just shows title and a prompt to begin case GameState.Menu: graphics.DrawString("Asteroids", m_BigFont, new SolidBrush(Color.LightGray), GameRectangle, centeredString); m_PromptColor = ColorAnimation(m_PromptColor); graphics.DrawString($"\n\n\nPress {(PlayerInput.m_connected ? "Start":"P")} to begin!", m_MediumFont, new SolidBrush(m_PromptColor), GameRectangle, centeredString); if (PlayerInput.m_start && !m_StateTransition) { //Initialize game m_CurrentState = GameState.Started; m_Lives = 3; m_Score = 0; m_Ship = new Ship(new PointF(GameRectangle.Width / 2, GameRectangle.Height / 2), Color.Red, c_FadeIterations); m_ShapeList.Add(m_Ship); m_GameTime.Restart(); m_SpawnCount = 1; m_LifeCounter = 1; for (int i = 0; i < 5; i++) { m_ShapeList.Add(new Rock(new PointF(Random.Next(0, GameRectangle.Width), Random.Next(0, GameRectangle.Height)), Size.Large, c_FadeIterations)); } m_StateTransition = true; m_Confirmation.Play(); m_MediaPlayer.URL = m_EpicURL; } if (m_StateTransition && !PlayerInput.m_start) { m_StateTransition = false; } break; //Once started the game has the centered ship as well as a set number of asteroids case GameState.Started: //Player actions start here //Pause handle if (PlayerInput.m_start && !m_PauseTransition && !m_StateTransition) { m_CurrentState = GameState.Paused; m_PauseTransition = true; m_Confirmation.Play(); } if (m_PauseTransition && !PlayerInput.m_start) { m_PauseTransition = false; } if (m_StateTransition && !PlayerInput.m_start) { m_StateTransition = false; } //Fire Handle if (PlayerInput.m_a && !m_FireTransition && m_BulletsOnScreen < 8) { m_LaserSound.Play(); m_ShapeList.Add(new Bullet(m_Ship.GunPortLocation, m_Ship.AngleDeg, 10)); m_FireTransition = true; } if (m_FireTransition && !PlayerInput.m_a) { m_FireTransition = false; } //Boost Handle if (PlayerInput.m_b) { m_Ship.Boost(); } else { m_Ship.Decay(); } //Rotate Ship m_Ship.AngleDeg += PlayerInput.m_xDir * 5; //Add score text graphics.DrawString($"Score: {m_Score.ToString()}\nLives: {m_Lives}", m_SmallFont, new SolidBrush(Color.LightGray), GameRectangle); //Do all shape movement based on bounds m_ShapeList.ForEach(q => q.Tick(GameRectangle)); //Reset bullets on screen m_BulletsOnScreen = 0; List <ShapeBase> mirrorList = new List <ShapeBase>(); //Label the rocks for their duplicates int identifier = 0; //Main mirror code entry here foreach (ShapeBase thing in m_ShapeList) { if (!(thing is Bullet)) { thing.ID = identifier++; //Find closest edges for mirroring BoundProximity bp = thing.EdgeProximity(GameRectangle); if (bp.proxSum.Equals(2)) { ShapeBase[] cornerMirrors = new ShapeBase[3]; //Make three clones for (int i = 0; i < cornerMirrors.Length; i++) { cornerMirrors[i] = (ShapeBase)thing.Clone(); } if (bp.left) { /* Three projections are needed in a corner * * o______o ______ * | | |x |o * | | | | * |x_____|o |__ ___| * o o */ cornerMirrors[0].TranslateX(GameRectangle.Width); cornerMirrors[2].TranslateX(GameRectangle.Width); if (bp.top) { cornerMirrors[1].TranslateY(-GameRectangle.Height); cornerMirrors[2].TranslateY(-GameRectangle.Height); } else { cornerMirrors[1].TranslateY(GameRectangle.Height); cornerMirrors[2].TranslateY(GameRectangle.Height); } } else { /* * o______o ______ * | | o| x| * | | | | * o|_____x| |_____ | * o o */ cornerMirrors[0].TranslateX(-GameRectangle.Width); cornerMirrors[2].TranslateX(-GameRectangle.Width); if (bp.top) { cornerMirrors[1].TranslateY(-GameRectangle.Height); cornerMirrors[2].TranslateY(-GameRectangle.Height); } else { cornerMirrors[1].TranslateY(GameRectangle.Height); cornerMirrors[2].TranslateY(GameRectangle.Height); } } mirrorList.AddRange(cornerMirrors); } else if (bp.proxSum.Equals(1)) { //Regular mirrors without two potential intersections ShapeBase mirror = (ShapeBase)thing.Clone(); if (bp.left) { mirror.TranslateX(GameRectangle.Width); } if (bp.right) { mirror.TranslateX(-GameRectangle.Width); } if (bp.top) { mirror.TranslateY(-GameRectangle.Height); } if (bp.bottom) { mirror.TranslateY(GameRectangle.Height); } mirrorList.Add(mirror); } } else { m_BulletsOnScreen++; } } //Do collision checks List <ShapeBase> all = new List <ShapeBase>(); List <Tuple <ShapeBase, ShapeBase> > closePairs = new List <Tuple <ShapeBase, ShapeBase> >(); all.AddRange(m_ShapeList); all.AddRange(mirrorList); //Sort the collection by type (ship > bullet > rock) to make tuple comparisons easier all = (from q in all orderby ShapeBase.SortHelper(q) select q).ToList(); //Iterate each object with others for (int i = 0; i < all.Count; i++) { for (int j = i + 1; j < all.Count; j++) { //Only different type pairs in proximity matter if (all[i].GetType() != all[j].GetType() && ShapeBase.DistanceTest(all[i], all[j])) { closePairs.Add(new Tuple <ShapeBase, ShapeBase>(all[i], all[j])); } } } foreach (Tuple <ShapeBase, ShapeBase> thing in closePairs) { //Rock to anything else interaction if (thing.Item2 is Rock) { //Get bullet or ship into region Region region = new Region(thing.Item1.GetPath().Dequeue().Item1); //Intersect with rock region.Intersect(thing.Item2.GetPath().Dequeue().Item1); if (!region.IsEmpty(graphics)) { //Bullet - Rock interaction if (thing.Item1 is Bullet) { //Play explosion sound m_ExplosionSound.Play(); //Bullet is the same regardless of collection thing.Item1.MarkedForDeath = true; //Pull the offending rock Rock deadRock = (Rock)m_ShapeList.Find(q => q.ID.Equals(thing.Item2.ID)); //Split rocks, one large into two mediums if (deadRock.Size.Equals(Size.Large)) { for (int i = 0; i < 2; i++) { m_ShapeList.Add(new Rock(deadRock.Location, Size.Medium, 0)); } m_Score += 100; } //One medium into three smalls else if (deadRock.Size.Equals(Size.Medium)) { for (int i = 0; i < 3; i++) { m_ShapeList.Add(new Rock(deadRock.Location, Size.Small, 0)); } m_Score += 200; } else { m_Score += 300; } deadRock.MarkedForDeath = true; } //Other case is a pair of unfaded ship and rock, then the player dun goofed and got hit else if (thing.Item1.Fade.Equals(0) && thing.Item2.Fade.Equals(0)) { //Play explosion sound m_ExplosionSound.Play(); //Reset fade m_Ship.Fade = c_FadeIterations; m_Lives--; //Determine gameover state if (m_Lives.Equals(0)) { m_CurrentState = GameState.Gameover; m_WowSound1.Play(); } } } } } //This code was in here before but I forgot to commit it, handles points per life if (m_Score / ((float)c_PointsPerLife * m_LifeCounter) > 1) { m_Lives++; m_LifeCounter++; m_WowSound2.Play(); } //Do all renders on collections, remove dead things mirrorList.ForEach(q => q.Render(graphics)); m_ShapeList.RemoveAll(q => q.MarkedForDeath); m_ShapeList.ForEach(q => q.Render(graphics)); if (m_GameTime.ElapsedMilliseconds > m_SpawnDelay) { //Timer gets +1 rock on ever add, start at one m_GameTime.Restart(); for (int i = 0; i < m_SpawnCount; i++) { m_ShapeList.Add(new Rock(new PointF(Random.Next(0, GameRectangle.Width), Random.Next(0, GameRectangle.Height)), Size.Large, c_FadeIterations)); } m_SpawnCount++; } break; case GameState.Paused: //Unpause handle if (m_PauseTransition && !PlayerInput.m_start) { m_PauseTransition = false; } if (PlayerInput.m_start && !m_PauseTransition) { m_CurrentState = GameState.Started; m_PauseTransition = true; m_Confirmation.Play(); } //Pause screen graphics.DrawString("Asteroids", m_BigFont, new SolidBrush(Color.LightGray), GameRectangle, centeredString); graphics.DrawString("\n\n\nPaused", m_MediumFont, new SolidBrush(Color.Red), GameRectangle, centeredString); break; //Game over shows the final score and a prompt to return to the menu screen case GameState.Gameover: if (PlayerInput.m_start) { m_CurrentState = GameState.Menu; m_StateTransition = true; m_MediaPlayer.URL = m_SlowURL; m_Confirmation.Play(); } m_ShapeList.Clear(); //Show the final score and prompt the user to continue graphics.DrawString($"Final score: {m_Score}", m_MediumFont, new SolidBrush(Color.Red), GameRectangle, centeredString); m_PromptColor = ColorAnimation(m_PromptColor); graphics.DrawString($"\n\nPress {(PlayerInput.m_connected ? "Start" : "P")} to continue", m_MediumFont, new SolidBrush(ColorAnimation(m_PromptColor)), GameRectangle, centeredString); break; } }