Sie sind auf Seite 1von 10

EX-7, EX-8, EX-9: Object-Oriented Methods

EECS 110 Northwestern University Jack Tumblin 2/22/2013 revised 2/27/2013

Towards the end of EECS110 we combine pointers, structures, dynamically-allocated arrays, and pass-byreference functions into good habits for object-oriented programming. While the PPT lecture notes may help explain, youll learn best by writing code to practice the methods yourself. In addition to this exercise, I encourage you to experiment -- extend the code we wrote in class (see instructions on Blackboard Documents In-Class Starter Code), and in these last graded exercises into object-oriented forms. Work on EX-07, EX-08, and EX-09 in order. Each exercise can build on your work for the previous one.

EX-7: Two (messy) Robot-Drawing Functions


Before you begin, please review the lecture notes for the week (Lecture Notes 7a,7b,7c), the assigned reading, and your in-class work and notes. Make your own copy of the starter code program 2013.Ex07_JointedRobot04 (posted alongside this document on Blackboard). This unstructured starter code, plagued with global variables (bad idea) draws a robot with jointed, movable arms, and : uses keyboard-adjustable global variables to set all the descriptors for just this one robot: its position, size, tilt and arm angle,etc., contains sensible #define directives for all the robot-shape-defining literals (e.g. robots width, height, arm length, etc.), in its myDisplay() callback function, uses these introGlutLib function calls: resetDrawingAxes(), moveDrawingAxes(), saveDrawingAxes() and retrieveDrawingAxes() to assemble the robot shape from connected rectangles.

Your goal is to reorganize this code in two ways, each one encapsulating all robot drawing code to make it re-usable for many robots. You must use these two required function prototypes:
void drawRobotVal(double xpos, double ypos, double tilt, double size, double shoulderL, double shoulderR, double elbowL, double elbowR, char *pName, double bodyColorR, double bodyColorG, double bodyColorB, double armLcolorR, double armLcolorG, double armLcolorB, double armRcolorR, double armRcolorG, double armRcolorB ); void drawRobotRef(double *pX, double *pY, double *pTilt, double *pSize, double *pShoulderL, double *pShoulderR, double *pElbowL, double *pElbowR, char *pName, double *bodyColr, double *armLColr, double *armRColr );
2/27/2013: SIMPLIFIED ---changed *bodyColr[] *bodyColr, *armLColr[] *armLColr, *armRColr[] *armRColr CAREFUL! Each new formal parameter expects arguments whose value is an address in memory at the start of a 3-element array of double. That array holds the colors: red in element 0, green in element 1, blue in element 2.

Your task is: STEP 1. Convert the starter codes long and messy sequence of robot-drawing statements found in myDisplay() into just one robot-drawing function named DrawRobotVal(). This pass-by-value function will be a bit messy and tedious, but will group together all the tasks of drawing a robot into one reusable function. Your function must (mandatory!) use the exact function prototype given above. Replace the mass of robot-drawing-related statements in myDisplay() with just one call to your DrawRobotVal() function, using global variables as arguments. Unlike the callback functions, your DrawRobotVal() function body must not contain ANY global variables! None! (Why? Global variables are a bad idea; any function can change them, and functions that use them cant do anything else). Instead, the function must receive all its changeable drawing values as arguments, and its formal parameters must have names different from the arguments used to call the function. Once complete, you will have a single robot-drawing function that can be called with different argument values to draw different robots at different on-screen locations. This will be your first of several jointedobject drawing functions; it will have many formal parameters (too many!), and void return type. STEP 2. Make a SECOND set of global variables (similar to the existing set) to describe a SECOND, visibly different robot that you could draw with a call to your drawRobotVal() function. Just as we did for the first robot, in main() you should set the initial values for these global variables that define this second robot. This second robot must have different color values, different values for position, tilt, size, arm angles, elbow angles, names, etc. Test your second set of robot-describing variables by using them as arguments in a call to drawRobotVal() within the myDisplay() callback function. STEP 3. Make a SECOND robot-drawing function named DrawRobotRef(). This pass-by-reference function resembles your existing DrawRobotVal() function, but accepts only pointers (or address values) as its arguments. Your function must use (mandatory) the exact function prototype given above STEP 1. (WARNING! Your DrawRobotRef() function must not call DrawRobotVal(). Both functions must work independently; you must be able to delete either one without affecting the other.) STEP 4. Modify your code to draw the first robot with DrawRobotVal(), and draw the second, different robot with DrawRobotRef(). Make sure your code compiles and runs. On CodeCritic, submit only the contents of your jointedRobot.c file dont submit any other files contents. STEP 5. OPTIONAL: Create your OWN pass-by-reference jointed object drawing function. Draw something substantially different that the robot supplied: some other animal, machine, or jointed shape, from octopus to tarantula to eagle or helicopter to fanciful space-ship with robot arms. Your function must use pass-byreference to supply all controllable drawing features, and must include the ability to change x,y position, size, rotation, joint angles, and color(s). Your program must also allow users to adjust those values using the keyboard and/or mouse.

OPTIONAL TUTORIAL: Push-Down Stacks


or: How can I draw my own jointed objects on-screen? SKIP THIS if youre short on time.

Background: Push-Down Stack Abstraction


Previous starter code showed us how to draw pictures with the introGlutLib library, and how callback functions let our picture-drawing programs respond to user inputs from the mouse, keyboard, and windowing system. In class recently, we explored ways to change the drawing axes used to make those pictures, as illustrated here by this offset box-spiral. At the start of myDisplay(), we called the library function ResetDrawingAxes(); to reset the drawing axes to the lower-left corner, with the x axis extending leftwards, y axis upwards, and distances expressed in pixels. We then called the MoveDrawingAxes() function to shift, rotate, and scale the current axes, followed by shape-drawing functions from the introGlutLib library, such as DrawBox(). Remember that the MoveDrawingAxes() calls act cumulatively. As we showed in class, we can make a pretty twisted spiral of nested boxes by repeated calls to MoveDrawingAxes() and DrawBox(). First, we moved the drawing axes from their original lower-leftcorner location to a location closer to the center of the screen (e.g. 300,300), then we then drew a box centered at the origin (e.g. DrawBox(-100,-100,100,100); caused the drawing axes to rotate and shrink slightly (e.g. MoveDrawingAxes(0.0, 0.0, 10.0, 0.95);) and repeated these draw-and-shrink steps a few tens of times. Next, we will learn how to selectively store and retrieve successive drawing axes in a way that enables us to create movable, jointed shapes on-screen. The robot-drawing starter code for this exercise demonstrates a new abstract data type (ADT) called a push-down stack used to store and retrieve drawing axes. Unlike built-in types (char, int, float, double), an abstract data type is one we that construct from others: we a) define what data it holds, and b) define the rules we follow for accessing and changing that data. For example, for the string abstract data type we a) define a string as a sequence of characters terminated by NULL, and b) agree that the string will be stored as a forward sequence in memory (e.g. that the char just before NULL is the LAST letter in the string, not the first), and we use these rules consistently in all string manipulating functions such as strlen(), strcat(), strcmp(), etc. A push-down stack is an abstract data type that lets you save and retrieve stored items one at a time, in last-in, first-out fashion. Unlike an array, a stack has no index, and it is analogous to a stack of heavy dinner plates in a spring-lifted dispenser in a cafeteria. Like the dispenser, the push-down stack allows you to perform only two operations: you may retrieve one plate from the top of the stack, and the spring will lift, or pop up the next plate to replace the one you took, or you may store one plate on the stack; you must place it on the existing stack, and push down the entire stack to put your new plate in the top position. You can store or retrieve the current drawing axes on a push-down stack by calling StoreDrawingAxes() or RetrieveDrawingAxes() respectively (functions from the introGlutLib library), in addition to the ResetDrawingAxes() and MoveDrawingAxes() functions we use already.

Drawing Jointed Objects


This push-down stack of drawing axes lets us construct jointed objects easily, as shown in starter code 2013.Ex07_JointedRobot04.zip. Compile and run the program. Try the arrow keys (moves robot left,right,up,down), the PgUp/PgDn keys (makes robot grow/shrink), the 1,2 keys (tilts robot left/right), the 3,4 keys (swings robot arms left/right), the 5,6 keys (pivots lower arms at the elbow) and the 7 key (makes left, right arms symmetric). Next, examine the code in the myDisplay() callback function. First we call ResetDrawingAxes(), which empties our push-down stack (no plates), and sets our current drawing axes upright at the lowerleft corner with no scaling; we draw everything in pixel units. Next, We call MoveDrawingAxes() to the desired robot location and SAVE these robot-location axes on the stack by calling StoreDrawingAxes(). Now the stack holds one plate that we can retrieve later it holds the the robot-location axes. Next, we modify the current drawing axes: we call MoveDrawingAxes() to rotate and scale them according to rob_tilt and rob_size global variables. (Note that the numbers used to call DrawBox() dont change, but the drawing axes do; we move, rotate, shrink or grow the axes to make the boxes drawn appear different on-screen.) These new drawing axes define the center of the robot body, with y-axis pointing upwards along the robots spine. We draw the gray-colored robot body as an axis-aligned rectangle, centered at the origin of these robot-body-axes. We will need to return to these robot-body-axes later, so we save them on the stack as well. How? we again call SaveDrawingAxes(): now the stack holds two plates: the robot-location axes at the bottom of the stack, and the robot-body-axes on top. Now we are ready to draw the robots left arm. Using the current robot-center drawing axes, we create a new current drawing axis at the same size and same location, but move it by amounts measured on the axes we copied. We shift the new drawing axes to the upper left corner of the body to the shoulder-joint location, and rotate the axes by the left-arm angle (global variable rob_shoulderL). The new drawing axes are located at the robot shoulder pivot point, and rotated so that the robots arm can stretch from the origin point (the shoulder) outwards along the minus-x direction. We draw the upper portion of left arm with a DrawBox() call. Repeat the process to draw the lower part of the arm, using the rob_elbowL value to set its rotation angle. To move our drawing axes to the right shoulder to draw the right arm, we will need to go back to drawing axes we stored earlier. Call RetrieveDrawingAxes() to get the plate at the top of the stack; it retrieves the robot-body-axes aligned with the robots spine. Now the only plate on the stack holds just the robot-location drawing axes, which do not include any effects of robot tilting or any left-arm drawing operations. We retrieved the robot-spine axes, but we will need them again if we want to draw other limbs, such as a robot head or robot legs. Before we do anything else, lets store them again on the stack; call SaveDrawingAxes()to again leave 2 plates on the stack, with robot-spine axes on top.

From here, we can draw the right arm just as easily as we drew the left arm: move drawing axes from robot-spine to the right-shoulder location and rotate these new drawing axes by the rightarm angle rob_ shoulderR. Now the our drawing axes are located at the robots right shoulder, and rotated so that the robots right arm stretches from the origin point (the shoulder) outwards along the +x direction. We can now draw the right arm using a simple call to DrawBox(), and drawn the right elbow and lower arm by following the same methods we used for the left arm. The starter code has finished the drawing here we need only to remove the two plates from the stack to avoid overflowing the stack as the program runs, and then we could safely leave myDisplay(). But suppose you wished to attach a 2-fingered hand at the end of the right arm: how would you do it? How would you add pivoting legs to the robot? Could you make knees in those legs? Hands at the ends of the arms? Fingers at the ends of the hands? Feet and toes at the ends of the legs? Lobster claws? A head with eyes on stalks and moth-like antennae? Briefly, lets trace through the process of making a 2-fingered right hand. Ask first: will you need to return to the current right shoulder drawing axes to complete your drawing? Put another way, will any other shapes attach to this current joint, to this shoulder?) If yes, then save your current drawing axes on the push-down stack. Next, create wrist drawing axes: move the drawing axes to the end of the arm and rotate by a palm angle variable, then draw the hands palm shape (perhaps a triangle) extending from the wrists origin. We will need to return to the wrist coordinates to draw each finger, so you must save its drawing axes on the stack before you further modify your drawing axes. Next, move the drawing axes from the wrist to the base of the first finger and rotate by a fingerangle variable to make the finger1 drawing axes. Draw the finger extending from the origin. Next, retrieve the wrist coordinates, save them again (in case we want to make more fingers), and move the drawing axes outwards to the pivot point for the second finger; draw finger. Continue the process for all fingers on that hand. Thus with sensible use of a push-down stack for intermediate drawing axes, you can make ANY jointed object and animate it on-screen!

HINTS: Always begin the myDrawing() callback function with ResetDrawingAxes(), to ensure that the drawing-axes are at a known location and that the drawing-axes stack is empty. Always call SaveDrawingAxes() and RetrieveDrawingAxes() in pairs in your code: this ensures you wont try to remove any axes from an empty stack. I always indent the statements between them too (see starter code), to help better illustrate the stack contents. Overflow/Underflow: The introGlutLib library keeps only the 32 most-recent drawing axes that were stored by SaveDrawingAxes(); if you save more than 32, introGlutlib discards the oldest stored values. If you call RetrieveDrawingAxes() too many times, you only get another copy of the most-recently-saved drawing axes.

EX-8: Object-Oriented Structures


Before you begin, please review the lecture notes for the week (Lecture Notes 8a,8b,8c), the assigned reading, and your in-class work and notes. In Exercise 07 you created two (optional: three) messy but object-oriented functions for the JointedRobot starter-code project. In this exercise, you will devise your own object-oriented structure that enables you to describe all the attributes of each robot you make in just one single variable. Using this structure, you will then replace your earlier, messy functions with a much simpler, cleaner, object-oriented robotdrawing function, and your earlier messy sets of many global variables with just two objects.

BACKGROUND:
The goal of this and the next project is to give you some practice with structure types (struct), and you will draw whole families of responsive moving objects on-screen to practice some object-oriented programming principles. Recall that a structure is NOT a variable, but a data type; a new kind of variable, just as int, char, float, and double are each a kind of variable. A variable whose data-type is a struct should be considered a packet that contains several member variables. and each member has its own value. As described in Chapter 12, in class, and in the lecture notes, using structures always requires these three steps in your program: 1. Define the structure data type. Specify the set of member variables that your structure data type will contain; you must give them names and specify the data type of each member. For example, in Lecture Notes 7C we define a new structure data type called workerT that contained a pointer-tochar member variable for the name string, a double member variable for salary, an array of char for the ssn number, and an int member variable ded for deductions. 2. Declare Variables using the newly-defined structure data type. In the lecture notes we declared variables emp1, staff[30]. Note that emp1 is a single variable of type workerT, and staff[30]; is an array of 30 elements, each one a single variable of type workerT. 3. Initialize and Use the struct Variables. As with any variable, we must set the initial values; here, we must set each member variables value individually.

Swimming Fish Show you how


The graphical starter code posted on Blackboard with this document includes CodeBlocks projects with the names 2013.Ex08_FishFancy02 and 2013.Ex08_FishFancy03; please compile and run each one of them. The FishFancy02 project demonstrates how to create a fishT structure to hold all the attributes of a fish in one single packet of variables, and one shortcut method to make a simple, clean object-oriented drawing function. In a well-organized object-oriented program, all the object attributes are held within the objects themselves, rather than scattered among miscellaneous variables and arrays. With all attributes grouped together like this, just one variable of type fishT describes the current state of one entire fish object,

rather than a scattered mess of different variables. As for any structure in C, we defined the new data type first (see typedef statement), then used the new type to declare two new (global) fish-like variables named nemo and mack. In main() we set all the initial values for the nemo and mack variables to define the nemo object as a small, slow-moving green fish and the mack object as a large, fast-moving blue fish. The myDisplay() callback function contains calls to DrawSwimmingFishT() for both the nemo and the mack objects. In addition to bundling together fish-related variables, we can also bundle together the behaviors of fishT objects by introducing efficient pass-by-reference functions with fish-related names. Whenever possible, these functions should accept ONLY a fishT object, and perform that objects behavior using only members of the fishT struct, as we did in DrawSwimmingFishT(). These functions are efficient because they only need to accept one struct argument using pass-byreference: as the functions argument is just the address of one, unified, great big fishT variable, it copies only that address to the functions formal parameter, instead of the structure itself. In computer-science terminology, the nemo object is not just the nemo variableit is the COMBINATION of the nemo variable AND the fishT-related functions that implement all of nemos behaviors. Thus an object is a bundle of attributes and behaviors that act together to define one particular fishs participation in our program. In C++, we extend the notion of a structure to include both member variables and member functions this struct-and-its-functions grouping is called a class. In programs written in C, we can imitate this grouping by a) using structures and by b) giving fish-related names to functions that accept a fishT struct to carry out fish-related behaviors for a single fishT object.

Exercise 08 Instructions:
Write a newer, object-oriented version of the JointedRobot program you made for EX 07. In this newer version, you must: 1. Create your own RobotT structure data type; define it above main(), and make sure it contains all the member variables it needs to completely describe a robot object. 2. Declare two new global variables of type RobotT named jim and pam (e.g. above main(), write this C statement: RobotT jim,pam; ) 3. Initialize all member variables of jim and pam in main().
4. In the myDisplay() callback, call your drawRobotVal() function that you wrote in Exercise

07 to draw the Jim and Pam objects on-screen. (never use any global variables inside either the drawRobotVal() or drawRobotRef() function bodies!). You will need to use the Jim and Pams member variables somehow as the arguments to drawRobotVal() see the FishFancy02 starter code if youre puzzled about how to do this. \

Now that your program draws jim and pam on-screen correctly, your next step is to write a neat, clean, object-oriented drawing function for RobotT. This pass-by-reference function accepts just one argumentthe memory location of the RobotT that you wish to draw on-screen. 5. Create your own RobotT drawing function. Use this (REQUIRED) prototype: void drawRobotT( RobotT *pSrc); Write the function and test it; you may wish to copy and modify code from your earlier robotdrawng functions. In your myDisplay() callback function, call drawRobotT() to draw the jim and pam RobotT objects. Once this works properly, 6. Remove/Delete all the old, now-unused, unstructured robot-drawing variables, so that jim and pam are the ONLY robot-describing global variables that remain. (e.g. delete the separate global variables rob_xpos, rob_ypos, rob_tilt, rob_size, etc. used to draw any and all non-jim, non-pam robots. Next, 7. Remove/Delete all the old, unstructured robot-drawing functions you wrote in Exercise 7. Yes, thats rightyou must now delete drawRobotVal() and drawRobotRef(). No, you cant use the trick shown in FishFancy02 of calling the old messy functions from the new, clean functions. This forces you to make a drawRobotT() that is clean, simple, self-contained and complete. It becomes your only available robot-drawing function, and it does everything you need. With this cleaned up object-oriented way to describe and draw robots, your program is simpler and far more flexible/expandable. Lets expand it: lets write more object-oriented, pass-by-reference functions to create more robot behaviors and activities: 8. Write a new RobotT-initializing function using this (REQUIRED) prototype: void initRandomRobotT( RobotT *pSrc); This pass-by-reference function sets the values of all the RobotT member variables to sensible but randomly-chosen values. (unsure how? Look at FishFancy03 for an example). Test it on jim and pam to be sure it works correctly. Every time you start the program, every time you apply initRandomRobotT() to any existing RobotT object and display it, you should see a different, randomly-chosen robot. 9. Create a fixed array of at least 20 RobotT objects (e.g. RobotT danceTeam[25]; ) --initialize all the robot objects in this array using repeated calls to initRandomRobot(), --draw all the robot objects in this array using repeated calls to drawRobotT(); --make sure all members of this array respond to keyboard inputs in the same way as the original Limbo robot did, before we created jim and pam. With all robots responding to the 1,2,3,4,5,6 and 7 keys, all members of your crowd of robot objects should appear to dance in unison! 10. On CodeCritic, submit the contents of your modified jointedRobot.c program file do not submit any other files contents, or any earlier versions of your code.

EX-9: Object Grouping and Crowd Control


This goal of this final exercise of the course is to create and destroy RobotT objects as needed, while our program runs. We will use nested data structures and dynamic memory allocation to replace the danceTeam fixed array in Exercise 09 with two or more CrowdT objects that contain user-adjustable collections of RobotT objects.

Exercise 09 Instructions:
Make a copy of the final version of your Exercise08 code, re-name it, and make the modifications described below. Again, the FishFancy03 starter code can act as your guide, as it accomplishes most of the goals described here using fishT and schoolT objects instead of RobotT and crowdT objects. 1. First, define your own CrowdT data structure that will hold a dynamically-allocated collection of RobotT objects, along with anything else we may need to describe and control a crowd of dancing RobotTs. As shown in the FishFancy03 example project, this new datatype can be very simple; its members could be just a pointer-to-RobotT, used to retain the location of a dynamicallyallocated array of RobotT objects, and an integer to keep track of the number of RobotT objects in our crowd. Add more members for additional features if you wish. 2. Next, make two global variables of type CrowdT named jets and sharks (Names of competing dancing street-gangs in the old Broadway musical & movie West-Side Story) 3. Using the FishFancy03 code as your guide and example, write a set of object-oriented pass-by-reference functions that: a)--safely initialize a CrowdT object (in FishFancy03, look at initSchoolT() function) b)--lets users safely change the number of RobotT objects held within a CrowdT object (in FishFancy03, look at the ResizeSchoolT() function. Note that it combines the tasks of initializing and re-sizing a dynamically allocated array. c)--draws on-screen all the RobotT objects contained in any given CrowdT object. For this function, you must use this (REQUIRED) function prototype: void drawCrowdT( CrowdT *pSrc); 4. As demonstrated in the FancyFish03 project, when users press the J or j keys, your program should ask for the number of RobotT objects you want in the CrowdT object named jets, make that change immediately on-screen. Similarly when users press the S or s key, your program should ask for the numberof RobotT objects to use in the CrowdT object named sharks, and then make that change immediately on-screen. Note that this use of scanf() causes your programs graphics window to freeze until you: a) select the console window, and then b) enter a number and hit ENTER. After that, the graphics display window starts working again, and your program no longer responds to the console window. To get another keystroke response, you must select the graphics window

again. 5. ENTIRELY ELIMINATE any and all previous individual RobotT object (no jim, no pam), and/or any fixed arrays of any other RobotT objects. 6. Write additional pass-by-reference functions that accept only (the address of) a CrowdT objects that will cause all RobotT objects in that crowdT object to respond to keyboard inputs (arrow keys, PageUp/Dn,1,2,3,4,5,6,7) in the same way as the original Limbo robot in the original JointedRobot04 starter code you used in Exercise 07.

You are welcome to follow the fancyFish03 method as closely as you wish (guaranteed to work, guaranteed to earn full credit if you do it that way) to accomplish step 4, but I hope you will recognize that you could make a myriad of other choices here. For example, you could write separate initialization and size-adjusting functions, or decide to perform your own buffer management (keep a dynamic array larger than the requested number of RobotT objects, show only the number of objects the user requested, and call malloc() ONLY when users ask for more memory than you hold in your current array), etc. 7. On CodeCritic, submit the contents of your modified jointedRobot.c program file do not submit any other files contents, or any previous versions of this file.

Das könnte Ihnen auch gefallen