1、游戏界面开发;
2、绘制棋盘,棋子,提示信息;
3、鼠标监听,需要保证棋子下在交叉线上;
4、胜负判定
5、按钮功能实现
6、计时器功能实现
//界面开发
public FiveChessFrame() {//设置界面标题和大小this.setTitle("五子棋");this.setSize(1280, 760);//保证游戏界面在桌面中心this.setLocation((width - 1280) / 2, (height - 720) / 2);// 窗体不可伸缩的this.setResizable(false);//界面关闭方式this.setDefaultCloseOperation(3);//添加鼠标监听this.addMouseListener(this);//用ImageIO读取背景图片文件try {bgImage = ad(new File("beijing2.jpg"));} catch (IOException e) {e.printStackTrace();}//窗体可视this.setVisible(true);// 线程启动t.start();// 线程挂起t.suspend();// 刷新屏幕,防止开始游戏时无法显示(黑屏paint();}
在这一模块中,主要使用了画笔(Graphics g)对棋盘、棋子绘制和运用双缓冲技术防止屏幕闪烁。在这一部分的主要难点在于对棋子的保存,让每下一个棋子都保留在棋盘上;而解决的思路是:用一个数组来保存棋盘上每个交点的数据,每下一棋,则改变此棋子为位置在数组中的数据;最后只需要每下一棋,就通过遍历数组就可以画出之前下过的棋子了,由此达到保存棋子的功能。
而双缓冲技术尚是初次接触,其核心思想是:把之前棋盘上的棋局合成一张图像,每次落子时调用paint方法只需要把图像画出就可以了,这样做可以避免当棋子多时,因为一个一个地画棋子形成的延时,在视觉上看就像是屏幕闪烁一样的问题。
//使用画笔绘制棋盘,棋子等// 鼠标点击坐标int x = 0;int y = 0;// 用二维数组保存下过的棋子数据(0没有棋子,1是黑子,2是白子)int[][] allchess = new int[29][17];// 提示当前下棋的是黑还是白String message = "黑方先行";// 保存双方剩余时间String blackMessage = "无限制";String whiteMessage = "无限制";//画笔public void paint(Graphics g) {// 双缓冲技术防止屏幕闪烁(大致原理为:把之前的面板当做一张图片画出,避免了工作量大时棋子画不过来)BufferedImage bufferedImage = new BufferedImage(1280, 760, BufferedImage.TYPE_INT_RGB);Graphics g1 = ateGraphics();// 画背景,g1.drawImage(bgImage, 0, 35, this);g1.setFont(new Font("微软雅黑", Font.BOLD, 30));g1.drawString("欢乐五子棋", 485, 70);g1.setFont(new Font("宋体", Font.BOLD, 40));//提示该谁落子了g1.drawOval(435, 680, 200, 65);g1.drawString(message, 450, 725);//倒计时提示款g1.setFont(new Font("黑体", Font.BOLD, 25));g1.drawString("黑方时间:" + blackMessage, 95, 725);g1.drawString("白方时间:" + whiteMessage, 710, 725);// 画棋盘// 画横线for (int i = 0; i < 17; i++) {g1.drawLine(20, 90 + 35 * i, 1002, 90 + 35 * i);}// 画竖线for (int j = 0; j < 29; j++) {g1.drawLine(20 + 35 * j, 90, 20 + 35 * j, 650);}// 标注四个点g1.fillOval(157, 192, 7, 7);g1.fillOval(857, 192, 7, 7);g1.fillOval(157, 542, 7, 7);g1.fillOval(857, 542, 7, 7);// 中心点g1.fillOval(507, 367, 7, 7);// 四条边的中点g1.fillOval(507, 193, 7, 7);g1.fillOval(507, 542, 7, 7);g1.fillOval(157, 367, 7, 7);g1.fillOval(857, 367, 7, 7);// 绘制全部棋子for (int i = 0; i < 29; i++) {for (int j = 0; j < 17; j++) {// 下过的黑子一一画出if (allchess[i][j] == 1) {int tempx = i * 35 + 20;int tempy = j * 35 + 90;g1.fillOval(tempx - 10, tempy - 10, 20, 20);}// 下过的白子一一画出if (allchess[i][j] == 2) {int tempx = i * 35 + 20;int tempy = j * 35 + 90;g1.setColor(Color.WHITE);g1.fillOval(tempx - 10, tempy - 10, 20, 20);g1.setColor(Color.BLACK);g1.drawOval(tempx - 10, tempy - 10, 20, 20);}}}//把面板内容当初一张图片画出g.drawImage(bufferedImage, 0, 0, this);}
在这一模块中,因为背景的原因,没有使用JButton来构建按钮,而是通过感应鼠标点击在背景中按钮的区域来达到类似按钮的功能。其中的难点在于悔棋功能的实现,而思路是:通过每下一个棋子就记录下棋子的位置,而悔棋只需要把之前数组中棋子位置的数据改为0(0是没棋子),把先行提示改回来,再调用repaint()重绘就可以了。
// 标识当前棋子的颜色boolean isBlack = true;// 标识当前游戏是否可以继续boolean canplay = true;// 记录每一个棋子的位置int chessX;int chessY;// 保存最多拥有多少时间(秒)int maxtime = 0;// 做倒计时的线程Thread t = new Thread(this);// 保存黑方和白方剩余时间int blacktime = 0;int whitetime = 0;@Overridepublic void mousePressed(MouseEvent e) {if (canplay == true) {// 获取点击位置x = e.getX();y = e.getY();// 判断鼠标是否点在棋盘上if (x >= 20 && x <= 1005 && y >= 90 && y <= 655) {// 保证输出的棋子在点上(说实话,这种方法下的棋子落点不够准确)x = (x - 20) / 35;y = (y - 90) / 35;// 判断位置是否可以下棋(空的)if (allchess[x][y] == 0) {// 判断当前下的是什么棋子if (isBlack == true) {// 保存棋子allchess[x][y] = 1;//保存刚下棋子的位置chessX = x;chessY = y;isBlack = false;message = "白方先行";} else {allchess[x][y] = 2;chessX = x;chessY = y;isBlack = true;message = "黑方先行";}} else {return;}// 每下一棋都重新画棋盘paint();// 每一步都判断胜负boolean winFlag = this.checkWin();if (winFlag == true) {JOptionPane.showMessageDialog(this, "游戏结束," + (allchess[x][y] == 1 ? "黑方" : "白方") + "获胜!");//锁棋盘,不再允许落子canplay = false;}}}// 开始游戏按钮if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 95 && e.getY() <= 135) {//提示弹框int result = JOptionPane.showConfirmDialog(this, "是否重新开始游戏?");//点击是if (result == 0) {JOptionPane.showMessageDialog(this, "重新开始游戏");canplay = true;// 清空棋盘(即把allchess数据清0,游戏信息改正,标识改正)allchess = new int[29][17];message = "黑方先行";isBlack = true;if (maxtime > 0) {blackMessage = maxtime / 3600 + ":" + (maxtime / 60 - maxtime / 3600 * 60) + ":"+ (maxtime - maxtime / 60 * 60);whiteMessage = maxtime / 3600 + ":" + (maxtime / 60 - maxtime / 3600 * 60) + ":"+ (maxtime - maxtime / 60 * 60);// 重新启动线程t.resume();} else {blackMessage = "无限制";whiteMessage = "无限制";}paint();}}// 游戏设置(时间)if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 170 && e.getY() <= 210) {String input = JOptionPane.showInputDialog("请输入游戏的最大时间(分钟)");try {maxtime = Integer.parseInt(input) * 60;if (maxtime < 0) {JOptionPane.showMessageDialog(this, "请输入正数");}if (maxtime == 0) {int result = JOptionPane.showConfirmDialog(this, "设置完成,是否重新开始游戏?");if (result == 0) {allchess = new int[29][17];message = "黑方先行";isBlack = true;blacktime = maxtime;whitetime = maxtime;blackMessage = "无限制";whiteMessage = "无限制";paint();}}if (maxtime > 0) {int result = JOptionPane.showConfirmDialog(this, "设置完成,是否重新开始游戏?");if (result == 0) {allchess = new int[29][17];message = "黑方先行";isBlack = true;//把剩余的时间赋给二者blacktime = maxtime;whitetime = maxtime;blackMessage = maxtime / 3600 + ":" + (maxtime / 60 - maxtime / 3600 * 60) + ":"+ (maxtime - maxtime / 60 * 60);whiteMessage = maxtime / 3600 + ":" + (maxtime / 60 - maxtime / 3600 * 60) + ":"+ (maxtime - maxtime / 60 * 60);t.resume();paint();}}} catch (NumberFormatException e1) {JOptionPane.showMessageDialog(this, "请规范输入时间!");}}// 游戏说明if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 255 && e.getY() <= 295) {JOptionPane.showMessageDialog(this, "此处没有说明。");}// 认输if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 395 && e.getY() <= 435) {int result = JOptionPane.showConfirmDialog(this, "是否确认认输?");if (result == 0) {if (isBlack) {JOptionPane.showMessageDialog(this, "黑方认输,游戏结束");} else {JOptionPane.showMessageDialog(this, "白方认输,游戏结束");}canplay = false;}}// 悔棋if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 470 && e.getY() <= 515) {int result = JOptionPane.showConfirmDialog(this, (isBlack == true ? "白方悔棋,黑方是否同意?" : "黑方悔棋,白方是否同意?"));if (result == 0) {JOptionPane.showMessageDialog(this, "对方允许悔棋,请重新落子。");//把刚下的棋子数据清0allchess[chessX][chessY] = 0;//改变先行提示if (isBlack == true) {isBlack = false;message = "白方先行";} else {isBlack = true;message = "黑方先行";}//重画棋局paint();}if (result == 1) {JOptionPane.showMessageDialog(this, "对方不允许你悔棋,请继续游戏。");}}// 退出if (e.getX() >= 1080 && e.getX() <= 1240 && e.getY() >= 555 && e.getY() <= 600) {JOptionPane.showMessageDialog(this, "游戏结束");//直接退出it(0);}}
在此模块中,通过五子棋的规则(横、竖、左斜、右斜任一方向上5个棋子连成一线)来判断胜负;而计数不仅要往右计数,还要往左计算,通过横纵坐标的加减来判断右边后,根据保存刚下棋子的坐标来重新向左边计数。
// 胜负判定(四个方向)private boolean checkWin() {boolean flag = false;// 保存共有相同颜色相连棋子的个数int count = 1;// 棋子用颜色标识,1是黑子,2是白子int color = allchess[x][y];// 判断横向count = this.checkCount(1, 0, color);if (count >= 5) {flag = true;} else {// 判断纵向count = this.checkCount(0, 1, color);if (count >= 5) {flag = true;} else {// 判断45°count = this.checkCount(1, -1, color);if (count >= 5) {flag = true;} else {// 判断135°count = this.checkCount(1, 1, color);if (count >= 5) {flag = true;}}}}return flag;}// 判定是否有5个棋子相连public int checkCount(int xChange, int yChange, int color) {int count = 1;// 保存刚下的棋子坐标int tempx = xChange;int tempy = yChange;//从刚下的棋子坐标开始往右边判断// 每次向前移动一次,遇到同一颜色就继续判断while (x + xChange >= 0 && x + xChange < 29 && y + yChange >= 0 && y + yChange < 17&& color == allchess[x + xChange][y + yChange]) {count++;if (xChange != 0)xChange++;if (yChange != 0) {if (yChange > 0) {// 使棋子沿着右下一条线移动,进行判断yChange++;} else {// 使棋子沿着右上一条线移动,进行判断yChange--;}}}//从刚下的棋子坐标开始往左边判断xChange = tempx;yChange = tempy; while (x - xChange >= 0 && x - xChange < 29 && y - yChange >= 0 && y - yChange < 17&& color == allchess[x - xChange][y - yChange]) {count++;if (xChange != 0) {xChange++;}if (yChange != 0) {if (yChange > 0) {// 使棋子沿着左上一条线移动,进行判断yChange++;} else {// 使棋子沿着左下一条线移动,进行判断yChange--;}}}return count;}
@Overridepublic void run() {//还有剩余时间,则开始计时if (maxtime > 0) {while (true) {if (isBlack) {blacktime--;if (blacktime == 0) {JOptionPane.showMessageDialog(this, "黑方超时,游戏结束!");canplay = false;//线程挂起,停止计时t.suspend();}} else {whitetime--;if (whitetime == 0) {JOptionPane.showMessageDialog(this, "白方超时,游戏结束!");canplay = false;t.suspend();}}// 睡眠1秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 通过线程的运行、睡眠来间断显示blackMessage或whiteMessage来达到时间跳动的效果blackMessage = blacktime / 3600 + ":" + (blacktime / 60 - blacktime / 3600 * 60) + ":"+ (blacktime - blacktime / 60 * 60);whiteMessage = whitetime / 3600 + ":" + (whitetime / 60 - whitetime / 3600 * 60) + ":"+ (whitetime - whitetime / 60 * 60);paint();}}}
在整个五子棋项目中,最让我印象深刻的是悔棋功能的事现;尽管我的思路是对的,但是老是没有找准棋子的坐标位置,甚至一度把我上一次鼠标点击的位置(x,y)当成了棋子的坐标,实际上上一次点击的位置是悔棋按钮,因此老是出现数组越界,更是揪着数组越界这个错误,又改成棋子数组的下标((x - 20) / 35, (y - 90) / 35);一番周折后才意识到问题的所在,才重新定义变量来保持棋子位置,悔棋功能得以实现。此外,在整个项目中,我对于通过JOptionPane设置弹框,通过 ateGraphics()实现双缓冲,通过线程的start()和suspend()、sleep()来达到计时的效果,通过标识的运用来减小代码量等等也有所收获。
此外,在这个项目里,我也遇到了一些问题,比如使用了双缓冲技术后,我发现我的棋盘,时间计时,先行提示信息等一些通过双缓冲后的画笔画的东西都由黑色变成了白色,而且一开始下的几个黑棋也变白了;对于这个问题,不知所以然。
本文发布于:2024-01-29 10:26:49,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170649521114634.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |