// Create a single object associated with the leg from courseControlId1 to courseControlId2. Does not consider // flagging (but does consider bends and gaps.) Used for highlighting on the map. public static CourseObj CreateSimpleLeg(EventDB eventDB, float scaleRatio, CourseAppearance appearance, Id<CourseControl> courseControlId1, Id<CourseControl> courseControlId2) { Id<ControlPoint> controlId1 = eventDB.GetCourseControl(courseControlId1).control; Id<ControlPoint> controlId2 = eventDB.GetCourseControl(courseControlId2).control; ControlPoint control1 = eventDB.GetControl(controlId1); ControlPoint control2 = eventDB.GetControl(controlId2); LegGap[] gaps; SymPath legPath = GetLegPath(eventDB, control1.location, control1.kind, controlId1, control2.location, control2.kind, controlId2, scaleRatio, appearance, out gaps); if (legPath == null) return null; return new LegCourseObj(controlId1, courseControlId1, courseControlId2, scaleRatio, appearance, legPath, gaps); }
// If a course control is the beginning of a variation split, return all course controls assigned to that variation split. // Otherwise, return just that course control. public static IEnumerable<Id<CourseControl>> AllVariationsOfCourseControl(EventDB eventDB, Id<CourseControl> courseControlId) { CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (courseControl.split) { return courseControl.splitCourseControls; } else { return new[] { courseControlId }; } }
// Get the text for a control lable private static string GetControlLabelText(EventDB eventDB, ControlLabelKind labelKind, CourseView.ControlView controlView, CourseView courseView) { string text = ""; List<CourseView.ControlView> repeatedControlViews = FindControlViewsWithControlId(courseView, controlView.controlId); if (repeatedControlViews.Count > 1 && repeatedControlViews[0] != controlView) { // This control is repeated (e.g., like a butterfly course) and is not the first use of this control. Don't put any text. return ""; } if (labelKind == ControlLabelKind.Sequence || labelKind == ControlLabelKind.SequenceAndCode || labelKind == ControlLabelKind.SequenceAndScore) { text += controlView.ordinal.ToString(); // Add in numbers for repeated controls. for (int i = 1; i < repeatedControlViews.Count; ++i) { text += "/"; text += repeatedControlViews[i].ordinal.ToString(); } } if (labelKind == ControlLabelKind.SequenceAndCode) text += "-"; if (labelKind == ControlLabelKind.SequenceAndCode || labelKind == ControlLabelKind.Code || labelKind == ControlLabelKind.CodeAndScore) { ControlPoint control = eventDB.GetControl(controlView.controlId); text += control.code; } if (labelKind == ControlLabelKind.CodeAndScore || labelKind == ControlLabelKind.SequenceAndScore) { int points = eventDB.GetCourseControl(controlView.courseControlIds[0]).points; if (points > 0) { text += "(" + points.ToString() + ")"; } } return text; }
// Create the view of all variations of a course with variations. Cannot be a single part of a multi-part course. // Does not contain ordinals. private static CourseView CreateAllVariationsCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); if (!courseDesignator.AllParts) throw new ApplicationException("Cannot create all variations of a single part"); CourseView courseView = new CourseView(eventDB, courseDesignator); courseView.courseName = course.name; courseView.scoreColumn = -1; // To get the ordinals correct, we get the course control ids for all parts. List<Id<CourseControl>> courseControls = QueryEvent.EnumCourseControlIds(eventDB, courseDesignator).ToList(); for (int index = 0; index < courseControls.Count; ++index) { Id<CourseControl> courseControlId = courseControls[index]; CourseControl courseControl = eventDB.GetCourseControl(courseControlId); // We add each split control only once, even though it has multiple variations. Check to see if we have already // handled it. bool alreadyHandled = false; if (courseControl.split) { foreach (ControlView cv in courseView.controlViews) { if (cv.courseControlIds.Contains(courseControlId)) alreadyHandled = true; } } if (!alreadyHandled) { ControlView controlView = new ControlView(); controlView.controlId = courseControl.control; // Set the ordinal number. All variations does not include an ordinal. controlView.ordinal = -1; // Set all course control ids associated with split control, or a single one for a non-split control. // Set the legTo array with the next courseControlID(s). This is later updated // to the indices. if (courseControl.split) { controlView.courseControlIds = QueryEvent.AllVariationsOfCourseControl(eventDB, courseControlId).ToArray(); if (courseControl.nextCourseControl.IsNotNone) { controlView.legTo = new int[controlView.courseControlIds.Length]; for (int i = 0; i < controlView.legTo.Length; ++i) { controlView.legTo[i] = eventDB.GetCourseControl(controlView.courseControlIds[i]).nextCourseControl.id; } } if (courseControl.loop) controlView.joinIndex = courseControlId.id; else controlView.joinIndex = courseControl.splitEnd.id; } else { controlView.courseControlIds = new[] { courseControlId }; if (courseControl.nextCourseControl.IsNotNone) controlView.legTo = new int[1] { courseControl.nextCourseControl.id }; // legTo initially holds course control ids, later changed. controlView.joinIndex = -1; } // Add the controlview. courseView.controlViews.Add(controlView); } } courseView.Finish(); return courseView; }
// Get the mapping from split course control to letter. public static Dictionary<Id<CourseControl>, char> GetVariantCodeMapping(EventDB eventDB, CourseDesignator courseDesignator) { Debug.Assert(!courseDesignator.IsVariation); char nextLetter = 'A'; Dictionary<Id<CourseControl>, char> result = new Dictionary<Id<CourseControl>, char>(); foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (courseControl.split) { foreach (Id<CourseControl> splitId in courseControl.splitCourseControls) { // The loop escape path doesn't get a letter. if (!(courseControl.loop && courseControl.splitCourseControls[0] == splitId)) { if (!result.ContainsKey(splitId)) { result.Add(splitId, nextLetter); if (nextLetter == 'Z') nextLetter = 'a'; else ++nextLetter; } } } } } return result; }
// Find all the course controls for a particular control in a particular course. If the course // doesn't contain the given controlId, an empty array is returned. public static Id<CourseControl>[] GetCourseControlsInCourse(EventDB eventDB, CourseDesignator courseDesignator, Id<ControlPoint> controlId) { List<Id<CourseControl>> list = new List<Id<CourseControl>>(); foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { if (eventDB.GetCourseControl(courseControlId).control == controlId) list.Add(courseControlId); } return list.ToArray(); }
// Find the closest leg to a given point on a course. The leg might be None/None if the course has no legs. public static LegInfo FindClosestLeg(EventDB eventDB, CourseDesignator courseDesignator, PointF pt) { LegInfo closestLegSoFar = new LegInfo(); float closestSoFar = 1E10F; foreach (LegInfo leg in EnumLegs(eventDB, courseDesignator)) { PointF temp; SymPath legPath = GetLegPath(eventDB, eventDB.GetCourseControl(leg.courseControlId1).control, eventDB.GetCourseControl(leg.courseControlId2).control); float distance = legPath.DistanceFromPoint(pt, out temp); if (distance < closestSoFar) { closestSoFar = distance; closestLegSoFar = leg; } else if (distance == closestSoFar) { // Distances are equal. Use leg with the largest angle between the end points. SymPath closestLegPath = GetLegPath(eventDB, eventDB.GetCourseControl(closestLegSoFar.courseControlId1).control, eventDB.GetCourseControl(closestLegSoFar.courseControlId2).control); if (Geometry.Angle(legPath.FirstPoint, pt, legPath.LastPoint) > Geometry.Angle(closestLegPath.FirstPoint, pt, closestLegPath.LastPoint)) { closestSoFar = distance; closestLegSoFar = leg; } } } return closestLegSoFar; }
// Return if a give course uses a given control. public static bool CourseUsesControl(EventDB eventDB, CourseDesignator courseDesignator, Id<ControlPoint> controlId) { eventDB.CheckControlId(controlId); foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { if (eventDB.GetCourseControl(courseControlId).control == controlId) return true; } return false; }
public static bool CourseIsForked(EventDB eventDB, CourseDesignator courseDesignator) { foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { if (eventDB.GetCourseControl(courseControlId).split) return true; } return false; }
// Does the course have a start control? public static bool HasStartControl(EventDB eventDB, Id<Course> courseId) { Id<CourseControl> firstId = eventDB.GetCourse(courseId).firstCourseControl; if (firstId.IsNone || eventDB.GetControl(eventDB.GetCourseControl(firstId).control).kind != ControlPointKind.Start) return false; else return true; }
// Does the course have a finish control? public static bool HasFinishControl(EventDB eventDB, Id<Course> courseId) { Id<CourseControl> lastId = QueryEvent.LastCourseControl(eventDB, courseId, false); if (lastId.IsNone || eventDB.GetControl(eventDB.GetCourseControl(lastId).control).kind != ControlPointKind.Finish) return false; else return true; }
// Describe a leg. private static TextPart[] DescribeLeg(EventDB eventDB, Id<CourseControl> courseControlId1, Id<CourseControl> courseControlId2, DescKind descKind) { Debug.Assert(descKind == DescKind.Tooltip || descKind == DescKind.DescPane); Id<ControlPoint> controlId1 = eventDB.GetCourseControl(courseControlId1).control; Id<ControlPoint> controlId2 = eventDB.GetCourseControl(courseControlId2).control; Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); List<TextPart> list = new List<TextPart>(); // Course name list.Add(new TextPart(TextFormat.Title, string.Format("{0} \u2013 {1}", Util.ControlPointName(eventDB, controlId1, NameStyle.Long), Util.ControlPointName(eventDB, controlId2, NameStyle.Long)))); // Course length list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Length)); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0:#,###} m", QueryEvent.ComputeLegLength(eventDB, controlId1, controlId2, legId)))); // Which courses list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.UsedIn : SelectionDescriptionText.UsedInCourses))); Id<Course>[] coursesUsingControl = QueryEvent.CoursesUsingLeg(eventDB, controlId1, controlId2); list.Add(new TextPart(descKind == DescKind.Tooltip ? TextFormat.SameLine : TextFormat.NewLine, CourseListText(eventDB, coursesUsingControl))); // What is the competitor load? int load = QueryEvent.GetLegLoad(eventDB, controlId1, controlId2); if (load >= 0) { list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.Load : SelectionDescriptionText.CompetitorLoad))); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", load))); } if (descKind == DescKind.DescPane) { // Flagging list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Flagging + " ")); list.Add(new TextPart(TextFormat.SameLine, FlaggingType(eventDB, controlId1, controlId2, legId))); } return list.ToArray(); }
// Create highlights to and from a point to course controls. If controlDrag is set (optional), it is // used to get the correct bends for legs. // Static because it is used from DragControlMode also. public static CourseObj[] CreateLegHighlights(EventDB eventDB, PointF newPoint, Id<ControlPoint>controlDrag, ControlPointKind controlKind, Id<CourseControl> courseControlId1, Id<CourseControl> courseControlId2, float scaleRatio, CourseAppearance appearance) { List<CourseObj> highlights = new List<CourseObj>(); if (courseControlId1.IsNotNone) { Id<ControlPoint> controlId1 = eventDB.GetCourseControl(courseControlId1).control; ControlPoint control1 = eventDB.GetControl(controlId1); LegCourseObj highlight = CreateLegHighlight(eventDB, control1.location, control1.kind, controlId1, newPoint, controlKind, controlDrag, scaleRatio, appearance); if (highlight != null) highlights.Add(highlight); } if (courseControlId2.IsNotNone) { Id<ControlPoint> controlId2 = eventDB.GetCourseControl(courseControlId2).control; ControlPoint control2 = eventDB.GetControl(controlId2); LegCourseObj highlight = CreateLegHighlight(eventDB, newPoint, controlKind, controlDrag, control2.location, control2.kind, controlId2, scaleRatio, appearance); if (highlight != null) highlights.Add(highlight); } return highlights.ToArray(); }
void WriteLegLoadSection(EventDB eventDB) { // Maps legs to load infos, so we only process each leg once. Dictionary<Pair<Id<ControlPoint>, Id<ControlPoint>>, LegLoadInfo> loadInfos = new Dictionary<Pair<Id<ControlPoint>, Id<ControlPoint>>, LegLoadInfo>(); // Get load information about each leg. To enumerate all legs, just enumerate all courses and all legs on each course. foreach (Id<Course> courseId in eventDB.AllCourseIds) { foreach (QueryEvent.LegInfo leg in QueryEvent.EnumLegs(eventDB, new CourseDesignator(courseId))) { Id<ControlPoint> controlId1 = eventDB.GetCourseControl(leg.courseControlId1).control; Id<ControlPoint> controlId2 = eventDB.GetCourseControl(leg.courseControlId2).control; Pair<Id<ControlPoint>, Id<ControlPoint>> key = new Pair<Id<ControlPoint>, Id<ControlPoint>>(controlId1, controlId2); if (!loadInfos.ContainsKey(key)) { // This leg hasn't been processed yet. Process it. LegLoadInfo loadInfo = new LegLoadInfo(); loadInfo.controlId1 = controlId1; loadInfo.controlId2 = controlId2; loadInfo.text = string.Format("{0}\u2013{1}", Util.ControlPointName(eventDB, controlId1, NameStyle.Medium), Util.ControlPointName(eventDB, controlId2, NameStyle.Medium)); loadInfo.numCourses = QueryEvent.CoursesUsingLeg(eventDB, controlId1, controlId2).Length; loadInfo.load = QueryEvent.GetLegLoad(eventDB, controlId1, controlId2); loadInfos.Add(key, loadInfo); } } } // Remove legs used only once. List<LegLoadInfo> loadInfoList = new List<LegLoadInfo>(loadInfos.Values); loadInfoList = loadInfoList.FindAll(delegate(LegLoadInfo loadInfo) { return loadInfo.numCourses > 1; }); // Sort the list of legs, first by load, then by number of courses loadInfoList.Sort(delegate(LegLoadInfo loadInfo1, LegLoadInfo loadInfo2) { if (loadInfo1.load < loadInfo2.load) return 1; else if (loadInfo1.load > loadInfo2.load) return -1; if (loadInfo1.numCourses < loadInfo2.numCourses) return 1; else if (loadInfo1.numCourses > loadInfo2.numCourses) return -1; return 0; }); // Write the table. WritePara(ReportText.Load_OnlyLegsMoreThanOnce); BeginTable("", 3, "leftalign", "rightalign", "rightalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Leg, ReportText.ColumnHeader_NumberOfCourses, ReportText.ColumnHeader_Load); foreach (LegLoadInfo loadInfo in loadInfoList) { WriteTableRow(loadInfo.text, Convert.ToString(loadInfo.numCourses), loadInfo.load >= 0 ? Convert.ToString(loadInfo.load) : ""); } EndTable(); }
// Get missing points. List<MissingThing> MissingScores(EventDB eventDB) { List<MissingThing> missingScores = new List<MissingThing>(); foreach (Id<Course> courseId in QueryEvent.SortedCourseIds(eventDB)) { Course course = eventDB.GetCourse(courseId); bool anyScores = false; List<MissingThing> missingScoresThisCourse = new List<MissingThing>(); if (course.kind == CourseKind.Score) { for (Id<CourseControl> courseControlId = course.firstCourseControl; courseControlId.IsNotNone; courseControlId = eventDB.GetCourseControl(courseControlId).nextCourseControl) { CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (eventDB.GetControl(courseControl.control).kind == ControlPointKind.Normal) { if (courseControl.points <= 0) missingScoresThisCourse.Add(new MissingThing(courseId, courseControl.control, ReportText.EventAudit_MissingScore)); else anyScores = true; } } if (anyScores) missingScores.AddRange(missingScoresThisCourse); // only report missing scores if some control in this course has a score. } } return missingScores; }
private static List<List<Id<CourseControl>>> GetVariations(EventDB eventDB, CourseDesignator courseDesignator, Id<CourseControl> start, HashSet<Id<CourseControl>> alreadyVisited) { List<List<Id<CourseControl>>> result = new List<List<Id<CourseControl>>>(); Id<CourseControl> nextCourseControlId = start; // Traverse the course control links. while (nextCourseControlId.IsNotNone) { CourseControl courseCtl = eventDB.GetCourseControl(nextCourseControlId); if (courseCtl.split) { // If its a loop, we can only continue on the loop skipping path if all other loop // paths have been visited. bool allLoopsVisited = true; // true if all loops in this loop are visited, or its a fork. if (courseCtl.loop) { for (int i = 1; i < courseCtl.splitCourseControls.Length; ++i) { if (!alreadyVisited.Contains(courseCtl.splitCourseControls[i])) allLoopsVisited = false; } } for (int i = (allLoopsVisited ? 0 : 1); i < courseCtl.splitCourseControls.Length; ++i) { Id<CourseControl> split = courseCtl.splitCourseControls[i]; Id<CourseControl> afterSplit = eventDB.GetCourseControl(split).nextCourseControl; if (afterSplit.IsNotNone && !alreadyVisited.Contains(split)) { // Mark this path as visited so if its part of a loop, we don't visit it again. alreadyVisited.Add(split); List<List<Id<CourseControl>>> tailVariants = GetVariations(eventDB, courseDesignator, afterSplit, alreadyVisited); alreadyVisited.Remove(split); foreach (List<Id<CourseControl>> v in tailVariants) { List<Id<CourseControl>> l = new List<PurplePen.Id<PurplePen.CourseControl>>(v.Count + 1); l.Add(courseCtl.splitCourseControls[i]); l.AddRange(v); result.Add(l); } } } break; } nextCourseControlId = courseCtl.nextCourseControl; } // If no variations found, there is one way to go. if (result.Count == 0) result.Add(new List<Id<CourseControl>>()); return result; }
// Get the number of parts that this course has. A course with no map exchanges has 1 part, with one // map exchange has 2 parts, etc. public static int CountCourseParts(EventDB eventDB, CourseDesignator courseDesignator) { Debug.Assert(courseDesignator.AllParts); int currentPart = 0; foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { if (eventDB.GetCourseControl(courseControlId).exchange) ++currentPart; } return currentPart + 1; }
// See if course has any variations. public static bool HasVariations(EventDB eventDB, Id<Course> courseId) { if (courseId.IsNone) return false; // All Control has no variations. Course course = eventDB.GetCourse(courseId); if (course.kind == CourseKind.Score) return false; // Score courses don't have variations. return EnumCourseControlIds(eventDB, new CourseDesignator(courseId)).Any(courseControlId => eventDB.GetCourseControl(courseControlId).split); }
// Find which courses are using a particular leg. If none, return an // empty array. public static Id<Course>[] CoursesUsingLeg(EventDB eventDB, Id<ControlPoint> control1, Id<ControlPoint> control2) { List<Id<Course>> list = new List<Id<Course>>(); foreach (Id<Course> courseId in SortedCourseIds(eventDB)) { foreach (LegInfo leg in EnumLegs(eventDB, new CourseDesignator(courseId))) { if (eventDB.GetCourseControl(leg.courseControlId1).control == control1 && eventDB.GetCourseControl(leg.courseControlId2).control == control2) { list.Add(courseId); break; } } } return list.ToArray(); }
public static bool CanAddVariation(EventDB eventDB, CourseDesignator courseDesignator, Id<CourseControl> courseControlId) { // Can't be all controls or score course. if (courseDesignator.IsAllControls) return false; Id<Course> courseId = courseDesignator.CourseId; if (eventDB.GetCourse(courseId).kind == CourseKind.Score) return false; if (courseControlId.IsNone) return false; // Must not be the last control in the course. if (QueryEvent.LastCourseControl(eventDB, courseId, false) == courseControlId) return false; // Can't already have a variation there. CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (courseControl.split) return false; return true; }
public static IEnumerable<LegInfo> EnumLegs(EventDB eventDB, CourseDesignator courseDesignator) { if (courseDesignator.IsAllControls) yield break; Id<Course> courseId = courseDesignator.CourseId; // Score courses, by definition, have no legs. if (eventDB.GetCourse(courseId).kind == CourseKind.Score) yield break; bool first = true; foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, courseDesignator)) { CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (first || courseDesignator.AllParts || !courseControl.exchange) { Id<CourseControl> nextCourseControlId = GetNextControl(eventDB, courseDesignator, courseControlId); if (nextCourseControlId.IsNotNone) { yield return new LegInfo(courseControlId, nextCourseControlId); } } first = false; } }
// Get the last course control in a course. In dontReturnFinish is true, will never return a finish control, but // always the control before the finish. Returns None if the course has no controls (or only a finish control.) public static Id<CourseControl> LastCourseControl(EventDB eventDB, Id<Course> courseId, bool dontReturnFinish) { Id<CourseControl> last = Id<CourseControl>.None; foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, new CourseDesignator(courseId))) { if (!dontReturnFinish || eventDB.GetControl(eventDB.GetCourseControl(courseControlId).control).kind != ControlPointKind.Finish) last = courseControlId; } return last; }
// Finds where a new regular control would be inserted into an existing course. courseControl1 and courseControl2 can either or both be none, to identify // a leg to insert into, a control to insert after, or no information about where to insert. Updates courseControl1 and courseControl2 to identify exactly // where on the course the control should be inserted as follows: // If inserting between two course controls -- these are denoted by courseControl1 and courseControl2 // If inserting as last course control -- courseControl1 is the current last control and courseControl2 is None (only occurs when there is no finish) // If inserting as first course control -- courseControl2 is None and courseControl2 is current first control (only occurs when there is no start) // If inserting as only course control -- both are none (only occurs if course is currently empty) public static void FindControlInsertionPoint(EventDB eventDB, CourseDesignator courseDesignator, ref Id<CourseControl> courseControl1, ref Id<CourseControl> courseControl2) { Id<Course> courseId = courseDesignator.CourseId; if (courseControl1.IsNotNone && courseControl2.IsNotNone) { CourseControl cc1 = eventDB.GetCourseControl(courseControl1); CourseControl cc2 = eventDB.GetCourseControl(courseControl2); if (cc1.nextCourseControl != courseControl2) { Debug.Assert(cc2.split && cc2.splitCourseControls.Contains(courseControl2)); courseControl2 = cc1.nextCourseControl; } return; } else { // Adding after courseControl1. If none, or a finish control, add at end, before the finish control if any. if (courseControl1.IsNone || eventDB.GetControl(eventDB.GetCourseControl(courseControl1).control).kind == ControlPointKind.Finish) courseControl1 = QueryEvent.LastCourseControl(eventDB, courseId, true); if (courseControl1.IsNone) { // Empty course or adding at start. courseControl2 = eventDB.GetCourse(courseId).firstCourseControl; return; } else { // Adding after courseControl1. CourseControl before = (CourseControl)eventDB.GetCourseControl(courseControl1); courseControl2 = before.nextCourseControl; return; } } }
// Determine if you should warn about moving a shared course control. If a normal control is being // moved more than 75 meters, and is in other courses, then warn. // Returns null to not warn, or array of other courses to warn. public static Id<Course>[] ShouldWarnAboutMovingControl(EventDB eventDB, Id<Course> courseId, Id<CourseControl> courseControlId, PointF newLocation) { Id<ControlPoint> controlId = eventDB.GetCourseControl(courseControlId).control; Debug.Assert(CourseUsesControl(eventDB, new CourseDesignator(courseId), controlId)); if (eventDB.GetControl(controlId).kind != ControlPointKind.Normal) return null; float distance = DistanceBetweenPointsInMeters(eventDB, eventDB.GetControl(controlId).location, newLocation); if (distance < MOVE_THRESHOLD) return null; List<Id<Course>> list = new List<Id<Course>>(); foreach (Id<Course> containingCourseId in SortedCourseIds(eventDB)) { if (containingCourseId != courseId && CourseUsesControl(eventDB, new CourseDesignator(containingCourseId), controlId)) { list.Add(containingCourseId); } } if (list.Count == 0) return null; else return list.ToArray(); }
private static List<CourseControlAndSplitStart> EnumCourseControlsAndSplitStartsToJoin(EventDB eventDB, Id<Course> courseId, Id<CourseControl> begin, Id<CourseControl> join, Id<CourseControl> splitStart) { List<CourseControlAndSplitStart> result = new List<CourseControlAndSplitStart>(); Id<CourseControl> nextCourseControlId = begin; // Traverse the course control links. while (nextCourseControlId.IsNotNone && nextCourseControlId != join) { CourseControl courseCtl = eventDB.GetCourseControl(nextCourseControlId); if (courseCtl.split) { // Follow all of the alternate paths for (int i = 0; i < courseCtl.splitCourseControls.Length; ++i) { if (! (courseCtl.loop && i == 0)) { Id<CourseControl> forkStart = courseCtl.splitCourseControls[i]; result.Add(new CourseControlAndSplitStart(forkStart, forkStart)); var splitControls = EnumCourseControlsAndSplitStartsToJoin(eventDB, courseId, eventDB.GetCourseControl(forkStart).nextCourseControl, courseCtl.splitEnd, forkStart); result.AddRange(splitControls); } } if (!courseCtl.loop) { nextCourseControlId = courseCtl.splitEnd; } else { nextCourseControlId = courseCtl.nextCourseControl; } } else { result.Add(new CourseControlAndSplitStart(nextCourseControlId, splitStart)); nextCourseControlId = courseCtl.nextCourseControl; } } return result; }
private static List<Id<CourseControl>> EnumCourseControlsToJoin(EventDB eventDB, CourseDesignator courseDesignator, Id<CourseControl> start, Id<CourseControl> join, IEnumerable<Id<CourseControl>> variationChoices, bool ignoreFirstSplit, int currentPart) { List<Id<CourseControl>> result = new List<Id<CourseControl>>(); int part = courseDesignator.Part; Id<CourseControl> nextCourseControlId = start; bool first = true; // Traverse the course control links. while (nextCourseControlId.IsNotNone && nextCourseControlId != join) { CourseControl courseCtl = eventDB.GetCourseControl(nextCourseControlId); if (courseCtl.split && !(first && ignoreFirstSplit)) { if (variationChoices != null && variationChoices.Any()) { // Follow the path given by the variantChoices. May be the same as this control. Id<CourseControl> choice = variationChoices.First(); Debug.Assert(courseCtl.splitCourseControls.Contains(choice)); variationChoices = variationChoices.Skip(1); nextCourseControlId = choice; courseCtl = eventDB.GetCourseControl(nextCourseControlId); } else { // Follow all of the alternate paths except the current one. for (int i = 0; i < courseCtl.splitCourseControls.Length; ++i) { if (courseCtl.splitCourseControls[i] != nextCourseControlId) { var splitControls = EnumCourseControlsToJoin(eventDB, courseDesignator, courseCtl.splitCourseControls[i], courseCtl.splitEnd, variationChoices, true, currentPart); result.AddRange(splitControls); } } } } if (courseDesignator.AllParts || currentPart == part) result.Add(nextCourseControlId); if (courseCtl.exchange) { ++currentPart; if (!courseDesignator.AllParts && currentPart == part) result.Add(nextCourseControlId); } nextCourseControlId = courseCtl.nextCourseControl; first = false; } return result; }
// Create the standard view onto a regular course, or a single variation of a variation course. private static CourseView CreateStandardCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); if (QueryEvent.HasVariations(eventDB, courseDesignator.CourseId) && courseDesignator.VariationInfo == null) throw new ApplicationException("Cannot create course view without specifying which variation"); // Get sub-part of the course. firstCourseControls is the first control to process, lastCourseControl is the last one to // process, or None if process to the end of the course. Id<CourseControl> firstCourseControl, lastCourseControl; if (courseDesignator.AllParts) { firstCourseControl = course.firstCourseControl; lastCourseControl = Id<CourseControl>.None; } else { QueryEvent.GetCoursePartBounds(eventDB, courseDesignator, out firstCourseControl, out lastCourseControl); } CourseView courseView = new CourseView(eventDB, courseDesignator); int ordinal; courseView.courseName = course.name; courseView.scoreColumn = -1; ordinal = 1; ordinal = course.firstControlOrdinal; // To get the ordinals correct, we get the course control ids for all parts. List<Id<CourseControl>> courseControls = QueryEvent.EnumCourseControlIds(eventDB, courseDesignator.WithAllParts()).ToList(); int index = 0; // Increase the ordinal value for each normal control before the first one we're considering. while (index < courseControls.Count && courseControls[index] != firstCourseControl) { CourseControl courseControl = eventDB.GetCourseControl(courseControls[index]); ControlPoint control = eventDB.GetControl(courseControl.control); if (control.kind == ControlPointKind.Normal) ++ordinal; ++index; } for (; index < courseControls.Count; ++index) { Id<CourseControl> courseControlId = courseControls[index]; ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); ControlPoint control = eventDB.GetControl(courseControl.control); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Set the ordinal number. if (control.kind == ControlPointKind.Normal) controlView.ordinal = ordinal++; else if (control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange) controlView.ordinal = 0; else controlView.ordinal = -1; controlView.joinIndex = -1; // Don't show the map exchange for the next part at the end of this part. if (courseControlId == lastCourseControl && !courseDesignator.AllParts && control.kind == ControlPointKind.MapExchange) { controlView.hiddenControl = true; } // Set the legTo array with the next courseControlID. This is later updated // to the indices. if (index < courseControls.Count - 1 && courseControlId != lastCourseControl) { Id<CourseControl> nextCourseControl = courseControls[index + 1]; controlView.legTo = new int[1] { nextCourseControl.id }; // legTo initially holds course control ids, later changed. } // Add the controlview. courseView.controlViews.Add(controlView); if (courseControlId == lastCourseControl) break; } // If this is a part that should also have the finish on it, and it isn't the last part, then // add the finish. if (courseDesignator.IsNotAllControls && !courseDesignator.AllParts && courseDesignator.Part != QueryEvent.CountCourseParts(eventDB, courseDesignator.CourseId) - 1 && QueryEvent.GetPartOptions(eventDB, courseDesignator).ShowFinish) { if (QueryEvent.HasFinishControl(eventDB, courseDesignator.CourseId)) courseView.extraCourseControls.Add(QueryEvent.LastCourseControl(eventDB, courseDesignator.CourseId, false)); } courseView.Finish(); return courseView; }
// Get the next course control private static Id<CourseControl> GetNextControl(EventDB eventDB, CourseDesignator courseDesignator, Id<CourseControl> courseControlId) { Id<CourseControl> next = eventDB.GetCourseControl(courseControlId).nextCourseControl; // Simple case, the next control is not starting a split. if (next.IsNone || !eventDB.GetCourseControl(next).split) return next; // If it does, then we have to do the complex thing. List<Id<CourseControl>> allCourseControls = EnumCourseControlIds(eventDB, courseDesignator).ToList(); for (int i = 0; i < allCourseControls.Count; ++i) { if (allCourseControls[i] == courseControlId) { if (i < allCourseControls.Count - 1) return allCourseControls[i + 1]; else return Id<CourseControl>.None; } } throw new Exception("Course does not contain give course control"); }
// Create the normal view onto a score course private static CourseView CreateScoreCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); CourseView courseView = new CourseView(eventDB, courseDesignator); Id<CourseControl> courseControlId; courseView.courseName = course.name; courseView.scoreColumn = course.scoreColumn; courseControlId = course.firstCourseControl; while (courseControlId.IsNotNone) { ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Ordinals assigned after sorting. controlView.ordinal = -1; controlView.joinIndex = -1; // Move to the next control. courseView.controlViews.Add(controlView); courseControlId = courseControl.nextCourseControl; } // Sort the control views: first by kind, then by score, then by code. courseView.controlViews.Sort(delegate(ControlView view1, ControlView view2) { ControlPoint control1 = eventDB.GetControl(view1.controlId); ControlPoint control2 = eventDB.GetControl(view2.controlId); CourseControl courseControl1 = eventDB.GetCourseControl(view1.courseControlIds[0]); CourseControl courseControl2 = eventDB.GetCourseControl(view2.courseControlIds[0]); if (control1.kind < control2.kind) return -1; else if (control1.kind > control2.kind) return 1; if (courseControl1.points != courseControl2.points) return courseControl1.points.CompareTo(courseControl2.points); int result = Util.CompareCodes(control1.code, control2.code); if (result != 0) return result; return view1.controlId.id.CompareTo(view2.controlId.id); }); // Assign ordinals, if applicable. If scores in column A, then no ordinals will be assigned. if (courseView.scoreColumn != 0) { int ordinal = course.firstControlOrdinal; foreach (ControlView control in courseView.controlViews) { if (eventDB.GetControl(control.controlId).kind == ControlPointKind.Normal) control.ordinal = ordinal++; } } courseView.Finish(); return courseView; }
// Does this control view have a custom number placement? private static bool CustomPlaceNumber(EventDB eventDB, CourseView.ControlView controlView) { return ((controlView.courseControlIds[0].IsNotNone && eventDB.GetCourseControl(controlView.courseControlIds[0]).customNumberPlacement) || (controlView.courseControlIds[0].IsNone && eventDB.GetControl(controlView.controlId).customCodeLocation)); }