Lab: Classes =================================== Notes on Assignments: - **Notes on GAI**: Note that the course policy is that you should not use generative AI (GAI) without authorization. GAI's are great tools and you should learn how to use it, but they are tools and should be used to facilitate but not replace your learning. If you are suspected to have used GAI tools to generate answers to the assignment questions instead of using it as a learning tool, you may be called up to explain/reproduce your work. If you fail to demonstrate your competence, all your **related assignments throughout the semester will be regraded as 0**. For example, if you fail to produce good code in ``while`` loops in midterm exam, your *lab06 while loop homework and lab* will be re-evaluated. #. Create a dotnet console app project (see :ref:`create-project` if you need to) in your ``introcscs`` directory (``C:\Users\*USERNAME*\workspace\introcscs`` for Windows or ``[COMPUTER]:introcscs [USERNAME]$`` for macOS) ; call it **Ch10ClassesLab**. #. Inside the folder, issue the command ``dotnet new console`` to create the project in the folder. #. Still inside the project directory, type ``code .`` to start VS Code with the folder as the default folder. #. Prepare your code in VS Code using the file ``Program.cs`` to code unless otherwise specified. #. The namespace of this project is *IntroCSCS* but you may use something else such as *IntroCS* as long as it is consistent among classes. #. The class name of this project is *Ch10ClassesLab* if program.cs is used. #. When executing code, you will *mostly* start with the Main() method in a designated class/file. #. You will prepare methods in the same class or project *mainly* to be called from the Main() method. #. Use a Word document to prepare your assignment. #. Number the questions and **annotate** your answers (using // in code) to show your understanding. #. For coding questions, screenshot and paste 1) your code in VS Code and 2) the results of the code's execution (**command prompt** and **username** are part of the execution). .. index:: class; convert static game to instance Converting A Static Game To A Game Instance ---------------------------------------------- - *Study this lab to get yourself familiarized with object-oriented programming*. - Remember that you can only have one Main method in the project. For a comparison of ``procedural`` and ``object-oriented`` coding, consider converting :ref:`lab-number-game` so that a GuessGame is an object, an instance of the GuessGame class. .. While our earlier example, Contact, is a simple but practical .. use of object-oriented programming, GuessGame is somewhat more artificial. .. The game is create it hoping that highlighting the differences between procedural .. and object-oriented presentation is informative. Also, we will see .. with :ref:`interface` that .. there are C# language features that require an .. object rather than just a function and data. .. In :ref:`igame-interface-exercise` you can use a Game object. Here is a procedural game version, example file :repsrc:`static_version/static_version.cs` .. literalinclude:: ../../examples/introcs/static_version/static_version.cs :start-after: chunk :end-before: chunk The project also refers to the library ``class UI``, with the functions we use to save keyboard input. It is all static methods. **copy ui.cs** to your project folder. Is there any reason to make this UI class have its own own instances? **No**. There is no state to remember between UI method calls. What comes in through the keyboard goes out through a return value, and then you are completely done with it. A simple static function works fine each time. *Do not get fancy for nothing*. What state would a game hold? We might set it up so the user chooses the size of the range of choices just once, and remember it for possibly multiple plays of the GuessGame. The variable was ``big`` before, we can keep the name. If we are going to remember it inside our GuessGame instance, then ``big`` needs to become an **instance variable**, and it will be something we can set in a constructor. What actions/methods will this object have? Only one - playing a GuessGame. The GuessGame could be played multiple times, and that action, play, makes sense as a method, Play, which will look a lot like the current static function. In the ``procedural`` version there are several other important variables: - ``Random rand``: That was static before, for good reason: We only need one Random number generator for the whole time the program is running, so one static variable makes sense. - The central number in the procedural Game and our future Play method is ``secret``. Should that be an instance variable? It would work, but it would be unhelpful and misleading: Secret is reset every time the game is played, and it has no meaning after a Play function would be finished. There is nothing to remember *between* time you Play. This is the perfect place for a local variable *as we have now*. A common newbie error is to make things into instance variables, just because you can, when an old-fashioned local variable is all that you need. It is good to have variables leave the programmer's consciousness when they are no longer needed, as a local variable does. An instance variable lingers on, leaving extra places to make errors. This introductory discussion could get you going, making a transformation. Go ahead and make the changes as far as you can: create project GuessGame inside the current solution. Have a class GuessGame for the GuessGame instance, with instance variable ``big`` and method ``Play``. You still need a static ``Main`` method to first create the GuessGame object. You could prompt the user for the value for ``big`` to send to the constructor. Once you have an object, you can call *instance method* ``Play``. What about parameters? What needs to change from the procedural version? .. There is also a video for this section that follows all the way through the steps. A possible final result is in :repsrc:`instance_version/guess_game.cs`. .. _animal-lab: Animal Class Lab ------------------ **Objectives**: Complete a simple (silly) class, with constructor and methods, including a ``ToString`` method, and a separate testing class. Make use of your project folder, copy in the .cs files from the example project :repsrc:`animal_lab_stub`. Then modify the two files as discussed below. #. Complete the simple class Animal in your copy of the file :repsrc:`animal.cs `. The bullets below name and describe the instance variables, constructor, and methods you need to write: * An Animal has a ``name`` and a ``gut``. In our version the ``gut`` is a List of strings describing the contents, in the order eaten. A newly created Animal gets a ``name`` from a parameter passed to the constructor, while the ``gut`` always starts off *empty*. * An Animal has a ``Greet`` method, so an animal named "Froggy" would say (that is, print) Hello, my name is Froggy. * An Animal can ``Eat`` a string naming the food, adding the food to the ``gut``. If Froggy eats "worm" and then "fly", its ``gut`` list contains "worm" and "fly". * An Animal can ``Excrete`` (printing abd removing what was *first* in the gut List). Recall the method ``RemoveAt`` in `List`. Print the empty string, "", if the ``gut`` *was already empty*. Following the Froggy example above, Froggy could ``Excrete``, and "worm" would be printed. Then its ``gut`` would contain only "fly". * A ``ToString`` method (:ref:`ToString`): Pay attention to the ``override`` keyword. Make it return a string in the format shown below for Froggy, including the Animal's name:: Animal: Froggy * Note that all the methods that print should be void. .. Try this first, and note the extra credit version below. #. Complete the file :repsrc:`test_animal.cs ` with its class ``TestAnimal`` containing the ``Main`` method, testing the class Animal: Create a couple of Animals and visibly test all the methods, with enough explanation that someone running the test program, but *not* looking at the code of either file, can see that everything works. .. #. 20% EXTRA CREDIT: Elaborate ``ToString`` so if Froggy had "worm", "fly" .. and "bug" in the gut, the string would be: .. "Animal: Froggy ate worm, fly and bug" with a comma separated list of the gut contents, except use proper English, so the last separator is " and ", not ", ". If the gut has nothing in it, list the contents as "nothing": "Animal: Froggy ate nothing" .. .. index:: class; user class as instance .. example; clock .. .. _clock: .. Clock Example .. ---------------------------------------------- .. Consider the logic for a digital 24 hour clock object, .. type ``Clock``, that shows hours and minutes, .. so 03:45 would be three forty-five. .. Note that there is no AM or PM: The hours go from 00, starting at midnight, .. through hour 23, the 11PM hour, so 23:59 would be a minute before midnight, .. and 13:00 would be 1PM. .. Assume there is some attached circuit to signal when a new minute starts. .. This class could have just a few methods: ``Tick``, .. called when a new minute is signaled, .. and ``GetTimeString`` to return the time in the format illustrated above, .. and ``SetTime`` specifying new values for the hours and minutes. .. We can start .. from a constructor that just sets the clock's time to midnight. .. We can imagine a demonstration class ``ClockDemo`` with a ``Main`` method .. containing .. .. literalinclude:: ../../examples/introcs/clock/clock_demo.cs .. :start-after: chunk .. :end-before: chunk .. It should print .. .. code-block:: none .. Midnight 00:00 .. Before midnight 23:58 .. Tick 23:59 .. Tick 00:00 .. Tick 00:01 .. Tick 00:02 .. A ``Clock`` object will need instance variables. .. One obvious approach would be to have ``int`` .. instance variables for the hours and minutes. Both can be set and can advance .. and will need ot be read. .. These actions are common to both the hours and minutes, .. so we might think how we can avoid writing some things twice. .. There is one main difference: The minutes roll over at 60 .. while the hours roll over at 24. Though the limits are different, .. they are both numbers, so we can store the limit for each, 60 or 24. .. Then the same code could be used to advance each one, just using a different value .. for the rollover limit. .. How would we neatly code this in a way that reuses code? The most significant .. thing to notice is that dealing with minutes involves data (the current count .. and the limit 60) and associated actions: being set, advanced and read. .. The same is true for the hours. .. The combination of *data and tightly associated actions*, particularly used .. in more than one situation, .. suggests a new class of objects, say ``RolloverCounter``. .. Notice the shift in this approach: The instance variables for hours and minutes .. would become instances of the ``RolloverCounter`` class. A ``RolloverCounter`` .. should know how to advance itself. Hence the logic for advancing a counter, .. sometimes rolling it over, would not be directly in the ``Clock`` class, .. but in the ``RolloverCounter`` class. .. So let's think more about what we would want in the ``RolloverCounter`` class. .. What instance variables? Of course we have the current count, .. and since we want the same class to work for both minutes and hours, we .. also need to have the rollover limit. They are both integers. .. The limit should just be set once for a particular counter, .. presumably when the object is created. For simplicity .. we can just assume the count is 0 when a ``RolloverCounter`` .. is first created. Of course we must have a method to let the count .. advance, rolling over back to 0 when the limit it reached. .. Throw in a getter and a setter for the count and we can have the following .. class: .. .. literalinclude:: ../../examples/introcs/clock/rollover_counter.cs .. Note how concise the ``Advance`` method is! With the remainder operation, .. we do not need an ``if`` statement. .. Check examples by hand if this seems strange. .. .. index:: format; 0-pad .. zero pad format .. overloading; constructors .. Finally we introduce the ``Clock`` class itself. .. We display the entire code first, and follow it with comments about a number .. of new features. .. .. literalinclude:: ../../examples/introcs/clock/clock.cs .. #. First the principal reason for this example: We illustrate .. writing a class where the instance variables are objects of a different .. user-defined type. Because the instance variables ``hours`` and .. ``minutes`` are objects, we must initialize them .. using the ``new`` syntax. .. #. Skip over the *second* constructor for now, and see the ``SetTime`` method: .. We call the appropriate method to update the individual .. ``RolloverCounter`` instances. .. #. Now go back to the second constructor. This is not really necessary: .. With the first constructor the calling code could just have one more .. ``SetTime`` line any time you want to .. to create a clock with a time other than midnight. .. We can make a case for .. this being so common, that we want to do it in just one line, .. with a constructor that sets a specified time. .. However, the main excuse was really to illustrate that .. constructors can be *overloaded*, like methods: You can have separate .. constructors with distinct signatures. .. In this case versions with no parameters vs. two ``int`` parameters. .. #. The ``Tick`` method has a bit of logic to it: while the minutes always .. advance, the hours only advance when the minutes roll over to 0. .. #. Finally the ``GetTimeString`` method illustrates a new integer string .. formatting mode: The D2 format specifier applies to an integer, and displays it .. as a minimum of 2 digits, padding on the left with 0's as necessary. .. This is just what we want here. In general the 2 could be replaced by another .. literal integer, so D6 would force at least 6 digits: With the D6 format .. specifier 12 would be formatted as 000012, .. and the longer 1234567 would add no extra 0's: still 1234567. .. The code for all the classes is in project :repsrc:`clock`. .. Admittedly, with this exact functionality and such a concise line to .. advance a count, it would actually have shorter to have done everything .. inside the ``Clock`` class, with no ``RolloverCounter``, but we were looking .. for a simple illustration of combining user-defined types this way, .. and a ``RolloverCounter`` is a clear unified .. concept that can be used in other situations. .. .. Alternate Clock Constructor Exercise .. .. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. .. Make a small change to :repsrc:`clock/clock_demo.cs`, so the second .. .. constructor is tested. .. .. Clock With Seconds Exercise .. .. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. .. Modify the project :repsrc:`clock`, assuming the Tick is for each second, and .. .. the time also show the seconds, like 55 seconds before midnight would be .. .. 23:59:05. .. .. index:: model-view-controller pattern .. Twelve Hour Time Exercise .. -------------------------- .. Modify the project :repsrc:`clock` so a ``GetTimeString12`` method returns .. the 12 hour time with AM or PM, like 11:05PM or 3:45AM. (The hours do .. not have a leading 0 in this format.) .. This could be done modifying a lot of things: .. keeping the actual hours and minutes that you will display .. and remembering AM or PM (with the hours .. being more complicated, not starting at 0). We suggest something else instead: .. This is a good place to note a very useful pattern for programming, called .. *model-view-controller*. The *model* is the way chosen to store the state internally. .. The *controller* has the logic to modify the model as it needs to evolve. .. A *view* of a part of .. the model is something shown to the user that .. does not need to be in the exact same form as the model itself: .. A view just needs to be something that can be *easily calculated* .. from the model, and presumably is desired by the user. .. In this case a simple (and already coded!) way to store and control .. the time model data .. is the minutes and up to 23 hours that do happen to directly correspond .. to the 24 hour clock view. .. The main control is to advance the time, .. and with just two 0-based counts we have the .. very simple remainder formulas. .. So the suggestion is to keep the *internal* data the same way as before. .. Just in the method to create the desire 12-hour view have the logic to do the .. *conversion* of the internal 24-hour model data. .. You could leave in the method to provide the time in the .. 24 hour format, giving the ``Clock`` class user the option to use either view .. of the shared model data. .. To be symmetrical in the naming, .. you might change the original name ``GetTimeString`` to .. ``GetTimeString24``. .. ForceMatch Exercise .. ---------------------- .. Suppose we have a class:: .. class Pair .. { .. private int x,y; .. public Pair(int x, int y) .. { .. this.x = x; this.y = y; .. } .. ///Mutate the parameter so its instance variables match this object .. public void ForceMatch(Pair p) .. { .. // need code ... .. } .. public override string ToString() .. { .. return string.Format("({0}, {1})", x, y); .. } .. } .. A test would be code in another class:: .. Pair first = new Pair(3, 7); .. Pair second = new Pair(1, 9) .. Console.WriteLine(second); // prints (1, 9) .. first.ForceMatch(second); .. Console.WriteLine(second); // prints (3, 7) .. a. Would this code work? If not, explain why not:: .. public void ForceMatch(Pair p) .. { .. p = new Pair(x, y); .. } .. If you do not see it, .. do a graphical play-computer like in the last section. .. b. Complete the body of ``ForceMatch`` correctly.