# Java Programming for absolute beginner- P23

Chia sẻ: Cong Thanh | Ngày: | Loại File: PDF | Số trang:20

0
44
lượt xem
4

## Java Programming for absolute beginner- P23

Mô tả tài liệu

Java Programming for absolute beginner- P23:Hello and welcome to Java Programming for the Absolute Beginner. You probably already have a good understanding of how to use your computer. These days it’s hard to find someone who doesn’t, given the importance of computers in today’s world. Learning to control your computer intimately is what will separate you from the pack! By reading this book, you learn how to accomplish just that through the magic of programming.

Chủ đề:

Bình luận(0)

Lưu

## Nội dung Text: Java Programming for absolute beginner- P23

1. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 398 398 Registering PlayAreaListeners Java Programming for the Absolute Beginner The registering of PlayAreaListeners takes place in the PlayArea class. You won’t see the entire source listing for PlayArea.java until a bit later, but I feel it is most relevant to discuss how it registers listeners here, and how it fires events in the next section, “Firing PlayAreaEvents.” Registering the listeners is not that com- plicated. Basically, PlayArea uses a Vector to maintain a dynamic list of PlayAre- aListeners: protected Vector listeners; The PlayArea class also provides two methods for updating this Vector object, addPlayAreaListener(PlayAreaListener) and removePlayAreaListener(Pla- yAreaListener). addPlayAreaListener(PlayAreaListener) adds the passed Pla- yAreaListener, any class that implements the PlayAreaListener interface, to listeners and removePlayAreaListener(PlayAreaListener) removes the speci- fied PlayAreaListener. Here is what they look like: public void addPlayAreaListener(PlayAreaListener pal) { listeners.addElement(pal); } public void removePlayAreaListener(PlayAreaListener pal) { listeners.removeElement(pal); } You can see that they work just by calling the Vector class’s methods. That’s all it takes. Now you have a list of nosy classes that you need to notify when you fire PlayAreaEvents. Firing PlayAreaEvents You fire PlayAreaEvents from the PlayArea class by creating an instance of the PlayAreaEvent class and then calling the registered PlayAreaListeners’ block- Landed(BlockEvent) methods. The PlayArea class has a private method for doing this: private void fireBlockLanded(PlayAreaEvent pae) { for (int l = 0; l < listeners.size(); l++) { ((PlayAreaListener)listeners.elementAt(l)).blockLanded(pae); } } fireBlockLanded(PlayAreaEvent) loops on the elements in the listeners Vector and calls their blockLanded(PlayAreaEvent) methods, passing in the given pae reference. As you can see, the PlayAreaEvent object must be created prior to call- ing fireBlockLanded(PlayAreaEvent). It does this by counting the number of rows completed and checking whether the block is out of area and passing those values along with this as the source of the event (meaning this PlayArea object TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
2. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 399 399 was the source of the event) and passing these values to the PlayAreaEvent con- structor method. I’ll talk more about the details of creating and firing Pla- Chapter 11 yAreaEvents, such as firing them in their own threads, in the next section, “Creating the PlayArea Class.” Creating the PlayArea Class The PlayArea class is the most significant part of this project. It extends the Custom Event Handling and File I/O BlockGrid class, which provides the graphical representation and already defines the methods for adding, removing, flipping, and painting blocks. The PlayArea class adds the capability to accept user input (as keyboard commands). It also implements the Runnable interface and animates the block. It animates falling blocks as time goes by and also repaints the blocks as users interact with them, flipping them around and such. It also handles the clearing of rows as they are completed and fires events, informing PlayAreaListeners, of them. Inner Classes The PlayArea class defines inner classes. Now is a good a time as any to start talk- ing about inner classes because I’m close to the end of the book. An inner class is a class that is defined within another class, that is, within the curly braces of some other class, which is called the outer class. Inner classes are also sometimes called nested classes. Consider the following simple example: public class Outer { private int x; Inner inner; public Outer() { inner = new Inner(); inner.innerMethod(); } public class Inner { private int y; public Inner() { x = 1; y = 2; } public void innerMethod() { System.out.println("x = " + x + ", y = " + y); } } public static void main(String args[]) { new Outer(); } } TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
3. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 400 400 This code would appear in a file named Outer.java because it is a definition of the class named Outer. Inside of the curly braces of the Outer class definition is Java Programming for the Absolute Beginner where I defined the inner class, Inner. The Inner class definition looks like any other class definition. The main differences are the fact that it is defined within another class, Outer, and has access to the Outer class’s members and methods, including private ones. The y variable is declared to be private and yet, the Inner class has access to it. See the inner variable declared as a member of Outer? That’s an Inner object. In the previous example, the Inner object is created by using the code new Inner(). The Inner class is directly accessible, as if it were a member itself, from the Outer class, but it is also accessible from other classes because it is declared to be public. To access the Inner class from a static method or from another class that has access to it, you do it as follows: Outer.Inner inner = new Outer().new Inner(); The inner class is referenced by Outer.Inner and a new instance of it is created by invoking the Inner() constructor as if it were an instance method. I wouldn’t blame you for scratching your head looking at this code. I did a shortcut. I could have done it this way too: Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); I created an instance of Outer, called outer, and then on a second line, I created an instance of Inner through outer. Before, I just did it all on one line. HIN T You saw me reference the Inner class using the syntax Outer.Inner, but that’s not the actual class name. If you take the time to write out this program and compile it, you will see two new class files generated by the compiler: Outer.class and Outer$Inner.class. The dollar sign ($) separates outer classes from inner classes, so the actual name of the Inner class is Outer$Inner. Inner classes can also be created from within methods of an enclosing class and you don’t even have to give them a name. You’ve done this before when you cre- ated listeners for your AWT components. Does this look familiar? addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); The previous code was taken from GUIFrame.java. It creates an anonymous inner class that implements the WindowListener interface (by subclassing Win- dowAdapter, which implements WindowListener). Anonymous inner classes are defined when you construct the object, right after the new keyword. Anonymous TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. 4. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 401 401 inner classes have no name, but Java does create a .class file for them. It uses the enclosing class’s name, and then a dollar sign followed by a number. An Chapter 11 example of this is GUIFrame$1.class. That is in fact the name of the class file that Java creates for the anonymous WindowAdapter. What you might not know is that you can even create an anonymous inner class for any other class. Here is an example: Canvas canvas = new Canvas() { Custom Event Handling and File I/O //define an anonymous subclass of Canvas, such as overriding paint() public void paint(Graphics g) { g.fillRect(0, 0, 10, 10); } }; In the preceding example, an anonymous inner class, which is a subclass of Can- vas, is created. The paint(Graphics) method is overridden and it fills a rectan- gle. This eliminates the need to define a completely separate class, just to subclass Canvas to do something as simple as filling a rectangle. Accepting User Input for Block Movements The PlayArea class accepts user input in the form of KeyEvents. Before you get into the KeyListener implementation, take a look at the PlayArea members and methods that are available to facilitate block movements. First off, there are the static integer constants that represent possible block movements. Their names are self-explanatory; they are BLOCK_DOWN, BLOCK_UP, BLOCK_LEFT, BLOCK_RIGHT, BLOCK_CLOCKWISE, and BLOCK_COUNTERCLOCKWISE. The motions that are the oppo- site of each other are stored with opposite values (negatives of each other). For example, BLOCK_LEFT is –2 and BLOCK_RIGHT is 2. So, BLOCK_LEFT is the same as –BLOCK_RIGHT. This just makes it easier to reverse a movement. You reverse a movement when attempting one movement causes the block to move out of bounds. Performing 0 minus the number constant that represents whatever the original move was, reverses that movement, which caused the block to go out of bounds. For example, (0 – BLOCK_DOWN == BLOCK_UP), (0 – BLOCK_LEFT == BLOCK_RIGHT), and so on. The moveBlock(int) method accepts these constants as its arguments and uses the aid of a private helper method, performMove(int), to actually move the block. Here’s how they work: /* Returns true if successful. If block movement causes block * to be out of bounds, movement is not performed, returning false; */ protected synchronized boolean moveBlock(int movement) { boolean moved = true; if (block == null) return false; removeBlock(); performMove(movement); TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
5. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 402 402 if (blockOutOfBounds()) { performMove(-movement); Java Programming for the Absolute Beginner if (movement == BLOCK_DOWN) { addBlock(); blockOut = blockOutOfArea(); block = null; return false; } moved = false; } addBlock(); return moved; } private void performMove(int movement) { switch (movement) { case BLOCK_DOWN: blockPos.translate(0, 1); break; case BLOCK_UP: blockPos.translate(0, -1); break; case BLOCK_LEFT: blockPos.translate(-1, 0); break; case BLOCK_RIGHT: blockPos.translate(1, 0); break; case BLOCK_CLOCKWISE: block.rotateClockwise(); break; case BLOCK_COUNTERCLOCKWISE: block.rotateCounterClockwise(); break; } } The moveBlock(int) method is synchronized so that you can be sure that there is at most only one thread in this method moving the block around at any given time. It returns a boolean value that indicates whether the originally intended movement was performed successfully. The way it determines this is it removes the block’s square’s colors from the matrix, and then calls the performMove(int) method to actually move the block by either translating its position point for up, down, left, and right movements, or by calling the block’s rotate methods for rotation movements. Note that nothing is repainted at this point, nor are there any colors added to the matrix; only the squares of the block are rearranged. Next, it checks if the move- ment causes the block to go out of bounds. If it does, it recalls the perform- Move(int) method, only passing in the negative of the original movement this time to reverse the movement. Don’t forget that the BlockGrid class considers out of bounds to be when a block’s square moves into an area where there is already a square. When the blocks are either to the left or to the right of the moving block, it simply doesn’t allow the block to be moved in the occupied direction, but when a block falls down out of bounds that means it landed on something, either another block’s square or the bottom of the play area. When a block lands in this way there’s no more need for the reference to the Block object, so you add the block by calling addBlock(), and then set the Block to null. Remember that this doesn’t remove the colors from the matrix, but TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
6. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 403 403 instead, it just allows you to use another Block object reference in the block member. On the other hand, if the movement is a success, there’s no need to Chapter 11 reverse it or set the block to null. Instead, it just adds the colors to the matrix and returns true. To accept user input, the PlayArea class uses a KeyAdapter, which incidentally is defined as an anonymous inner class. The KeyAdapter listens for key presses. Specifically, it listens for the key presses that correspond to the keyboard com- Custom Event Handling and File I/O mands listed in Table 11.1. For example, it listens for KeyEvent.VK_LEFT and attempts to move the block left. If it is successfully moves the block (move- Block(int) returns true), it will repaint the PlayArea so that you can see the block move: if (ke.getKeyCode() == KeyEvent.VK_LEFT) { if (moveBlock(BLOCK_LEFT)) repaint(); } It does this for all the keyboard commands it’s interested in. The only one that works a bit differently is handling the down arrow (KeyEvent.VK_DOWN). There is already a thread that moves the block down, which I’ll get to next; it pauses once every second. Pressing down is supposed to make the block drop faster, so it causes the sleep interval, which is stored in currentDropPeriod, to be faster. fastDropPeriod is assigned the value 50 in the PlayArea constructor method. The normal drop period, stored in dropPeriod, is 1000. Another thing I checked for was automatic key repeating (holding a key might automatically cause it to quickly repeat). That’s fine for the other movements, especially right and left, but not okay for pressing down because there is an inter- rupt that tells the block not to wait any more and just fall. Automatic repeating would cause the block to fall as fast as possible, which is too fast! I just set up a boolean variable pressingDown that gets set to true the first time it detects a KeyEvent.VK_DOWN, and doesn’t get set back to false until the down arrow key is released. Here is the code for handling the down arrow key press: else if (!pressingDown && ke.getKeyCode() == KeyEvent.VK_DOWN) { pressingDown = true; currentDropPeriod = fastDropPeriod; blockDrop.interrupt(); //causes immediate effect } The code that handles the release of the down arrow sets currentDropPeriod back to dropPeriod and sets pressingDown to false. As you know, Canvases normally display graphics and don’t accept user input. PlayArea extends BlockGrid, which extends Canvas, so PlayArea is a Canvas. In order to let the PlayArea gain user focus, I overrode the isFocusTraversable() method to return true. It indicates whether the Canvas should normally gain TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
7. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 404 404 focus using Tab or Shift+Tab focus traversal. If it returns false, it can still get focus by calling requestFocus(), but any component that you want the user to be Java Programming for the Absolute Beginner able to focus on should return true here to make it easier. Making Blocks Fall The PlayArea class implements the Runnable interface. It overrides the run() method to animate the block falling down. It uses a Thread object, called block- Drop, to run the PlayArea block dropping animation. Here is the code for the run() method: public void run() { stopRequest = false; try { Thread.sleep (dropPeriod); } catch (InterruptedException ie) {} while (block != null && !stopRequest) { if (moveBlock(BLOCK_DOWN)) repaint(); if (block != null) { try { Thread.sleep(currentDropPeriod); } catch (InterruptedException ie) {} } } if (!stopRequest) handleBlockLanded(); } stopRequest is a boolean variable that is true if the thread is told to stop running because you shouldn’t call the deprecated stop() method. The first Thread.sleep(long) call exists so that no matter what, the block will pause at the top of the PlayArea at least the amount of time specified by dropPeriod, which is the normal pause between each successive down movement. The while loop con- tinues while block is not null (or it doesn’t have anything to move) and while there is no request to stop running. It attempts to move the block down and repaints if it does, and then checks if block is null again (because moveBlock(int) sets block to null when the block lands), and then it sleeps and repeats. After the while loop, the run() method checks whether it stopped because of a stop request. If it didn’t it, assumes that it stopped because the block landed on something and calls handleBlockLanded() as a result. CK The PlayArea class implements the Runnable interface. Its run() method moves TRI the block down. However, the block must be provided by a different class by calling the introduceBlock(Block) method. Doing this triggers a Thread, blockDrop to start running the PlayArea, which moves one block as far down as it can go, and then stops. No outside class needs to run the PlayArea in a differ- ent thread because PlayArea takes care of it itself. In the real world, you TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
8. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 405 405 probably don’t want to do it this way. If you needed to hide the thread from the outside world, you are better off creating a protected or private inner class Chapter 11 that extends Thread so that classes that don’t need to start the thread can’t run the thread by creating a new Thread object using an instance of PlayArea. The handleBlockLanded() method checks if any rows were completed and calls rowClear(int[]) if there were any. Here’s how this works. It declares a boolean array, rows[], which is the same size as the number of rows. true indicates rows, Custom Event Handling and File I/O that, by their index, are completed and need to be cleared. Initially, they are all explicitly set to true and a variable, nComplete, which counts the number of rows that are completed, is set to the number of rows. Then it loops on the columns and rows, checking whether there is one square in every column of the rows (these rows are complete). Any time it finds a cell that doesn’t contain a square, it sets that row’s rows[] array to false and decrements nComplete. It won’t check that row again. The loop will also stop as soon as it knows for sure there aren’t any completed rows (nComplete is zero): for (int c=0; c < getCols() && nComplete >= 0; c++) { for (int r=0; r < getRows(); r++) { if (matrix[c][r] == null && rows[r]) { rows[r] = false; nComplete--; } } } After this loop, the rows[] array is true at the index of every row that is complete and needs to be cleared. It rearranges this information so that an array, rowsTo- Clear[], which is the size of the number of rows that are complete is created and the elements of the array are set to the row indices of the matrix array that actu- ally need to be cleared. Using the subscript Rindex++ sets the current value of the index to Rindex before incrementing it, so it is initially zero: int Rindex = 0; int[] rowsToClear = new int[nComplete]; for (int r=0; r < getRows(); r++) { if (rows[r]) rowsToClear[Rindex++] = r; } Next it calls the rowClear(int[]) method, passing in rowsToClear[] as the para- meter. rowClear(int[]) animates all the rows that need to be cleared with flash- ing colors. The flashing colors are set up in the flashColors[] array. It does this by looping on this array and on all the cells in the rows that need to be cleared, and then setting the color of the squares to the different colors and repainting after each color is set. The last color of the flashColors[] array is null, so that makes the squares disappear. TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
9. JavaProgAbsBeg-11.qxd 2/25/03 8:57 AM Page 406 406 Now there is the matter of making the blocks piled up on top of the rows fall down to fill up the newly vacated space. This isn’t as straight-forward as you Java Programming for the Absolute Beginner might think. You have to consider the possibility that when multiple rows are cleared simultaneously, they might not necessarily be successive rows (for exam- ple, row 10 and row 12 need to be cleared, but not row 11). You also have to handle the fact that as you’re moving rows down, new rows are being formed at the top because there aren’t any rows above the play area to fall down. Here is the complicated loop that handles this: for (int c=0; c < getCols(); c++) { nRows = 1; for (int r = rows[rows.length - 1]; r >= 0; r--) { while (nRows < rows.length && r - nRows == rows[rows.length - nRows - 1]) { nRows++; } if (r >= nRows) matrix[c][r] = matrix[c][r - nRows]; else matrix[c][r] = null; } } repaint(); } It loops on all the columns of the PlayArea matrix and sets the nRows variable to 1. nRows counts the number of rows that have been cleared. It loops on the rows, starting at the bottom-most row that was cleared, indicated by the last element of the rows array, rows[rows.length – 1] and works its way up to the top of the PlayArea. nRows starts at 1 for each column because it already accounts for the bottom-most row that was cleared. For any cleared row, you have to move the squares in the row above the cleared row down into the cleared row. Hopefully that makes sense, but it’s not that straight-forward. If two rows were cleared, the squares in the row that is two rows up have to move down into this cleared row. That’s why it has to count the number of cleared rows as it works its way up to the top. It counts them in the while loop. Although the number of cleared rows I counted so far is still less than the total number of cleared rows (nRows < rows.length) and the row above this row (r - nRows) was cleared to (rows[rows.length - nRows - 1]) increment nRows (nRows++). Remem- ber that the rows[] array elements store the matrix row indices that were cleared, so if the row above this one needs to be cleared, it will be stored at the next index of the rows[] array, which is why this check works. As it moves up in rows, it moves the squares of rows that are nRows above this row down into this row. For example if you clear rows 10 and 8, the squares in row 9 move down into row 10. But because row 8 was cleared, row 7 moves down into row 9, and then row 6 moves into row 8, row 5 into row 7, and so on. It also has to be careful near the TEAM LinG - Live, Informative, Non-cost and Genuine! Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.