Tracked-Wander-a-thon
CMPE2300 – Lab03 Thursday, April 4, 2019
Lab 03 – Tracked-Wander-a-thon
In this multifaceted lab we will utilize many elements from this course to construct a simulation of some Wanderers, who will wander around a derived CDrawer environment with the ultimate goal of generating something someone might characterize as art.
Program Specification
Part I
Before we can let any wanderers loose, we need somewhere appropriate for them to wander in.
Derive a new class from the CDrawer called CTracker.
Functionally its primary enhancements over the base CDrawer will be that it will track what background pixels have been set / occupied and stop re-assignment to an occupied background pixel.
Helper class ColorInfo : contains 3 properties, an integer representing the integer equivalent of the key color, an integer holding the number of Points of this color, and a double representing the fill percentage - but will hold the number of points and act as a placeholder when this class is used to populate the user form DataGridView - at which time the value will be corrected to %. Create a CTOR to initialize the color member. Additional members as required ( Hint : Increment() ).
CTracker - derived members :
• HashSet of type Point - this will be our fast lookup / tracking of background Point/pixels.
• Dictionary, key of Color, value of ColorInfo instance - this will provide tracking for current colors used and their respective number of pixels occupied.
• a public property of int representing the current number of occupied spots via the HashSet collection.
• a public property of type List of ColorInfo. Taking appropriate precautions, return a List of all ColorInfo values in your dictionary. This will be used to map to a binding source for display.
CTracker will also require a new event created called BackgroundFull. It will notify registered users when all available pixels in the drawer background have been set. You may use the empty system supplied delegate EventHandler which accepts the standard ( sender, EventArgs ) parameters :
CMPE2300 – Lab03 Thursday, April 4, 2019
public event EventHandler onBackgroundFull = null;
public method Reset() method is also required to clear the HashSet and Dictionary to prepare for a new population.
public method ColorExists(), returning a bool of true if the supplied Color argument exists in the Dictionary.
SetBBScaledPixel will be replaced with a new version that returns a bool. This method will only set pixels that have not been previously set since the last Reset() method call. It will return false if the pixel has been previously set. If the pixel is valid to be set, it will also be included/updated in HashSet<> and Dictionary<> members as required. HashSet is used for quick existence checks, and the dictionary will be keyed by Color with a helper class ColorInfo holding tracking information. Remember to Render() on a successful set. Finally, determine if all pixel locations have been filled, if so, check and Invoke() your onBackgroundFull event to notify any subscribers.
This derived class will be used in a multi-threaded environment, so take care to protect all data that would not inherently be thread-safe ( collection access anyone ? ).
This class should be primitively tested to ensure set pixel acceptance and rejection work, and your BackgroundFull event correctly notifies subscribers. CTracker should work on whatever scale is set, and correctly handle Reset() operations.
Part II
Create the UI as shown :
The UI consists of a button, NumericUpDown, GroupBox holding 3 RadioButtons, a progress bar and DataGridView and Timer. Ensure your anchoring makes sense.
CMPE2300 – Lab03 Thursday, April 4, 2019
Your Base form will require : • a BindingSource
• a CTracker • a List of Thread ( not used yet )
CTOR : Your DataGridView will make use of CellFormatting, so bind a callback event. Invoke your CreateCanvas() ( next ). Enable and bind a timer callback of 200ms. Bind your DataGridView to your BindingSource.
method CreateCanvas() - Reset everything - close any existing CTracker, allocate a new CTracker, set the scale to your NumericUpDown value ( range : 1 - 100 ). Bind a MouseLeftClickScaled of your CTracker to an event handler. Bind a onBackgroundFull of your CTracker to an event handler. Re-initialize your progress bar to 0, and max to CTracker scaled pixel total count ( W x H ).
Timer Interval Event Callback - perform UI updates. If no CTracker is evident, return. Otherwise, retrieve and bind your ColorInfo data from your CTracker to your Binding Source. Set appropriate properties of your DataGridView to enjoy the best viewing experience - you know..Finally, update the progress bar with currently occupied pixel count.
onBackgroundFull event handler : this notification from your CTracker will be firing in on a different thread, through a delegate Invoke ( not form Invoke() ). Meaning that the firing thread will be blocked from continuing until this event completes... but if you use a form.Invoke() to allow UI updates we have the potential for a Deadlock ( not a new Marvel Hero, rather a stalemate in processing where things stop ). This can be alleviated by using the non-blocking version BeginInvoke() instead. This is acceptable as we do not require a return value, so there is no need to await completion. For now, BeginInvoke to a form method that will provide a MessageBox that the CTracker is full ( this will be used later to reset some threads ).
DataGridView Cell formatting event handler. Your 3 ColorInfo member should be racing through to be processed for display. For the 1st column, the e.Value may be cast to an integer and used to re-construct a Color to set the background with. The 2nd column may pass through unmodified. The 3rd column will hold a double representing the same as the 2nd but modify it to represent the percentage of the entire CTracker that this color currently occupies.
Testing : temporarily populate the CTracker left click handler to blast some background pixels. Write a test method to call from the handler : consider some test requirements. If this is the only Part completed, validation of the CTracker methods and events as well as verifying the DataGridView updates showing CTracker status are required. This will be replaced with the inclusion of Part III, and IV.
Part III
Create an abstract base class called Wanderer. It will “drive” a spawned Thread around its environment based on various rules implemented by derived Wanderers. It shall incorporate a Non-Virtual Interface for its primary Move() functionality.
CMPE2300 – Lab03 Thursday, April 4, 2019
Some core members are required : • a static list of Points – this is the reference collection of possible directions to wander
• a static constructor – populate the 4 core directions : Up, Down, Left and Right. ie. ( 0,1) is Down
• a static Random member • a Stack<> of Point – rather than recursion, we will save our potential path thus allowing
non-recursive backtracking • a Color
• a static public auto property of type CTracker, the CTracker to wander on.
The instance constructor accepts a start point, which is pushed on the Stack, and a color – saved for wandering.
Include an appropriate abstract pair of methods to incorporate NVI with method bool Move()
Dumb Wanderer - Derive a new Wanderer : DumbWanderer
No new members
Override your virtual Move method, and rather than recursion, implement a stack based traversal algoritm. Essentially :
• Location Stack empty ? Done – return false indicating no more moves
• Have a brief Sleep(), must play nice with other threads later... • Obtain Stack of shuffled possible Moves ( ie. our reference direction list )
• Peek/save, but don't remove the next Stack Location Point – we start turn here • Until our Stack of Possible Moves is empty
• Remove next possible Move • Attempt to set the pixel with the Move temporarily applied
• Attempt Successful ? Pixel was added ! • Make Point of our current location, add to location Stack, return true
• Loop failed find a spot, backtrack by Pop'ing location Stack, thereby backing up • return true, next time this is called, we will have moved back a step and will try other
directions looking for a valid move • **When this method returns false, it is done and should not continue to call Move()
This algorithm allows backtracking when a wanderer spirals in on itself or gets cornered.
Virtual method Shuffle, returns a IEnumerable of Point, accepting an IEnumerable of Point. This ultimately will be the “brains” of the Wanderer. Where to wander...first, second, etc. For this DumbWanderer, Shuffle should just take the prepared set of directions, and return a new identical set of directions. The result of this is like Recursive FloodFill - always go Up first, then Down, etc. Very boring, but its Dumb, what do you expect ?
To incorporate Wanderers into the main application we need a Thread method to drive our Wanderers. It should accept a Wanderer, and if valid, Move() it until a false is returned OR the termination flag is set. Put appropriate Diagnostic Trace output indicating the start and stop of
CMPE2300 – Lab03 Thursday, April 4, 2019
the thread ( with ID ). Replace your testcode in the LeftClick handler to find a RandColor not yet used in the CTracker, and create a DumbWanderer at the supplied location. Make a thread appropriately, adding it to your thread collection, and start it with your new Wanderer.
You should be able to start lots of dummies, watch their progress in both the CTracker and DataGridView as they try to consume the entire background. Upon filling you should see your callback complete, at which point you should Kill any existing threads ( use the thread isAlive member ) and the termination flag.
RandomWanderer
Derive a new Wanderer, called RandomWanderer from DumbWanderer.
This derived class will perform the wandering by a more random approach. On each new Move all possible directions will be possible. From the default direction set, remove direction that would return you to your last location ( which, of course will fail anyway ). Update, this removal is optional, as you don't have an easy way to keep a “last” position value.
You must override Shuffle to properly return a Fisher-Yates shuffled possible direction collection to use for wandering. Ensure you comment your approach in performing this operation. You cannot override your virtual Move method to complete this.
The inclusion of Wanderers now requires the completion of the UI and main application to allow a reset [Spawn], whereby the current CTracker is disposed of and a new one is made, all threads are killed and the collection cleared, awaiting clicking in the CTracker to add new Wanderers. Determine which Wanderer is selected and add appropriately on the click.
Part IV
ProbabilityWanderer
Derive a new Wanderer, called ProbabilityWanderer from DumbWanderer.
This derived class will introduce the inclusion of probability biasing in randomizing your possible direction choices. To more easily verify this, no reverse direction removal will be done on the possible direction set. All directions will be possible in this outcome, just not all of equivalent probabilities. To do this a roulette wheel probability algorithm will be used.
Overriding Shuffle() again, this time producing our biased result.
A collection of double initialized to : 0.4, 0.3, 0.2 and 0.1 is required as a member, defining the probability factors relative to their respective direction counterparts. ( ie. Up = 0.4 ).
Using the roulette wheel algorithm, appropriately select a direction one at a time ( biased ), removing it from contention each time until your shuffled directions are populated and
CMPE2300 – Lab03 Thursday, April 4, 2019
returned. Hint : a local copy of your probability values should be made and can be modified along with your direction collection as your make selections.
Obviously this needs to be verified. Keep a collection that counts the frequency a direction is put as first in the collection. Over many iterations, the percentage of each direction should migrate to their respective probability value. Provide a property that returns a string showing each of the probability values and their respective real life actual frequency. Use the form : [ 0.4 : 0.378 ] [ 0.3 : 0.312 ], .. + remaining – to 3 decimal places. Be sure to use the actual probability values, as these will be modified to verify.
In the main application, in your Thread method, if a terminating thread is a ProbabilityWanderer, output the probability statistics with their respective thread ID termination Trace output.
Programming Assumptions
This seems hard, but is actually not.
Programming Requirements / Glossary
1. NMB – No message Box allowed for this output. 2. DAEM – Display an appropriate error message – An appropriate message includes the
method name, error condition, and error state ( what made the error ) wrapped in a MessageBox with appropriate caption and icon.
Program Signoff
Meet the Spec ! Ask if you are unsure that your solution meets the requirements – before signoff.
Hints That troublesome stuff...– TBD - as encountered
Marking Rubric
Part Component Details Mark
Part I CTracker If the only part complete, commented, full test code for verifying CTracker, using a basic UI with buttons for each test.
35
Part II UI with DataGridView
Commented test code, with extra buttons to drive test functions.
15
Part III Wanderer, DumbWanderer, RandomWanderer
30
Part IV ProbabilityWander 20
- Lab 03 – Tracked-Wander-a-thon
- Program Specification
- Part I
- Part II
- Part III
- Part IV
- Programming Assumptions
- Programming Requirements / Glossary
- Program Signoff
- Hints – That troublesome stuff...
- Marking Rubric