|
CSE Home | About Us | Search | Contact Info |
Note: I'm only allowing 2 late days on this one -- late turnin closes Sunday 6/3, 5:00PM.
Overview: Here's a simple task that web browsers, word processing programs and many others routinely solve: Given a sequence of "widgets" (for lack of a better name; things like characters, words, check boxes, radio buttons, icons, etc.) to be displayed on your screen, break them into a sequence of "lines" for display, as well as track their screen coordinates so that, e.g., a mouse click on a check box can be translated into the appropriate action. Ruby integrates nicely with several graphical user interface (GUI) toolkits, but there isn't a universally convenient one, so we're going to model this with a simple character-based one -- just plain character I/O with irb, the Ruby interpreter.
As a useful warm-up, implement a Ruby class HWindow (H for homework) that provides a 2-D "window" in which character strings can be displayed. The underlying data structure can be whatever you want, e.g., an array of arrays of characters (or character strings of length 1). The key advantage of this over ordinary "print" interfaces is that you have random access to arbitrary screen coordinates, instead of being restricted to a left-right/line-by-line order. Computer graphics systems conventionally use a coordinate system with its origin in the upper left hand corner of the screen, with increasing positive x/y values moving successively farther right/down the screen. You should do the same, in units of characters (not pixels). I.e., x=3, y=5 denotes the fourth character position in the sixth row on the screen. Methods:
puts HWindow.new.add(3,0,"lo,").add(0,0,"Hel").add(2,1,"World.") ==> +------- |Hello, | World.(The +-| "framing" characters are not actually printed; just shown here to represent the upper left corner of the virtual window.)
The Widget Hierarchies: Recall that your basic "input" is a sequence of widgets, and a major goal is to pack them into a fixed width window, basically fitting each in left to right order until you've filled the width of the screen, then dropping to the start of the next "line" and continuing with the rest. You'll implement 4 kinds of widget.
There's a natural hierarchy here, with HRadio being a subclass of HCheck which in turn is a subclass of HText.
Finally, it is convenient to have a fifth widget class (HWidget) which is the superclass of both HBox and HText. HWidget is where you gather those attributes and methods that are common to all widgets, such as height, width, and (after layout), their x,y coordinates. Thus, the overall class hierarchy is:
HWidget / \ HText HBox | HCheck | HRadio
Methods: In addition to appropriate initialization methods, widgets should respond to:
blah blah little blah bigger blah box box here next line more blah blah blah.
"User Input": How do check boxes get checked? By "user mouse clicks" of course, which we will simulate by explicitly calling "click(x,y)" where x,y are the coordinates of the mouse click. The desired response is that if x,y is in a checkbox widget (including the displayed text associated with the it, not just the [X] part), then toggle its checked/unchecked state. Use each. Ditto for radio buttons (only part of what radio buttons should do, but again the full deal is extra credit).
Freedom of Choice: Several things were intentionally left vague. (How is a window object associated with a set of widgets. Global variables? A class variable? Extra parameters on some of the method calls shown above? A "singleton" pattern? Other? Should checkboxes have a toggle method or accessor functions? Does each class need its own each method or can inheritance help? Etc.) If in doubt as to whether something is intentionally vague versus just plain vague, by all means ask, but in general you're free to choose how to do these things. Do give some thought to which choices to make. Try to keep everything clean and simple. Try to make best use of OO design principles, exploit inheritance, etc., while also exploiting the unique Ruby features like blocks and iterators.
Examples: Here are some of my tests, picked totally at random. (I made a class called "GUI" to glue it all together, but that's completely optional.)
bigbox = HBox.new(100, [] << HBox.new(43, (%w{Instructor contribution to the course was:} + %w{Overall, the course was:}) . map{|x| HText.new(x)}) << HBox.new(40, [HText.new('Ok')] + [1,2,3,4,5].map{|i| HRadio.new(i.to_s,:contrib)} + [HText.new('Awesome')] + [HText.new('Ok')] + [1,2,3,4,5].map{|i| HRadio.new(i.to_s,:overall)} << HText.new('Awesome')) << HCheck.new('And give the instructor a Big Box Of Chocolate')) gui = GUI.new(100,[bigbox]) Instructor contribution to the course was: Ok ( ) 1 ( ) 2 ( ) 3 ( ) 4 ( ) 5 Awesome Overall, the course was: Ok ( ) 1 ( ) 2 ( ) 3 ( ) 4 ( ) 5 Awesome [ ] And give the instructor a Big Box Of Chocolate puts gui.click(75,0).click(75,1).click(40,2) Instructor contribution to the course was: Ok ( ) 1 ( ) 2 ( ) 3 ( ) 4 (*) 5 Awesome Overall, the course was: Ok ( ) 1 ( ) 2 ( ) 3 ( ) 4 (*) 5 Awesome [X] And give the instructor a Big Box Of ChocolateTest cases: Include a few test cases designed to show that your basic HWindow class works, as well as widget layout and click.
General Comments: My solution has approximately 7 classes with about 3 methods per class (not counting a few attribute accessors). The bodies of the majority of the methods are literally 1 line long, with only two of them more than 5 lines long. Now, code length is a good measure of neither code quality nor code complexity, and I'm certainly not saying that you should aim to duplicate these statistics. I quote them to underscore that this assignment is really about code structure. By exploiting good object-oriented design principles, inheritance etc., you should be able to keep things simple. Where in the hierarchy should this method go? Where/how can it exploit code in the super/sub class? What parameters do I need to pass, versus gather from instance variables, etc.? Again, I think this is an assignment where thinking it through in advance will save you a lot of time.
A suggested plan is to start with HWindow---pick a data structure, decide what initialize needs to do, then add, then to_s. Run some test cases. Understand the difference between puts and to_s, for example. Next, try HWidget and HText. Test by directly calling layout, then puts on the resulting window. How is data/functionality divided between Hwidget and Htext? (It may help to imagine that there will be other widget classes; how much can be factored into HWidget?) Now do HBox and the layout of nested text/box widgets. Are you still happy with your factoring between HWidget and its subclasses, especially as you start to think about checkboxes, etc.? If not, change it before you get mired more deeply. Then do layout/display of checkboxes and radio buttons. Add the each iterator. Add click last.
As I warned you, I'm a Ruby newbie, too, so I don't have a tried-and-true guide to Ruby editing and debugging, but I'll try to jot down a few suggestions: (Click here.) Please send mail to the class list if you have other suggestions.
Grading criteria: As usual, correct functionality is worth about 2/3 of the grade, with the rest for "style" issues including comments, sensible variable names, clarity of code, and especially for appropriate use of object-oriented design features like inheritance.
Extra Credit: Many things suggest themselves, including the following. The required implementations of click in checkboxes and radio buttons specified above are identical. Instead, change it so that clicking a radio button checks the clicked button and unchecks the other buttons in the same radio group. Add methods simulating click-drag to select all widgets in the rectangle between the x1,y1 of the click and x2,y2 where the drag ended. Perhaps uppercase all strings (do ri String#upcase for docs) in the selected region as visual feedback. Add facilities for inserting and deleting widgets dynamically. (Adding +, << and/or []= methods to HBox might be convenient as part of this.) Allow boxes to specify centered or right-justified layout options. Allow resizing the window dynamically. Read about Ruby's TK interface and extend all of this to actually display in a TK window and respond to real mouse clicks there. Etc.! Lots of room for creativity.
Turnin Instructions: Give us a single .rb file, including your test cases. Please include a comment saying how long you spent on this assignment (separately for basic and extra credit). Again, this won't affect your grade, just curious. Include YOUR NAME in a comment at the front of your file. Turn in your file electronically via the Catalyst link on the course home page. There will be slots for 2 turnin files; use the second for an extra-credit solution, if any. (Confine yourself to the basic assignment in the first; debug it thoroughly and save a copy before you start on extra credit.)
Reminder: barring exceptional circumstances, no turnins accepted after 5:00 PM Sunday, 6/3.
Computer Science & Engineering University of Washington Box 352350 Seattle, WA 98195-2350 (206) 543-1695 voice, (206) 543-2969 FAX |