1. Trang chủ
  2. » Công Nghệ Thông Tin

Java Programming for absolute beginner- P23 pot

20 120 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 296,79 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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.. The dollar sign $ separates oute

Trang 1

Registering PlayAreaListeners

The registering of PlayAreaListeners takes place in the PlayAreaclass You won’t see the entire source listing for PlayArea.javauntil 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, PlayAreauses a Vectorto maintain a dynamic list of PlayAre-aListeners:

protected Vector listeners;

The PlayAreaclass also provides two methods for updating this Vectorobject,

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 Vectorclass’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 PlayAreaclass by creating an instance of the

PlayAreaEventclass and then calling the registered PlayAreaListeners’ block-Landed(BlockEvent)methods The PlayAreaclass 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 PlayAreaEventobject 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 thisas the source of the event (meaning this PlayAreaobject

398

J a

s o

l ut

n e

Trang 2

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-yAreaEvents, such as firing them in their own threads, in the next section,

“Creating the PlayAreaClass.”

Creating the PlayArea Class

The PlayArea class is the most significant part of this project It extends the

BlockGridclass, 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 Runnableinterface 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 PlayAreaclass 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();

} }

399

l i

i l

/ O

Trang 3

This code would appear in a file named Outer.javabecause it is a definition of the class named Outer Inside of the curly braces of the Outerclass definition is where I defined the inner class, Inner The Innerclass 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 Outerclass’s members and methods, including private ones The yvariable 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 Innerobject In the previous example, the Innerobject is created by using the code new Inner() The Innerclass is directly accessible, as if it were a member itself, from the Outerclass, but it is also accessible from other classes because it is declared to be public To access the Innerclass 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 innerclass is referenced by Outer.Innerand 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 Innerthrough outer Before, I just did it all on one line

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 newkeyword Anonymous

H I N T

400

J a

s o

l ut

n e

Trang 4

inner classes have no name, but Java does create a classfile for them It uses the enclosing class’s name, and then a dollar sign followed by a number An 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() { //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 Canvasto do something as simple as filling a rectangle

Accepting User Input for Block Movements

The PlayAreaclass accepts user input in the form of KeyEvents Before you get into the KeyListenerimplementation, take a look at the PlayAreamembers 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 oppooppo-site 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);

401

l i

i l

/ O

Trang 5

if (blockOutOfBounds()) { performMove(-movement);

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 booleanvalue 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 BlockGridclass 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

Blockobject, 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

402

J a

s o

l ut

n e

Trang 6

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 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 PlayAreaclass 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-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

fastDropPeriodis assigned the value 50 in the PlayAreaconstructor 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 falseuntil 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 dropPeriodand sets pressingDownto 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 PlayAreagain user focus, I overrode the isFocusTraversable()

method to return true It indicates whether the Canvas should normally gain

403

l i

i l

/ O

Trang 7

focus using Tabor Shift+Tabfocus traversal If it returns false, it can still get focus by calling requestFocus(), but any component that you want the user to be able to focus on should return truehere 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 Threadobject, 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();

}

stopRequestis a booleanvariable that is trueif 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 PlayAreaat least the amount of time specified by dropPeriod, which is the normal pause between each successive down movement The whileloop con-tinues while blockis 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 blockis nullagain (because moveBlock(int)

sets blockto nullwhen the block lands), and then it sleeps and repeats

After the whileloop, the run()method checks whether it stopped because of a

stoprequest If it didn’t it, assumes that it stopped because the block landed on something and calls handleBlockLanded()as a result

The PlayArea class implements the Runnable interface Its run() method moves 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

T R I C K

404

J a

s o

l ut

n e

Trang 8

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 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 trueindicates rows, that, by their index, are completed and need to be cleared Initially, they are all explicitly set to trueand 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 falseand 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 (nCompleteis 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 trueat 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 matrixarray that actu-ally need to be cleared Using the subscript Rindex++sets the current value of the index to Rindexbefore 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 flashflash-ing 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

405

l i

i l

/ O

Trang 9

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 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 PlayAreamatrix and sets the nRowsvariable to

1 nRowscounts 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 rowsarray, rows[rows.length – 1]and works its way up to the top of the

PlayArea nRowsstarts 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 whileloop 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 nRowsabove 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

406

J a

s o

l ut

n e

Trang 10

top What row moves down into row 0? It’s the top row, so there are no rows above

it to move down These rows just have all their colors set to null That’s what the last ifstatement does If this row index is greater than or equal to the number

of rows cleared, there must be some actual rows still left to move down, but if this is row index 1, for example, and four lines were cleared, there aren’t any more rows to fall down, so set the colors to null

This PlayArea’s run() method is invoked from within the introduceBlock (Block)method:

public void introduceBlock(Block b) {

if (block == null) { blockPos = new Point();

block = b;

blockPos.x = (getCols() - b.getSize()) / 2;

blockPos.y = 1 - block.getSize();

addBlock();

repaint();

blockDrop = new Thread(this);

blockDrop.start();

} }

If this PlayAreadoesn’t already have a block (block == null), set blockto the passed in Blockparameter Then center its position at the top (actually above the top), add the colors by calling addBlock(), show the colors by calling repaint(), and create a new thread using this Runnable PlayArea, and then start a-droppin’

the block

The EventThread Inner Class

The handleBlockLanded()method also takes care of setting up and dispatching the EventThreads EventThread is an inner class that extends Thread and fires

PlayAreaEvents The reason why you fire them off in their own threads is because there is no reason to tie up the current thread (blockDrop) with calling the lis-teners’ PlayAreaEvent handling methods Who knows what they do and how long they will tie you up? Well, you do because you’re writing the code for it even-tually It is definitely good practice to fire events off in their own threads so that the current thread is not taken over by any of the listeners to perform what could

be enormous tasks Nope, you’ll just fire off a thread and then go about your business Here is a listing of the EventThreadinner class:

private class EventThread extends Thread { final static int BLOCK_LANDED = 0;

int event;

PlayAreaEvent pae;

407

l i

i l

/ O

Ngày đăng: 03/07/2014, 05:20