From ad9330f9a69a907c24918b4b5ae31d226d64b96b Mon Sep 17 00:00:00 2001 From: Susan Yuen <susan_loves_cheese@hotmail.com> Date: Thu, 3 Nov 2016 02:20:14 -0400 Subject: [PATCH] Changed path finding to use BFS --- src/Blaze-Brigade/Blaze_Brigade/Game.cs | 77 +++++----- .../Blaze_Brigade/GameFunction.cs | 135 +++++++++--------- src/Blaze-Brigade/Blaze_Brigade/GameState.cs | 4 +- src/Blaze-Brigade/Blaze_Brigade/Graph.cs | 11 ++ .../Blaze_Brigade/MouseHandler.cs | 19 ++- src/Blaze-Brigade/Blaze_Brigade/Node.cs | 11 ++ 6 files changed, 141 insertions(+), 116 deletions(-) diff --git a/src/Blaze-Brigade/Blaze_Brigade/Game.cs b/src/Blaze-Brigade/Blaze_Brigade/Game.cs index 29ea05e..655a6cf 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/Game.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/Game.cs @@ -100,13 +100,13 @@ namespace Controller //initializes players and units private void initializeGame() { - graph = new Graph(50,32); + graph = new Graph(50, 32); player1 = new Player(); player2 = new Player(); // load character sprite and set position - player1.addUnit(getNewUnit(UnitType.Warrior, new Vector2(4*32f, 6*32f),1)); - player2.addUnit(getNewUnit(UnitType.Warrior, new Vector2(15*32f, 6*32f),2)); + player1.addUnit(getNewUnit(UnitType.Warrior, new Vector2(4 * 32f, 6 * 32f), 1)); + player2.addUnit(getNewUnit(UnitType.Warrior, new Vector2(15 * 32f, 6 * 32f), 2)); GameState.currentPlayer = (player1); GameState.enemyPlayer = (player2); @@ -139,7 +139,7 @@ namespace Controller { mMenu.Close(); //close Main Menu screen tut.Close(); //close How To Play Menu - Form GameForm = (Form)Form.FromHandle(Window.Handle); + Form GameForm = (Form)Form.FromHandle(Window.Handle); GameForm.Opacity = 100; // make screen show currentGameState = GameMenuState.Playing; //set game state to Playing break; @@ -225,7 +225,7 @@ namespace Controller protected override void Draw(GameTime gameTime) { spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null); // begin spriteBatch - + //only draw objects relevent to current game state switch (currentGameState) { @@ -242,31 +242,30 @@ namespace Controller #endregion #region When Unit is selected - //if unit is currently clicked on - if (GameState.playableUnitSelected) - { - Unit unit = GameState.selectedUnit; + //if unit is currently clicked on + if (GameState.playableUnitSelected) + { + Unit unit = GameState.selectedUnit; if (!GameState.isAnimating) { if (GameState.beforeMove) // if unit has yet to move, display the overall move and attack range of unit { - #region Highlight nodes - // Highlight movable nodes in blue - LinkedList<Node> moveableNodes = GameFunction.getMovableNodes(graph, unit); - foreach (Node move in moveableNodes) - { - spriteBatch.Draw(moveableNode, move.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); - } + #region Highlight nodes + // Highlight movable nodes in blue + foreach (Node move in GameState.moveableNodes) + { + spriteBatch.Draw(moveableNode, move.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); + } - // Highlight attackable nodes in red - LinkedList<Node> attackableNodes = GameFunction.getAttackableNodes(graph, unit); - foreach (Node attack in attackableNodes) - { - if (!moveableNodes.Contains(attack)) + // Highlight attackable nodes in red + LinkedList<Node> attackableNodes = GameFunction.getAttackableNodes(graph, unit); + foreach (Node attack in attackableNodes) { - spriteBatch.Draw(attackableNode, attack.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); + if (!GameState.moveableNodes.Contains(attack)) + { + spriteBatch.Draw(attackableNode, attack.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); + } } - } #endregion } else // else if unit has already moved, only display the attack range @@ -274,7 +273,7 @@ namespace Controller LinkedList<Node> attackableNodes = GameFunction.getAttackRangeAfterMoving(graph, unit); foreach (Node attack in attackableNodes) { - spriteBatch.Draw(attackableNode, attack.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); + spriteBatch.Draw(attackableNode, attack.getPosition(), null, Color.White * 0.2f, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.9f); } } @@ -286,7 +285,7 @@ namespace Controller { if (button.getActive()) { - spriteBatch.Draw(button.getImage(), button.getPixelCoordinates(), null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0f); + spriteBatch.Draw(button.getImage(), button.getPixelCoordinates(), null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0f); } } } @@ -295,9 +294,9 @@ namespace Controller #region Character Info Screen player1 - if(GameState.currentPlayer == player1){ //if player 1, prints info screen for player 1 - - + //if player 1, prints info screen for player 1 + if (GameState.currentPlayer == player1) + { Vector2 statLocation = new Vector2(180, 533); //starting location for first stat Vector2 increment = new Vector2(0, 11.5f); //increment downwards for each stat Vector2 infoLocation = new Vector2(20, 513); @@ -311,7 +310,8 @@ namespace Controller spriteBatch.Draw(unit.getCharInfo(), infoLocation, null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.7f); //draw charInfoBackground texture } - else //else, info screen for player 2 + //else, info screen for player 2 + else { Vector2 statLocation = new Vector2(795, 533); //starting location for first stat Vector2 increment = new Vector2(0, 11.5f); //increment downwards for each stat @@ -324,12 +324,12 @@ namespace Controller } spriteBatch.DrawString(largeFont, unit.Hp.ToString(), new Vector2(861, 512), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.6f); //draws each stat spriteBatch.Draw(unit.getCharInfo(), infoLocation, null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.7f); //draw charInfoBackground texture - } - #endregion - } #endregion + } + #endregion + #region Player 2 Units // draws units for player 2 for (int i = 0; i < player2.getNumOfUnits(); i++) @@ -339,9 +339,9 @@ namespace Controller unit.getCurrentFrame(), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0.8f); } #endregion - + spriteBatch.Draw(backGround, Vector2.Zero, null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 1); - + break; } @@ -381,11 +381,10 @@ namespace Controller return null; } + // manually sets obstacle nodes for graph private void setObstacles(Graph graph) { - - // manually sets obstacle nodes for graph - for (int y=5; y<=14; y++) + for (int y = 5; y <= 14; y++) { graph.getNode(2, y).setIsObstacle(true); } @@ -404,12 +403,12 @@ namespace Controller graph.getNode(7, 4).setIsObstacle(true); graph.getNode(7, 14).setIsObstacle(true); graph.getNode(8, 4).setIsObstacle(true); - for (int y=14; y<=17; y++) + for (int y = 14; y <= 17; y++) { graph.getNode(8, y).setIsObstacle(true); } graph.getNode(9, 4).setIsObstacle(true); - for(int y=17; y<=20; y++) + for (int y = 17; y <= 20; y++) { graph.getNode(9, y).setIsObstacle(true); } diff --git a/src/Blaze-Brigade/Blaze_Brigade/GameFunction.cs b/src/Blaze-Brigade/Blaze_Brigade/GameFunction.cs index 2354718..8f3e720 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/GameFunction.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/GameFunction.cs @@ -24,7 +24,7 @@ namespace Controller foreach (Unit unit in player.getUnits()) { MenuButton[] menuButtons = unit.getMenuButtons(); - for (int i=0; i<menuButtons.Count(); i++) + for (int i = 0; i < menuButtons.Count(); i++) { menuButtons[i].setActive(true); } @@ -38,11 +38,12 @@ namespace Controller int range = unit.getEquippedWeapon().getRange(); // use this to determine all hitable squares return null; } - + // checks if specified unit can perform actions public static bool hasUnitFinishedActions(Unit unit) { - if (!unit.isButtonActive(MenuButtonType.Attack)) { + if (!unit.isButtonActive(MenuButtonType.Attack)) + { return true; } if (!unit.isButtonActive(MenuButtonType.Wait)) @@ -70,7 +71,7 @@ namespace Controller // TODO return false; } - + // checks if the node is attackable based on the unit's class and position // I THINK THIS WOULD WORK MUCH SIMPLER BASED ON EQUIPPED WEAPON NOT UNIT TYPE!! // IF logic < equippedweapon.range or IF logic < weapon with highest range. @@ -108,22 +109,24 @@ namespace Controller } // returns a list of nodes that the unit can move onto - public static LinkedList<Node> getMovableNodes(Graph graph, Unit unit) + public static LinkedList<Node> setMovableNodes(Graph graph, Unit unit) { LinkedList<Node> moveableNodes = new LinkedList<Node>(); Node currentNode = graph.getNode(unit.Position); // iterate through all nodes in the graph - for (int x=0; x<graph.getWidth(); x++) + for (int x = 0; x < graph.getWidth(); x++) { - for (int y=0; y<graph.getHeight(); y++) + for (int y = 0; y < graph.getHeight(); y++) { // if a path exists to that node, add it to list of moveable nodes - if (pathFinder(graph, unit, currentNode, graph.getNode(x, y)) != null && !graph.getNode(x, y).isOccupied()) { + if (pathFinder(graph, unit, currentNode, graph.getNode(x, y)) != null && !graph.getNode(x, y).isOccupied()) + { moveableNodes.AddLast(graph.getNode(x, y)); } } } + GameState.moveableNodes = moveableNodes; return moveableNodes; } @@ -131,10 +134,9 @@ namespace Controller public static LinkedList<Node> getAttackableNodes(Graph graph, Unit unit) { LinkedList<Node> attackableNodes = new LinkedList<Node>(); - LinkedList<Node> moveableNodes = getMovableNodes(graph, unit); // determine attackable nodes for each moveable node - foreach(Node moveableNode in moveableNodes) + foreach (Node moveableNode in GameState.moveableNodes) { // iterate through entire grid to determine attackable nodes for moveable node for (int x = 0; x < graph.getWidth(); x++) @@ -181,78 +183,79 @@ namespace Controller return null; } - int numOfMovementsLeft = unit.getMovability(); - int endNodeX = end.getPositionX(); - int endNodeY = end.getPositionY(); - bool nodeAdded = false; + // uses a variation of breadth-first search + Queue<LinkedList<Node>> paths = new Queue<LinkedList<Node>>(); // queue of paths - LinkedList<Node> path = new LinkedList<Node>(); - path.AddFirst(start); + // sets up queue of paths + LinkedList<Node> startPath = new LinkedList<Node>(); + startPath.AddFirst(start); // adds the starting node to starting path + paths.Enqueue(startPath); - while (numOfMovementsLeft > 0) + while (paths.Count != 0) // while queue isn't empty { - int currentNodeX = path.Last().getPositionX(); - int currentNodeY = path.Last().getPositionY(); - - // if it is already at the end node, break - if (currentNodeX == endNodeX && currentNodeY == endNodeY) - { - break; - } - - // check if left gets closer to end node - if (!nodeAdded && Math.Abs(currentNodeX - 1 - endNodeX) < Math.Abs(currentNodeX - endNodeX) - && !graph.getNode(currentNodeX - 1, currentNodeY).isOccupied() - && !graph.getNode(currentNodeX - 1, currentNodeY).getIsObstacle()) + LinkedList<Node> path = paths.Dequeue(); // get the first path from the queue + Node lastNode = path.Last(); // get the last node from the path + if (lastNode == end) // if last node is desired end node, return path { - path.AddLast(graph.getNode(currentNodeX - 1, currentNodeY)); - nodeAdded = true; + return path; } - - // check if right gets closer to end node - if (!nodeAdded && Math.Abs(currentNodeX + 1 - endNodeX) < Math.Abs(currentNodeX - endNodeX) - && !graph.getNode(currentNodeX + 1, currentNodeY).isOccupied() - && !graph.getNode(currentNodeX + 1, currentNodeY).getIsObstacle()) + if (path.Count <= unit.getMovability()) // only consider paths within the unit's movability { - path.AddLast(graph.getNode(currentNodeX + 1, currentNodeY)); - nodeAdded = true; + // add paths with adjacent nodes of last node to queue + foreach (Node adjacentNode in getAdjacentNodes(graph, lastNode)) + { + if (!path.Contains(adjacentNode)) // if adjacent node doesn't already exist in the path + { + LinkedList<Node> newPath = new LinkedList<Node>(); + // copies path to new path + foreach (Node node in path) + { + newPath.AddLast(node); + } + // add adjacent node to new path that will be enqueued + newPath.AddLast(adjacentNode); + paths.Enqueue(newPath); + } + } } + } + return null; + } - // check if up gets closer to end node - if (!nodeAdded && Math.Abs(currentNodeY + 1 - endNodeY) < Math.Abs(currentNodeY - endNodeY) - && !graph.getNode(currentNodeX, currentNodeY + 1).isOccupied() - && !graph.getNode(currentNodeX, currentNodeY + 1).getIsObstacle()) - { - path.AddLast(graph.getNode(currentNodeX, currentNodeY + 1)); - nodeAdded = true; - } + // returns movable adjacent nodes; used for BFS path finding + private static LinkedList<Node> getAdjacentNodes(Graph graph, Node node) + { + LinkedList<Node> adjacentNodes = new LinkedList<Node>(); + Node upNode = graph.getNode(node.getPositionX(), node.getPositionY() + 1); + Node downNode = graph.getNode(node.getPositionX(), node.getPositionY() - 1); + Node rightNode = graph.getNode(node.getPositionX() + 1, node.getPositionY()); + Node leftNode = graph.getNode(node.getPositionX() - 1, node.getPositionY()); - // check if down gets closer to end node - if (!nodeAdded && Math.Abs(currentNodeY - 1 - endNodeY) < Math.Abs(currentNodeY - endNodeY) - && !graph.getNode(currentNodeX, currentNodeY - 1).isOccupied() - && !graph.getNode(currentNodeX, currentNodeY - 1).getIsObstacle()) - { - path.AddLast(graph.getNode(currentNodeX, currentNodeY - 1)); - nodeAdded = true; - } + // up node + if (!upNode.isOccupied() && !upNode.getIsObstacle()) + { + adjacentNodes.AddLast(upNode); + } - // if no movement gets closer to the end node, there is no valid movement - if (!nodeAdded) - { - break; - } + // down node + if (!downNode.isOccupied() && !downNode.getIsObstacle()) + { + adjacentNodes.AddLast(downNode); + } - numOfMovementsLeft--; - nodeAdded = false; + // left node + if (!leftNode.isOccupied() && !leftNode.getIsObstacle()) + { + adjacentNodes.AddLast(leftNode); } - // if the node the algorithm ends on isn't the same as the end node, there is no valid path - if (!(path.Last() == end)) + // right node + if (!rightNode.isOccupied() && !rightNode.getIsObstacle()) { - return null; + adjacentNodes.AddLast(rightNode); } - return path; + return adjacentNodes; } public static void firstTurn() diff --git a/src/Blaze-Brigade/Blaze_Brigade/GameState.cs b/src/Blaze-Brigade/Blaze_Brigade/GameState.cs index 7269ac3..8b33313 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/GameState.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/GameState.cs @@ -10,13 +10,15 @@ namespace Model { public static readonly int SCREEN_HEIGHT = 640; public static readonly int SCREEN_WIDTH = 960; + public static bool playableUnitSelected { get; set; } //indicates playable selected unit public static Unit selectedUnit { get; set; } // indicates current selected unit public static Player currentPlayer { get; set; } // indicates player 1 public static Player enemyPlayer { get; set; } // indicates enemy player (player 2) public static bool dropDownMenuOpen { get; set; } // indicates whether drop down menu should be open public static bool beforeMove { get; set; } // true before unit moves, false after it moves. Used to determine what tiles are highlighted - public static bool isAnimating { get; set; } // indicates whether an animation sequence is on screen + public static bool isAnimating { get; set; } // indicates whether an animation sequence is on screen + public static LinkedList<Node> moveableNodes { get; set; } // holds moveable nodes that can be retreived without calling path finding } enum GameMenuState // enumerated list for different possible Game States diff --git a/src/Blaze-Brigade/Blaze_Brigade/Graph.cs b/src/Blaze-Brigade/Blaze_Brigade/Graph.cs index 1b433c0..125b3b1 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/Graph.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/Graph.cs @@ -66,5 +66,16 @@ namespace Model { return height; } + + public void setAllNodesUnmarked() + { + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + nodes[i, j].setIsMarked(false); + } + } + } } } diff --git a/src/Blaze-Brigade/Blaze_Brigade/MouseHandler.cs b/src/Blaze-Brigade/Blaze_Brigade/MouseHandler.cs index 66c3921..894b3c6 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/MouseHandler.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/MouseHandler.cs @@ -47,7 +47,6 @@ namespace Controller } Vector2 mouseClickCoordinates = new Vector2(currentMouseState.X, currentMouseState.Y); // mouse click coordinates - Node nodeClickedOn = graph.getNode(mouseClickCoordinates); #region Check if a unit is clicked @@ -82,6 +81,7 @@ namespace Controller turnState = TurnState.Wait; } } + // if player clicks when a unit is not selected ... else { @@ -90,6 +90,7 @@ namespace Controller if (unit != null) { setSelectedUnit(unit, true); + GameFunction.setMovableNodes(graph, unit); GameState.dropDownMenuOpen = (true); GameState.beforeMove = (true); } @@ -102,7 +103,6 @@ namespace Controller // sets selection of unit state inside GameFunction and the unit itself private static void setSelectedUnit(Unit unit, bool selected) { - GameState.playableUnitSelected = selected; GameState.selectedUnit = unit; } @@ -134,6 +134,8 @@ namespace Controller foreach (Node node in path) { animateUnitPosition(graph, unit, node); + unit.Position = (new Tuple<int, int>(node.getPositionX(), node.getPositionY())); + node.setUnitOnNode(unit); GameState.dropDownMenuOpen = (true); } @@ -147,7 +149,7 @@ namespace Controller { int nodePixelX = node.getPositionX() * 32; int nodePixelY = node.getPositionY() * 32; - + int pixelDifference = 4; graph.getNode(unit.Position).setUnitOnNode(null); while (unit.PixelCoordinates.X != nodePixelX) @@ -155,15 +157,14 @@ namespace Controller if (unit.PixelCoordinates.X < nodePixelX) { unit.animate(2); - - unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X + 4, unit.PixelCoordinates.Y); + unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X + pixelDifference, unit.PixelCoordinates.Y); Game.Instance.Tick(); Thread.Sleep(40); } else { unit.animate(1); - unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X - 4, unit.PixelCoordinates.Y); + unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X - pixelDifference, unit.PixelCoordinates.Y); Game.Instance.Tick(); Thread.Sleep(40); } @@ -174,20 +175,18 @@ namespace Controller if (unit.PixelCoordinates.Y < nodePixelY) { unit.animate(0); - unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X, unit.PixelCoordinates.Y + 4); + unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X, unit.PixelCoordinates.Y + pixelDifference); Game.Instance.Tick(); Thread.Sleep(40); } else { unit.animate(3); - unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X, unit.PixelCoordinates.Y - 4); + unit.PixelCoordinates = new Vector2(unit.PixelCoordinates.X, unit.PixelCoordinates.Y - pixelDifference); Game.Instance.Tick(); Thread.Sleep(40); } } - - node.setUnitOnNode(unit); } // returns a menu button that was clicked; if no menu button was clicked, returns null diff --git a/src/Blaze-Brigade/Blaze_Brigade/Node.cs b/src/Blaze-Brigade/Blaze_Brigade/Node.cs index c092b35..7925ac5 100644 --- a/src/Blaze-Brigade/Blaze_Brigade/Node.cs +++ b/src/Blaze-Brigade/Blaze_Brigade/Node.cs @@ -13,6 +13,7 @@ namespace Model private Unit unitOnNode; // holds the unit that is on the node private int positionX; // position x on the grid private int positionY; // position y on the grid + private bool isMarked; // used for BFS path finding public Node(int x, int y) { @@ -77,5 +78,15 @@ namespace Model } return false; } + + public void setIsMarked(bool b) + { + isMarked = b; + } + + public bool getIsMarked() + { + return isMarked; + } } } -- GitLab