/// <summary> /// Initializes a new <see cref="GameLevel"/> instance with the specified lists of vertices /// and line segments, and resets the vertices' positions if requested. /// </summary> /// <param name="vertices">An enumeration of vertices.</param> /// <param name="lineSegments">An enumeration of line segments.</param> /// <param name="resetPositions">Specifies whether the vertices' positions should be reset, /// arranging all vertices in a circle in random order.</param> private GameLevel( IEnumerable<Vertex> vertices, IEnumerable<LineSegment> lineSegments, bool resetPositions) { _vertices = vertices.ToArray(); _lineSegments = lineSegments.ToArray(); _intersections = new Dictionary<LineSegment, HashSet<LineSegment>>(); foreach (LineSegment lineSegment in _lineSegments) { _intersections[lineSegment] = new HashSet<LineSegment>(); } _draggedVertex = null; _intersectionCount = 0; if (resetPositions) { while (_intersectionCount == 0) { ResetVertexPositions(); CalculateAllIntersections(); } } else CalculateAllIntersections(); }
/// <summary> /// Changes the current state of a vertex and possibly the states of vertices which are /// directly connected to it and line segments which are attached to it. /// </summary> /// <param name="vertex">The vertex whose state should be changed.</param> /// <param name="state">The new state of the vertex.</param> private void ChangeVertexState(Vertex vertex, VertexState state) { VertexState oldState = vertex.State; vertex.State = state; if (state == VertexState.Dragged || state == VertexState.UnderMouse) { if (oldState != VertexState.Dragged && oldState != VertexState.UnderMouse) { // The vertex was neither under the mouse, nor was it being dragged by the // user, but now either of those events has occurred foreach (Vertex connectedVertex in vertex.ConnectedVertices) { connectedVertex.State = VertexState.ConnectedToHighlighted; } foreach (LineSegment lineSegment in vertex.LineSegments) { lineSegment.State = LineSegmentState.Highlighted; } } } else if (oldState == VertexState.Dragged || oldState == VertexState.UnderMouse) { // The vertex was under the mouse or was being dragged by the user, but // that is no longer the case foreach (Vertex connectedVertex in vertex.ConnectedVertices) { connectedVertex.State = VertexState.Normal; } foreach (LineSegment lineSegment in vertex.LineSegments) { lineSegment.State = (_intersections[lineSegment].Count > 0 ? LineSegmentState.Intersected : LineSegmentState.Normal); } } }
/// <summary> /// Identifies any changes in the intersections between line segments attached to a /// specific vertex and any line segments in the game level, after that vertex has been /// dragged to a new position. /// </summary> /// <param name="vertex">The vertex which has been dragged to a new position.</param> /// <remarks> /// <para>The a single vertex has been dragged to a new position, only the intersections /// of the line segments attached to it might have changed, so it is unnecessary to /// recalculate all intersections in the game level.</para> /// </remarks> private void RecalculateIntersectionsForVertex(Vertex vertex) { foreach (LineSegment lineSegment in vertex.LineSegments) { HashSet<LineSegment> intersectingSegments = _intersections[lineSegment]; foreach (LineSegment otherSegment in _lineSegments) { if (otherSegment == lineSegment) continue; if (CalculationHelper.CheckLinesIntersect( lineSegment.Point1, lineSegment.Point2, otherSegment.Point1, otherSegment.Point2)) { if (!intersectingSegments.Contains(otherSegment)) AddIntersection(lineSegment, otherSegment); } else if (intersectingSegments.Contains(otherSegment)) RemoveIntersection(lineSegment, otherSegment); } } }
/// <summary> /// Sets the vertex which is currently under the mouse cursor. /// </summary> /// <param name="vertex">The vertex which is currently under the mouse cursor.</param> public void SetVertexUnderMouse(Vertex vertex) { if (_vertexUnderMouse == vertex) return; if (_vertexUnderMouse != null && !IsDragging) ChangeVertexState(_vertexUnderMouse, VertexState.Normal); _vertexUnderMouse = vertex; if (_vertexUnderMouse != null && !IsDragging) ChangeVertexState(_vertexUnderMouse, VertexState.UnderMouse); }
/// <summary> /// Initiates the dragging of a specified vertex requested by the user. /// </summary> /// <param name="draggedVertex">The vertex which should be dragged.</param> public void StartDrag(Vertex draggedVertex) { _draggedVertex = draggedVertex; _draggedVertex.State = VertexState.Dragged; }
/// <summary> /// Completes the dragging of the vertex which is currently being dragged, and /// recalculates all current and potential intersections which might have been affected by /// the dragging. /// </summary> public void FinishDrag() { ChangeVertexState(_draggedVertex, VertexState.Normal); RecalculateIntersectionsForVertex(_draggedVertex); _draggedVertex = null; if (_vertexUnderMouse != null) ChangeVertexState(_vertexUnderMouse, VertexState.UnderMouse); if (_intersectionCount == 0) OnLevelSolved(); }
/// <summary> /// Creates a new <see cref="GameLevel"/> instance from an enumeration of vertices /// generated by a <see cref="Generation.LevelGenerator"/> instance. /// </summary> /// <param name="generatedVertices">An enumeration of vertices generated by the game level /// generator.</param> /// <returns>The created game level instance.</returns> /// <remarks> /// <para>The generated vertices are automatically arranged in a circle in random order /// during the game level's initialization.</para> /// </remarks> public static GameLevel Create(IEnumerable<Generation.Vertex> generatedVertices) { var vertexMappings = new Dictionary<Generation.Vertex, Vertex>(); var lineSegments = new List<LineSegment>(); foreach (Generation.Vertex generatedVertex in generatedVertices) { var vertex = new Vertex(); vertexMappings[generatedVertex] = vertex; foreach (Generation.Vertex connectedGeneratedVertex in generatedVertex.ConnectedVertices) { if (!vertexMappings.ContainsKey(connectedGeneratedVertex)) { // The connected vertex has not been mapped to a vertex view model yet; // skip this line segment for now, it will be added when the other vertex // is enumerated continue; } Vertex otherVertex = vertexMappings[connectedGeneratedVertex]; LineSegment lineSegment = vertex.ConnectToVertex(otherVertex); lineSegments.Add(lineSegment); } } return new GameLevel(vertexMappings.Values, lineSegments, true); }
/// <summary> /// Creates a new <see cref="GameLevel"/> instance from an enumeration of vertices loaded /// from a saved game file. /// </summary> /// <param name="savedVertices">An enumeration of vertices loaded from the saved game file. /// </param> /// <returns>The created game level instance.</returns> /// <remarks> /// <para>The loaded vertices' positions are preserved during the game level's /// initialization.</para> /// </remarks> public static GameLevel Create(IEnumerable<Saves.Vertex> savedVertices) { var vertexMappings = new Dictionary<int, Vertex>(); var lineSegments = new List<LineSegment>(); foreach (Saves.Vertex savedVertex in savedVertices) { var vertex = new Vertex(); vertex.SetPosition(new Point(savedVertex.X, savedVertex.Y)); vertexMappings[savedVertex.Id] = vertex; foreach (int connectedVertexId in savedVertex.ConnectedVertexIds) { if (!vertexMappings.ContainsKey(connectedVertexId)) { // The connected vertex has not been mapped to a vertex view model yet; // skip this line segment for now, it will be added when the other vertex // is enumerated continue; } Vertex otherVertex = vertexMappings[connectedVertexId]; LineSegment lineSegment = vertex.ConnectToVertex(otherVertex); lineSegments.Add(lineSegment); } } return new GameLevel(vertexMappings.Values, lineSegments, false); }
/// <summary> /// Connects the vertex to another vertex with a line segment and returns it. /// </summary> /// <param name="otherVertex">The vertex which should be connected to the current one. /// </param> /// <returns>The created line segment between the two vertices.</returns> /// <exception cref="InvalidOperationException"> /// An attempt is made to connect the vertex to itself. /// /// -or- /// A line segment between the two vertices already exists. /// </exception> public LineSegment ConnectToVertex(Vertex otherVertex) { if (this == otherVertex) throw new InvalidOperationException("A vertex cannot be connected to itself."); if (_lineSegmentsMap.ContainsKey(otherVertex)) { throw new InvalidOperationException( "A line segment between the two vertices already exists."); } var lineSegment = new LineSegment(this, otherVertex); _lineSegmentsMap[otherVertex] = lineSegment; otherVertex._lineSegmentsMap[this] = lineSegment; return lineSegment; }
/// <summary> /// Initializes a new <see cref="LineSegment"/> instance with the specified two endpoint /// vertices. /// </summary> /// <param name="vertex1">The vertex at the first endpoint of the line segment.</param> /// <param name="vertex2">The vertex at the second endpoint of the line segment.</param> public LineSegment(Vertex vertex1, Vertex vertex2) { _vertex1 = vertex1; _vertex2 = vertex2; }