package org.liky.game.frame;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Toolkit;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.JFrame;import javax.swing.JOptionPane;public class FiveChessFrame extends JFrame implements MouseListener, Runnable { // 取得屏幕的宽度 int width = Toolkit.getDefaultToolkit().getScreenSize().width; // 取得屏幕的高度 int height = Toolkit.getDefaultToolkit().getScreenSize().height; // 背景图片 BufferedImage bgImage = null; // 保存棋子的坐标 int x = 0; int y = 0; // 保存之前下过的全部棋子的坐标 // 其中数据内容 0: 表示这个点并没有棋子, 1: 表示这个点是黑子, 2:表示这个点是白子 int[][] allChess = new int[19][19]; // 标识当前应该黑棋还是白棋下下一步 boolean isBlack = true; // 标识当前游戏是否可以继续 boolean canPlay = true; // 保存显示的提示信息 String message = "黑方先行"; // 保存最多拥有多少时间(秒) int maxTime = 0; // 做倒计时的线程类 Thread t = new Thread(this); // 保存黑方与白方的剩余时间 int blackTime = 0; int whiteTime = 0; // 保存双方剩余时间的显示信息 String blackMessage = "无限制"; String whiteMessage = "无限制"; public FiveChessFrame() { // 设置标题 this.setTitle("五子棋"); // 设置窗体大小 this.setSize(500, 500); // 设置窗体出现位置 this.setLocation((width - 500) / 2, (height - 500) / 2); // 将窗体设置为大小不可改变 this.setResizable(false); // 将窗体的关闭方式设置为默认关闭后程序结束 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 为窗体加入监听器 this.addMouseListener(this); // 将窗体显示出来 this.setVisible(true); t.start(); t.suspend(); // 刷新屏幕,防止开始游戏时出现无法显示的情况. this.repaint(); String imagePath = "" ; try { imagePath = System.getProperty("user.dir")+"/bin/image/background.jpg" ; bgImage = ImageIO.read(new File(imagePath.replaceAll("\\\\", "/"))); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void paint(Graphics g) { // 双缓冲技术防止屏幕闪烁 BufferedImage bi = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); Graphics g2 = bi.createGraphics(); g2.setColor(Color.BLACK); // 绘制背景 g2.drawImage(bgImage, 1, 20, this); // 输出标题信息 g2.setFont(new Font("黑体", Font.BOLD, 20)); g2.drawString("游戏信息:" + message, 130, 60); // 输出时间信息 g2.setFont(new Font("宋体", 0, 14)); g2.drawString("黑方时间:" + blackMessage, 30, 470); g2.drawString("白方时间:" + whiteMessage, 260, 470); // 绘制棋盘 for (int i = 0; i <19; i++) { g2.drawLine(10, 70 + 20 * i, 370, 70 + 20 * i); g2.drawLine(10 + 20 * i, 70, 10 + 20 * i, 430); } // 标注点位 g2.fillOval(68, 128, 4, 4); g2.fillOval(308, 128, 4, 4); g2.fillOval(308, 368, 4, 4); g2.fillOval(68, 368, 4, 4); g2.fillOval(308, 248, 4, 4); g2.fillOval(188, 128, 4, 4); g2.fillOval(68, 248, 4, 4); g2.fillOval(188, 368, 4, 4); g2.fillOval(188, 248, 4, 4); /* * //绘制棋子 x = (x - 10) / 20 * 20 + 10 ; y = (y - 70) / 20 * 20 + 70 ; * //黑子 g.fillOval(x - 7, y - 7, 14, 14); //白子 g.setColor(Color.WHITE) ; * g.fillOval(x - 7, y - 7, 14, 14); g.setColor(Color.BLACK) ; * g.drawOval(x - 7, y - 7, 14, 14); */ // 绘制全部棋子 for (int i = 0; i <19; i++) { for (int j = 0; j <19; j++) { if (allChess[i][j] == 1) { // 黑子 int tempX = i * 20 + 10; int tempY = j * 20 + 70; g2.fillOval(tempX - 7, tempY - 7, 14, 14); } if (allChess[i][j] == 2) { // 白子 int tempX = i * 20 + 10; int tempY = j * 20 + 70; g2.setColor(Color.WHITE); g2.fillOval(tempX - 7, tempY - 7, 14, 14); g2.setColor(Color.BLACK); g2.drawOval(tempX - 7, tempY - 7, 14, 14); } } } g.drawImage(bi, 0, 0, this); } public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub /* * System.out.println("X:"+e.getX()); System.out.println("Y:"+e.getY()); */ if (canPlay == true) { x = e.getX(); y = e.getY(); if (x >= 10 && x <= 370 && y >= 70 && y <= 430) { x = (x - 10) / 20; y = (y - 70) / 20; if (allChess[x][y] == 0) { // 判断当前要下的是什么颜色的棋子 if (isBlack == true) { allChess[x][y] = 1; isBlack = false; message = "轮到白方"; } else { allChess[x][y] = 2; isBlack = true; message = "轮到黑方"; } // 判断这个棋子是否和其他的棋子连成5连,即判断游戏是否结束 boolean winFlag = this.checkWin(); if (winFlag == true) { JOptionPane.showMessageDialog(this, "游戏结束," + (allChess[x][y] == 1 ? "黑方" : "白方") + "获胜!"); canPlay = false; } } else { JOptionPane.showMessageDialog(this, "当前位置已经有棋子,请重新落子!"); } this.repaint(); } } /* System.out.println(e.getX() + " -- " + e.getY()); */ // 点击 开始游戏 按钮 if (e.getX() >= 400 && e.getX() <= 470 && e.getY() >= 70 && e.getY() <= 100) { int result = JOptionPane.showConfirmDialog(this, "是否重新开始游戏?"); if (result == 0) { // 现在重新开始游戏 // 重新开始所要做的操作: 1)把棋盘清空,allChess这个数组中全部数据归0. // 2) 将 游戏信息: 的显示改回到开始位置 // 3) 将下一步下棋的改为黑方 for (int i = 0; i <19; i++) { for (int j = 0; j <19; j++) { allChess[i][j] = 0; } } // 另一种方式 allChess = new int[19][19]; message = "黑方先行"; isBlack = true; blackTime = maxTime; whiteTime = maxTime; 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 = "无限制"; } this.canPlay = true; this.repaint(); } } // 点击 游戏设置 按钮 if (e.getX() >= 400 && e.getX() <= 470 && e.getY() >= 120 && e.getY() <= 150) { String input = JOptionPane .showInputDialog("请输入游戏的最大时间(单位:分钟),如果输入0,表示没有时间限制:"); try { maxTime = Integer.parseInt(input) * 60; if (maxTime <0) { JOptionPane.showMessageDialog(this, "请输入正确信息,不允许输入负数!"); } if (maxTime == 0) { int result = JOptionPane.showConfirmDialog(this, "设置完成,是否重新开始游戏?"); if (result == 0) { for (int i = 0; i <19; i++) { for (int j = 0; j <19; j++) { allChess[i][j] = 0; } } // 另一种方式 allChess = new int[19][19]; message = "黑方先行"; isBlack = true; blackTime = maxTime; whiteTime = maxTime; blackMessage = "无限制"; whiteMessage = "无限制"; this.canPlay = true; this.repaint(); } } if (maxTime > 0) { int result = JOptionPane.showConfirmDialog(this, "设置完成,是否重新开始游戏?"); if (result == 0) { for (int i = 0; i <19; i++) { for (int j = 0; j <19; j++) { allChess[i][j] = 0; } } // 另一种方式 allChess = new int[19][19]; 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(); this.canPlay = true; this.repaint(); } } } catch (NumberFormatException e1) { // TODO Auto-generated catch block JOptionPane.showMessageDialog(this, "请正确输入信息!"); } } // 点击 游戏说明 按钮 if (e.getX() >= 400 && e.getX() <= 470 && e.getY() >= 170 && e.getY() <= 200) { JOptionPane.showMessageDialog(this, "这个一个五子棋游戏程序,黑白双方轮流下棋,当某一方连到五子时,游戏结束。
每次刷新界面的时候,先把背景画上去,然后再画棋子。为此,你的整个棋盘状况应该是储存起来的,代码大概要做到这样:游戏在电脑运行,显示给人看的部分跟整个游戏的运行没有关系,整个游戏可以随时以任意形式显示出来。
就像是程序在主机运行,你把显示器关了,程序还在跑,把显示器开了就能看到。
一、实验题目 五子棋游戏。 二、问题分析 五子棋是双人博弈棋类益智游戏,由围棋演变而来,属纯策略型。
棋盘通常15*15,即15行,15列,共225个交叉点,即棋子落点;棋子由黑白两色组成,黑棋123颗,白棋122颗。
游戏规则为黑先白后,谁先五子连成一条直线谁赢,其中直线可以是横的、纵的、45度、135度。 本次Java编程我的目的是现实人机对战,即游戏者一方是人,另一方计算机。这就要求程序不仅要具备五子棋的基本界面,还要编程指导计算机与人进行对弈。为了使程序尽可能智能,我采用了贪心策略、传统搜索算法、极大极小博弈树算法,对应游戏玩家的3个等级:简单、中等、困难。
三、功能设计 我的程序基本功能是实现人机对弈五子棋。人和电脑交替下棋,谁先五子连成一条直线谁就赢。下面是我程序的功能模块: 1.等级设置 核心功能是实现不同策略与算法的对比运用,纯贪心策略实现简单等级对手,直接搜索算法实现中等等级对手,极大极小博弈树算法实现困难等级对手。
对应程序中的3选1单选按钮。 2.悔棋功能 模拟栈机制实现人悔棋,不限步长的悔棋。对应程序中的悔棋按钮。
3.棋面绘制 根据不同机计算机的屏幕分辨率,绘制逼真的棋盘。 4.图片引入 两张古典的人物图片,生动模拟对弈双方。人物图片旁的黑白棋钵图片显示黑白棋归属。
5.背景设置 支持用户选择背景,包括棋盘、棋盘边框、窗口边框,彰显个性。 6.音乐播放 下棋时有棋子落地的声音,一方胜利时有五子连成一片的声音。同时在设置背景时相应的改变整个对弈过程中的背景音乐。 7.时间显示 在棋盘正上方有一模拟文本框显示当前棋局用时。
8.其他小功能 支持和棋、认输、开启新游戏、退出游戏等操作。 四、数据结构与算法设计 数据结构部分 1.当前棋局的存储结构 我的五子棋程序选择通常用到的15行*15列棋盘,可以开二维数组PositionFlag = new int[15][15],PositionFlag[i][j]为0表示(i,j)点尚无棋,为1表示(i,j)点是人的棋子,为2表示(i,j)点是机器的棋子。之所以选择二维数组,主要原因有两点: 1.本程序需要频繁随机访问15*15的交叉点,对应查询该点状态以及改变该点状态,随机访问是数组的特点。 2.15*15=225开二维数组的内存需求相对现在内存为2G及以上的计算机完全可以接受,且数组实现简单、操作方便。
基于以上两点,尽管创建动态的顺序表—链表可能可以节省少量内存(可以只存当前有棋的点,原数组对应位置为0的点可以不存),但选择数组的优势完全在上述两点体现了出来。 2.实现悔棋操作的数据结构 由于每次悔棋只需回退当前几步,后进先出原则,这正是栈这种典型数据结构的设计思想,于是我选择栈。我自己先写了用自定义数组模拟的栈,但由于是学Java语言且由于悔棋的存储空间需要随当前步数增大而增大(由于每局最多下225步,即最多要悔225步,所以自己开个225的数组完全可以避免存储空间自增长的问题且内存完全可以接受,之所以不用自定义数组而用ArrayList类主要是为了尝试Java中STL的用法),所有我最终改为用Java类库中的ArrayList类。 确定用ArrayList类实现栈机制后就必须考虑每个ArrayList单元具体存储什么。
刚开始我存储的是当前的棋局,即整个局面,而每个局面对应一个二维数组,这样是很占用内存的。试想一下,在最坏情况下,225个ArrayList单元,每个单元存放一个15*15的二维数组,尽管225*15*15在Java的内存管理机制下不会爆栈,但也是极不划算的。之所以说不划算,是因为有更好的解决方案。
由于每次悔棋只是在回退倒数一步,多步悔棋只需循环回退,所以可以只存储当前棋局最后一步的下法,对应一个二维点,完全可以自定义一个二维坐标类chessOneStep。 算法设计部分 Java语言是面向对象的语言。我在进行五子棋游戏编程是总共传创建了11个自定义的类。
在编写程序的过程中,我有一个明显的体验就是面向对象编程就是一项有关对象设计和对象接口技术,很多关键的技术就是如何设计自定义的对象。 下面我先概括给出我的所有类的作用: 1.mainFrame类:主框架类,我应用程序的入口; 2.chessPositon类:主控类,这个类是我程序的核心类,负责控制双方的下棋,以及调用其他的类完成当前棋局的显示绘制; 3.chessPanel类:面板类,调用其他底层类完成当前棋局的显示绘制; 4.chessBoard类:棋盘绘制类,负责棋盘的绘制; 5.chessImage类:文件类,包含各种资源(背景图片、背景音乐)以及静态全局变量(public static Type); 6.chessButton类:组件类,定义各种组件,包括按钮、单选按钮、文本框等; 7.chessMusic类:音乐类,负责调用Java库类完成背景音乐、下棋音乐、取胜音乐等的播放; 8.chessPiece类:棋局类,定义棋局二维数组数据结构并完成相关操作; 9.chessList类:栈类,完成悔棋等操作; 10. chessOneStep类:棋子类,定义每步坐标以及下在该处获得的估价值; 11.myCompare类:排序类,完成chessOneStep类的自定义排序 详细设计 1.mainFrame类 作为我的五子棋程序的主类,mainFrame类主要实例化相关的对象,如chessbutton,chessborad等,从而完成框架的创建。更重要的是实例化chessposition,这是本程序的核心类,控制游戏双方行棋过程完成人机互动下棋,然后将MyChessPosition与鼠标响应addMouseListener()关联起来。 2.chessMusic类 一个好的游戏必须给人一种身临其境的感觉,而声音是营造这种氛围的重要因素。
参照网上各游戏运行商的音乐配置,我选择相关逼真的声音。包括背景音乐、下棋棋子落到棋盘发出的声音以及一方胜出的配乐。所有这些功能的实现,依赖于自定义的chessMusic类,采用AudioInputStream配合Clip的方式完成音乐播放的软硬件工作,然后定义两个接口chessmusic(String Name)和Stop(),前者完成播放功能,后者完成关闭当前音乐功能。
因为音频文件相对较大,而我的程序提供在不同背景乐之间切换的功能,所以在打开另一个音频文件之前必须关闭前一个正在播放的音频文件,防止出现溢出。 3.chessImage类 适当的动画或图片能给游戏玩家带来美的体验。所以我的五子棋程序界面在不失和谐的前提下引入了尽可能多的图片,包括对弈双方、棋钵等。图片引入的具体工作通过语句import javax.imageio.ImageIO完成。
同时,由于图片要在用到它的类中被访问,为了避免频繁调用函数,我直接将图片相关联的对象定义为public static,表明是公用的、静态的。进一步引申开去,我将程序中用到的静态全局变量都定义在chessImage类中。具体如下: public static Date begin;//每局开始时间 public static Date cur;//每局结束时间 public static chessOneStep LineLeft;//结束端点1 public static chessOneStep LineRight;//结束端点2 public static boolean IsGameOver;//是否只有一方获胜 public static int ColorOfBackGround[][]= {{255, 227, 132},{0,255,127},{218,165,32}};//背景颜色 public static int ColorOfWindows[][]= {{ 60,179,113},{245,245,245},{122,122,122}};//背景颜色 public static int WitchMatch;//背景搭配 public static String MusicOfBackGround;//背景音乐 public static int CurrentStep;//记录当前步数 public static int Rank;//设置难度等级 public static boolean IsSurrender;//判断是否认输 public static boolean IsTie;//判断是否认输 public static String Message;//输出提示信息 public static Image IconImage;// 图标 public static Image blackBoard;//白棋盘 public static Image whiteBoard;//黑棋盘 public static Image blackChess;// 白棋棋子图片 public static Image whiteChess;// 白棋棋子图片 public static Image RightPlayer;//白棋棋罐图片 public static Image LeftPlayer;//白棋玩家头像图片 public static String path = "src/";// 图片的保存路径 4.chessButton类 这个是程序的组件类。
定义了各种功能键,完善程序功能,营造逼真的人机对战游戏效果。分为3类:效果。。 (1)、按钮组件 本程序有5个按钮,支持和棋、。
最简单的方法: JFrame mainframe = new JFrame("五子棋"); mainframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel cp = (JPanel) mainframe.getContentPane(); cp.setLayout(new BorderLayout()); ImageIcon background = new ImageIcon("D:\\java documents\\Gobang\\棋盘.jpg"); JLabel label=new JLabel(background); cp.add("Center", label); mainframe.pack(); mainframe.setVisible(true);不管用哪种方法,由于窗口有最小大小限制(主要是要显示标题栏上的按钮,所以如果设置成不要按钮,就可以不用本限制,但Jframe不好办,需要用其他类型的窗口),所以如果图片过窄的话,两边会显示空白。