JAVA版井字棋的设计与实现.doc

上传人:PIYPING 文档编号:10745757 上传时间:2021-06-02 格式:DOC 页数:17 大小:155.51KB
返回 下载 相关 举报
JAVA版井字棋的设计与实现.doc_第1页
第1页 / 共17页
JAVA版井字棋的设计与实现.doc_第2页
第2页 / 共17页
JAVA版井字棋的设计与实现.doc_第3页
第3页 / 共17页
JAVA版井字棋的设计与实现.doc_第4页
第4页 / 共17页
JAVA版井字棋的设计与实现.doc_第5页
第5页 / 共17页
点击查看更多>>
资源描述

《JAVA版井字棋的设计与实现.doc》由会员分享,可在线阅读,更多相关《JAVA版井字棋的设计与实现.doc(17页珍藏版)》请在三一文库上搜索。

1、优秀论文,值得下载!JAVA版井字棋的设计与实现仇宾摘要:井字棋是大家所熟知的一个小游戏,虽然简单,但其中包含了一些编程的基本技巧和基本算法,本文将在Eclipse环境下用Java语言编写一个可以人人、人机对弈的井字棋游戏。一 引言井字棋,即棋盘是一个井字,是一种在3X3格子上进行的连珠游戏,和五子棋比较类似,由于棋盘一般不画边框,格线排成井字而得名。游戏规则很简单,游戏双方一方为“X”,一方为“O”,哪方率先实现三子相连即为胜者。见图1:图1 正在进行中的井字棋游戏现在我们来对井字棋游戏的代码实现做一个探讨,首先介绍人人对弈方式的实现过程,然后在此基础上介绍人机对弈井字棋的实现。二 人人对弈

2、井字棋的实现1、难点释疑人人对弈实现起来较为简单,游戏双方交替在棋盘上落下棋子“X”或“O”即可,最大问题就是如何判定胜负。从棋盘我们可以看出,获胜(即任一方出现三连子)一共有8中情况:三连横、三连竖以及两个斜对角,如果我们给每个落子点从0到8编号,如图2所示: 图2 棋盘落子点编号那么,这8中获胜情况我们可以用一个二维数组来表示:static final int WIN_STATUS = 0, 1, 2,3, 4, 5,6, 7, 8,0, 3, 6,1, 4, 7,2, 5, 8,0, 4, 8,2, 4, 6;这样,再定义一个一维数组,每走一步棋就对上面的二维数组进行遍历从二维数组中依次

3、取出8种情况放入一维数组,然后查看一维数组中的三个棋子是否相同,如果相同可以判定获胜。2、设计实现第一步:写一个类继承自JFrame,然后定义几个必要的变量和常量,如下:public class Tic extends JFrame JButton jb = new JButton9;/ 按钮数组构成棋盘的8个落子点static final char empty = 32; / 代表空格static int clicknum = 0; / 记录单击次数,决定是走X还是走Ostatic final int INFINITY = 100;/ 带标无穷值static final int WIN =

4、+INFINITY;/ O获胜static final int LOSE = -INFINITY;/ X获胜static final int DRAW = 0;/ 平局/ 获胜棋盘状态static final int WIN_STATUS = 0, 1, 2,3, 4, 5,6, 7, 8,0, 3, 6,1, 4, 7,2, 5, 8,0, 4, 8,2, 4, 6;第二步:构建棋盘在Tic类的构造方法中,JFrame的布局方式设置为GridLayout,然后每个格子里放置一个按钮即可,代码如下:public Tic()this.setDefaultCloseOperation(JFrame

5、.EXIT_ON_CLOSE);this.setSize(400, 400);/棋盘大小this.setLayout(new GridLayout(3,3);this.setTitle(井字棋);/ 让窗口居中显示Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();/ 获取屏幕尺寸封装到screen中this.setLocation(screen.height - this.getHeight()/2, (screen.width - this.getWidth()/2); / 窗口居中/ 把按钮加入窗体for(int

6、i=0; i9; i+)jbi = new JButton();jbi.setFont(new Font(Arial,Font.BOLD,30);jbi.addActionListener(new JBClick();/ 事件监听this.add(jbi);this.setVisible(true);第三步:经过前两步,现在加上main方法,就可以执行显示棋盘了,代码如下:public static void main(String args) new Tic();JOptionPane.showMessageDialog(null,O代表玩家1,X代表玩家2,玩家1先走,提示,JOption

7、Pane.DEFAULT_OPTION);但是按钮还没有事件响应,现在单击按钮没有任何反应。下面我们给按钮添加事件响应。每次单击按钮需要完成如下任务:因为是两个玩家进行游戏,所以按钮要轮流显示“X”和“O”,表示游戏双方交替走棋。每次单击走棋后还要判定是否有获胜方,有则提示哪方获胜,游戏结束,无则继续走棋。代码如下:private class JBClick implements ActionListener/ 当单击按钮时public void actionPerformed(ActionEvent e) for(int i=0; i9; i+)if(e.getSource() = jbi)

8、/ 哪个按钮被单击if(+clicknum % 2 = 0)/偶数次是Xjbi.setText(X);elsejbi.setText(O);/奇数次是Ojbi.setEnabled(false);/被单击过的按钮不可用int gamestate = gameState(jb);/ 调方法获取棋盘状态/ 输出棋局胜负switch (gamestate) case WIN:JOptionPane.showMessageDialog(null, O方获胜, 提示,JOptionPane.DEFAULT_OPTION);break;case LOSE:JOptionPane.showMessageDi

9、alog(null, X方获胜, 提示,JOptionPane.DEFAULT_OPTION);break;case DRAW:JOptionPane.showMessageDialog(null, 平局, 提示,JOptionPane.DEFAULT_OPTION);break;/ 如果结束,则提示if (gamestate = WIN | gamestate = LOSE | gamestate = DRAW) int over = JOptionPane.showConfirmDialog(null, 是否再来一局?, 提示, JOptionPane.YES_NO_OPTION,JOpt

10、ionPane.QUESTION_MESSAGE);if (over = JOptionPane.YES_OPTION) for (int i = 0; i 9; i+) jbi.setText( );jbi.setEnabled(true); else System.exit(0); / 退出游戏 其中代码:int gamestate = gameState(jb);表示调用gameState方法来判定是否有获胜方。如何判定呢?首先,遍历整个棋盘,看棋盘是否已经满,如果未满,就直接返回,继续走棋,如果满了,则判断是否符合8种数组中的一种,符合则分出胜负,不符合则说明平局。代码如下:publi

11、c int gameState(JButton jb)int result = 1; / 棋局状态初始值boolean isFull = true;/ 棋盘是否已满/ 判断棋盘是否已满for (int pos = 0; pos 9; pos+) char chess = jbpos.getText().charAt(0);if (empty = chess) isFull = false;return result; / 未满则返回,继续走棋/ 棋局已满,判定胜负for(int status:WIN_STATUS)/遍历8中棋局获胜状态/得到某个获胜棋局状态的第一个索引的字符char ches

12、s = jbstatus0.getText().charAt(0);/ 如果为空,说明此处未下棋子,跳出循环,找下一个状态if(chess=empty)continue;int i ;/不为空,则看其余两子是否与其相同for(i=1; istatus.length; i+) if(jbstatusi.getText().charAt(0)!=chess)break;/不同则跳出循环 if(i=status.length)/ 三子连线result = chess= O ? WIN : LOSE;break;if (result != WIN & result != LOSE & isFull)

13、result = DRAW;/不输不赢且棋盘满则为平return result;调用该方法后,如果返回值result等于1,说明棋局没有下满,继续走棋;等于WIN,说明走“O”的一方获胜;等于LOSE,说明“X”方获胜;等于DRAW,则平局。执行过程如图3所示。图3 O方获胜3、 编程技巧尽管很简单的一个小程序,但是其中却包含一些编程技巧,比如使用一个二维数组来记录棋局获胜的情况,如果不用数组,则需要使用8个if语句来描述8种获胜情况。再比如程序中用到的for each语句,很轻松的做到了用一个一维数组去遍历二维数组。如果使用for语句,那么这个循环将会很难写。三 人机对弈井字棋的实现1、难点

14、释疑人机对弈的难点在于当人走一步棋之后,计算机如何走下一步,即计算机如何找出最合适的位置去走棋。这就需要一定的算法,或者叫做计算机的AI。对于井字棋、五子棋等两方较量的游戏来说,Minimax算法(极小极大算法)是最基本也是最常用的。算法的原理不在这里解释了,我们直接看该算法在井字棋中的应用。井字棋中,假设使用“X”的是人,使用“O”的是计算机。“X”方先走,设定X方的最大利益为正无穷(程序使用常量+INFINITY表示),O方的最大利益为负无穷(程序中使用-INFINITY表示),即X方和O方走的每步棋都要力图使自己的利益最大化,而使对方的利益最小化。这样我们称X方为MAX(因为他总是追求更

15、大的值),O方为MIN(它总是追求更小的值),各自都为争取自己的最大获益而努力。现在举例说明,比如图4所示的棋局树:图4 棋局形成的树X方先走,有三种选择,如图4中第二层所示。假设X方选择最左边的走法,那么O方接下来将有5种走法,O方会选择最小化的走法,即值为-1的走法,因为它的最大利益是负无穷;同理,X方的另外两种走法会分别得到O方的最小值1和-2。这样,对于X方来说,三种走法会导致O方最小化值分别为-1、1、-2,X方的最佳策略则是选择其中最大的,即第二层中间的走法,因为它的最大利益是正无穷,这就是极小极大算法的体现X方的选择总是极大化,O方的选择总是极小化。对于其中那些值的是如何计算的,

16、我们举例说明,比如对于第三层最左边的棋局,在这种状态下,如果把棋局空白处都填上X,则X共有6中3连子情况,即获胜情况;如果把空白处都填上O,则O共有5种3连子情况,所以结果是二者相减等于1。在具体走起过程中,MAX面对MIN最大获利中的最小值时,会选择其中最大的,比如图4第二层小括号内的值都是第三层中能使MIN最大获利的最小值,这时候MAX选择其中最大的,这对MAX最为有利,所以MAX方选择图4第二层中间的走法最好。同样道理,MIN也会一样,选择对自己最有利的,即MAX有可能获得的最大值。这时候,MIN在走棋时会考虑MAX方占据哪个位置对MAX最有利,然后MIN把这个位置先占了。有点难理解,其

17、实就是抢先把对对手有利的位置抢占了。简单说,X方或者MAX方的走棋时由人来控制的,我们不仔细说了。对于O方或者MIN方,它走棋时要考虑哪个位置对X方最有利,然后把该位置占据,即O的最佳走棋就是X的最佳走棋。所以O在走棋之前,先站在X的角度寻找最佳走棋位置。后文中minimax方法就是站在X角度来考虑极小极大算法,找到X的最佳走棋位置,然后由O方来占据该位置。2、极小极大算法整个算法包括如下几个部分:首先要有一个评估方法gameState,对每走一步棋后的棋局进行评估,估值为WIN常量说明X方,即MAX方获胜;估值为LOSE则O方,即MIN方获胜;估值DRAW为平局;估值为INPROGRESS,

18、说明棋未走完;估值为DOUBLE_LINK,说明棋局中有两连子情况然后用一个minimax方法寻找在当前棋局状态下X方的最佳位置, X方的最佳位置就是当X走该位置后,O方所有走法中最小值里的最大值,比如图4中第二层X 的位置选择。当找到该位置后,由O方来抢先占据该位置。最后用两个递归方法min和max来遍历所有的棋局。min方法负责找出O方的最小值,比如图4第二层最左边的棋局会导致5中O方的走法,min方法就是找出这5种走法中的最小值。同理,max方法负责找出X方的最大值,比如图4第二层三种棋局中的中间棋局。3、代码实现在刚才那个人人对弈井字棋的基础上,我们进行一些修改来实现人机对弈。首先单击

19、事件加入调用方法让计算机走棋的代码,如下:/ 按钮的监听事件private class JBClick implements ActionListener / 当单击按钮时public void actionPerformed(ActionEvent e) for (int i = 0; i 9; i+) if (e.getSource() = jbi) jbi.setText(X); / 被单击的按钮走“X”jbi.setEnabled(false); /置为不可用int gamestate = gameState(jb); / 获取棋盘状态/ 如果棋局未结束,则计算机走下一步if (!(g

20、amestate = WIN | gamestate = LOSE | gamestate = DRAW) int nextpos = getNextMove(jb); / 获取下一步走棋位置jbnextpos.setText(O); / 走棋“O”jbnextpos.setEnabled(false);gamestate = gameState(jb); / 获取最新的棋盘状态/ 输出棋局胜负switch (gamestate) case WIN: JOptionPane.showMessageDialog(null, X方获胜, 提示,JOptionPane.DEFAULT_OPTION)

21、; break;case LOSE:JOptionPane.showMessageDialog(null, O方获胜, 提示,JOptionPane.DEFAULT_OPTION);break;case DRAW: JOptionPane.showMessageDialog(null, 平局, 提示,JOptionPane.DEFAULT_OPTION);break;/ 如果结束,则提示if (gamestate = WIN | gamestate = LOSE | gamestate = DRAW) int over = JOptionPane.showConfirmDialog(null,

22、 是否再来一局?, 提示, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);if (over = JOptionPane.YES_OPTION) / 再来一局for (int i = 0; i 9; i+) jbi.setText( );jbi.setEnabled(true); else System.exit(0); / 退出游戏然后,获取棋局状态的方法加入寻找两连子的代码,如下:/ 获取棋盘当前状态public int gameState(JButton jb) int result = INPROGRESS;boolea

23、n isFull = true;/ 判断棋盘是否已满for (int pos = 0; pos 9; pos+) char chess = jbpos.getText().charAt(0);if (empty = chess) isFull = false;/ 寻找三连子情况for (int status : WIN_STATUS) / 遍历8中棋局获胜状态/ 得到某个获胜棋局状态的第一个索引的字符char chess = jbstatus0.getText().charAt(0);/ 如果为空,说明此处未下棋子,跳出循环,找下一个状态if (chess = empty) continue;

24、int i;for (i = 1; i status.length; i+) / 查看其余两个字符if (jbstatusi.getText().charAt(0) != chess) / 不与第一个索引字符一致break; / 表明未三子连线,跳出if (i = status.length) / 三子连线result = chess = X ? WIN : LOSE;break;/ 寻找两连子情况if (result != WIN & result != LOSE) if (isFull) result = DRAW;/不输不赢且棋盘满则为平 else int finds = new int

25、2;/ 存放X或O的两连子情况for (int status : WIN_STATUS) char chess = empty;boolean hasEmpty = false;int count = 0;/ 计数for (int i = 0; i 1) if (chess = X) finds0+; else finds1+;/ 两连子情况if (finds1 0) / O的两连子result = -DOUBLE_LINK; else if (finds0 0) / X的两连子result = DOUBLE_LINK;return result;/ 记录了胜负平或者两连子情况O方走棋时,要得

26、到走棋位置,我们用一个方法来获取该位置,如下:public int getNextMove(JButton board) int nextPos = minimax(board, 3);return nextPos;上面方法中调用了极小极大算法minimax,如下:/以X的角度来考虑的极小极大算法public int minimax(JButton board, int depth) int bestMoves = new int9;/存放最佳走棋位置int index = 0;int bestValue = -INFINITY;/ 搜索所有空位,试探填上X,然后选其中最小值的for (int

27、 pos = 0; pos bestValue) / 选择最小值里最大的bestValue = value;index = 0;bestMovesindex = pos; else if (value = bestValue) index+;bestMovesindex = pos;boardpos.setText( );return bestMovesindex;最后,两个递归方法min和max如下:/对于O,估值越小对其越有利public int min(JButton board, int depth) int evalValue = gameState(board);boolean i

28、sGameOver = (evalValue = WIN | evalValue = LOSE | evalValue = DRAW);if (depth = 0 | isGameOver) return evalValue;int bestValue = INFINITY;for (int pos = 0; pos 9; pos+) if (boardpos.getText().charAt(0) = empty) boardpos.setText(O);/ 选择最小值bestValue = Math.min(bestValue, max(board, depth - 1);boardpos

29、.setText( );return evalValue;/对于X,估值越大对其越有利public int max(JButton board, int depth) int evalValue = gameState(board);boolean isGameOver = (evalValue = WIN | evalValue = LOSE | evalValue = DRAW);if (depth = 0 | isGameOver) return evalValue;int bestValue = -INFINITY;for (int pos = 0; pos 9; pos+) if (

30、boardpos.getText().charAt(0) = empty) boardpos.setText(X);/ 选择最大值bestValue = Math.max(bestValue, min(board, depth - 1)boardpos.setText( );return evalValue;四 结束语至此,井字棋的两种方式人人对弈,人机对弈就都完成了。人人对弈较为简单,把对胜负判定的代码写好就行了,人机对弈需要考虑算法,让计算机来计算下一步走棋的位置,这比较复杂一些,尽管用了很大的篇幅来讲解极小极大算法,可能还是不太好理解,需要大家对照代码进一步仔细思考了。还有一点在文章中没有交代,就是搜索棋局过程中,限定了搜索的深度,即在min和max方法中通过depth变量来控制的,程序中限定depth是3,即搜索三层,因为每增加一层,棋局状态都会成几何指数的增长,层数太多会加大计算机的计算时间。这里不再详细探讨了,有兴趣的可以查看相关资料。此外,这里介绍的算法是最基本的,还有在此基础上的剪枝算法,负极小极大算法等,大家可以深入的去进一步学习。联系方式:电话:13393118922地址:石家庄桥西区新石中路39号嘉实栖园2-1-502邮箱:优秀论文精选!

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 科普知识


经营许可证编号:宁ICP备18001539号-1