/// <summary> /// Sets all the tiles in a specified rectangle on this grid to the specified type. /// Note that lowerLeft and upperRight MUST have this relationship /// (for example, upperRight.y cannot be lower than lowerLeft.y), /// or I'm going to crash your program. /// If prioritize is false, /// this function will only mark tiles which are currently empty. /// Also, this function technically can mark single lines, /// but it's recommended that you use setTypeLine() instead for that. /// </summary> /// <param name="lowerLeft">Lower leftmost point of the desired rectangle</param> /// <param name="upperRight">Upper rightmost point of the desired rectangle</param> /// <param name="type">Tile type to set this rectangle to</param> /// <param name="prioritize">Whether this action should override existing non-empty tiles</param> public void setTypeRect(Coord2DObject lowerLeft, Coord2DObject upperRight, TileObject.TileType type, bool prioritize) { assertBounds(lowerLeft); assertBounds(upperRight); Assert.IsTrue(lowerLeft.x <= upperRight.x && lowerLeft.y <= upperRight.y, "Invalid argument order. " + "lowerLeft should be " + upperRight.ToString() + "and upperRight should be " + lowerLeft.ToString()); // Just setTypeLine if they gave us a line instead of a rectangle if (lowerLeft.x == upperRight.x || lowerLeft.y == upperRight.y) { setTypeLine(lowerLeft, upperRight, type, prioritize); return; } // If we're here, then we're marking a non-line rectangle, // and the arguments were provided in correct order for (int thisY = lowerLeft.y; thisY <= upperRight.y; thisY++) { // Go row by row Coord2DObject thisRowLeft = new Coord2DObject(lowerLeft.x, thisY); Coord2DObject thisRowRight = new Coord2DObject(upperRight.x, thisY); setTypeLine(thisRowLeft, thisRowRight, type, prioritize); } }
//public override bool Equals(object obj) { // if (!this.GetType().IsAssignableFrom(obj.GetType())) // return false; // TileObject otherTile = (TileObject)obj; // return type == otherTile.type // && distance == otherTile.distance // && location.Equals(otherTile.location); //} //public override int GetHashCode() { // //if (location != null) // // return location.GetHashCode() + (distance % 29); // //else // // return type.GetHashCode() + (distance % 17); // return ToString().GetHashCode(); //} private void init(TileType t, int dist, TileObject prev, Coord2DObject location) { this.type = t; this.distance = dist; this.prev = prev; this.location = location; }
/// <summary> /// Add a joint to this path at the specified index. /// Adjacent points are required to be compatible, /// i.e. they lie on the same line as their adjacent joint. /// If you try to use this function and add a joint which isn't compatible with its neighbors, /// this function returns false and does NOT add the joint. /// Else, it adds the joint to this path and returns true. /// Don't worry if this Path isn't yet complete (i.e. doesn't have 2 or more joints); /// you can add joints with this method and still return true safely. /// </summary> /// <param name="newJoint">Joint to be added</param> /// <param name="index">Index in this list of joints</param> /// <returns>True if this joint was compatible and added successfully, false otherwise</returns> public bool addJoint(Coord2DObject newJoint, int index) { joints.Insert(index, newJoint); // Check compatibility with joint before, if it exists if (index > 0) { Coord2DObject leftNeighbor = joints[index - 1]; if (!areCompatibleJoints(leftNeighbor, newJoint)) { joints.RemoveAt(index); return(false); } } // Check compatibility with joint after, if it exists if (index < joints.Count - 1) { Coord2DObject rightNeighbor = joints[index + 1]; if (!areCompatibleJoints(newJoint, rightNeighbor)) { joints.RemoveAt(index); return(false); } } return(true); }
/// <summary> /// Sets all the tiles in a specified line on this grid to the specified type. /// Note that point1 and point2 MUST lie on the same line /// (i.e. either their x coordinates are equal or their y coordinates are equal), /// or I'm going to crash your program. /// If prioritize is false, /// this function will only mark tiles which are currently empty. /// </summary> /// <param name="point1">An endpoint</param> /// <param name="point2">Another endpoint</param> /// <param name="type">Tile type to set this line to</param> /// <param name="prioritize">Whether this method should override existing non-empty tiles</param> public void setTypeLine(Coord2DObject point1, Coord2DObject point2, TileObject.TileType type, bool prioritize) { assertBounds(point1); assertBounds(point2); Assert.raiseExceptions = true; Assert.IsTrue(point1.x == point2.x || point1.y == point2.y, "point1 " + point1.ToString() + " and point2 " + point2.ToString() + " must lie on a straight line"); if (point1.Equals(point2) && (prioritize || getTile(point1).type == TileObject.TileType.EMPTY)) { getTile(point1).type = type; return; } // If on the same row if (point1.y == point2.y) { // Iterate from least x to greater x, // whichever is which int biggerX = (point1.x > point2.x ? point1.x : point2.x); int smallerX = (point1.x < point2.x ? point1.x : point2.x); for (int i = smallerX; i <= biggerX; i++) { TileObject thisTile = getTile(new Coord2DObject(i, point1.y)); if (thisTile == null) { Debug.Log("i is " + i + ", point1.y is " + point1.y); } if (prioritize || thisTile.type == TileObject.TileType.EMPTY) { thisTile.type = type; } } } // Else, they're on the same column else { // Iterate from least y to greatest y, // whichever is which int biggerY = (point1.y > point2.y ? point1.y : point2.y); int smallerY = (point1.y < point2.y ? point1.y : point2.y); for (int i = smallerY; i <= biggerY; i++) { TileObject thisTile = getTile(new Coord2DObject(point1.x, i)); if (prioritize || thisTile.type == TileObject.TileType.EMPTY) { thisTile.type = type; } } } }
public PathObject(Grid2DObject grid, Coord2DObject point1, Coord2DObject point2, int thickness) { this.grid = grid; joints = new List <Coord2DObject>(); this.thickness = thickness; populateBestPath(point1, point2); }
public bool checkBounds(Coord2DObject location) { if (location.x < 0 || location.y < 0) { return(false); } return(location.x < dimensions.x && location.y < dimensions.y); }
public TileObject getRight(Coord2DObject fromHere) { if (!canGoRight(fromHere)) { return(null); } return(getTile(new Coord2DObject(fromHere.x + 1, fromHere.y))); }
public TileObject getDown(Coord2DObject fromHere) { if (!canGoDown(fromHere)) { return(null); } return(getTile(new Coord2DObject(fromHere.x, fromHere.y - 1))); }
public TileObject getUp(Coord2DObject fromHere) { if (!canGoUp(fromHere)) { return(null); } return(getTile(new Coord2DObject(fromHere.x, fromHere.y + 1))); }
/// <summary> /// Sets all the tiles in a specified line on this grid to the specified type. /// This overload adds the "thickness" parameter. /// A single straight line of Tiles is considered a row with layers = 0 (total thickness 1). /// layers = 1 surrounds the line on each side with additional rows, for a total thickness of 3. /// All other parameters remain the same as the other overload for this method, /// including that part about me crashing your program /// if point1 and point2 aren't on the same line. /// </summary> /// <param name="point1">An endpoint</param> /// <param name="point2">Another endpoint</param> /// <param name="type">Tile type to set this line to</param> /// <param name="layers">Number of layers on each side of this line</param> /// <param name="prioritize">Whether this method should override existing non-empty tiles</param> public void setTypeLine(Coord2DObject point1, Coord2DObject point2, TileObject.TileType type, int layers, bool prioritize) { for (int thisLayer = 0; thisLayer <= layers; thisLayer++) { // Rows (horizontal) if (point1.y == point2.y) { // Do row on top, offset by thisLevel Coord2DObject point1Layered = new Coord2DObject(point1.x, point1.y + thisLayer); Coord2DObject point2Layered = new Coord2DObject(point2.x, point2.y + thisLayer); if (checkBounds(point1Layered) && checkBounds(point2Layered)) { setTypeLine(point1Layered, point2Layered, type, prioritize); } // Do row on bot, offset by thisLevel point1Layered = new Coord2DObject(point1.x, point1.y - thisLayer); point2Layered = new Coord2DObject(point2.x, point2.y - thisLayer); if (checkBounds(point1Layered) && checkBounds(point2Layered)) { setTypeLine(point1Layered, point2Layered, type, prioritize); } } // Columns (vertical) else if (point1.x == point2.x) { // Do column on left, offset by thisLevel Coord2DObject point1Layered = new Coord2DObject(point1.x - thisLayer, point1.y); Coord2DObject point2Layered = new Coord2DObject(point2.x - thisLayer, point2.y); if (checkBounds(point1Layered) && checkBounds(point2Layered)) { setTypeLine(point1Layered, point2Layered, type, prioritize); } // Do column on right, offset by thisLevel point1Layered = new Coord2DObject(point1.x + thisLayer, point1.y); point2Layered = new Coord2DObject(point2.x + thisLayer, point2.y); if (checkBounds(point1Layered) && checkBounds(point2Layered)) { setTypeLine(point1Layered, point2Layered, type, prioritize); } } else { Assert.IsTrue(false, "point1" + point1.ToString() + " and point2 " + point2.ToString() + " not on a line"); } } }
public override bool Equals(object other) { if (!this.GetType().IsAssignableFrom(other.GetType())) { return(false); } Coord2DObject otherCoord = (Coord2DObject)other; return(x == otherCoord.x && y == otherCoord.y); }
public Grid2DObject(Coord2DObject dimensions) { this.dimensions = new Coord2DObject(dimensions); grid = new TileObject[dimensions.x, dimensions.y]; for (int i = 0; i < dimensions.x; i++) { for (int j = 0; j < dimensions.y; j++) { setTile(TileObject.TileType.EMPTY, new Coord2DObject(i, j)); } } }
/// <summary> /// Constructs a new Path (with the desired thickness) between each adjacent landmark given, /// and returns that series of Path objects. /// </summary> /// <param name="landmarks">Landmarks to be connected. Note that these don't necessarily have to be on the same line.</param> /// <param name="thickness">Thickness of the Path objects to be created.</param> /// <returns></returns> private List <PathObject> getFullPath(List <Coord2DObject> landmarks, int thickness) { List <PathObject> paths = new List <PathObject>(landmarks.Count); for (int i = 0; i < landmarks.Count - 1; i++) { Coord2DObject landmark1 = landmarks[i]; Coord2DObject landmark2 = landmarks[i + 1]; PathObject p = new PathObject(this, landmark1, landmark2, thickness); paths.Add(p); } return(paths); }
/// <summary> /// Get the set of tiles immediately adjacent (up, down, left, right) to the specified coordinate. /// Note that in cases where the given location is on an edge or corner, /// the returned set will a size less than the usual four. /// Also note that this only returns neighbors which aren't non-traversable, /// so the returned tiles may be of type EMPTY or TRAVERSABLE. /// </summary> /// <param name="location">Location of tile whose neighbors should be returned</param> /// <returns>HashSet of TileObject references to this location's eligible neighbors</returns> public HashSet <TileObject> getTraversableNeighbors(Coord2DObject location) { assertBounds(location); HashSet <TileObject> neighbors = new HashSet <TileObject>(); if (canGoUp(location)) { TileObject upNeighbor = getUp(location); if (upNeighbor.type != TileObject.TileType.NON_TRAVERSABLE) { neighbors.Add(upNeighbor); } } if (canGoDown(location)) { TileObject downNeighbor = getDown(location); if (downNeighbor.type != TileObject.TileType.NON_TRAVERSABLE) { neighbors.Add(downNeighbor); } } if (canGoLeft(location)) { TileObject leftNeighbor = getLeft(location); if (leftNeighbor.type != TileObject.TileType.NON_TRAVERSABLE) { neighbors.Add(leftNeighbor); } } if (canGoRight(location)) { TileObject rightNeighbor = getRight(location); if (rightNeighbor.type != TileObject.TileType.NON_TRAVERSABLE) { neighbors.Add(rightNeighbor); } } return(neighbors); }
public Grid2DObject(Grid2DObject other) { this.dimensions = new Coord2DObject(other.dimensions); grid = new TileObject[dimensions.x, dimensions.y]; for (int i = 0; i < dimensions.x; i++) { for (int j = 0; j < dimensions.y; j++) { Coord2DObject thisCoord2D = new Coord2DObject(i, j); TileObject thisOtherTile = other.getTile(thisCoord2D); setTile(thisOtherTile.type, thisCoord2D); } } }
/// <summary> /// Sets types for bases, which are BASE_WIDTH x BASE_WIDTH corners in super.grid. /// Also initializes p1UpRight and p2LowLeft, which specify these rectangles. /// (I know it's bad practice to make those coordinates member variables instead of local variables, /// but I don't feel like passing them through every function that needs them /// and dude it's like 3AM rn) /// </summary> private void drawBases() { int gridX = base.dimensions.x; int gridY = base.dimensions.y; Coord2DObject p1LowLeft = new Coord2DObject(0, 0); p1UpRight = new Coord2DObject(p1LowLeft.x + BASE_WIDTH, p1LowLeft.y + BASE_WIDTH); Coord2DObject p2UpRight = new Coord2DObject(gridX - 1, gridY - 1); p2LowLeft = new Coord2DObject(p2UpRight.x - BASE_WIDTH + 1, p2UpRight.y - BASE_WIDTH + 1); base.setTypeRect(p1LowLeft, p1UpRight, TileObject.TileType.TRAVERSABLE, true); base.setTypeRect(p2LowLeft, p2UpRight, TileObject.TileType.TRAVERSABLE, true); }
/// <summary> /// Sets the type of tiles on this path. /// Should be used to either set a path as traversable, /// or to "erase" by setting them to empty (or something else!). /// Note that you must first set this object's path by giving it 2 or more joints, /// otherwise you're trying to draw an ill-defined path /// and I'm going to abort your program. /// </summary> /// <param name="type">Type that all tiles on this path should be set to</param> /// <param name="prioritize">True if should override non-empty tiles, false otherwise</param> public void setPathType(TileObject.TileType type, bool prioritize) { Assert.raiseExceptions = true; Assert.IsTrue(joints.Count >= 2, "Not enough joints in this path, here are all joints: " + joints.ToString()); Coord2DObject firstJoint = joints[0]; IEnumerator <Coord2DObject> e = joints.GetEnumerator(); while (e.MoveNext()) { Coord2DObject secondJoint = e.Current; grid.setTypeLine(firstJoint, secondJoint, type, thickness, prioritize); // Slide first joint // (second joint is slid using the while-loop condition firstJoint = secondJoint; } }
/// <summary> /// Returns a list of random Coord2DObjects of specified size. /// As long as p1UpRight and p2LowLeft are initialized, /// you are guaranteed that this list will have no duplicate values, /// and will also not contain the values p1UpRight or p2LowLeft. /// Additionally, none of these points shall fall within the bases. /// </summary> /// <param name="amount">Number of random points to generate</param> /// <returns>List of distinct Coord2DObject's</returns> private List <Coord2DObject> getDistinctRandomPoints(int amount) { HashSet <Coord2DObject> pointsSet = new HashSet <Coord2DObject>(); // Use a while loop instead of a for loop // because there's a small chance // that we could accidentally generate duplicate Coord2D's while (pointsSet.Count < amount) { Coord2DObject randCoord = getRandomNonBase(); // These two will populate pointsSet later, // so check for duplicates now if (!randCoord.Equals(p1UpRight) && !randCoord.Equals(p2LowLeft)) { pointsSet.Add(randCoord); } } // As far as this function is concerned, // order does not matter, // so we can clumsily return a list from our set return(new List <Coord2DObject>(pointsSet)); }
public void setTile(TileObject.TileType type, Coord2DObject location) { assertBounds(location); grid[location.x, location.y] = new TileObject(type, new Coord2DObject(location)); }
/// <summary> /// Checks if two coordinates are eligible to be a straight path. /// If they are diagonal relative to each other, /// this will return false. /// Otherwise, if they're on the same horizontal row or vertical column, /// this will return true. /// </summary> /// <param name="joint1">an endpoint</param> /// <param name="joint2">another endpoint</param> /// <returns>True if the points are compatible, false otherwise</returns> public bool areCompatibleJoints(Coord2DObject joint1, Coord2DObject joint2) { return(joint1.x == joint2.x || joint1.y == joint2.y); }
/// <summary> /// Populates this Path's "joints" list with the best path between point1 and point2. /// This is accomplished by implementing Dijkstra's algorithm. /// This path's grid and joints MUST be initialized before this method is called. /// Algorithm adopted from the pseudocode found here: /// https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Pseudocode /// </summary> /// <param name="src">The coordinate that the path should start from</param> /// <param name="dest">The coordinate that the path should start from</param> private void populateBestPath(Coord2DObject src, Coord2DObject dest) { Grid2DObject tempGrid = new Grid2DObject(grid); // generate copy, maintaining tile types TileObject srcTile = tempGrid.getTile(src); TileObject destTile = tempGrid.getTile(dest); srcTile.distance = 0; // Do some error checking Assert.raiseExceptions = true; Assert.IsTrue(!src.Equals(dest), "Attempted autopath to the same tile " + src.ToString()); Assert.IsTrue(srcTile.type != TileObject.TileType.NON_TRAVERSABLE, "Path attempted on srcTile non-traversable tile " + srcTile.ToString() + " to destTile " + destTile.ToString()); Assert.IsTrue(destTile.type != TileObject.TileType.NON_TRAVERSABLE, "Path attempted on destTile non-traversable tile " + destTile.ToString() + " from srcTile " + srcTile.ToString()); // Populate set Q HashSet <TileObject> setQ = new HashSet <TileObject>(); foreach (TileObject t in tempGrid) { if (t.type != TileObject.TileType.NON_TRAVERSABLE) { setQ.Add(t); } } List <TileObject> listQ = new List <TileObject>(setQ); shuffleList <TileObject>(listQ); TileObject uTile = null; while (listQ.Count > 0) { // Get tile with minimum distance from setQ int runningMin = System.Int32.MaxValue; foreach (TileObject t in listQ) { if (t.distance < runningMin) { runningMin = t.distance; uTile = t; } } // Make sure uTile is properly set, // then remove it from setQ Assert.IsTrue(uTile != null, "Minimum distance tile uTile not properly set"); Assert.IsTrue(listQ.Contains(uTile), "setQ doesn't contain uTile " + uTile.ToString()); listQ.Remove(uTile); // Break out if we've reached the destination, // we now need to construct the path via reverse iteration if (uTile == destTile) // check for identity, not just equivalence { break; } // Update distances of all uTile's current neighbors HashSet <TileObject> uNeighbors = tempGrid.getTraversableNeighbors(uTile.location); foreach (TileObject thisNeighbor in uNeighbors) { int currentDist = uTile.distance + 1; if (currentDist < thisNeighbor.distance) { thisNeighbor.distance = currentDist; thisNeighbor.prev = uTile; } } } // Ensure that uTile is actually usable Assert.IsTrue(uTile.prev != null || uTile == srcTile, "Condition specified by Dijkstra's not met"); // Populate joints by backtracing while (uTile != null) { joints.Add(uTile.location); uTile = uTile.prev; // Make sure if we're about to break out, // that we have enough joints to do so // (i.e. that we have at least 2 joints) Assert.IsTrue(!(uTile == null && joints.Count < 2), "Not enough prev's? For sure not enough joints\n" + "Perhaps src and dest are the same?\n" + "src: " + srcTile.ToString() + '\n' + "dest: " + destTile.ToString() + '\n' + "src.equals(dest)? " + src.Equals(dest)); } }
public bool canGoDown(Coord2DObject location) { return(checkBounds(new Coord2DObject(location.x, location.y - 1))); }
public bool canGoRight(Coord2DObject location) { return(checkBounds(new Coord2DObject(location.x + 1, location.y))); }
public TileObject(TileType type, Coord2DObject location) { init(type, System.Int32.MaxValue, null, location); }
public GameGrid2DObject(Coord2DObject dimensions, int thickness, int landmarks, int baseWidth) : base(dimensions) { init(thickness, landmarks, baseWidth); }
public char getChar(Coord2DObject location) { return(getTile(location).getChar()); }
public Coord2DObject(Coord2DObject other) { this.x = other.x; this.y = other.y; }
public TileObject getTile(Coord2DObject location) { return(grid[location.x, location.y]); }
public void assertBounds(Coord2DObject location) { Assert.raiseExceptions = true; Assert.IsTrue(checkBounds(location), "Invalid coordinate " + location.ToString()); }