public override void handleMouseDown(Point mouseLocation, 
                                         PlayFrame frame,
                                         PitchScreenCoordConverter converter,
                                         VisualOverlay overlay)
        {
            if (mSelectedPlayer == null)
              {
            PointF pitchCoords = converter.screenToPitchCoords(mouseLocation);

            // No player is yet selected so attempt to find the one closest
            // to the selected spot.
            mSelectedPlayer = frame.GetClosestPlayer(pitchCoords,
                                                 Settings.Default.PlayerDiameter * 2.0f);

            if (mSelectedPlayer == null)
            {
              LinearMovement prevCut = frame.GetClosestCutEnd(pitchCoords,
                                                          Settings.Default.PlayerDiameter * 2.0f);

              if (prevCut != null &&
              prevCut != frame.DiscFrameMovement.ReceivingCut)
              {
            mSelectedPlayer = prevCut.Player;
            overlay.DrawingNewCut = true;
            overlay.CutStart = prevCut;
              }
            }
            else
            {
              overlay.DrawingNewCut = true;
              overlay.CutStart = frame.PlayerMovement[mSelectedPlayer][0];
            }
              }
        }
 /// <summary>
 /// Clears the overlay so that nothing is displayed on top of the 
 /// design.
 /// 
 /// Should be called whenever a tool completes.
 /// </summary>
 internal void Clear()
 {
     TriggerPlayer = null;
       SelectedTrigger = null;
       SelectedPlayer = null;
       CutStart = null;
       DrawingNewCut = false;
       DrawingDiscMovement = false;
       PlacingDisc = false;
       PlacingTrigger = false;
       MovingDiscControlPoint = false;
 }
        /// <summary>
        /// In order to move an item we catch the mouse down event to decide which
        /// thing has been selected. This function chooses either the selected
        /// cut, trigger or player.
        /// </summary>
        /// <param name="mouseLocation">Screen coordinates.</param>
        /// <param name="frame">Frame currently being designed.</param>
        /// <param name="converter">Used to convert from screen to pitch 
        /// coordinates.</param>
        public override void handleMouseDown(Point mouseLocation, 
                                         PlayFrame frame,
                                         PitchScreenCoordConverter converter,
                                         VisualOverlay overlay)
        {
            PointF pitchCoords = converter.screenToPitchCoords(mouseLocation);

              // Choose closest selected item.
              mSelectedPlayer = frame.GetClosestPlayer(pitchCoords,
                                               SELECTION_MAX_DISTANCE);
              mSelectedCut = frame.GetClosestCutEnd(pitchCoords,
                                              SELECTION_MAX_DISTANCE);
              mSelectedTrigger = frame.GetClosestTrigger(pitchCoords,
                                                 SELECTION_MAX_DISTANCE);
              mIsMovingDiscControlPoint = frame.IsNearControlPoint(pitchCoords,
                                                           SELECTION_MAX_DISTANCE);

              // Note that the ordering of priorities here MUST be the same as the
              // ordering on mouse up.
              if (mSelectedTrigger != null)
              {
            overlay.SelectedTrigger = mSelectedTrigger;
            overlay.TriggerPlayer = mSelectedTrigger.AffectedPlayer;
              }
              else if (mSelectedCut != null)
              {
            overlay.DrawingNewCut = false;
            overlay.CutStart = mSelectedCut;
              }
              else if (mSelectedPlayer != null)
              {
            overlay.SelectedPlayer = mSelectedPlayer;
              }
              else if (mIsMovingDiscControlPoint)
              {
            overlay.MovingDiscControlPoint = true;
              }
        }
        /// <summary>
        /// Given a position on the pitch and a player who is going to be moved we
        /// are able to check whether the trigger lies on a cut from any other 
        /// player and if so to create a new trigger at that position so that the
        /// player moves when the trigger is hit.
        /// </summary>
        /// <param name="pitchCoords"></param>
        /// <param name="maxDistanceFromLine"></param>
        /// <param name="affectedPlayer"></param>
        /// <returns>True if a trigger was created and false otherwise.</returns>
        public Boolean MaybeCreateTrigger(PointF pitchCoords, 
                                      float maxDistanceFromLine, 
                                      Player affectedPlayer)
        {
            Boolean foundCut = false;
              CutRatio cutRatio = GetClosestCutPoint(pitchCoords,
                                             maxDistanceFromLine,
                                             affectedPlayer);

              if (cutRatio != null)
              {
            foundCut = true;
            Trigger trigger = new Trigger(cutRatio, affectedPlayer);
            Triggers.Add(trigger);
              }

              return foundCut;
        }
        /// <summary>
        /// Retrieves the closest point on any cut to the given coordinates. This 
        /// is used for placing triggers and indicating where the disc should go
        /// </summary>
        /// <param name="pitchCoords"></param>
        /// <param name="selectionMaxDistance"></param>
        /// <param name="playerToIgnore">Compares the unique ids rather than
        /// references.</param>
        /// <param name="selectionMinDistance"></param>
        /// <returns></returns>
        public CutRatio GetClosestCutPoint(PointF pitchCoords, 
                                       float selectionMaxDistance,
                                       Player playerToIgnore,
                                       float selectionMinDistance = -1.0f)
        {
            CutRatio cutRatio = null;
              LinearMovement previousMovement = null;
              float minDistanceFromLine = selectionMaxDistance;
              LinearMovement closestMovement = null;
              LinearMovement closestPreviousMovement = null;
              Player closestPlayer = null;
              PointF triggerPoint = new PointF();

              foreach (Player player in PlayerMovement.Keys.Where(player => (playerToIgnore == null) ||
                                                                    (player.UniqueId != playerToIgnore.UniqueId)))
              {
            List<LinearMovement> playerCut = PlayerMovement[player];

            if (playerCut.Count > 1)
            {
              previousMovement = playerCut[0];
              PointF startCut = playerCut[0].FinalPosition;

              foreach (LinearMovement cutSection in PlayerMovement[player].GetRange(1, playerCut.Count - 1))
              {
            PointF endCut = cutSection.FinalPosition;
            PointF closestPoint = GeometryUtils.ClosestPointLineSegment(startCut, endCut, pitchCoords);
            float distanceFromCut = GeometryUtils.DistBetweenPoints(closestPoint, pitchCoords);

            if (distanceFromCut < minDistanceFromLine &&
                distanceFromCut > selectionMinDistance)
            {
              minDistanceFromLine = distanceFromCut;
              closestMovement = cutSection;
              closestPlayer = player;
              closestPreviousMovement = previousMovement;
              triggerPoint = closestPoint;
            }

            startCut = cutSection.FinalPosition;
            previousMovement = cutSection;
              }
            }
              }

              if (closestPlayer != null)
              {
            float cutLength = GeometryUtils.DistBetweenPoints(closestMovement.FinalPosition,
                                                          closestPreviousMovement.FinalPosition);
            float distanceToTrigger = GeometryUtils.DistBetweenPoints(closestPreviousMovement.FinalPosition,
                                                                  triggerPoint);

            cutRatio = new CutRatio();
            cutRatio.CausingCut = closestMovement;
            cutRatio.PreviousCut = closestPreviousMovement;
            cutRatio.RatioAlongCut = Math.Abs(distanceToTrigger / cutLength);
            cutRatio.Player = closestPlayer;
              }

              return cutRatio;
        }
        /// <summary>
        /// Overriding the onpopup handler (gets called before the menu is 
        /// displayed) to set up the menu based on what was clicked on.
        /// 
        /// This is to get around the fact that each player and cut is not
        /// actually a control.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPopup(EventArgs e)
        {
            this.MenuItems.Clear();

              if (mViewPanel.IsDesignMode)
              {
            // The mouse location comes in here as the location on the entire application
            // We need to use PointToClient to convert it to mouse location on the view
            // panel.
            Point mouseLocation = Cursor.Position;
            mMouseLocation = mViewPanel.PointToClient(mouseLocation);
            PointF pitchCoordinates = mViewPanel.Converter.screenToPitchCoords(mMouseLocation);
            PlayFrame currentFrame = mViewPanel.CurrentFrame;

            // Find out what was near the right click.
            mClickedPlayer = currentFrame.GetClosestPlayer(pitchCoordinates,
                                                       Settings.Default.PlayerDiameter);
            mClickedTrigger = currentFrame.GetClosestTrigger(pitchCoordinates,
                                                         Settings.Default.PlayerDiameter);
            mClickedCutRatio = mViewPanel.CurrentFrame.GetClosestCutPoint(pitchCoordinates,
                                                                      Settings.Default.PlayerDiameter,
                                                                      null);
            bool discPathClicked = currentFrame.IsOnFlightPath(pitchCoordinates,
                                                           Settings.Default.PlayerDiameter);
            if (mClickedPlayer != null)
            {
              SetUpPlayerMenu();
            }
            if (mClickedTrigger != null)
            {
              SetUpTriggerMenu();
            }
            if (mClickedCutRatio != null)
            {
              SetUpCutMenu();
            }
            if (discPathClicked)
            {
              SetUpDiscFlightMenu();
            }
            if (currentFrame.CanDrawCut(pitchCoordinates, Settings.Default.PlayerDiameter, 0.0f))
            {
              SetUpNewCutMenu();
            }

            base.OnPopup(e);
              }
        }
 public LinearMovement(float x, float y, int speedPercentage, Player player)
 {
     FinalPosition = new PointF(x, y);
       SpeedPercentage = speedPercentage;
       Player = player;
 }
        internal void ClearTrigger(Player clickedPlayer)
        {
            viewPanel.CurrentFrame.Triggers.RemoveAll(trigger => trigger.AffectedPlayer == clickedPlayer);

              viewPanel.Refresh();
              ModelChanged();
        }
 public PlaceCutTool(PictureBox toolPicture, Player player)
     : this(toolPicture)
 {
     mSelectedPlayer = player;
 }
 /// <summary>
 /// Checks whether a given player is the thrower.
 /// </summary>
 /// <param name="player">Any player object.</param>
 /// <returns>True if the player is the thrower. False is no thrower or 
 /// player is not the thrower</returns>
 internal bool IsThrower(Player player)
 {
     return (DiscFrameMovement.Thrower == player);
 }
 internal CyclicDataModelException(Player player)
 {
     mPlayer = player;
 }
        /// <summary>
        /// Adds a new entry in the player delay table for the given player if
        /// one does not already exist.
        /// 
        /// If one DOES exist then it only updates it if this delay is larger.
        /// </summary>
        /// <param name="player">The player who is being delayed</param>
        /// <param name="delay">The number of cycles before the player starts
        /// moving.</param>
        /// <returns>True if an addition or update made. False otherwise.</returns>
        private Boolean AddUpdateDelay(Player player, int delay)
        {
            if (mPlayerDelays.ContainsKey(player))
              {
            if (mPlayerDelays[player] < delay)
            {
              mPlayerDelays[player] = delay;
              return true;
            }
            else
            {
              return false;
            }
              }
              else
              {
            mPlayerDelays.Add(player, delay);

            return true;
              }
        }
        /// <summary>
        /// We capture the mouse up event and use it to place whatever it was that
        /// was selected on the mouse down event. If nothing was selected this does
        /// nothing. 
        /// </summary>
        /// <param name="mouseLocation"></param>
        /// <param name="frame"></param>
        /// <param name="converter"></param>
        public override void handleMouseUp(Point mouseLocation,
                                       PlayFrame frame,
                                       PitchScreenCoordConverter converter,
                                       VisualOverlay overlay)
        {
            PointF pitchCoords = converter.screenToPitchCoords(mouseLocation);

              if (mSelectedTrigger != null)
              {
            // If we are moving a trigger then only move it if the position it is
            // being placed is valid.
            if (frame.MaybeCreateTrigger(pitchCoords,
                                     SELECTION_MAX_DISTANCE,
                                     mSelectedTrigger.AffectedPlayer))
            {
              frame.RemoveTrigger(mSelectedTrigger);
              ModelChanged = true;
            }
              }
              else if (mSelectedPlayer != null)
              {
            frame.PlayerMovement[mSelectedPlayer][0].FinalPosition = pitchCoords;
            ModelChanged = true;
              }
              else if (mSelectedCut != null)
              {
            mSelectedCut.FinalPosition = pitchCoords;
            ModelChanged = true;
              }
              else if (mIsMovingDiscControlPoint)
              {
            frame.DiscFrameMovement.ControlPoint = pitchCoords;
            ModelChanged = true;
              }

              // Regardless of whether anything moved the move tool is complete on
              // mouse up.
              mSelectedCut = null;
              mSelectedPlayer = null;
              mSelectedTrigger = null;
              IsComplete = true;
        }
 /// <summary>
 /// Creates a dialog box that allows the user to set the speed of a given
 /// player within certain limits.
 /// </summary>
 /// <param name="player"></param>
 internal void SetPlayerSpeed(Player player)
 {
     using (SpeedDialog dialog = new SpeedDialog(Settings.Default.MinPlayerSpeed,
                                          Settings.Default.MaxPlayerSpeed,
                                          player.MaxSpeed))
       {
     if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
     {
       player.MaxSpeed = dialog.GetSpeed();
     }
       }
 }
 /// <summary>
 /// Called when we want to delete a specific player.
 /// </summary>
 /// <param name="player"></param>
 internal void DeletePlayer(Player player)
 {
     viewPanel.CurrentFrame.RemovePlayer(player);
       viewPanel.Refresh();
       ModelChanged();
 }
        /// <summary>
        /// Remove a single player from the frame including anything attached to 
        /// the player
        /// </summary>
        /// <param name="player"></param>
        public void RemovePlayer(Player player)
        {
            // If the player being removed was the one with the disc then we need
              // to remove the disc movement completely.
              //
              // If they were the one receiving the disc then we need to reset the
              // disc movement.
              if (this.DiscFrameMovement.Thrower != null &&
              this.DiscFrameMovement.Thrower.Equals(player))
              {
            DiscFrameMovement = new DiscMovement(this);
              }
              else if (DiscFrameMovement.ReceivingCut != null &&
               DiscFrameMovement.ReceivingCut.Player.Equals(player))
              {
            DiscFrameMovement.ReceivingCut = null;
            DiscFrameMovement.HasMoved = false;
              }

              // Delete any triggers which reference this player at all
              Triggers.RemoveAll(trigger => trigger.AffectedPlayer == player ||
                                    trigger.CausingCutRatio.Player == player);

              PlayerMovement.Remove(player);
        }
        /// <summary>
        /// This is a utilty function which generates the list of points that a 
        /// player passes through on the single frame. 
        /// 
        /// It includes the padding at the start (as a parameter).
        /// </summary>
        /// <param name="player"></param>
        /// <param name="cycleStart">The number of stationary cycles for this
        /// player.</param>
        /// <returns>A list of points that the player passes through.</returns>
        private List<ItemPlayData> GeneratePlayerPoints(Player player, 
                                                    int cycleStart)
        {
            List<ItemPlayData> playerData = new List<ItemPlayData>();
              Boolean isFirstCut = true;
              PointF startPoint = new PointF();

              // If the player has a trigger value stored off from previous
              // calculations then this is the number of stationary cycles.
              if (mPlayerDelays.ContainsKey(player))
              {
            cycleStart = mPlayerDelays[player];
              }

              foreach (LinearMovement cut in mPlayerMovement[player])
              {
            List<Trigger> applicableTriggers = new List<Trigger>();
            List<PointF> playerPoints = new List<PointF>();

            // For each cut that we process we need to check if any trigger was
            // fired during that cut.
            applicableTriggers.AddRange(mTriggers.Where(
                       trigger => trigger.CausingCutRatio.CausingCut == cut));

            // The first cut is actually just a placeholder to indicate where
            // the player starts out on the field.
            if (isFirstCut)
            {
              startPoint = cut.FinalPosition;
              playerPoints.Add(startPoint);

              // Add on the number of cycles for which the player should stay at
              // the start location. We add on 1 so that the player spends at LEAST
              // one cycle at their start location.
              for (int ii = 0; ii < cycleStart + 1; ii++)
              {
            playerData.Add(new PlayerPlayData(startPoint,
                                              player.PlayerTeam.Sprite,
                                              Settings.Default.PlayerDiameter,
                                              Settings.Default.PlayerDiameter,
                                              player.VisibleID,
                                              player.Name,
                                              player.PlayerTeam));
              }

              isFirstCut = false;
            }
            else
            {
              playerPoints = cut.GeneratePoints(startPoint,
                                            PlayThread.MAX_FRAME_TIME / 4.0,
                                            player.MaxSpeed);

              foreach (PointF point in playerPoints)
              {
            playerData.Add(new PlayerPlayData(point,
                                              player.PlayerTeam.Sprite,
                                              Settings.Default.PlayerDiameter,
                                              Settings.Default.PlayerDiameter,
                                              player.VisibleID,
                                              player.Name,
                                              player.PlayerTeam));

            // Compare the point to each of the triggers fired during this cut
            // noting that since these are PointFs then a comparison can't be
            // absolute.
            foreach (Trigger trigger in applicableTriggers)
            {
              PointF triggerPoint = trigger.CausingCutRatio.GetAbsolutePosition();
              if (GeometryUtils.DistBetweenPoints(triggerPoint, point) <
                  player.MaxSpeed * PlayThread.MAX_FRAME_TIME / 1000.0f)
              {
                AddUpdateDelay(trigger.AffectedPlayer, playerData.Count);
              }
            }
              }

              startPoint = cut.FinalPosition;
            }
              }

              return playerData;
        }
        /// <summary>
        /// Removes all of the cuts from a given starting location and a given 
        /// player. Also removes and triggers or disc movement to those cuts.
        /// </summary>
        /// <param name="clickedPlayer"></param>
        /// <param name="clickedCut">This cut will be removed as well.</param>
        internal void ClearCuts(Player clickedPlayer, 
                            LinearMovement clickedCut = null)
        {
            int totalCuts;
              int index;

              if (clickedCut == null)
              {
            // -1 because the first in the list is the starting location.
            index = 1;
              }
              else
              {
            index = PlayerMovement[clickedPlayer].IndexOf(clickedCut);
              }

              totalCuts = PlayerMovement[clickedPlayer].Count - index;

              // First we remove anything attached to the cuts that we're about to
              // delete and then we delete the cuts themselves.
              PlayerMovement[clickedPlayer].GetRange(index, totalCuts).ForEach(ClearSingleCut);
              PlayerMovement[clickedPlayer].RemoveRange(index, totalCuts);
        }
 public Trigger(CutRatio cutRatio, Player affectedPlayer)
 {
     CausingCutRatio = cutRatio;
       AffectedPlayer = affectedPlayer;
       UniqueId = NextUniqueId++;
 }
 /// <summary>
 /// Clears the disc of all movement and resets the speed to default.
 /// </summary>
 internal void Clear()
 {
     ClearFlightPath();
       Thrower = null;
 }
        /// <summary>
        /// Draw a single player onto the given display object. The conversion 
        /// from pitch to screen coordinates is done internally.
        /// </summary>
        /// <param name="player"></param>
        /// <param name="position"></param>
        /// <param name="display"></param>
        /// <param name="converter"></param>
        /// <param name="isSelected">A selected player shows a slightly different 
        /// sprite to indicate the selection.</param>
        /// <param name="isOutline">Set to true to draw only the outline of the 
        /// player.</param>
        internal void DrawPlayer(Player player, 
                             PointF position, 
                             Graphics display, 
                             PitchScreenCoordConverter converter,
                             Boolean isOutline)
        {
            Image playerImage = isOutline ? player.PlayerTeam.OutlineSprite :
                                      player.PlayerTeam.Sprite;

              PointF offsetCentre = new PointF(position.X, position.Y);
              offsetCentre.X -= Settings.Default.PlayerDiameter / 2.0f;
              offsetCentre.Y -= Settings.Default.PlayerDiameter / 2.0f;
              DrawSprite(display,
                 converter,
                 playerImage,
                 offsetCentre,
                 Settings.Default.PlayerDiameter,
                 Settings.Default.PlayerDiameter);

              using (Font font = CreatePlayerIdFont(converter))
              {
            // Draw the player id as an overlay onto the sprite.
            DrawString(display,
                   converter,
                   player.VisibleID.ToString(CultureInfo.InvariantCulture),
                   offsetCentre,
                   font,
                   Brushes.White);
              }
        }
        /// <summary>
        /// Retrieve the upmost parent of the player in the tree. This relies on 
        /// each player being triggered by at most one other player.
        /// 
        /// The parent of a player is deemed to be the player who triggers the 
        /// current player.
        /// 
        /// The upmost parent is what we get if we continue this processing up the
        /// tree until we find a player who has no parent.
        /// </summary>
        /// <param name="player">The player to search for.</param>
        /// <returns>The current playe if no parents.</returns>
        private Player GetParent(Player player)
        {
            Player parent = player;
              Boolean isFinished;

              do
              {
            isFinished = true;
            foreach (Trigger trigger in mTriggers)
            {
              if (trigger.AffectedPlayer == parent)
              {
            parent = trigger.CausingCutRatio.Player;
            isFinished = false;
            break;
              }
            }
              } while (player != parent && !isFinished);

              return parent;
        }
        /// <summary>
        /// Clear all cuts up the specified one for a given player.
        /// </summary>
        /// <param name="clickedPlayer"></param>
        /// <param name="clickedCut"></param>
        internal void ClearCuts(Player clickedPlayer, 
                            LinearMovement clickedCut = null)
        {
            viewPanel.CurrentFrame.ClearCuts(clickedPlayer, clickedCut);

              viewPanel.Refresh();
              ModelChanged();
        }
 public PlaceTriggerTool(PictureBox toolPicture, Player player)
     : base(toolPicture)
 {
     mSelectedPlayer = player;
 }
        private void ProcessPlayer(Player player)
        {
            // Since we process the players in a tree structure if a player is hit
              // more than once then there is a cycle which will not be possible to
              // resolve. This should have been prevented at the design level so
              // we throw an exception.
              if (mProcessedPlayers.Contains(player))
              {
            throw new CyclicDataModelException(player);
              }

              int cycleStart = 0;
              if (mPlayerDelays.ContainsKey(player))
              {
            cycleStart = mPlayerDelays[player];
              }

              mFramePlayData.PlayData.Add(GeneratePlayerPoints(player, cycleStart));

              mProcessedPlayers.Add(player);

              foreach (Trigger trigger in mTriggers.Where(
                   trigger => trigger.CausingCutRatio.Player == player))
              {
            ProcessPlayer(trigger.AffectedPlayer);
              }
        }
        /// <summary>
        /// Render a small square for a player. This is used when we're rendering
        /// into a small space.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="player"></param>
        /// <param name="location"></param>
        /// <param name="converter"></param>
        private void DrawSmallPlayer(Graphics graphics, 
                                 Player player, 
                                 PointF location, 
                                 PitchScreenCoordConverter converter)
        {
            Point screenCoords = converter.pitchToScreenCoords(location);
              Brush playerBrush = player.PlayerTeam.UniqueId ==
                          Team.RED_TEAM.UniqueId ? Brushes.Red : Brushes.Blue;

              graphics.FillRectangle(playerBrush,
                             screenCoords.X,
                             screenCoords.Y,
                             4, 4);
        }
 /// <summary>
 /// Adds the disc to the frame on a given player. Sets all of the receiving
 /// elements to null so that the disc does not move even if it wasn't 
 /// deleted properly previously.
 /// </summary>
 /// <param name="player"></param>
 public void AddDisc(Player player)
 {
     DiscFrameMovement.Thrower = player;
       DiscFrameMovement.ClearFlightPath();
 }
 public LinearMovement(PointF finalPosition, int speedPercentage, Player player)
 {
     FinalPosition = finalPosition;
       SpeedPercentage = speedPercentage;
       Player = player;
 }
 /// <summary>
 /// Adds a single player to the frame and sets the first 
 /// </summary>
 /// <param name="player"></param>
 /// <param name="startingPosition"></param>
 public void AddPlayer(Player player, PointF startingPosition)
 {
     PlayerMovement.Add(player, new List<LinearMovement>());
       PlayerMovement[player].Add(new LinearMovement(startingPosition,
                                             0,
                                             player));
 }