📄 ch16.htm
字号:
the parameters for the depth search algorithm. The algorithm used
by <TT><FONT FACE="Courier">evaluate</FONT></TT> determines the
best move based on the calculated "goodness" of each
possible move. The <TT><FONT FACE="Courier">alpha</FONT></TT>
and <TT><FONT FACE="Courier">beta</FONT></TT> parameters specify
cutoffs that enable the algorithm to eliminate some moves entirely,
thereby speeding things up. It is a little beyond today's focus
to go any further into the low-level theory behind the algorithm.
If, however, you want to learn more about how it works, look into
the Web sites mentioned at the end of yesterday's lesson.
<P>
The <TT><FONT FACE="Courier">calcScore</FONT></TT> method in <TT><FONT FACE="Courier">Connect4State</FONT></TT>
is responsible for calculating the score for a player:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">private int calcScore(int player) {<BR>
int s = 0;<BR>
for (int i = 0; i < winPlaces; i++)<BR>
s += score[player][i];<BR>
return s;<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
In <TT><FONT FACE="Courier">calcScore</FONT></TT>, the score of
a player is calculated by summing each element in the <TT><FONT FACE="Courier">score</FONT></TT>
array. The <TT><FONT FACE="Courier">updateScore</FONT></TT> method
handles updating the score for a player after a move:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">private void updateScore(int player,
int x, int y) {<BR>
for (int i = 0; i < winPlaces; i++)<BR>
if (map[x][y][i]) {<BR>
score[player][i] <<=
1;<BR>
score[getOtherPlayer(player)][i]
= 0;<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
The <TT><FONT FACE="Courier">updateScore</FONT></TT> method sets
the appropriate entries in the <TT><FONT FACE="Courier">score</FONT></TT>
array to reflect the move; the move is specified in the <TT><FONT FACE="Courier">x</FONT></TT>
and <TT><FONT FACE="Courier">y</FONT></TT> parameters. The last
method in <TT><FONT FACE="Courier">Connect4State</FONT></TT> is
<TT><FONT FACE="Courier">getOtherPlayer</FONT></TT>, which simply
returns the number of the other player:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">private int getOtherPlayer(int player)
{<BR>
return (1 - player);<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
That wraps up the game engine. You now have a complete Connect4
game engine with AI support for a computer player. Keep in mind
that although you've been thinking in terms of a human versus
the computer, the game engine is structured so that you could
have any combination of human and computer players. Yes, this
means you could set up a game so that two computer players duke
it out! Pretty neat, huh?
<H3><A NAME="TheConnect4Class"><B>The </B><TT><B><FONT SIZE=4 FACE="Courier">Connect4</FONT></B></TT><B><FONT SIZE=4>
Class</FONT></B></A></H3>
<P>
The game engine classes are cool, but they aren't all that useful
by themselves; they need an applet class with some graphics and
a user interface. The <TT><FONT FACE="Courier">Connect4</FONT></TT>
class is exactly what they need. The <TT><FONT FACE="Courier">Connect4</FONT></TT>
class takes care of all the high-level game issues such as drawing
the graphics and managing human moves through mouse event handlers.
Even though it doesn't rely on the sprite classes, the <TT><FONT FACE="Courier">Connect4</FONT></TT>
applet class is still similar to other applet classes you've developed.
Let's look at its member variables first:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">private Image offImage,
boardImg, handImg;<BR>
private Image[] pieceImg
= new Image[2];<BR>
private AudioClip newGameSnd,
sadSnd, applauseSnd,<BR>
badMoveSnd,
redSnd, blueSnd;<BR>
private Graphics offGrfx;
<BR>
private Thread thread;
<BR>
private MediaTracker tracker;<BR>
private int delay
= 83; // 12 fps<BR>
private Connect4Engine gameEngine;<BR>
private boolean gameOver
= true,<BR>
myMove;
<BR>
private int level
= 2, curXPos;<BR>
private String status
= new String("Your turn.");<BR>
private Font statusFont
= new Font("Helvetica", Font.PLAIN, 20);<BR>
private FontMetrics statusMetrics;</FONT></TT>
</BLOCKQUOTE>
<P>
The first member variables you probably noticed are the ones for
all the graphics and sound in the game. The most important member
variable, however, is <TT><FONT FACE="Courier">gameEngine</FONT></TT>,
which is a <TT><FONT FACE="Courier">Connect4Engine</FONT></TT>
object. There are also two boolean member variables that keep
up with whether the game is over (<TT><FONT FACE="Courier">gameOver</FONT></TT>)
and whose move it is (<TT><FONT FACE="Courier">myMove</FONT></TT>).
The <TT><FONT FACE="Courier">level</FONT></TT> member variable
specifies the current level of the game, and the <TT><FONT FACE="Courier">curXPos</FONT></TT>
member keeps up with which column the hand selector is currently
over. Finally, there are a few member variables for managing the
status line text and its associated font and font metrics. Let's
move on to the methods.
<P>
The <TT><FONT FACE="Courier">init</FONT></TT> method in <TT><FONT FACE="Courier">Connect4</FONT></TT>
is pretty standard; it just loads the images and audio clips:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public void init() {<BR>
// Load and track the images<BR>
tracker = new MediaTracker(this);<BR>
boardImg = getImage(getCodeBase(), "Res/Board.gif");
<BR>
tracker.addImage(boardImg, 0);<BR>
handImg = getImage(getCodeBase(), "Res/Hand.gif");
<BR>
tracker.addImage(handImg, 0);<BR>
pieceImg[0] = getImage(getCodeBase(), "Res/RedPiece.gif");
<BR>
tracker.addImage(pieceImg[0], 0);<BR>
pieceImg[1] = getImage(getCodeBase(), "Res/BluPiece.gif");
<BR>
tracker.addImage(pieceImg[1], 0);<BR>
<BR>
// Load the audio clips<BR>
newGameSnd = getAudioClip(getCodeBase(), "Res/NewGame.au");
<BR>
sadSnd = getAudioClip(getCodeBase(), "Res/Sad.au");
<BR>
applauseSnd = getAudioClip(getCodeBase(), "Res/Applause.au");
<BR>
badMoveSnd = getAudioClip(getCodeBase(), "Res/BadMove.au");
<BR>
redSnd = getAudioClip(getCodeBase(), "Res/RedMove.au");
<BR>
blueSnd = getAudioClip(getCodeBase(), "Res/BlueMove.au");
<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
Although <TT><FONT FACE="Courier">init</FONT></TT> is certainly
important, the <TT><FONT FACE="Courier">run</FONT></TT> method
is where things get interesting, because the computer player's
move is handled in the main update loop inside it. Listing 16.4
contains the source code for the <TT><FONT FACE="Courier">run</FONT></TT>
method.
<HR>
<BLOCKQUOTE>
<B>Listing 16.4. The </B><TT><B><FONT FACE="Courier">Connect4</FONT></B></TT><B>
class's </B><TT><B><FONT FACE="Courier">run</FONT></B></TT><B>
method.<BR>
</B>
</BLOCKQUOTE>
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public void run() {<BR>
try {<BR>
tracker.waitForID(0);<BR>
}<BR>
catch (InterruptedException e) {<BR>
return;<BR>
}<BR>
<BR>
// Start a new game<BR>
newGame();<BR>
<BR>
// Update everything<BR>
long t = System.currentTimeMillis();<BR>
while (Thread.currentThread() == thread) {<BR>
// Make the computer's move<BR>
if (!gameOver && !myMove) {<BR>
Point pos = gameEngine.computerMove(1,
level);<BR>
if (pos.y >= 0) {<BR>
if (!gameEngine.isWinner(1))
<BR>
if
(!gameEngine.isTie()) {<BR>
blueSnd.play();
<BR>
status
= new String("Your turn.");<BR>
myMove
= true;<BR>
}
<BR>
else
{<BR>
sadSnd.play();
<BR>
status
= new String("It's a tie!");<BR>
gameOver
= true;<BR>
}
<BR>
else {<BR>
sadSnd.play();
<BR>
status
= new String("You lost!");<BR>
gameOver
= true;<BR>
}<BR>
repaint();<BR>
}<BR>
}<BR>
<BR>
try {<BR>
t += delay;<BR>
Thread.sleep(Math.max(0, t
- System.currentTimeMillis()));<BR>
}<BR>
catch (InterruptedException e) {<BR>
break;<BR>
}<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<HR>
<P>
If it is the computer player's turn, the <TT><FONT FACE="Courier">run</FONT></TT>
method attempts a move using the current level by calling <TT><FONT FACE="Courier">computerMove</FONT></TT>
on the <TT><FONT FACE="Courier">gameEngine</FONT></TT> object.
If the move is successful, <TT><FONT FACE="Courier">run</FONT></TT>
checks for a win or tie, and then it plays the appropriate sound
and updates the status text.
<P>
<CENTER><TABLE BORDERCOLOR=#000000 BORDER=1 WIDTH=80%>
<TR><TD><B>Note</B></TD></TR>
<TR><TD>
<BLOCKQUOTE>
The <TT><FONT FACE="Courier">level</FONT></TT> member variable ultimately determines how smart the computer player is by affecting the depth of the look-ahead search. This is carried out by <TT><FONT FACE="Courier">level</FONT></TT> being passed as the
second parameter of <TT><FONT FACE="Courier">computerMove</FONT></TT>. If you find the game too easy or too difficult, feel free to tinker with the <TT><FONT FACE="Courier">level</FONT></TT> member variable, or even supply your own calculation as the
second parameter to <TT><FONT FACE="Courier">computerMove</FONT></TT>.
</BLOCKQUOTE>
</TD></TR>
</TABLE></CENTER>
<P>
<P>
The <TT><FONT FACE="Courier">update</FONT></TT> method handles
the details of drawing the game graphics. Listing 16.5 shows the
source code for the <TT><FONT FACE="Courier">update</FONT></TT>
method.
<HR>
<BLOCKQUOTE>
<B>Listing 16.5. The </B><TT><B><FONT FACE="Courier">Connect4</FONT></B></TT><B>
class's </B><TT><B><FONT FACE="Courier">update</FONT></B></TT><B>
method.<BR>
</B>
</BLOCKQUOTE>
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public void update(Graphics g) {<BR>
// Create the offscreen graphics context<BR>
if (offGrfx == null) {<BR>
offImage = createImage(size().width, size().height);
<BR>
offGrfx = offImage.getGraphics();<BR>
statusMetrics = offGrfx.getFontMetrics(statusFont);
<BR>
}<BR>
<BR>
// Draw the board<BR>
offGrfx.drawImage(boardImg, 0, 0, this);<BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -