ftp.nice.ch/pub/next/games/board/Ergo.NIHS.bs.tar.gz#/Ergo/Article/Article.rtf

This is Article.rtf in view mode; [Download] [Up]

{fonttbl{f21fswiss Helvetica;}{f3fswiss Helvetica;}}margl1780margr1800margt0margb0 deftab31680 sectdsbknone linemod0linex0 headery0footery0 cols1 pard li0ri0fi0sl-380 f21b Getting With the Programpar par Building a game 'b1 or any application 'b1 in NeXTstep is a winning propositionpar par by Charles L. Perkinspar Two common myths surround object-oriented programming: the first is that it takes years of training to do it properly.  The second is that object-oriented programming is no more than traditional good programming practices dressed up.  Both are wrong.par Object-orientation is a new way of looking at problems, a new point of view. As such, traditional programmers can't just use it without guidance.  But it doesn't take years 'b1 or even months 'b1 to begin using it.par  par In one sense, object-oriented programming is as old as programming itself. It codifies clever strategies hard won over the decades into a new, coherent whole. It goes beyond those strategies, however, to suggest new ways of viewing the process of programming and of its result.  [See 'aaObject Lessons,'ba i NeXTWORLDi0   Spring 1992, for some history and background on object-oriented programming.]par b0i0 Like any good programming paradigm, the object-oriented approach promises to help programmers simply and quickly create programs that are easier to read, comprehend, maintain, and reuse.  These promises translate into four key goals:par Modularityi0 . Programs are broken into small, comprehensible pieces that can be easily combined and reused in later programs.par Factoringi0 . Each parameter and code fragment appears in one and only one place, to ease modification and later enhancement.par Encapsulationi0 . Information in the program is kept near the parts of the program that manipulate that information, making it easier to change its representation.par Abstractioni0 . Implementation details are kept hidden from the higher levels of the program, so later changes at lower levels do not affect the rest of the program.par Object-oriented programming, though, differs in the approach it takes. Rather than requiring separate language facilities to achieve each of these goals, an object-orientated language's basic elements perform double duty, allowing programmers to restructure the way they think (and solve) their problems.par What is an object, anyway?par par The fundamental unit of an object-oriented program is the object. Objects contain all the information about one small part of the program and all the procedures allowed to manipulate that information. This achieves part of the modularity, factoring, and encapsulation goals right away.par sectdsbknone headery0footery0 cols1 linemod0linex0 An object is also an instance (or member) of a class, which describes all the common behaviour of its members. For example, NeXTstep objects in the class called Window display information on the NeXT computer's screen. Each window on the screen is represented by a different member, or instance, of the Window class.par Every class defines a set of messages that objects of that class can receive. Simple messages, like commands, tell objects what to do. If the programmer sends an orderOut:b0  message to a window, for example, the window takes itself off the screen. Sending the window the message b orderFront:b0  puts the window on top of all of the other windows on the screen. Inside the object, a procedure (called a method) acts to carry out the message.par Much of an object-oriented program consists of objects sending messages to each other, and returning other objects as the result of those messages. Since all the information is inside objects, and the only access to that information is through messages, we have both encapsulated and abstracted that information at the same time. Other objects can refer to the information by a message name, without knowing how that information is actually stored 'b1 or even where.  This model generalizes naturally to messaging over a network, and to parallel computations.par By viewing computation as the interaction of numerous objects, each sending messages to other related objects, the object-oriented model provides powerful metaphors for recasting the tasks of any program via natural and intuitive analogies with the objects that inhabit the real world.  For example, the NeXT MusicKit is inhabited by familiar objects like Notes, Scores, Instruments, Orchestras, and even a Conductor.  Our innate understanding of these objects brings us in closer contact with the world being modeled, and thus with the computer representation of that world.par Objective-C arranges its classes into a tree hierarchy, in which every class inherits the properties of its parent, or superclass, and is then free to make changes to the parent's behaviour. For example, the NeXTstep Panel class is a subclass of the Window class. Panels are like windows except they are removed from the screen when the user clicks the mouse on another application, and they reappear on the screen when the user clicks the mouse on the Panel's application. The Panel class is itself subclassed to make the NeXTstep Menu class. Menus are like Panels, except they always float on top of all other windows, contain a set of menu cells, and can be made submenus of other menus.  Inheritance can contribute to all four goals.par i0 Thinking like an objectpar par Objects in the world around us are defined by their interactions 'b1 pull a doorknob and the door opens; drop a glass and it breaks. This simple model also underlies all object-oriented languages. If an application's goals represent tasks in the real world 'b1 like simulating an airplane, for example 'b1 then they can be naturally implemented in an object-oriented way. One class of object could represent the landing gear, another the fuel tank, and so on. When the simulation runs, the objects all interact by sending messages to each other, and the virtual airplane flies.par Many traditional computer applications can be thought of in the same way.par Within any NeXTstep application, for example, each object that is visible on the screen 'b1 each object you can touch with the mouse 'b1 has an underlying object associated with it that 'aafeels'ba your touch. When a Button object is pressed, a message called b mouseDown:b0  is sent to the button, which then sends another message to its target object to carry out the requested action. It is the interaction of these active objects 'b1 each knowing how to display itself, modify itself, and interact with the other objects on the screen 'b1 that creates the whole NeXT environment.par Ergo, a gamepar par Ergo is an implementation of a simple board game from a video arcade. The program is simple enough to be implemented quickly yet clearly shows many of the characteristics of good object-oriented design. par Ergo is a little like Go, Othello, and Reversi. Players take turns moving a piece of their color either one or two squares. A one-square move replicates the old piece, creating a new piece in the new square. Two-square moves are jumps; the moving piece does not stay behind, but travels into the new square. A piece can't land on a square that is blocked or otherwise occupied (it can, however, jump over such squares). If, in its new position, the moved piece is directly adjacent to pieces of the opposite color, it captures those pieces by switching them to its color (seeb  Figure 1b0 ).par Whenever you design a new NeXT application, always start by designing the most important part: the user interface. Often, you will have a good idea of what behaviour you want for your users but an unclear idea of the internal details. In addition, Interface Builder is an excellent sandbox for trying out ideas and throwing out the bad ones without wasting much effort. Interface Builder will also create and organize all the files you'll need to build and maintain your application over time, so starting there makes sense.par In this case, the interface is dictated by the game we are writing. There must be a game board (a Window) and a whole array of squares, some filled and others not (seeb  Figure 2b0 ). To move, the user clicks the mouse in a filled square, drags it to an empty square and releases the mouse, changing an empty square into a filled one. Each square should 'aafeel'ba the mouse and know whether or not it is filled and whether it can be legally part of the current move. (Notice that we have already begun to somewhat personify the squares, thinking of them as separate, active participants in the game. This is a valuable way to think about the communication between the parts of most object-oriented programs.)par The next step is to think about the various classes of objects we will have in the game; finding natural classes is the most important step in the design effort. If you feel uncomfortable with the choices that you've made, scrap them and start over. It's always better to begin anew than to stick with classes that don't naturally fit your problem.par Since we want to represent the whole board, and some squares on the board have no pieces, we probably want our most abstract 'aapiece objects'ba to be the squares of the board rather than the pieces themselves. This allows us to have both empty and filled squares. Some squares cannot contain pieces 'b1 they're blocked 'b1 and they form a third class of square. Finally, filled squares can contain either black or white pieces. The resulting class hierarchy is shown in b Figure 3b0 .par Notice that to represent the squares of the game board, we've created a new class: AbstractSquare, from which all other square classes will inherit characteristics. AbstractSquare is a subclass of the NeXT AppKit class called View. As a View subclass, objects of the class AbstractSquare (or any of its subclasses) will automatically be able to receive mouse events and draw themselves in windows. par Once you have a hierarchy of classes, the next step in designing any application is to flesh it out with a protocol of key messages that the objects will use for communication and to drive the action of the application. Look at the protocol for Ergo in b Figure 3b0 . From the density of messages, you can see that the design focuses most of the behaviour in the Abstract classes, some in ErgoApp (our subclass of the AppKit's Application object), and very little in the other peripheral classes, which will mostly inherit their behaviour from their superclasses. Cooperation between objects from these various classes will drive the game forward.par Mouse behaviour is mostly handled by the class AbstractFilledSquare, which we would expect since game moves involve manipulating filled squares. Note that the processes of capturing, checking adjacent squares, taking turns, and distinguishing between black and white squares are all suggested even within the simple protocol of b Figure 3b0 .par Finally, we must flesh out the details of the design by implementing it. Learning the tricks and idioms of object-oriented implementation is a matter of experience, but having a natural hierarchy of classes and a sense of freedom about the patterns of message-passing that you will allow between your objects takes you far along the road. In examining the parts of the implementation presented below, you can read between the lines to get a feeling for how the design choices were made. The results will often suggest the approaches underlying them.par Details, detailspar par When any NeXTstep application begins, the program's Application object (or in this case, the program's ErgoApp object) automatically gets sent the b appDidInit:b0  message (see b Listing 1b0 ). ErgoApp's b appDidInit: b0 method sets who goes first (white) and asks all of the squares on the board to initialize themselves by sending each square the same message. This distribution of a function across a whole set of objects is a common theme in object-oriented programming, and we knew we would do it often enough to provide a general message for it: b makeSquaresPerform:b0 .par The method for b makeSquaresPerform:b0  works because every square on the board 'b1 every View 'b1 is really a subview of the master View object that displays the board's window, called the contentView. Every View keeps a List of all of its subviews; to get that List, we simply send that View the b subviews b0 message. That's what the method for b viewList b0 in ErgoApp does. Since all the Views we have in the window are squares of one type or another, this will forward the message to all the squares on the board.par In general, the squares in the Ergo game communicate by broadcasting their messages to every square on the playing board; the only squares that actually do anything are those which can help advance the task at hand. For example, most squares will respond to the broadcast message b appDidInitb0  by setting a tracking rectangle for themselves (b Listing 2b0 ), but BlockedSquares (b Listing 4b0 ) will do nothing. par Setting a tracking rectangle is the way to tell the WindowServer that you want to be told when the mouse enters and exits a particular region on the screen. Tracking rectangles let a View receive b mouseEntered: and b mouseExited:b0  messages in addition to the usual b mouseDown:b0  and b mouseUp:b0  messages received as the user moves and presses the mouse in the View. Since BlockedSquares never wants to receive such messages (it doesn't matter if you move the mouse into a BlockedSquare), they simply override the default appDidInitb0  to not set a tracking rectangle. par Once the tracking rectangles have been set, the rest of the game is entirely event-driven: Only when the user moves into a View or presses the mouse will anything further happen.par Make your movepar par To understand the handling of a move, you should know that each square can potentially be highlighted, and that each filled square can also be selected. Highlighting means that a square can participate in the current move: A square automatically highlights when you move the mouse into it while you are thinking about making a valid move. Highlighting is shown visually as a band of gray just inside the square's border. Selection indicates that a square contains the piece that is trying to move during this turn. It is shown visually as a large gray in the center of the square. par As the user moves the mouse, all filled squares of the proper color will highlight as they are passed over. AbstractFilledSquare's method for b mouseEntered:b0  implements this behaviour (b Listing 3b0 ). As long as either the square is selected, or it is the proper color and no one is selected, the square will highlight itself. The actual highlighting is done by AbstractSquare's method for b setIsHighlighted:b0  (b Listing 2b0 ), which sets the proper state, records it globally if necessary, and then redisplays the square (self) to show the new gray band (see the method for b drawSelf:b0 ). This globally recorded state, handled by our application object (NXApp), is one of only two or three small bits of state information we were unable to distribute across the squares. b Listing 2b0  also contains the default mouseExited:b0  implementation, which, if highlighted, unhighlights the square. Placing this here allows the two subclasses below that use mouse movement to share this one implementation.par When the user pushes down on the mouse, any non-filled square will ignore it (since they don't implement a mouseDown:b0  method). But properly colored filled squares will handle a b mouseDown:b0  message (b Listing 3b0 ) by selecting themselves. When the b setIsSelected:b0  message is sent, another piece of globally recorded state is changed. This global state is the key to what happens next: When the mouse now moves about, filled squares will again receive b mouseEntered:b0  messages, but this time b isSomeoneSelectedb0  (b Listing 1b0 ) will be true, so only the originally selected filled square will highlight (allowing a player to drop the piece back on the original square).par Now we would like all legal empty squares to highlight themselves so the user can see the legal squares to move to. For this, the other implementation of b mouseEntered:b0 , in EmptySquare (b Listing 4b0 ), becomes important. In that method, the empty square asks all squares to check their adjacency with itself. If one of them finds that it is adjacent and has a legal move to the empty square sending the message, it will send that empty square back the message b foundAdjacentb0  (just below in b Listing 4b0 ). This, in turn, causes that empty square (where we started) to highlight itself. par To see how b checkAdjancenyWith:b0  works, we'll begin in AbstractSquare (b Listing 2b0 ). Most squares simple ignore the message. However, filled squares (b Listing 3b0 ) override that version to ask themselves, 'aaAm I selected and am I two squares away (or less) from the sender?'ba An affirmative answer means that the filled square has a legal move to the sending empty square, and it tells the empty square so. The empty square may get many affirmative answers, but this won't matter.par The user, who is still holding down and moving the mouse, can now see all legal squares where the selected piece can move. Eventually the user gets tired and releases the mouse, which always sends a b mouseUp:b0  message to the same object that received the b mouseDown:b0 . This has to be a filled square. Thus, the rest of the rules of the game are implemented in the method for b mouseUp: b0 (b Listing 3b0 ).par Mouse up!par par When the mouse is released, the square that the user wanted to move into should still be highlighted. If that square is the same square that received the b mouseUp:b0  message, we do nothing 'b1 this is not a move, since the user pressed and released the mouse in the same square.par Otherwise, if the EmptySquare is still highlighted, we can perform a legal move. First, we remember the class of the highlighted square and how far we are away from it. Then we ask all the squares to perform a b capture:b0  with the highlighted square (an EmptySquare), after we have made it b become:b0  the same class that we are (this makes it a filled square of our color). We'll explore capturing in a moment. If we have jumped from more than one square away, we must vacate our old location (we b become:b0  the class of the empty square we just moved to, i.e., we become empty). Otherwise, we need only deselect ourselves and stay where we are (we have replicated ourselves). Finally, we ask the ErgoApp object to b letOtherColorMoveb0 .par The last two details of a move are capturing and taking turns. Most squares do nothing for a b capture:b0  (see b Listing 2b0 ). In AbstractFilledSquare (b Listing 3b0 ), though, a filled square tests whether it should be captured by the sender, a matter of being a different color (class) and of being exactly one square away. If it should be captured, it will b become:b0  the same class as the sender (change to the sender's color). Many filled squares can be captured by the same sender in this way. It is a rather simple and elegant way to express the capture rule.par Turn-taking is handled by ErgoApp (b Listing 1b0 ) via the message b letOtherColorMoveb0 . The implementation inverts the answer that b isWhiteMoveb0  will return and simply redisplays the board. Turn-taking alternates forever, since this version of the method does not check for the end of the game (see the sidebar on b Extending Ergob0 ).par On becomingpar par When programming in an object-oriented language, situations frequently arise in which one object wants to literally become another object. Often the objects will simply switch identities. Our b become:b0  (b Listing 2b0 ) has an interesting implementation. Since we want to preserve the 'aaidentity'ba of the square, we record and pass along all the state information associated with the square, but assign them to a square of an entirely different class. In an unusual move, the original object then frees itself (by sending itself the b freeb0  message) and returns the newly created object in its place.par The final point of some interest is the implementation of the subclasses BlackFilledSquare and WhiteFilledSquare. Rather than having an instance-dependent variable hold the black/white distinction, we use the class hierarchy to store the information directly.  For example, in class WhiteFilledSquare, the  b colorb0  message returns NX_WHITE explicitly; in BlackFilledSquare the same message returns NX_BLACK.  One nice side-effect: Any new classes created later can choose to return a shade of gray without changing the rest of Ergo.par Hiding an object's state behind messages may seem frivolous at first, but there is power in the abstraction: We can choose to implement b colorb0 's response as a state-variable lookup, a computed value, or as a constant (as it is here). Any of these implementations would behave identically under the protocol of messages we have defined; if the way b colorb0  is implemented changes, nothing would have to be changed in the other objects that use the b colorb0  message. Flexibility like this, which enhances the reusability of a class, begins to demonstrate the important benefits object orientation brings to application development, maintenance, and reuse.par The winning waypar par Whenever you design an application, start by asking yourself how the tasks could best be broken up and distributed to a whole host of small, cooperating objects, each of which does one thing well. par Whenever you set out to design an object for your application, ask yourself what other purposes it could be used for, how best it could be extended to more fully generalize the one function needed today, and what implementation will be clean and clear enough that you will be able to reuse it in the future. par The simple classes in Ergo have each tried to live up to these standards. As a bonus, you may discover that your application itself is now general enough to be reused by your users within the larger NeXT environment. Good object-oriented design can be applied to all levels.par Object-oriented thinking can help you achieve simple, natural, and elegant solutions. The old 90/10 rule should tell you that most of your program can afford the abstraction and clarity that a high-level, object-oriented design will bring. Don't settle for the familiar or traditional; that leads to programs that are unique, fragile, and difficult to share. Use object-oriented programming for your next project, and begin to build for the future.par 

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.