Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Saturday, July 14, 2018

Pluto Rover Coding Challenge

This is a spin-off of the famous Mars Rover coding assignment.

The Assignment

After NASA’s New Horizon successfully flew past Pluto, they now plan to land a Pluto Rover to further investigate the surface. You are responsible for developing an API that will allow the Rover to move around the planet. As you won’t get a chance to fix your code once it is on board, you are expected to use test driven development.

To simplify navigation, the planet has been divided up into a grid. The rover's position and location is represented by a combination of x and y coordinates and a letter representing one of the four cardinal compass points. An example position might be 0, 0, N, which means the rover is in the bottom left corner and facing North. Assume that the square directly North from (x, y) is (x, y+1).

In order to control a rover, NASA sends a simple string of letters. The only commands you can give the rover are ‘F’,’B’,’L’ and ‘R’

  • Implement commands that move the rover forward/backward (‘F’,’B’). The rover may only move forward/backward by one grid point, and must maintain the same heading.
  • Implement commands that turn the rover left/right (‘L’,’R’). These commands make the rover spin 90 degrees left or right respectively, without moving from its current spot.
  • Implement wrapping from one edge of the grid to another. (Pluto is a sphere after all)
  • Implement obstacle detection before each move to a new square. If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point and reports the obstacle.

Here's an example

  • Let's say that the rover is located at 0,0 facing North on a 100x100 grid.
  • Given the command "FFRFF" would put the rover at 2,2 facing East.

Tips!

  • Don't worry about the structure of the rover. Let the structure evolve as you add more tests.
  • Start simple. For instance you might start with a test that if at 0,0,N with command F, the robots position should now be 0,1,N.
  • Don’t worry about bounds checking until step 3 (implementing wrapping).
  • Don't start up/use the debugger, use your tests to implement the kata. If you find that you run into issues, use your tests to assert on the inner workings of the rover (as opposed to starting the debugger).

My Solution

I started with defining a few things:

  • Enum Command that represents each command (forward, backward, left and right)
  • Enum Orientation that represents the four cardinal directions (north, east, south and west)
  • Class Position that represents the current rover's position in the grid
public enum Command 
{
   Forward,
   Backward,
   Left,
   Right
}
  
public enum Orientation
{
   North,
   South,
   East,
   West
}
    
public class Position
{
   private readonly int x;
   private readonly int y;
   private readonly Orientation orientation;
    
   public Position(int x, int y, Orientation orientation) 
   {
      this.x = x;
      this.y = y;
      this.orientation = orientation;
   }
}

Then I define a static class InputParser that knows how to parse a character from the string of input letters eg. "FFRFF" into a Command. For simplicity, I won't show the code here, it's just a bunch of switch case statements.

Next I define a class Pluto which represents the surface/grid where the rover can move around on. I also think it makes sense to have this class responsible for holding information of the obstacles.

public class Pluto
{
   private readonly int width;
   private readonly int length;
   private readonly List<Tuple<int, int>> obstacles;
     
   public Pluto(int width, int length) 
   {
      this.width = width;
      this.length = length;
      obstacles = new List<Tuple<int, int>>();
   }

   public int Width 
   {
      get { return width; }
   }
   
   public int Length
   {
      get { return length; }
   }
     
   public List<Tuple<int, int>> Obstacles 
   {
      get { return obstacles; }
   }

   public void AddObstacle(Tuple<int, int> obstacle) 
   {
      if (obstacles.Contains(obstacle)) 
      {
         obstacles.Remove(obstacle);
      }
      
      obstacles.Add(obstacle);
   }
}

Finally I define a Rover class that can be initiated as so:

public Rover(int x, int y, Orientation orientation) 
{
   this.x = x;
   this.y = y;
   this.orientation = orientation;
}

This class holds a reference to Pluto with the help of a DeployTo function:

public void DeployTo(Pluto pluto)
{
   this.pluto = pluto;
}

and implements each single move logic as so:

  1. Determine the candidate new position
  2. int candidateX = x;
    int candidateY = y;
    switch (command)
    {
       case Command.Forward:
          if (orientation == Orientation.North) candidateY = candidateY + 1;
          else if (orientation == Orientation.South) candidateY = candidateY - 1;
          else if (orientation == Orientation.East) candidateX = candidateX + 1;
          else candidateX = candidateX - 1;
          break;
       case Command.Backward:
          if (orientation == Orientation.North) candidateY = candidateY - 1;
          else if (orientation == Orientation.South) candidateY = candidateY + 1;
          else if (orientation == Orientation.East) candidateX = candidateX - 1;
          else candidateX = candidateX + 1;
          break;
       case Command.Left:
          if (orientation == Orientation.North) orientation = Orientation.West;
          else if (orientation == Orientation.South) orientation = Orientation.East;
          else if (orientation == Orientation.East) orientation = Orientation.North;
          else orientation = Orientation.South;
          break;
       case Command.Right:
          if (orientation == Orientation.North) orientation = Orientation.East;
          else if (orientation == Orientation.South) orientation = Orientation.West;
          else if (orientation == Orientation.East) orientation = Orientation.South;
          else orientation = Orientation.North;
          break;
       default:
          throw new Exception($"Command '{command}' is not valid.");
    }
  3. Ask Pluto for positions of the obstacles
  4. If there is an obstacle on this candidateX and candidateY position, the Rover just stays on its current x and y position, although it still can change its orientation. If there is no obstacle, candidateX and candidateY is the new position.
  5. If candidateX or candidateY position exceeds Pluto's width and length, adjust candidateX and candidateY accordingly by deducting the width and length.

Things to Improve

If I have more time, here are a few things I want to improve:

  • Replace Tuple<int,int> with Coordinate class
  • Replace Rover(int x, int y, ...) with this Coordinate class
  • if else statements in Move() strikes me as a bit of a code smell (see below my thoughts of how to possibly improve this)
  • Move() function is too long, there's code smell here. Moving logic is tangled with obstacles detection logic. They need to be separated. Think of a change of requirement for example if the Rover is deployed to a planet that's not a wrapped grid, but a torus or an infinite grid.
  • Abstract out Pluto to guard for change of requirement as mentioned before. Maybe call it IPlanet?

My thoughts to improve Command Left and Right

We could use some mathematical tricks like assigning integer number to the Orientation enum. Pick an orientation, assign integer value 1 then go clockwise and assign the next integer value, so something like: East = 1, South = 2, West = 3, North = 4.

Apply the following for Left:

  if (current orientation - 1) < 1, then get the previous orientation as if the enum is a circle 

  else (current orientation - 1) 
  

Example:

  • current orientation: N ==> (4 - 1) = 3 (West)
  • current orientation: S ==> (2 - 1) = 1 (East)
  • current orientation: E ==> (1 - 1) = 0 (the previous orientation is North)
  • current orientation: W ==> (3 - 1) = 2 (South)

Apply similar trick to Right, it is the opposite of Left after all:

  if (current orientation + 1) > 4, then get the next orientation as if the enum is a circle 

  else (current orientation + 1) 
  

Example:

  • current orientation: N ==> (4 + 1) = 5 (the next orientation is East)
  • current orientation: S ==> (2 + 1) = 3 (West)
  • current orientation: E ==> (1 + 1) = 2 (South)
  • current orientation: W ==> (3 + 1) = 4 (North)

This trick would reduce the 8 if else statements to 2 functions.

Note however that I haven't thought about whether the two above pseudocodes would work for different enumeration value ie. if you start with North = 1 for example.

For Command Forward and Backward, we can see the following pattern:

  • Forward North = Backward South
  • Foward South = Backward North
  • Forward East = Backward West
  • Forward West = Backward East

So perhaps we can reduce some of the if else statements based on these facts.

Source Code

https://github.com/velianarie/PlutoRover

Saturday, February 3, 2018

Thursday, August 10, 2017

Getting VSTO project to Jenkins

I was a bit frustrated last week with a task that seemed so simple but took me the whole week to get it done.. and in a rather dirty way I must say. We have a legacy VSTO project that we want to get into Jenkins. This project has a legacy installer that's built as a .vdproj (Visual Studio Installer project). This has always been built locally on a developer machine and released from there. I know, I know, that's nasty but it is what it is. So I was trying to do two things: getting the solution AND the installer to build on Jenkins.

Problems Encountered

Vdproj has been deprecated

Microsoft only supports vdproj that shipped with VS2010. We don't have license for VS2010 anymore. We're using VS2015. Fortunately there's an extension we can install for higher VS version. Download link here.

MSBuild does not know anything about .vdproj file

This is very problematic because MSBuild does not know how to build .vdproj, only Visual Studio does, so we have to install Visual Studio in Jenkins. The command to automate this is:

devenv /SolutionName.sln /project InstallerName.vdproj /build Release
Make sure that you call this from Visual Studio Developer Command Prompt.

There is however a pre-build validation that the whole projects in the solution should load. The VSTO project cannot load without having Office installed. So we have to install Office.
You will see the following error if you don't:

ERROR: Cannot find outputs of project output group '(unable to determine name)'. 
Either the group, its configuration, or its project may have been removed from the solution.

Cryptic .vdproj pre-build validation error

ERROR: An error occurred while validating. HRESULT = '8000000A'
Googling leads me to this page. Yes unfortunately it is a registry hack. Create a DWORD key with the name "EnableOutOfProcBuild" and set it's value to zero in
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0_Config\MSBuild\

Why not use ClickOnce as installer?

I did try to create an installer using ClickOnce, but came across another problem with a different computed hash in the manifest. This is most likely caused by an xml transformation specific to this application that happens after the hash has been computed.

Conclusion

Avoid VSTO at all cost. I have to pollute the machine where Jenkins is run on to make this legacy application works. Here's a list:
  • Install Visual Studio 2015
  • Install Microsoft Visual Studio 2015 Installer Projects extension
  • Install Microsoft Office
  • Install Visual Studio 2010 Tools for Office Runtime (provides the COM 'glue' between Excel and C#)
  • Hack the registry

Wednesday, May 31, 2017

Feature Toggling with LaunchDarkly

Feature toggle is a software development technique allowing developers to modify system behaviour during run time without code change[1]. Feature toggle is used to enable or disable features. We can for example enable some features for testing even before they are ready for release, but disabled these same features in production.

LaunchDarkly is a feature flag rather than a feature toggle[2]. A toggle implies a two-state condition while a flag can return multiple states or values. In this blog I’m just going to show you how to use LaunchDarkly as a feature toggle to keep things simple.

Project environments

By default LaunchDarkly provides 2 environments, Production and Test. This can be managed in Account settings.

Feature flags

Create a new feature flag by entering:

  • Name
  • Key
  • Description (optional)
  • Flag type

Note that Key is generated automatically as we type in Name. We can alter this if we’re not happy with the auto generated Key. Before saving the flag, please make sure you’re happy with the Key representation. As far as I’m aware, there’s no way we can edit the Key once the flag is created. We will need to delete the whole flag and create a new one with the desirable Key representation. The Key is important here as it is going to be used in your application.

Using LaunchDarkly in your C# application

  1. First thing you need to do is install LaunchDarkly SDK using NuGet in the appropriate project of your C# solution.
  2. Install-Package LaunchDarkly.Client
  3. Import the LaunchDarkly package to your class.
  4. using LaunchDarkly.Client;
  5. Create a new LdClient with your environment-specific SDK key. I'm using Production SDK key here.
  6. var client = new LdClient("sdk-123a4bcd-5ef6-78g0-hi12-34j56k7890l1");
  7. Create a user. I'm only interested in the user name and machine name. You can adjust this according to your need.
  8. var user = User.WithKey(System.Environment.UserName)
                   .AndSecondaryKey(System.Environment.MachineName);
  9. Retrieve LaunchDarkly toggle value.
  10. var isEnabled = client.BoolVariation("calc-scenario-mappings-crud-buttons", user);
    isEnabled returns true if the feature flag is switched ON and returns false if the feature flag is switched OFF.
  11. Use this isEnabled value where you want to enable or disable features in your application.
  12. I'm using LaunchDarkly in a WPF application called CALC to disable the CRUD buttons in Scenario Mappings screen. I already have a view model with a logic that determines whether the CRUD buttons need to be enabled or disabled, so all I need to do is just append this boolean value to the existing logic.

Dev console and run result

Dev console is a handy event listener, it’ll show events as they stream in, live. Have this console open before you run your application.

I’ll run CALC with the feature flag first switched OFF and then switched ON so we can see the different effects.

On the left tab, we see that when the flag is switched OFF, the CRUD buttons are all disabled. On the right tab, when the flag is switched ON, the CRUD buttons are enabled.

Note that the reason why the Dev console show 2 events is because we have 2 tab items (Index and Curve) in the Scenario Mappings screen.