0
\$\begingroup\$

After one year of learning coding by my own I just finished a little mowing robot simulation project. Because I have no one to show my code to (as already mentioned self taught) I'm uploading it here. I would be very happy to get some feedback to improve my skills and write cleaner and better code. I used the MVC concept to build this project. Because I had to use an accurate timer to build this and c# has no built-in timer with the accuracy I needed I used the code ken.loveday wrote here. I've splitted up my model, view and controller into different folders so hopefully it's clear to you what is model, what is view and what is controller. I've also added much comments in my code so hopefully you can understand what the code is supposed to do.

 public abstract class RobSimulationObject
 {
 #region FIELDS
 // The bounds of the simulation object
 public Rect Bounds { get; protected set; }
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// sets the objects bounds
 /// </summary>
 /// <param name="bounds">bounds of the simulation object</param>
 public RobSimulationObject(Rect bounds)
 {
 Bounds = bounds;
 }
 #endregion
 }
 public sealed class ChargingStation : RobSimulationObject
 {
 #region CONSTANTS
 // Standard Constants for some the ChargingStation Fields
 public static Rect STANDARD_CHARGING_STATION_BOUNDS { get; } = new Rect(90, 90, 10, 10);
 private static readonly SolidColorBrush _STANDARD_CHARGING_STATION_COLOR = Brushes.Black;
 #endregion
 #region FIELDS
 public SolidColorBrush ChargingStationColor { get; private set; }
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// calls the more specific Constructor passing the standard charging station color
 /// </summary>
 /// <param name="bounds"></param>
 public ChargingStation(Rect bounds) : this(bounds, _STANDARD_CHARGING_STATION_COLOR)
 {
 }
 /// <summary>
 /// calls the base constructor passing in the bounds param
 /// sets the charging station color 
 /// </summary>
 /// <param name="bounds">The bounds of the charging station</param>
 /// <param name="chargingStationColor">The color of the charging station</param>
 public ChargingStation(Rect bounds, SolidColorBrush chargingStationColor) : base(bounds)
 {
 ChargingStationColor = chargingStationColor;
 }
 #endregion
 }
 public sealed class Court : RobSimulationObject
 {
 #region CONSTANTS
 // Standard values for some of the court's fields
 public static Rect STANDARD_COURT_BOUNDS { get; } = new Rect(0, 0, 600, 230);
 public static Rect STANDARD_GRASS_BOUNDS { get; } = new Rect(5, 5, 590, 220);
 private static readonly SolidColorBrush _STANDARD_GRASS_COLOR = Brushes.Green;
 private static readonly SolidColorBrush _STANDARD_COURT_BORDER_COLOR = Brushes.Brown;
 private static readonly SolidColorBrush _STANDARD_CUTTED_GRASS_COLOR = Brushes.White;
 #endregion
 #region FIELDS
 public Rect GrassBounds { get; private set; }
 public SolidColorBrush GrassColor { get; private set; }
 public SolidColorBrush CourtBorderColor { get; private set; }
 public SolidColorBrush CuttedGrassColor { get; private set; }
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// calls more specific controller passing in the standard constant values defined in the constants block
 /// </summary>
 /// <param name="courtBounds">The bounds of the complete court</param>
 /// <param name="grassBounds">The bounds of the grass tile in the court</param>
 public Court(Rect courtBounds, Rect grassBounds) : this(courtBounds, grassBounds, _STANDARD_GRASS_COLOR, _STANDARD_COURT_BORDER_COLOR, _STANDARD_CUTTED_GRASS_COLOR)
 {
 if (!courtBounds.Contains(grassBounds))
 throw new ArgumentException("The Court doesn't contain the grassBounds");
 GrassBounds = grassBounds;
 }
 /// <summary>
 /// calls base constructor passing in the court's bounds
 /// Initializes the object fields
 /// </summary>
 /// <param name="courtBounds">The bounds of the complete court</param>
 /// <param name="grassBounds">The bounds of the grass tile in the court</param>
 /// <param name="grassColor">The grasses color</param>
 /// <param name="courtBorderColor">The borders color</param>
 /// <param name="cuttedGrassColor">The color of the cutted grass area</param>
 public Court(Rect courtBounds, Rect grassBounds, SolidColorBrush grassColor, SolidColorBrush courtBorderColor, SolidColorBrush cuttedGrassColor) : base(courtBounds)
 {
 GrassColor = grassColor;
 CourtBorderColor = courtBorderColor;
 CuttedGrassColor = cuttedGrassColor;
 }
 #endregion
 }
 public sealed class Robot : RobSimulationObject
 {
 #region CONSTANTS
 // Standard values for some of the robot's fields
 public static Rect STANDARD_ROBOT_BOUNDS { get; } = new Rect(5, 5, 10, 10);
 public const double STANDARD_ROBOT_CUTTING_KNIFE_RADIUS = 4.5;
 private static readonly SolidColorBrush _STANDARD_ROBOT_COLOR = Brushes.Blue;
 private static readonly SolidColorBrush _STANDARD_ROBOT_CUTTING_KNIFE_AREA_COLOR = Brushes.Yellow;
 private const double _STANDARD_BATTERY_CAPACITY = 3500.00;
 private const double _STANDARD_BATTERY_LOADING_SPEED = 5;
 private const double _STANDARD_BATTERY_DISCHARGING_SPEED = 0.1;
 private const double _STANDARD_ROBOT_MOVING_SPEED = 0.2;
 private static readonly Vector _STANDARD_ROBOT_MOVING_DIRECTION_VECTOR = new Vector(0, 1);
 private static Random _random = new Random();
 #endregion
 #region FIELDS
 public SolidColorBrush Color { get; private set; }
 public SolidColorBrush CuttingKnifeAreaColor { get; private set; }
 public double CuttingKnifeRadius { get; private set; }
 public double BatteryCapacity { get; private set; }
 public double CurrentBatteryCapacity { get; private set; }
 public double MovingSpeed { get; private set; }
 private bool _isCharging;
 private double _batteryLoadingSpeed;
 private double _batteryDischargingSpeed;
 private Vector _robotMovingDirectionVector;
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// calls the more specific constructor passing in the standard constant values
 /// </summary>
 /// <param name="bounds">The bounds of the robot</param>
 /// <param name="cuttingKnifeRadius">The radius of the robot's cutting knife</param>
 public Robot(Rect bounds, double cuttingKnifeRadius) : this(bounds, _STANDARD_ROBOT_COLOR, _STANDARD_ROBOT_CUTTING_KNIFE_AREA_COLOR, cuttingKnifeRadius, _STANDARD_BATTERY_CAPACITY, 
 _STANDARD_BATTERY_LOADING_SPEED, _STANDARD_BATTERY_DISCHARGING_SPEED, _STANDARD_ROBOT_MOVING_SPEED, 
 _STANDARD_ROBOT_MOVING_DIRECTION_VECTOR)
 {
 }
 /// <summary>
 /// calls the base constructor passing in the robots bounds
 /// initializes the robots fields
 /// </summary>
 /// <param name="bounds"></param>
 /// <param name="robotColor"></param>
 /// <param name="cuttingKnifeAreaColor"></param>
 /// <param name="cuttingKnifeRadius"></param>
 /// <param name="batteryCapacity"></param>
 /// <param name="batteryLoadingSpeed"></param>
 /// <param name="batteryDischargingSpeed"></param>
 /// <param name="movingSpeed"></param>
 /// <exception cref="ArgumentException">Thrown when the diameter of the cutting knife is bigger than the robots bounds</exception>
 public Robot(Rect bounds, SolidColorBrush robotColor, SolidColorBrush cuttingKnifeAreaColor, double cuttingKnifeRadius, double batteryCapacity, double batteryLoadingSpeed, 
 double batteryDischargingSpeed, double movingSpeed, Vector robotMovingDirectionVector) : base(bounds)
 {
 Color = robotColor;
 CuttingKnifeAreaColor = cuttingKnifeAreaColor;
 if (cuttingKnifeRadius * 2 >= Bounds.Width || cuttingKnifeRadius * 2 >= Bounds.Height)
 throw new ArgumentException("The Cutting Knife Diameter can't be bigger than the whole Robot");
 CuttingKnifeRadius = cuttingKnifeRadius;
 BatteryCapacity = batteryCapacity;
 CurrentBatteryCapacity = batteryCapacity;
 _batteryLoadingSpeed = batteryLoadingSpeed;
 _batteryDischargingSpeed = batteryDischargingSpeed;
 MovingSpeed = movingSpeed;
 _robotMovingDirectionVector = robotMovingDirectionVector;
 }
 #endregion
 #region METHODS
 /// <summary>
 /// Moves robot by the specific vectors values
 /// Discharges the robot
 /// </summary>
 /// <param name="vector">vector by that the robot is being moved</param>
 public void MoveRobot()
 {
 if (CurrentBatteryCapacity > 0)
 {
 Vector movingVector = new Vector(_robotMovingDirectionVector.X * MovingSpeed, _robotMovingDirectionVector.Y * MovingSpeed);
 var bounds = Bounds;
 bounds.Offset(movingVector);
 Bounds = bounds;
 CurrentBatteryCapacity -= _batteryDischargingSpeed;
 if(_isCharging)
 _isCharging = false;
 }
 }
 /// <summary>
 /// Moves robot by a specific vector
 /// </summary>
 /// <param name="vec"></param>
 public void MoveRobotBy(Vector vec)
 {
 _robotMovingDirectionVector = vec;
 MoveRobot();
 }
 /// <summary>
 /// Lets the robot turn by random degree
 /// </summary>
 public void Collide()
 {
 if (!_isCharging)
 {
 var robBounds = Bounds;
 _robotMovingDirectionVector.Negate();
 robBounds.Offset(_robotMovingDirectionVector);
 Bounds = robBounds;
 var radians = _random.Next(30, 330) * (Math.PI / 180);
 _robotMovingDirectionVector = new Vector(_robotMovingDirectionVector.X * Math.Cos(radians) - _robotMovingDirectionVector.Y * Math.Sin(radians),
 _robotMovingDirectionVector.X * Math.Sin(radians) + _robotMovingDirectionVector.Y * Math.Cos(radians));
 }
 }
 /// <summary>
 /// Resets the robots values to the initial standard constant values
 /// </summary>
 public void ResetRobot()
 {
 Bounds = STANDARD_ROBOT_BOUNDS;
 BatteryCapacity = _STANDARD_BATTERY_CAPACITY;
 CurrentBatteryCapacity = BatteryCapacity;
 _robotMovingDirectionVector = _STANDARD_ROBOT_MOVING_DIRECTION_VECTOR;
 }
 public void ChargeRobot()
 {
 if (CurrentBatteryCapacity < BatteryCapacity)
 {
 CurrentBatteryCapacity += _batteryLoadingSpeed;
 _isCharging = true;
 }
 else
 _isCharging = false;
 }
 /// <summary>
 /// Returns the current battery live in percentage
 /// </summary>
 /// <returns>Battery live in percentage</returns>
 public double GetBatteryLiveInPercentage()
 {
 return (CurrentBatteryCapacity / BatteryCapacity) * 100;
 }
 #endregion
 }
 public sealed class RobotSimulationModel
 {
 #region FIELDS
 // Model Objects
 public Robot Robot { get; private set; }
 public Court Court { get; private set; }
 public ChargingStation ChargingStation { get; private set; }
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// Calls more specific constructor passing in the model objects standard data constants
 /// </summary>
 public RobotSimulationModel() : this(Robot.STANDARD_ROBOT_BOUNDS, Robot.STANDARD_ROBOT_CUTTING_KNIFE_RADIUS, Court.STANDARD_COURT_BOUNDS, 
 Court.STANDARD_GRASS_BOUNDS, ChargingStation.STANDARD_CHARGING_STATION_BOUNDS)
 {
 }
 /// <summary>
 /// Initializes the Model
 /// </summary>
 /// <param name="robot_Bounds">The bounds the robot will have</param>
 /// <param name="robot_CuttingKnifeRadius">The cutting knife radius the robot will have</param>
 /// <param name="court_Bounds">The bounds the court will have</param>
 /// <param name="court_GrassBounds">The courts grass tile bounds</param>
 /// <param name="chargingStation_Bounds">The charging station bounds</param>
 public RobotSimulationModel(Rect robot_Bounds, double robot_CuttingKnifeRadius, Rect court_Bounds, Rect court_GrassBounds, Rect chargingStation_Bounds)
 {
 Robot = new Robot(robot_Bounds, robot_CuttingKnifeRadius);
 Court = new Court(court_Bounds, court_GrassBounds);
 ChargingStation = new ChargingStation(chargingStation_Bounds);
 }
 #endregion
 }
 public sealed class MainRobotSimulationController
 {
 #region FIELDS
 // model and view
 private RobotSimulationModel _robotSimulationModel;
 private MainWindow _robotSimulationView;
 private ViewUpdater _viewUpdater;
 private RobotMover _robotMover;
 private RobotCollisionDetector _robotCollisionDetector;
 // Model updating timer
 private AccurateTimer _accurateSimulationUpdatingTimer;
 private Stopwatch _simulationStopwatch;
 private bool _isSimulationRunning;
 private double _simulationSpeed;
 private int _viewUpdatingRate = 1;
 private int _viewUpdatingRateCounter;
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// Initializes the controller
 /// Standard constructor
 /// </summary>
 public MainRobotSimulationController()
 {
 _robotSimulationModel = new RobotSimulationModel();
 _robotSimulationView = new MainWindow();
 _viewUpdater = new ViewUpdater(_robotSimulationModel, _robotSimulationView);
 _robotMover = new RobotMover(_robotSimulationModel);
 _robotCollisionDetector = new RobotCollisionDetector(_robotSimulationModel);
 _accurateSimulationUpdatingTimer = new AccurateTimer(33_000);
 _accurateSimulationUpdatingTimer.MicroTimerElapsed += AccurateSimulationUpdatingTimer_Tick;
 _simulationStopwatch = new Stopwatch();
 _simulationSpeed = 1;
 _robotSimulationView.Show();
 SubscribeToViewEvents();
 }
 #endregion
 #region METHODS
 private void SubscribeToViewEvents()
 {
 _robotSimulationView.btn_StartSimulation.Click += View_Btn_StartSimulation_Click;
 _robotSimulationView.btn_StopSimulation.Click += View_Btn_StopSimulation_Click;
 _robotSimulationView.btn_ResetSimulation.Click += View_Btn_ResetSimulation_Click;
 _robotSimulationView.btn_Home.Click += View_Btn_Home_Click;
 _robotSimulationView.cb_SelectSpeed.SelectionChanged += View_Cb_SelectSpeed_SelectionChanged;
 _robotSimulationView.Closed += View_Window_Closed;
 _robotSimulationView.btn_StartAutomaticMowing.Click += View_Btn_StartAutomaticMowing_Click;
 }
 #endregion
 #region EVENT_HANDLERS
 public void AccurateSimulationUpdatingTimer_Tick(object sender, AccurateTimerEventArgs e)
 {
 // model updating
 _robotCollisionDetector.CheckForCollisions();
 _robotMover.MoveRobot();
 // view updating if necessary
 if(_viewUpdatingRateCounter >= _viewUpdatingRate)
 {
 Application.Current.Dispatcher.Invoke(() => _viewUpdater.UpdateView(_simulationStopwatch.Elapsed));
 _viewUpdatingRateCounter = 0;
 }
 else
 {
 _viewUpdatingRateCounter++;
 }
 }
 #endregion
 #region VIEW_EVENT_HANDLERS
 public void View_Btn_StartSimulation_Click(object sender, EventArgs e)
 {
 if (!_isSimulationRunning)
 {
 _accurateSimulationUpdatingTimer.Start();
 _simulationStopwatch.Start();
 _isSimulationRunning = true;
 _robotSimulationView.btn_StartSimulation.IsEnabled = false;
 _robotSimulationView.btn_StopSimulation.IsEnabled = true;
 _robotSimulationView.tb_RobotAutomaticMowingEndingTime.IsEnabled = false;
 _robotSimulationView.tb_RobotAutomaticMowingStartingTime.IsEnabled = false;
 }
 }
 public void View_Btn_StopSimulation_Click(object sender, EventArgs e)
 {
 if (_isSimulationRunning)
 {
 _accurateSimulationUpdatingTimer.Stop();
 _simulationStopwatch.Stop();
 _isSimulationRunning = false;
 _robotSimulationView.btn_StartSimulation.IsEnabled = true;
 _robotSimulationView.btn_StopSimulation.IsEnabled = false;
 _robotSimulationView.tb_RobotAutomaticMowingEndingTime.IsEnabled = true;
 _robotSimulationView.tb_RobotAutomaticMowingStartingTime.IsEnabled = true;
 }
 }
 public void View_Btn_ResetSimulation_Click(object sender, EventArgs e)
 {
 if (_isSimulationRunning)
 {
 View_Btn_StopSimulation_Click(null, null);
 }
 _simulationStopwatch.Reset();
 _robotSimulationModel.Robot.ResetRobot();
 _viewUpdater.ResetView();
 _robotMover.MoveRobotToChargingStation = false;
 }
 public void View_Btn_Home_Click(object sender, EventArgs e)
 {
 _robotMover.MoveRobotToChargingStation = true;
 }
 public void View_Btn_StartAutomaticMowing_Click(object sender, EventArgs e)
 {
 DispatcherTimer automaticMowingTimer = new DispatcherTimer();
 int minutesStarting = 0;
 int minutesEnding = 0;
 bool isSecondTime = false;
 try
 {
 minutesStarting = Convert.ToInt32(_robotSimulationView.tb_RobotAutomaticMowingStartingTime.Text);
 minutesEnding = Convert.ToInt32(_robotSimulationView.tb_RobotAutomaticMowingEndingTime.Text);
 }
 catch
 {
 _robotSimulationView.btn_StartAutomaticMowing.Foreground = Brushes.Red;
 return;
 }
 if (_robotSimulationView.btn_StartAutomaticMowing.Foreground == Brushes.Red)
 _robotSimulationView.btn_StartAutomaticMowing.Foreground = Brushes.Black;
 automaticMowingTimer.Interval = new TimeSpan(0, minutesStarting, 0);
 automaticMowingTimer.Tick += (tickSender, tickArgs) => 
 {
 if (!isSecondTime)
 {
 View_Btn_StartSimulation_Click(null, null);
 automaticMowingTimer.Interval = new TimeSpan(0, minutesEnding, 0);
 }
 else
 {
 View_Btn_StopSimulation_Click(null, null);
 automaticMowingTimer.Stop();
 }
 isSecondTime = true;
 };
 automaticMowingTimer.Start();
 }
 public void View_Cb_SelectSpeed_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
 var item = e.AddedItems[0];
 if (item is ComboBoxItem)
 {
 // updating the simulationSpeed, timer interval and updating rate based on the selected speed
 double speed = Convert.ToDouble(((ComboBoxItem)item).Tag);
 _simulationSpeed = speed;
 _accurateSimulationUpdatingTimer.Interval = (long)(33000 / _simulationSpeed);
 _viewUpdatingRate = (int)(16000 / (33000 / _simulationSpeed));
 }
 }
 public void View_Window_Closed(object sender, EventArgs e)
 {
 _accurateSimulationUpdatingTimer.Abort();
 }
 #endregion
 }
 public sealed class RobotCollisionDetector
 {
 #region FIELDS
 private RobotSimulationModel _simulationModel;
 #endregion
 #region CONSTRUCTORS
 public RobotCollisionDetector(RobotSimulationModel simulationModel)
 {
 _simulationModel = simulationModel;
 }
 #endregion
 #region METHODS
 /// <summary>
 /// Calls the Robot.Collide() method when the robot is colliding with another obstacle
 /// </summary>
 public void CheckForCollisions()
 {
 Rect robBounds = _simulationModel.Robot.Bounds;
 double robKnifeDiam = _simulationModel.Robot.CuttingKnifeRadius * 2;
 double robKnifeOffset = (robBounds.Width - robKnifeDiam) / 2;
 Rect knifeBounds = new Rect(robBounds.X + robKnifeOffset, robBounds.Y + robKnifeOffset, robKnifeDiam, robKnifeDiam);
 if (!_simulationModel.Court.GrassBounds.Contains(knifeBounds))
 _simulationModel.Robot.Collide();
 else if (robBounds.IntersectsWith(_simulationModel.ChargingStation.Bounds))
 _simulationModel.Robot.Collide();
 }
 #endregion
 }
 public sealed class RobotMover
 {
 #region FIELDS
 private RobotSimulationModel _simulationModel;
 public bool MoveRobotToChargingStation { get; set; }
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// Default constructor
 /// Initializes the RobotMover
 /// </summary>
 /// <param name="simulationModel">The simulation model object passed in by the MainRobotSimulationController</param>
 public RobotMover(RobotSimulationModel simulationModel)
 {
 _simulationModel = simulationModel;
 }
 #endregion
 #region METHODS
 /// <summary>
 /// Moves the robot
 /// </summary>
 public void MoveRobot()
 {
 if (!MoveRobotToChargingStation)
 {
 _simulationModel.Robot.MoveRobot();
 if (_simulationModel.Robot.GetBatteryLiveInPercentage() < 10)
 MoveRobotToChargingStation = true;
 }
 else
 {
 Robot r = _simulationModel.Robot;
 Rect chargBounds = _simulationModel.ChargingStation.Bounds;
 if ((int)r.Bounds.Y < (int)chargBounds.Y)
 r.MoveRobotBy(new Vector(0, 1));
 else if ((int)r.Bounds.Y > (int)chargBounds.Y)
 r.MoveRobotBy(new Vector(0, -1));
 else if (r.Bounds.X > chargBounds.X + chargBounds.Width)
 r.MoveRobotBy(new Vector(-1, 0));
 else if (r.Bounds.X + r.Bounds.Width < chargBounds.X)
 r.MoveRobotBy(new Vector(1, 0));
 if (r.Bounds.IntersectsWith(chargBounds))
 if (r.GetBatteryLiveInPercentage() >= 100)
 MoveRobotToChargingStation = false;
 else
 r.ChargeRobot();
 }
 }
 #endregion
 }
 public sealed class ViewUpdater
 {
 #region FIELDS
 private RobotSimulationModel _simulationModel;
 private MainWindow _simulationView;
 #endregion
 #region CONSTRUCTORS
 /// <summary>
 /// Initializes the ViewUpdater
 /// </summary>
 /// <param name="simulationModel">The simulation model passed in by MainRobotSimulationController</param>
 public ViewUpdater(RobotSimulationModel simulationModel, MainWindow simulationView)
 {
 _simulationModel = simulationModel;
 _simulationView = simulationView;
 InitializeView();
 }
 #endregion
 #region METHODS
 /// <summary>
 /// Initializes the view
 /// </summary>
 private void InitializeView()
 {
 Rect robBounds = _simulationModel.Robot.Bounds;
 double robKnifeDiam = _simulationModel.Robot.CuttingKnifeRadius * 2;
 double robKnifeOffset = (robBounds.Width - robKnifeDiam) / 2;
 Rect complCourtBounds = _simulationModel.Court.Bounds;
 Rect grassCourtBounds = _simulationModel.Court.GrassBounds;
 Rect chargingStationBounds = _simulationModel.ChargingStation.Bounds;
 _simulationView.InitializeSimulationDrawingComponents(new Point(robBounds.Width, robBounds.Height), _simulationModel.Robot.Color,
 new Point(robKnifeDiam, robKnifeDiam), _simulationModel.Robot.CuttingKnifeAreaColor,
 new Point(robKnifeDiam, robKnifeDiam), _simulationModel.Court.CuttedGrassColor,
 new Point(complCourtBounds.Width, complCourtBounds.Height), _simulationModel.Court.CourtBorderColor,
 new Point(grassCourtBounds.Width, grassCourtBounds.Height), _simulationModel.Court.GrassColor,
 new Point(chargingStationBounds.Width, chargingStationBounds.Height), _simulationModel.ChargingStation.ChargingStationColor);
 _simulationView.AddInitializedDrawingComponentsToCanvas(new Vector(robBounds.X, robBounds.Y), new Vector(robBounds.X + robKnifeOffset, robBounds.Y + robKnifeOffset),
 new Vector(complCourtBounds.X, complCourtBounds.Y),
 new Vector(grassCourtBounds.X, grassCourtBounds.Y), new Vector(chargingStationBounds.X, chargingStationBounds.Y));
 }
 /// <summary>
 /// Updates the simulation view based on the model
 /// </summary>
 public void UpdateView(TimeSpan simulationTimeRunning)
 {
 Rect robBounds = _simulationModel.Robot.Bounds;
 double robKnifeDiam = _simulationModel.Robot.CuttingKnifeRadius * 2;
 double robKnifeOffset = (robBounds.Width - robKnifeDiam) / 2;
 Rect knifeBounds = new Rect(robBounds.X + robKnifeOffset, robBounds.Y + robKnifeOffset, robKnifeDiam, robKnifeDiam);
 _simulationView.MoveDrawingComponentBy(_simulationView.RobotDrawingRectangle, robBounds);
 _simulationView.MoveDrawingComponentBy(_simulationView.RobotCuttingKnifeDrawingEllipse, knifeBounds);
 _simulationView.pb_RobotBatteryLive.Value = _simulationModel.Robot.GetBatteryLiveInPercentage();
 _simulationView.lbl_TimePassedSinceSimulationStart.Content = string.Format("{0:00}:{1:00}", simulationTimeRunning.Minutes, simulationTimeRunning.Seconds);
 // Adding the robot positions
 _simulationView.AddRobotPosition(knifeBounds);
 }
 public void ResetView()
 {
 UpdateView(new TimeSpan());
 _simulationView.ResetView();
 }
 #endregion
 }
 <Window x:Class="RobotSimulation.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:local="clr-namespace:RobotSimulation"
 mc:Ignorable="d"
 Title="MainWindow" Height="500" Width="800">
 <Grid>
 <Grid.RowDefinitions>
 <RowDefinition Height="1*"/>
 <RowDefinition Height="5*"/>
 <RowDefinition Height="1*"/>
 </Grid.RowDefinitions>
 <Grid Grid.Column="0" Grid.Row="0">
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="1*"/>
 <ColumnDefinition Width="1*"/>
 <ColumnDefinition Width="1*"/>
 <ColumnDefinition Width="1*"/>
 <ColumnDefinition Width="1*"/>
 </Grid.ColumnDefinitions>
 <Button Grid.Column="0" x:Name="btn_StartSimulation" Content="Start Simulation" Width="Auto"/>
 <Button Grid.Column="1" x:Name="btn_StopSimulation" Content="Stop Simulation" IsEnabled="False" Width="Auto"/>
 <Button Grid.Column="2" x:Name="btn_ResetSimulation" Content="Reset Simulation" Width="Auto"/>
 <Label HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="3" x:Name="lbl_TimePassedSinceSimulationStart" Content="00:00"/>
 <ProgressBar Grid.Column="4" x:Name="pb_RobotBatteryLive" Value="100"/>
 </Grid>
 <Grid Grid.Row="2" Grid.Column="0">
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="1*"/>
 <ColumnDefinition Width="4*"/>
 <ColumnDefinition Width="6*"/>
 <ColumnDefinition Width="2*"/>
 </Grid.ColumnDefinitions>
 <ComboBox VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Column="1" Grid.Row="2" x:Name="cb_SelectSpeed" Width="Auto" SelectedIndex="0">
 <ComboBoxItem Content="x1.0" Tag="1,0"/>
 <ComboBoxItem Content="x1.5" Tag="1,5"/>
 <ComboBoxItem Content="x2.0" Tag="2,0"/>
 <ComboBoxItem Content="x5.0" Tag="5,0"/>
 <ComboBoxItem Content="x7.5" Tag="7,5"/>
 <ComboBoxItem Content="x10.0" Tag="10,0"/>
 <ComboBoxItem Content="x20.0" Tag="20,0"/>
 <ComboBoxItem Content="x50.0" Tag="50,0"/>
 </ComboBox>
 <Label Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Speed"/>
 <Grid Grid.Column="2">
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="2*"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition Height="*"/>
 </Grid.RowDefinitions>
 <Grid>
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="*"/>
 <ColumnDefinition Width="2*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition Height="*"/>
 <RowDefinition Height="*"/>
 </Grid.RowDefinitions>
 <TextBox Grid.Column="1" Grid.Row="0" VerticalContentAlignment="Center" x:Name="tb_RobotAutomaticMowingStartingTime" TextWrapping="Wrap"/>
 <TextBox Grid.Column="1" Grid.Row="1" VerticalContentAlignment="Center" x:Name="tb_RobotAutomaticMowingEndingTime" TextWrapping="Wrap"/>
 <Label Grid.Column="0" Grid.Row="0" Content="Starting Time" HorizontalAlignment="Center" VerticalAlignment="Center"/>
 <Label Grid.Column="0" Grid.Row="1" Content="Ending Time" HorizontalAlignment="Center" VerticalAlignment="Center"/>
 </Grid>
 <Button Grid.Column="2" Grid.Row="1" x:Name="btn_StartAutomaticMowing" Content="Start"/>
 </Grid>
 <Button Grid.Column="3" x:Name="btn_Home" Content="Home"/>
 </Grid>
 <Canvas Grid.Row="1" x:Name="cnv_SimulationCanvas" HorizontalAlignment="Center" Height="230" VerticalAlignment="Center" Width="600"/>
 </Grid>
</Window>
 public partial class MainWindow : Window
 {
 #region FIELDS
 // Simulation Drawing Components
 public Ellipse RobotCuttingKnifeDrawingEllipse { get; set; }
 public Rectangle RobotDrawingRectangle { get; set; }
 public Rectangle CourtCompleteDrawingRectangle { get; set; }
 public Rectangle CourtGrassTileDrawingRectangle { get; set; }
 public Rectangle ChargingStationDrawingRectangle { get; set; }
 private SolidColorBrush _robotPositionColor;
 #endregion
 #region CONSTRUCTORS
 public MainWindow()
 {
 InitializeComponent();
 }
 #endregion
 #region METHODS
 /// <summary>
 /// Initializes the simulation drawing components
 /// </summary>
 public void InitializeSimulationDrawingComponents(Point robotDrawingRectangleDimensions, SolidColorBrush robotDrawingEllipseColor,
 Point robotCuttingKnifeDrawingEllipseDimensions, SolidColorBrush robotCuttingKnifeDrawingRectangleColor,
 Point robotCuttedGrassDrawingPathDimensions, SolidColorBrush robotCuttedGrassDrawingPathColor,
 Point courtCompleteDrawingRectangleDimensions, SolidColorBrush courtCompleteDrawingRectangleColor,
 Point courtGrassTileDrawingRectangleDimensions, SolidColorBrush courtGrassTileDrawingRectangleColor,
 Point chargingStationDrawingRectangleDimensions, SolidColorBrush chargingStationDrawingRectangleColor)
 {
 RobotCuttingKnifeDrawingEllipse = new Ellipse() { Width = robotCuttingKnifeDrawingEllipseDimensions.X, Height = robotCuttingKnifeDrawingEllipseDimensions.Y, Fill = robotCuttingKnifeDrawingRectangleColor };
 RobotDrawingRectangle = new Rectangle() { Width = robotDrawingRectangleDimensions.X, Height = robotDrawingRectangleDimensions.Y, Fill = robotDrawingEllipseColor };
 CourtCompleteDrawingRectangle = new Rectangle() { Width = courtCompleteDrawingRectangleDimensions.X, Height = courtCompleteDrawingRectangleDimensions.Y, Fill = courtCompleteDrawingRectangleColor };
 CourtGrassTileDrawingRectangle = new Rectangle() { Width = courtGrassTileDrawingRectangleDimensions.X, Height = courtGrassTileDrawingRectangleDimensions.Y, Fill = courtGrassTileDrawingRectangleColor };
 ChargingStationDrawingRectangle = new Rectangle() { Width = chargingStationDrawingRectangleDimensions.X, Height = chargingStationDrawingRectangleDimensions.Y, Fill = chargingStationDrawingRectangleColor };
 _robotPositionColor = robotCuttedGrassDrawingPathColor;
 }
 /// <summary>
 /// Adds the Initialized drawing components to the canvas
 /// </summary>
 public void AddInitializedDrawingComponentsToCanvas(Vector robotDrawingEllipseOffset, Vector robotCuttingKnifeDrawingEllipseOffset, 
 Vector courtCompleteDrawingRectangleOffset, Vector courtGrassTileDrawingRectangleOffset, Vector chargingStationDrawingRectangleOffset)
 {
 cnv_SimulationCanvas.Children.Add(CourtCompleteDrawingRectangle);
 Canvas.SetTop(CourtCompleteDrawingRectangle, courtCompleteDrawingRectangleOffset.Y);
 Canvas.SetLeft(CourtCompleteDrawingRectangle, courtCompleteDrawingRectangleOffset.X);
 cnv_SimulationCanvas.Children.Add(CourtGrassTileDrawingRectangle);
 Canvas.SetTop(CourtGrassTileDrawingRectangle, courtGrassTileDrawingRectangleOffset.Y);
 Canvas.SetLeft(CourtGrassTileDrawingRectangle, courtGrassTileDrawingRectangleOffset.X);
 cnv_SimulationCanvas.Children.Add(ChargingStationDrawingRectangle);
 Canvas.SetTop(ChargingStationDrawingRectangle, chargingStationDrawingRectangleOffset.Y);
 Canvas.SetLeft(ChargingStationDrawingRectangle, chargingStationDrawingRectangleOffset.X);
 cnv_SimulationCanvas.Children.Add(RobotDrawingRectangle);
 Canvas.SetTop(RobotDrawingRectangle, robotDrawingEllipseOffset.Y);
 Canvas.SetLeft(RobotDrawingRectangle, robotDrawingEllipseOffset.X);
 cnv_SimulationCanvas.Children.Add(RobotCuttingKnifeDrawingEllipse);
 Canvas.SetTop(RobotCuttingKnifeDrawingEllipse, robotCuttingKnifeDrawingEllipseOffset.Y);
 Canvas.SetLeft(RobotCuttingKnifeDrawingEllipse, robotCuttingKnifeDrawingEllipseOffset.X);
 }
 /// <summary>
 /// Moves the UIElement by the rects bounds
 /// </summary>
 /// <param name="drawingComponent"></param>
 /// <param name="rect"></param>
 public void MoveDrawingComponentBy(UIElement drawingComponent, Rect rect)
 {
 if (drawingComponent is Rectangle)
 {
 ((Rectangle)drawingComponent).Width = rect.Width;
 ((Rectangle)drawingComponent).Height = rect.Height;
 }
 else if(drawingComponent is Ellipse)
 {
 ((Ellipse)drawingComponent).Width = rect.Width;
 ((Ellipse)drawingComponent).Width = rect.Width;
 }
 Canvas.SetTop(drawingComponent, rect.Y);
 Canvas.SetLeft(drawingComponent, rect.X);
 }
 // adds a robot position to the canvas
 public void AddRobotPosition(Rect bounds)
 {
 Ellipse e = new Ellipse() { Width = bounds.Width, Height = bounds.Height, Fill = _robotPositionColor };
 cnv_SimulationCanvas.Children.Insert(2, e);
 Canvas.SetTop(e, bounds.Y);
 Canvas.SetLeft(e, bounds.X);
 }
 public void ResetView()
 {
 cb_SelectSpeed.SelectedIndex = 0;
 cnv_SimulationCanvas.Children.RemoveRange(2, cnv_SimulationCanvas.Children.Count - 5);
 }
 #endregion
 }
 namespace MicroLibrary
{
 /// <summary>
 /// MicroStopwatch class
 /// </summary>
 public class MicroStopwatch : System.Diagnostics.Stopwatch
 {
 readonly double _microSecPerTick =
 1000000D / System.Diagnostics.Stopwatch.Frequency;
 public MicroStopwatch()
 {
 if (!System.Diagnostics.Stopwatch.IsHighResolution)
 {
 throw new Exception("On this system the high-resolution " +
 "performance counter is not available");
 }
 }
 public long ElapsedMicroseconds
 {
 get
 {
 return (long)(ElapsedTicks * _microSecPerTick);
 }
 }
 }
 /// <summary>
 /// MicroTimer class
 /// </summary>
 public class MicroTimer
 {
 public delegate void MicroTimerElapsedEventHandler(
 object sender,
 MicroTimerEventArgs timerEventArgs);
 public event MicroTimerElapsedEventHandler MicroTimerElapsed;
 System.Threading.Thread _threadTimer = null;
 long _ignoreEventIfLateBy = long.MaxValue;
 long _timerIntervalInMicroSec = 0;
 bool _stopTimer = true;
 public MicroTimer()
 {
 }
 public MicroTimer(long timerIntervalInMicroseconds)
 {
 Interval = timerIntervalInMicroseconds;
 }
 public long Interval
 {
 get
 {
 return System.Threading.Interlocked.Read(
 ref _timerIntervalInMicroSec);
 }
 set
 {
 System.Threading.Interlocked.Exchange(
 ref _timerIntervalInMicroSec, value);
 }
 }
 public long IgnoreEventIfLateBy
 {
 get
 {
 return System.Threading.Interlocked.Read(
 ref _ignoreEventIfLateBy);
 }
 set
 {
 System.Threading.Interlocked.Exchange(
 ref _ignoreEventIfLateBy, value <= 0 ? long.MaxValue : value);
 }
 }
 public bool Enabled
 {
 set
 {
 if (value)
 {
 Start();
 }
 else
 {
 Stop();
 }
 }
 get
 {
 return (_threadTimer != null && _threadTimer.IsAlive);
 }
 }
 public void Start()
 {
 if (Enabled || Interval <= 0)
 {
 return;
 }
 _stopTimer = false;
 System.Threading.ThreadStart threadStart = delegate()
 {
 NotificationTimer(ref _timerIntervalInMicroSec,
 ref _ignoreEventIfLateBy,
 ref _stopTimer);
 };
 _threadTimer = new System.Threading.Thread(threadStart);
 _threadTimer.Priority = System.Threading.ThreadPriority.Highest;
 _threadTimer.Start();
 }
 public void Stop()
 {
 _stopTimer = true;
 }
 public void StopAndWait()
 {
 StopAndWait(System.Threading.Timeout.Infinite);
 }
 public bool StopAndWait(int timeoutInMilliSec)
 {
 _stopTimer = true;
 if (!Enabled || _threadTimer.ManagedThreadId ==
 System.Threading.Thread.CurrentThread.ManagedThreadId)
 {
 return true;
 }
 return _threadTimer.Join(timeoutInMilliSec);
 }
 public void Abort()
 {
 _stopTimer = true;
 if (Enabled)
 {
 _threadTimer.Abort();
 }
 }
 void NotificationTimer(ref long timerIntervalInMicroSec,
 ref long ignoreEventIfLateBy,
 ref bool stopTimer)
 {
 int timerCount = 0;
 long nextNotification = 0;
 MicroStopwatch microStopwatch = new MicroStopwatch();
 microStopwatch.Start();
 while (!stopTimer)
 {
 long callbackFunctionExecutionTime =
 microStopwatch.ElapsedMicroseconds - nextNotification;
 long timerIntervalInMicroSecCurrent =
 System.Threading.Interlocked.Read(ref timerIntervalInMicroSec);
 long ignoreEventIfLateByCurrent =
 System.Threading.Interlocked.Read(ref ignoreEventIfLateBy);
 nextNotification += timerIntervalInMicroSecCurrent;
 timerCount++;
 long elapsedMicroseconds = 0;
 while ( (elapsedMicroseconds = microStopwatch.ElapsedMicroseconds)
 < nextNotification)
 {
 System.Threading.Thread.SpinWait(10);
 }
 long timerLateBy = elapsedMicroseconds - nextNotification;
 if (timerLateBy >= ignoreEventIfLateByCurrent)
 {
 continue;
 }
 MicroTimerEventArgs microTimerEventArgs =
 new MicroTimerEventArgs(timerCount,
 elapsedMicroseconds,
 timerLateBy,
 callbackFunctionExecutionTime);
 MicroTimerElapsed(this, microTimerEventArgs);
 }
 microStopwatch.Stop();
 }
 }
 /// <summary>
 /// MicroTimer Event Argument class
 /// </summary>
 public class MicroTimerEventArgs : EventArgs
 {
 // Simple counter, number times timed event (callback function) executed
 public int TimerCount { get; private set; }
 // Time when timed event was called since timer started
 public long ElapsedMicroseconds { get; private set; }
 // How late the timer was compared to when it should have been called
 public long TimerLateBy { get; private set; }
 // Time it took to execute previous call to callback function (OnTimedEvent)
 public long CallbackFunctionExecutionTime { get; private set; }
 public MicroTimerEventArgs(int timerCount,
 long elapsedMicroseconds,
 long timerLateBy,
 long callbackFunctionExecutionTime)
 {
 TimerCount = timerCount;
 ElapsedMicroseconds = elapsedMicroseconds;
 TimerLateBy = timerLateBy;
 CallbackFunctionExecutionTime = callbackFunctionExecutionTime;
 }
 }
}
 [1]: https://github.com/HubGitDesktop/MyProjects
 [2]: https://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer
asked Jan 20, 2019 at 10:39
\$\endgroup\$
9
  • \$\begingroup\$ codereview.stackexchange.com/editing-help \$\endgroup\$ Commented Jan 20, 2019 at 10:42
  • \$\begingroup\$ @πάνταῥεῖ the problem is that I formatted my code exactly like the link you posted is telling me to do. I also googled why it's not working but I wasn't able to find a working solution for this \$\endgroup\$ Commented Jan 20, 2019 at 10:44
  • 2
    \$\begingroup\$ Not working code isn't ready for review here anyways. Try Stack Overflow may be. \$\endgroup\$ Commented Jan 20, 2019 at 10:45
  • \$\begingroup\$ @πάνταῥεῖ My code is working. The only Thing I would be really happy about is some feedback. I'm not having any issues with my code \$\endgroup\$ Commented Jan 20, 2019 at 10:46
  • 2
    \$\begingroup\$ Then post your code here properly formatted and not as links. Just indent every line of code with four blanks, what's so hard about that? You can even select all your code lines and press CTRL-K which does that automatically for you. \$\endgroup\$ Commented Jan 20, 2019 at 10:48

1 Answer 1

3
\$\begingroup\$

For reference purposes your application UI looks like: Mower

There are following parts in the simulation:

  • red fence
  • green grass
  • yellow/black automatic mower
  • black charging station

The mower picks a direction and starts mowing until he reaches the fence, then it will change its direction, or it is at below 10< on battery and it will return to the charging station. The battery lasts roughly 25s at x50 speed.

If the mower had an AI that picks optimal path then it would mow the whole field in roughly 12 minutes 50 seconds or 15.4 seconds at x50 speed, without needing to recharge once.

Instead of optimizing for time/cost efficiency, it has no guarantee that it will actually complete its task or have an estimation when complete. It can be used as a reference to others as being a brute force solution that roughly does the task as well.

So how good is it? Well, at x50 speed it took about 4 min of simulation time to roughly covered 95% of the area and after 6 min it has reached roughly 99%. However, after this, the efficiency drops.

enter image description here

(6 min x50 speed = 5 hours at x1 speed)


There are too many things to note that can be improved, so I will try to just name a few obvious ones. Let's say that you have a goal to separate code into MVVM (model, view model and view) where the view is the XAML, view mode is the data context that the view uses and model is your data structures and logic.

View model should not virtually know anything about the view. Instead of having controls with names that you reference from your view model, you should use bindings to the view model properties.

<StackPanel>
 <ProgressBar 
 Minimum="0" 
 Maximum="100" 
 Value="{Binding Battery}" 
 Name="pb_RobotBatteryLive" />
 <!--Note that even here you can replace 
 'ElementName=pb_RobotBatteryLive, Path=Value' 
 with just 'Battery' --> 
 <TextBlock 
 Text="{Binding ElementName=pb_RobotBatteryLive, Path=Value, StringFormat={}{0:0}%}" 
 HorizontalAlignment="Center" 
 VerticalAlignment="Center" />
</StackPanel>

and in the code:

_simulationView.pb_RobotBatteryLive.Value = _simulationModel.Robot.GetBatteryLiveInPercentage();
//vs
Battery = _simulationModel.Robot.GetBatteryLiveInPercentage();

in c# the region, autodoc/comments are often an antipattern. Comments do not fix the poorly written code - try to write clean code and comments are not needed.

What could really benefit the quality and tests would be a use of virtual time scheduler. However, at this point, there are no clear requirements to test and you should work on that - what is the real world problem that you are trying to solve.

At the moment, you have interwoven several layers and there is no clear separation of concerns. In addition, this accurate time does have a thread, but you use it just to kick off a timer event that calls method AccurateSimulationUpdatingTimer_Tick and all the work seems to happen on the dispatcher thread.

When the properties are changed then they generally should implement INotifyProperty. Generally, a code library weaver like https://github.com/Fody/PropertyChanged is used to add the implementation, so you would just need to add the interface. This would allow you to update UI correctly as well as do something when any model property changes.


The variable names are chosen decently, property access visibility is mostly fine, app does deadlock the main thread if you tab out and in a few times but until then it does work. What you have managed to write is a working base for something you can use later. Its functionality resembles 90s screen saver.

Personally, I would recommend you try something simpler, doing graphic in c# with threads is not beginner-friendly. Check out Khan Academy, for example, I wrote the following demo under 15 min: https://www.khanacademy.org/computer-programming/walkers-redgreenblueblackwhite-x10/5113615912009728

answered Jan 20, 2019 at 16:38
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.