private static int FindMinSteps(HashSet <char> lowers, Dictionary <char, Dictionary <char, StepsToPoint> > graph, int minSteps) { Stack <ToHere> attempts = new Stack <ToHere>(); Dictionary <long, int> bestToHere = new Dictionary <long, int>(); // bitmask of keys found a = 1, b = 2, c = 4, etc., plus current key point as (key << 32) ToHere start = new ToHere() { current = '@' }; attempts.Push(start); while (attempts.Count > 0) { var currentPos = attempts.Pop(); List <NextStepCandidate> possible = new List <NextStepCandidate>(); foreach (char destination in lowers) { //if not already visited destination and can get from source to destination with current set of keys if (((currentPos.keys & (1 << (destination - 'a'))) == 0) && (graph[currentPos.current][destination].doors & currentPos.keys) == graph[currentPos.current][destination].doors) { NextStepCandidate nextStep = new NextStepCandidate(); nextStep.val = destination; nextStep.steps = currentPos.steps + graph[currentPos.current][destination].steps; long state = currentPos.keys + (1L << (destination - 'a')) + (1L << ((destination - 'a') + 32)); //only add this as a candidate next step if the combination of collected keys and ending position take less steps to reach than previously found for that combination if (nextStep.steps < minSteps && (!bestToHere.ContainsKey(state) || bestToHere[state] > nextStep.steps)) { possible.Add(nextStep); } } } possible.Sort((NextStepCandidate x, NextStepCandidate y) => { return(x.steps.CompareTo(y.steps)); }); possible.Reverse(); //put the longest options on the stack first so that shortest possible are tried first; foreach (var nextStep in possible) { var destination = nextStep.val; var next = new ToHere(); next.current = destination; next.keys = currentPos.keys; next.keys += 1 << (destination - 'a'); next.steps = currentPos.steps; next.steps += graph[currentPos.current][destination].steps; if (next.keys == 67108863) //lowest 26 bits all 1 { minSteps = Math.Min(minSteps, next.steps); } else if (next.steps < minSteps) { long state = currentPos.keys + (1L << (destination - 'a')) + (1L << ((destination - 'a') + 32)); bestToHere[state] = next.steps; attempts.Push(next); } } } return(minSteps); }
/// <summary> /// Modified version of FindMinSteps to deal with 4 separate quadrants /// </summary> /// <returns></returns> private static int FindMinSteps2(HashSet <char> lowers, Dictionary <char, Dictionary <char, StepsToPoint> > graph, int minSteps) { Stack <ToHere2> attempts = new Stack <ToHere2>(); Dictionary <long, int> bestToHere = new Dictionary <long, int>(); // bitmask of keys found a = 1, b = 2, c = 4, etc., plus current key points as (key << 32) ToHere2 start = new ToHere2(); attempts.Push(start); while (attempts.Count > 0) { var currentPos = attempts.Pop(); List <NextStepCandidate> possible = new List <NextStepCandidate>(); foreach (char destination in lowers) { if ((currentPos.keys & (1 << (destination - 'a'))) == 0) //if not already visited destination { //find same quadrant character - if there's a non '@' character, assume that is the correct choice char quadChar = ' '; foreach (char possibleQ in currentPos.current) { if ((possibleQ != '@' || quadChar == ' ') && graph[possibleQ].ContainsKey(destination)) { quadChar = possibleQ; } } //if can get from source to destination with current set of keys if ((graph[quadChar][destination].doors & currentPos.keys) == graph[quadChar][destination].doors) { NextStepCandidate nextStep = new NextStepCandidate(); nextStep.val = destination; nextStep.steps = currentPos.steps + graph[quadChar][destination].steps; long state = currentPos.keys + (1L << (destination - 'a')) + (1L << ((destination - 'a') + 32)); //only add this as a candidate next step if the combination of collected keys and ending position take less steps to reach than previously found for that combination if (nextStep.steps < minSteps && (!bestToHere.ContainsKey(state) || bestToHere[state] > nextStep.steps)) { possible.Add(nextStep); } } } } possible.Sort((NextStepCandidate x, NextStepCandidate y) => { return(x.steps.CompareTo(y.steps)); }); possible.Reverse(); //put the longest options on the stack first so that shortest possible are tried first; foreach (var nextStep in possible) { var destination = nextStep.val; char quadChar = ' '; foreach (char possibleQ in currentPos.current) { if ((possibleQ != '@' || quadChar == ' ') && graph[possibleQ].ContainsKey(destination)) { quadChar = possibleQ; } } var next = new ToHere2(); next.current = new List <char>(currentPos.current); if (quadChar == '@') { if (next.current.Count == 4) { next.current.Remove(quadChar); //only remove @ once all 4 quadrants have been accessed } next.current.Add(destination); } else { next.current.Remove(quadChar); next.current.Add(destination); } next.keys = currentPos.keys; next.keys += 1 << (destination - 'a'); next.steps = currentPos.steps; next.steps += graph[quadChar][destination].steps; if (next.keys == 67108863) //lowest 26 bits all 1 { minSteps = Math.Min(minSteps, next.steps); } else if (next.steps < minSteps) { long state = currentPos.keys + (1L << (destination - 'a')) + (1L << ((destination - 'a') + 32)); bestToHere[state] = next.steps; attempts.Push(next); } } } return(minSteps); }
void ArrangeShipGroupsOnSquarePlane() { Vector2 footprint = Vector2.zero; List <int> addedGroupIDs = new List <int>(); List <AttachmentPoint> attachmentPoints = new List <AttachmentPoint>(); attachmentPoints.Add(new AttachmentPoint(Vector2.zero, new Vector2[] { Vector2.one *Mathf.Infinity, Vector2.one *Mathf.Infinity, Vector2.one *Mathf.Infinity, Vector2.one *Mathf.Infinity })); NextStepCandidate bestCandidate = new NextStepCandidate(0, Vector2.zero, false, Vector2.one, Mathf.Infinity); for (int i = 0; i < groups.Length; i++) { //DETERMINE BEST CANDIDATE STEP for (int candidateGroupID = 0; candidateGroupID < groups.Length; candidateGroupID++) { if (!addedGroupIDs.Contains(candidateGroupID)) { for (int verticalIndex = 0; verticalIndex < 2; verticalIndex++) { //CALCULATE CORNER POSITIONS Vector2[] groupCorners = verticalIndex == 1 ? groups[candidateGroupID].verticalCorners : groups[candidateGroupID].horizontalCorners; //CALCULATE FOOTPRINT Vector2 size = groupCorners[2] - groupCorners[1]; //TRY ALL POSITIONING OPTIONS foreach (AttachmentPoint attachmentPoint in attachmentPoints) { for (int examinedCorner = 0; examinedCorner < 4; examinedCorner++) { Vector2 sizeLimitation = attachmentPoint.quadrantSizeLimits[examinedCorner]; if (size.x <= sizeLimitation.x && size.y <= sizeLimitation.y) { NextStepCandidate newCandidate; newCandidate.groupID = candidateGroupID; newCandidate.position = attachmentPoint.position - groupCorners[examinedCorner]; newCandidate.vertical = verticalIndex == 1; Vector4 boundaries = Vector4.zero; for (int newCandidateCornerID = 0; newCandidateCornerID < groupCorners.Length; newCandidateCornerID++) { boundaries = PushBoundaries(boundaries, newCandidate.position + groupCorners[newCandidateCornerID]); } foreach (int addedGroupID in addedGroupIDs) { Vector2[] addedGroupCorners = groups[addedGroupID].Corners; for (int cornerID = 0; cornerID < 4; cornerID++) { boundaries = PushBoundaries(boundaries, groups[addedGroupID].rect.position + addedGroupCorners[cornerID]); } } newCandidate.wholeFootprint = new Vector2(Mathf.Abs(boundaries.x - boundaries.z), Mathf.Abs(boundaries.y - boundaries.w)); newCandidate.balance = newCandidate.wholeFootprint.x / newCandidate.wholeFootprint.y; if (newCandidate.wholeFootprint.x < 0.9f * plateSize && newCandidate.wholeFootprint.y < 0.9f * plateSize) { if (Mathf.Abs(1 - newCandidate.balance) < Mathf.Abs(1 - bestCandidate.balance)) { bestCandidate = newCandidate; } } } } } } } } //APPLY BEST STEP addedGroupIDs.Add(bestCandidate.groupID); ShipRectangleGroup positionedGroup = groups[bestCandidate.groupID]; positionedGroup.rect.position = bestCandidate.position; positionedGroup.vertical = bestCandidate.vertical; groups[bestCandidate.groupID] = positionedGroup; footprint = bestCandidate.wholeFootprint; // Debug.Log("Cycle: " + i); // Debug.Log("Group ID: " + bestCandidate.groupID); // Debug.Log("Position: " + positionedGroup.rect.position); // Debug.Log("Vertical: " + positionedGroup.vertical); bestCandidate = new NextStepCandidate(0, Vector2.zero, false, Vector2.one, Mathf.Infinity); //RECALCULATE ATTACHMENT POINTS attachmentPoints = new List <AttachmentPoint>(); foreach (int groupID in addedGroupIDs) { ShipRectangleGroup managedGroup = groups[groupID]; for (int cornerIndex = 0; cornerIndex < 4; cornerIndex++) { AttachmentPoint potentialAttachmentPoint = new AttachmentPoint(Vector2.zero, new Vector2[4]); potentialAttachmentPoint.position = managedGroup.rect.position + managedGroup.Corners[cornerIndex]; Vector2[] calculatedQuadrants = new Vector2[4]; for (int quadrantID = 0; quadrantID < 4; quadrantID++) { Vector2 quadrantDirectional = new Vector2((quadrantID == 0 || quadrantID == 1) ? 1 : -1, (quadrantID == 1 || quadrantID == 3) ? 1 : -1); Vector2 size = Vector2.one * Mathf.Infinity; foreach (int potentialIntersectorIndex in addedGroupIDs) { ShipRectangleGroup potentialIntersector = groups[potentialIntersectorIndex]; for (int intersectorCornerIndex = 0; intersectorCornerIndex < 4; intersectorCornerIndex++) { Vector2 cornerGlobalPosition = potentialIntersector.rect.position + potentialIntersector.Corners[intersectorCornerIndex]; Vector2 cornerPositionRelativeToAttachmentPoint = cornerGlobalPosition - potentialAttachmentPoint.position; Vector2 cornerNormalizedQuadrantPosition = Vector2.Scale(cornerPositionRelativeToAttachmentPoint, quadrantDirectional); if (cornerNormalizedQuadrantPosition.x >= -0.000015f && cornerNormalizedQuadrantPosition.y >= -0.000015f && cornerNormalizedQuadrantPosition.x <= size.x && cornerNormalizedQuadrantPosition.y <= size.y) { Vector2 oppositeCornerNormalizedQuadrantPosition = Vector2.Scale(potentialIntersector.rect.position - potentialIntersector.Corners[intersectorCornerIndex] - potentialAttachmentPoint.position, quadrantDirectional); Vector2 sides = oppositeCornerNormalizedQuadrantPosition - cornerNormalizedQuadrantPosition; sides = sides - Vector2.Scale(new Vector2(Mathf.Clamp(-oppositeCornerNormalizedQuadrantPosition.x, 0, Mathf.Abs(sides.x)), Mathf.Clamp(-oppositeCornerNormalizedQuadrantPosition.y, 0, Mathf.Abs(sides.y))), new Vector2(Mathf.Sign(sides.x), Mathf.Sign(sides.y))); if (sides.y != 0 && sides.x != 0) { if (sides.x > 0) { size.x = cornerNormalizedQuadrantPosition.x < size.x ? cornerNormalizedQuadrantPosition.x : size.x; } else { size.y = cornerNormalizedQuadrantPosition.y < size.y ? cornerNormalizedQuadrantPosition.y : size.y; } } } } } //TEST // Debug.Log("Cycle: " + i); // Debug.Log("Group: " + groupID + " Corner: " + cornerIndex + " Quadrant: " + quadrantID); // Debug.Log("Size: " + size); //TEST // if (groupID == 3 && cornerIndex == 2 && quadrantID == 2) // { // Debug.Log("Conflicting Quadrant Size: " + size); // } calculatedQuadrants[quadrantID] = size; } potentialAttachmentPoint.quadrantSizeLimits = calculatedQuadrants; attachmentPoints.Add(potentialAttachmentPoint); } } } //NORMALIZE RECTANGLE POSITIONS Vector2 topRightCorner = Vector2.one * Mathf.NegativeInfinity; Vector2 bottomLeftCorner = Vector2.one * Mathf.Infinity; for (int i = 0; i < groups.Length; i++) { Vector2[] corners = groups[i].Corners; Vector2 groupTopRightCorner = corners[2] + groups[i].rect.position; Vector2 groupBottomLeftCorner = corners[1] + groups[i].rect.position; topRightCorner.x = groupTopRightCorner.x > topRightCorner.x ? groupTopRightCorner.x : topRightCorner.x; topRightCorner.y = groupTopRightCorner.y > topRightCorner.y ? groupTopRightCorner.y : topRightCorner.y; bottomLeftCorner.x = groupBottomLeftCorner.x < bottomLeftCorner.x ? groupBottomLeftCorner.x : bottomLeftCorner.x; bottomLeftCorner.y = groupBottomLeftCorner.y < bottomLeftCorner.y ? groupBottomLeftCorner.y : bottomLeftCorner.y; } Vector2 positionAdjustment = (bottomLeftCorner + topRightCorner) / 2.0f; for (int i = 0; i < groups.Length; i++) { groups[i].rect.position -= positionAdjustment; } //POSITION SHIPS ON RECTANGLE GROUPS for (int groupIndex = 0; groupIndex < groups.Length; groupIndex++) { ShipRectangleGroup group = groups[groupIndex]; float shipSpacing = group.rect.width / group.ships.Length * 0.8f; float reservedSpace = shipSpacing * (group.ships.Length - 1); Vector3 startingPosition = new Vector3(group.rect.x, 0, group.rect.y) + (group.vertical ? Vector3.forward : Vector3.right) * (reservedSpace / 2.0f); Vector3 positionStep = (group.vertical ? Vector3.back : Vector3.left) * shipSpacing; for (int shipIndex = 0; shipIndex < group.ships.Length; shipIndex++) { Ship ship = group.ships[shipIndex]; ship.transform.localPosition = startingPosition + positionStep * shipIndex; ship.transform.localRotation = new Quaternion(0, 1, 0, group.vertical ? 1 : 0); ship.placementInfo.localShipboxPosition = ship.transform.localPosition; ship.placementInfo.localShipboxRotation = ship.transform.localRotation; } // GameObject tmp = GameObject.CreatePrimitive(PrimitiveType.Cube); // tmp.transform.localScale = new Vector3(group.vertical ? group.rect.height : group.rect.width, 0.1f, group.vertical ? group.rect.width : group.rect.height); // tmp.transform.position = new Vector3(group.rect.x, 0, group.rect.y); } }