基于java的五子棋博弈系统
前言
本文是以学校Java面向对象程序设计课程设计要求为背景,利用了Java语言、人工智能算法知识和Eclipse IDE开发平台,开发了五子棋小游戏,本论文论述了五子棋小游戏的概念、结构、基本思路和方法,全文共分为绪论、设计分析、详细分析、设计结果及分析、总结和展望、参考文献以及附录(代码)等。
设计目的
- 巩固加深对java语言课程基本知识的理解和掌握;
- 掌握java语言编程和调试的基本技能;
- 掌握javaGUI编程的基本能力;
- 掌握JDK、WindowBuilder等开发工具的运用;
- 拓宽常用库类的应用;
- 了解人工智能算法。
设计分析
该课程设计是要求编写五子棋小游戏,能实现双方轮流下棋,人人对弈、人机对弈并能判断游戏胜利。具体功能:
- 登录注册功能:新用户可以使用注册功能,进行账号密码注册;旧用户直接使用账号密码登录;
- 界面搭建:根据不同的计算机屏幕分辨率,搭建并显示界面;
- 背景设置:将棋盘图片以背景的形式显示在界面上,同时能够显示棋子;
- 时间显示:在棋盘正上方有两个文本框,用于显示黑方和白方当前棋局所剩时间;
- 对弈模式:默认为“玩家VS玩家”,用户可以选择“玩家VS电脑”;
- 对弈先手:默认先手为黑方,不推荐用户选择白方为先手;
- 悔棋功能:将点击前最后一步棋子悔去,对应界面中悔棋按钮;
- 投降功能:实现任何一方下棋过程中认输,并弹出消息对话框显示相应信息;
- 开始游戏,实现在开始新的游戏之前,将棋盘上原有的棋子清空,以及对弈时间以及棋谱的相关文本清空;
- 棋谱保存,实时显示棋子的坐标,将之输出在文本域中,并实现保存成txt文件;
- 退出系统。
总体设计
本程序主要分为十大模块(功能模块图见图):登录注册、开始游戏、绘制棋盘、时间显示、棋谱保存、显示当前执行方、悔棋功能、投降功能、对弈模式和退出系统。
详细设计
一、LoginFrame类详细说明
1.LoginFrame()构造方法
public LoginFrame() {
login = new BaseFrame("|五子棋游戏|登录界面");
login.addMouseListener(this);
login.setBounds((width-800)/2,(height-800)/2, 800, 800);
login.setResizable(false);
//创建一个面板
JPanel contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
login.setContentPane(contentPane);
ImageIcon logoimage = new ImageIcon("\\Renju_Project\\src\\Renju_Image\\界面logo.png");
logoimage.setImage(logoimage.getImage().getScaledInstance(600, 180, Image.SCALE_DEFAULT));
logo.setIcon(logoimage);//将标签设置为logo图片
logo.setBounds(90, 50, 600, 180);
contentPane.add(logo);
user.setFont(new Font("微软雅黑", Font.BOLD, 30));
user.setBounds(200, 300, 100, 50);
contentPane.add(user);
password.setFont(new Font("微软雅黑", Font.BOLD, 30));
password.setBounds(200, 400, 100, 50);
contentPane.add(password);
user_Text.setFont(new Font("微软雅黑",Font.PLAIN,25));
//设置文字颜色(前景色)
user_Text.setForeground(new Color(181, 181, 181));
user_Text.setBounds(300, 310, 300, 40);
user_Text.setText("请输入账号:");
contentPane.add(user_Text);
password_Text.setFont(new Font("微软雅黑",Font.PLAIN,25));
password_Text.setForeground(new Color(181, 181, 181)); //设置文字颜色(前景色)
password_Text.setBounds(300, 410, 300, 40);
contentPane.add(password_Text);
user_Null.setFont(new Font("微软雅黑",Font.PLAIN,20));
user_Null.setForeground(Color.red);
user_Null.setBounds(620, 310, 300, 40);
user_Null.setVisible(false);
contentPane.add(user_Null);
password_Null.setFont(new Font("微软雅黑",Font.PLAIN,20));
password_Null.setForeground(Color.red);
password_Null.setBounds(620, 410, 300, 40);
password_Null.setVisible(false);
contentPane.add(password_Null);
user_Error.setFont(new Font("微软雅黑",Font.PLAIN,20));
user_Error.setForeground(Color.red);
user_Error.setBounds(620, 310, 300, 40);
user_Error.setVisible(false);
contentPane.add(user_Error);
password_Error.setFont(new Font("微软雅黑",Font.PLAIN,20));
password_Error.setForeground(Color.red);
password_Error.setBounds(620, 410, 300, 40);
password_Error.setVisible(false);
contentPane.add(password_Error);
login_Button.setFont(new Font("微软雅黑",Font.BOLD,30));
login_Button.setBounds(200, 500, 400, 50);
contentPane.add(login_Button);
register.setFont(new Font("微软雅黑",Font.PLAIN,25));
register.setForeground(Color.gray);
register.setBounds(500, 580, 100, 50);
contentPane.add(register);
about.setFont(new Font("微软雅黑",Font.PLAIN,25));
about.setForeground(Color.gray);
about.setBounds(200, 580, 70, 40);
contentPane.add(about);
login.setVisible(true);
}
本部分主要是对登录界面的搭建,显示五子棋logo,账号与密码文本框,登录按钮以及关于游戏和立即注册的标签按钮,如图5所示。在图像设置时采用了getScaledInstance()方法,其作用是可以是图片自动缩放使其自适应JFrame。
getScaledInstance(int width,int height,int hints)方法中有三个参数,分别为width:图片缩放到的宽度、height:图片缩放到的高度、hints:指示用于图像重新取样的算法类型标志。其中图像重新取样的算法类型有多种,各种优缺点不同,开发者在开发时可以更据不同需求使用不同的算法类型。
大致分为:
- SCALE_DEFAULT:采用默认的图像缩放算法
- SCALE_FAST:采用快速的图像缩放算法,速度快但图像不平滑
- SCALE_AREA_AVERAGING:采用区域平均图像缩放算法
- SCALE_SMOOTH:采用侧重缩放效果,图像平滑但速度慢
- SCALE_REPLICATE:采用ReplicateScaleFilter类中包含的图像缩放算法。
2.登录界面鼠标事件
login_Button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String userName = user_Text.getText().trim();
String Password = new String(password_Text.getPassword()).trim();
System.out.println(Password);
//判断账号和密码是否合法
if(userName.length() != 0) {//账号不为空
user_Null.setVisible(false);
if(Password.length() != 0) {//密码不为空
if(f.readData("UserInformation.xls",userName)&& Password.equals(f.backData("UserInformation.xls",userName,"PassWord"))) {// 用户输入的账号密码正确
user_Error.setVisible(false);//隐藏提示信息
password_Error.setVisible(false);
password_Null.setVisible(false);
login_Button.setText("正在登陆...");
closeThis();
GameFrame frame = new GameFrame();//跳转到游戏界面
frame.setVisible(true);
}else if(f.readData("UserInformation.xls", userName) && !Password.equals(f.backData("UserInformation.xls", userName, "PassWord"))) {//用户输入密码错误
password_Error.setVisible(true);
user_Error.setVisible(false);
password_Null.setVisible(false);
password_Text.setText("");//密码框清空
password_Text.requestFocus();//光标定位到密码框
}
else {//账号未注册
user_Error.setVisible(true); //提示该账号未注册
password_Error.setVisible(false);
password_Null.setVisible(false);
}
}else {//密码为空
if(userName.equals("admin")) {
password_Null.setVisible(true);
user_Error.setVisible(false);
user_Null.setVisible(false);
password_Error.setVisible(false);
}else {//用户名为空
password_Null.setVisible(false);
user_Null.setVisible(false);
user_Error.setVisible(true);
password_Error.setVisible(false);
}
}
}else {//账号为空
password_Null.setVisible(false);
user_Null.setVisible(true);//提示输入账号
user_Error.setVisible(false);
password_Error.setVisible(false);
password_Text.setText("");//将密码框置为空
if(Password.length() == 0) {//密码为空
password_Null.setVisible(true);
user_Error.setVisible(false);
password_Error.setVisible(false);
}
}
}
});
首先通过getText()方法获得用户输入的用户名和密码,通过长度判断是否为空,以及获取excel文件中的数据与用户输入的字符串进行对比,若值相同则登陆成功,否则,重新输入账号和密码。
3.其他鼠标事件详细设计
user_Text.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
user_Text.setText("");
user_Text.setForeground(new Color(0, 0, 0));
}
});
password_Text.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
password_Text.setText("");
password_Text.setForeground(new Color(0, 0, 0));
}
});
about.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
closeThis();//销毁当前页面
new AboutFrame();
}
public void mouseEntered(MouseEvent e) {
about.setForeground(Color.red);
}
public void mouseExited(MouseEvent e) {
about.setForeground(Color.gray);
}
});
register.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
closeThis();
new RegisterFrame();
}
public void mouseEntered(MouseEvent e) {
register.setForeground(Color.red);
}
public void mouseExited(MouseEvent e) {
register.setForeground(Color.gray);
}
});
本部分对用户名、密码输入文本框、关于游戏、立即注册标签添加了鼠标事件,点击文本框时,将其原有内容置空,以及设置对应颜色。设置鼠标移入和移出时标签颜色的变化,点击跳转到对应界面,并调用closeThis()方法销毁当前界面。closeThis()方法是开发者本人自己写的方法,通过login.dispose()方法来实现销毁界面。
二、RegisterFrame类详细说明
1.RegisterFrame()构造方法
public RegisterFrame() {
Container contentPane = getContentPane();//创建容器
register = new BaseFrame("|五子棋游戏|注册界面");
register.setBounds((width-600)/2,(height-600)/2, 600,600);
contentPane.setBackground(new Color(232, 232, 220));
contentPane.setLayout(null);
register.setResizable(false);
register.setContentPane(contentPane);
drawSC.setBounds(310,310,105,60);
register.add(drawSC);
title.setFont(new Font("微软雅黑",Font.BOLD,30));
title.setBounds(165,10, 270, 40);
contentPane.add(title);
register_user.setFont(new Font("微软雅黑",Font.BOLD,26));
register_user.setBounds(80,100, 80, 40);
contentPane.add(register_user);
register_password.setFont(new Font("微软雅黑",Font.BOLD,26));
register_password.setBounds(80,170, 80, 40);
contentPane.add(register_password);
confirm_password.setFont(new Font("微软雅黑",Font.BOLD,26));
confirm_password.setBounds(35,240, 120, 40);
contentPane.add(confirm_password);
security_code.setFont(new Font("微软雅黑",Font.BOLD,26));
security_code.setBounds(60,310, 100, 40);
contentPane.add(security_code);
register_user_Text.setFont(new Font("微软雅黑",Font.PLAIN,20));
register_user_Text.setBounds(172, 104,250, 40);
contentPane.add(register_user_Text);
register_password_Text.setFont(new Font("微软雅黑",Font.PLAIN,20));
register_password_Text.setBounds(172, 174,250, 40);
contentPane.add(register_password_Text);
confirm_password_Text.setFont(new Font("微软雅黑",Font.PLAIN,20));
confirm_password_Text.setBounds(172, 244,250, 40);
contentPane.add(confirm_password_Text);
security_code_Text.setFont(new Font("微软雅黑",Font.PLAIN,18));
security_code_Text.setBounds(172,314,125, 40);
security_code_Text.setForeground(Color.gray);
contentPane.add(security_code_Text);
change_code.setFont(new Font("微软雅黑",Font.PLAIN,18));
change_code.setBounds(430,314,130, 40);
change_code.setForeground(Color.gray);
contentPane.add(change_code);
register_button.setFont(new Font("微软雅黑",Font.BOLD,30));
register_button.setBounds(150,400,300, 50);
contentPane.add(register_button);
back.setFont(new Font("微软雅黑",Font.PLAIN,18));
back.setBounds(360,450,100,50);
back.setForeground(Color.gray);
contentPane.add(back);
user_repeat.setFont(new Font("微软雅黑",Font.PLAIN,18));
user_repeat.setBounds(430,100, 150, 40);
user_repeat.setForeground(Color.RED);
user_repeat.setVisible(false);
contentPane.add(user_repeat);
user_null.setFont(new Font("微软雅黑",Font.PLAIN,18));
user_null.setBounds(430,100, 150, 40);
user_null.setForeground(Color.RED);
user_null.setVisible(false);
contentPane.add(user_null);
user_short.setFont(new Font("微软雅黑",Font.PLAIN,18));
user_short.setBounds(430,100, 150, 40);
user_short.setForeground(Color.RED);
user_short.setVisible(false);
contentPane.add(user_short);
password_null.setFont(new Font("微软雅黑",Font.PLAIN,18));
password_null.setBounds(430,173, 150, 40);
password_null.setForeground(Color.RED);
password_null.setVisible(false);
contentPane.add(password_null);
password_short.setFont(new Font("微软雅黑",Font.PLAIN,18));
password_short.setBounds(430,173, 150, 40);
password_short.setForeground(Color.RED);
password_short.setVisible(false);
contentPane.add(password_short);
password_error.setFont(new Font("微软雅黑",Font.PLAIN,18));
password_error.setBounds(430,243, 150, 40);
password_error.setForeground(Color.RED);
password_error.setVisible(false);
contentPane.add(password_error);
password_correct.setFont(new Font("微软雅黑",Font.PLAIN,18));
password_correct.setBounds(430,243, 150, 40);
password_correct.setForeground(Color.RED);
password_correct.setVisible(false);
contentPane.add(password_correct);
security_code_error.setFont(new Font("微软雅黑",Font.PLAIN,18));
security_code_error.setBounds(172,355, 150, 40);
security_code_error.setForeground(Color.RED);
security_code_error.setVisible(false);
contentPane.add(security_code_error);
register.setVisible(true);
}
本部分主要对注册界面进行搭建,显示“账号”、“密码”、“确认密码”、“验证码”等标签,以及对应的输入文本框,界面如图所示。
2.注册按钮鼠标事件监听详细设计
register_button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
userName = register_user_Text.getText().trim();//验证用户名
if(new FileOperation().readData("UserInformation.xls", userName)) {//用户名已存在
user_repeat.setVisible(true);//提示账号已经被注册
user_null.setVisible(false);//隐藏账号为空提示
user_short.setVisible(false);//隐藏账号长度小于5字符提示
flagUserName = 0;
}
//如果账号长度小于5字符
else if(userName.length() < 5 && userName.length() > 0) {
user_short.setVisible(true);//显示账号长度小于5字符提示
user_repeat.setVisible(false);//隐藏账号已经被注册
user_null.setVisible(false);//隐藏账号为空提示
flagUserName = 0;
}
//如果账号为空
else if(userName.length() == 0) {
user_null.setVisible(true);//显示账号为空提示
user_short.setVisible(false);//隐藏账号长度小于5字符提示
user_repeat.setVisible(false);//隐藏账号已经被注册
flagUserName = 0;
}
//账号合法
else {
user_null.setVisible(false);//隐藏账号为空提示
user_short.setVisible(false);//隐藏账号长度小于5字符提示
user_repeat.setVisible(false);//隐藏账号已经被注册
flagUserName = 1;
}
passWord = new String (register_password_Text.getPassword()).trim();
//获取初次输入的密码
//如果密码长度小于6个字符
if(passWord.length() < 6 && passWord.length() > 0) {
password_null.setVisible(false);//隐藏密码为空提示
//显示密码长度小于6字符提示
password_short.setVisible(true); flagPassWord = 0;
}
else if(passWord.length() == 0) {
password_null.setVisible(true);//显示密码为空提示
password_short.setVisible(false);//隐藏密码长度小于6提示
flagPassWord = 0;
}
//密码输入合法
else {
password_null.setVisible(false);//隐藏密码为空提示
password_short.setVisible(false);//隐藏密码长度小于6提示
flagPassWord = 1;
}
confirmPassword = new String(confirm_password_Text.getPassword()).trim();
//如果再次输入的密码长度大于等于6个字符并且和之前输入的 密码值相等
if(confirmPassword.length() >= 6 && confirmPassword.equals(passWord)) {
password_correct.setVisible(true);//显示确认密码输入正确
password_error.setVisible(false);//隐藏确认密码输入错误
flagConfirmPassword = 1;
}
//确认密码输入错误
else if(! confirmPassword.equals(passWord)) {
password_correct.setVisible(false);//隐藏确认密码输入正确
password_error.setVisible(true);//显示确认密码输入错误
flagConfirmPassword = 0;
}
SecurityCode = security_code_Text.getText().trim();//检查验证码 是否输入正确
if(SecurityCode.equalsIgnoreCase(sc)) {
security_code_error.setVisible(false);//隐藏验证码错误提示
flagSecurityCode = 1;
}
else {
security_code_error.setVisible(true);//显示验证码输入错误提示
flagSecurityCode = 0;
}
if(flagUserName == 1 && flagPassWord == 1 &&
flagConfirmPassword == 1 && flagSecurityCode == 1) {// 表示输入的值都合法
//将用户填写的信息全部写入文件中
new FileOperation().writeDate("UserInformation.xls", userName, "UserName", userName);
new FileOperation().writeDate("UserInformation.xls", userName, "PassWord", passWord);
new FileOperation().writeDate("UserInformation.xls", userName, "WinType", "");
new FileOperation().writeDate("UserInformation.xls", userName, "TotalNum", "0");
//将注册时间写入文件中
Date date = new Date();
String form = String.format("%tF", date);
new FileOperation().writeDate("UserInformation.xls", userName, "LoginTime", form);
closeThis();
new LoginFrame();
}
else {
;
}
}
});
本部分通过getText()方法获得用户输入的用户名、密码、确认密码以及验证码,首先通过readData(“UserInformation.xls”, userName)方法来判断输入的用户名是否已经注册,若excel文件中存在该用户名数据,则注册失败显示“该账号已经注册”;否则利用userName.length() < 5 && userName.length() > 0和userName.
length() == 0进行账号合法性的判断,账号条件为长度不能为空,并且长度要大于5,若不合法则显示对应错误提示;接下来进行密码合法性的判断,原理与账号判断相同;调用equalsIgnoreCase()方法可以进行字符串忽略大小写的比较,例如,当比较两个字符串时,它会认为A-Z和a-z是一样的,都返回true。
当判断所有输入的值都合法后,注册成功,并调用writeDate()方法将数据写入excel文件中。
3.验证码生成类详细设计
char[] SC = new char[62];{
for(int i = 0;i < 26;i++) {
SC[i] = (char)('A'+1);
}
for(int i = 26;i<52;i++) {
SC[i] = (char)('a'+i-26);
}
for(char i = 52;i<62;i++) {
SC[i] = (char)(i-4);
}
}
class SecurityCode extends JPanel{
private static final long serialVersionUID = 1L;
public void paint(Graphics g) {
setBounds(310,310,105,60);
//随机产生0-255之间的整数用于绘制颜色
int R = r.nextInt(255);
int G = r.nextInt(255);
int B = r.nextInt(255);
super.paintComponent(g);//绘制此容器的每一个组件
Graphics2D g2 = (Graphics2D)g;
int n = 0;
sc = "";
for(int i=0;i<4;i++) {//产生四位验证码
n = r.nextInt(62);
sc += SC[n];
int flag = r.nextInt(2);
Color color = new Color(r.nextInt(200)+20,r.nextInt(200)+20,r.nextInt(200)+20);
g2.setColor(color);
Graphics2D g2d = (Graphics2D)g;
AffineTransform trans = new AffineTransform();//将文字旋转指定 角度
if(flag == 0) {
trans.rotate(- r.nextInt(45) * 3.14 / 180, 275 + i * 25, 307);
}else {
trans.rotate(r.nextInt(45) * 3.14 / 180, 275 + i * 25, 307);
}
g2d.setTransform(trans);
g2.setFont(new Font("微软雅黑",1,26));
g2.drawString(SC[n]+"",310+i*22,360);
}
System.out.println(sc);
}
本部分用于随机生成验证码,首先定义了char类型的一维数组SC,用于存放26个英文小写字符a-z,26个英文大写字符A-Z,以及10个数字0-9,共计62个字符。定义了三个整型变量R、G、B用于存放随机产生的0-255之间的整数,作为红、绿、蓝三个颜色通道的变化,以及他们之间相互叠加来得到各式各样的颜色,这个标准几乎包括了人类视力能感知的所有颜色,用此方法来设置验证码的颜色非常合适,接着通过for循环四次产生四位数的验证码。
本部分同时还使用了AffineTransform类,它描述了一种二维仿射变换的功能,是一种二维坐标到二维坐标之间的线性变换,保持了二维图形的“平直性”(即变换后直线还是直线,圆弧还是圆弧)和“平行性”(即保持二维图形间的相对位置关系不变,平行线还是平行线,相交直线的交角不变)。仿射变换可以通过一些列的原子变换的复合来实现平移、缩放、翻转、旋转和剪切。本设计中使用到了其旋转功能,通过rotate()方法将其以左上角点作为圆形,随机旋转任意角度。
三、GameFrame类详细说明
public class GameFrame extends JFrame implements MouseListener , Runnable {
private static final long serialVersionUID = 1L;
private JPanel contentPane;//定义一个私有面板
private JTextArea chessManual_textArea;//定义棋谱文本域
private JTextField black_TimeText;//显示黑棋下棋时间
private JTextField white_TimeText;//显示白棋下棋时间
private JTextField userName;
final FileOperation f = new FileOperation();//实例化文件操作对象
Image imgB=new ImageIcon("\\Renju_Project\\src\\Renju_Image\\黑棋.png").getImage();
Image imgW=new ImageIcon("\\Renju_Project\\src\\Renju_Image\\白棋.png").getImage();
//定义两个整型变量存放获取到的屏幕宽和高
int width = Toolkit.getDefaultToolkit().getScreenSize().width;
int height = Toolkit.getDefaultToolkit().getScreenSize().height;
//定义两个整型变量存放棋子坐标
int x = 0;
int y = 0;
//TotalNum 记录总局数
private int TotalNum = 0;
/ *
* 定义2个整型变量记录棋子状态
* 0—>电脑
* 1—>玩家
*/
private int black_sign = -1;
private int white_sign = -1;
//记录棋子横坐标字母
private char letter;
/**
* 保存之前下过的全部棋子坐标
* 数据为:0 -> 这个点没有棋子
* 数据为:1 -> 这个点为黑子
* 数据为:2 -> 这个点为白子
*/
private int[][] allChess = new int[15][15];
//每次机器找寻落子位置,评分都重新算一遍(虽然算了很多多余的,因为上次落子时候算的大多都没变)
//先定义一些变量
private int humanChessmanNum = 0;//五元组中的玩家黑棋数量
private int machineChessmanNum = 0;//五元组中的电脑白棋数量
private int tupleScoreTmp = 0;//五元组得分临时变量
private int goalX = -1;//目标位置x坐标
private int goalY = -1;//目标位置y坐标
private int maxScore = -1;//最大分数
//记录胜利方棋种
private String WinType ;
//每个位置得分
private int[][] score = new int[15][15];
//标识当前是黑棋还是白棋
boolean isBlack = true;
//标识当前游戏是否继续
boolean canPlay = true;
//显示当前执行方文本
String message = "Black";
protected Frame GameFrame;
//保存游戏最大时间(秒S)
int maxTime = 0;
//保存黑方与白方的剩余时间
int blackTime = 0;
int whiteTime = 0;
//保存双方剩余时间的显示信息
String blackMessage = "无限制";
String whiteMessage = "无限制";
//计时线程类
Thread t = new Thread(this);
本部分构造了GameFrame类通过继承JFrame类以及实现MouseListener和Runnable接口。
通过继承JFrame类来实现初始化窗口、添加按钮、添加文本框、添加标签、设置控件大小、设置字体、添加事件响应等。
通过实现MouseListener接口完成对鼠标事件的监听,实现该接口必须定义该接口内的所有方法,这个接口中的方法有很多,mouseClicked(MouseEvent e)鼠标点击、mousePressed(MouseEvent e)鼠标按下、mouseReleased
(MouseEvent e)鼠标抬起、mouseEntered(MouseEvent e)鼠标进入、
mouseExited(MouseEvent e)鼠标离开。我们在写窗口事件的监听器类时,不可能把这些方法都重写,这时可以用到MouseAdapter类就可以重写自己想要的那一部分,可以节省写那些无用代码的时间,提高效率。但是考虑到java的单继承特性,GameFrame类已经继承了JFrame子类,故本程序在设计时还是采用实现MouseListener接口的方法。
通过实现Runnable接口的方法创建多线程,这是创建线程的第二种方式,同时创建线程还可以使用继承Thread子类来实现,但同样考虑到java的单继承特性,通过实现Runnable接口可以避免这一局限性。本程序在设置双方对局最大时间时,用到了线程操作。首先重写了Runnable接口中的run方法,将线程要运行的代码放在了改run方法中,接着通过Thread类建立线程对象,将Runnable中的子类对象作为实际参数传递给Thread的构造方法,再调用Thread类的start()方法开启线程,但并不需要程序运行时就开启,所以通过t.suspend()方法暂时将线程挂起,在需要时通过t.resume()方法重新启动线程,并调用Runnable接口中的run()方法。
通过调用Toolkit.getDefaultToolkit().getScreenSize().width来获取屏幕的宽度,和Toolkit.getDefaultToolkit().getScreenSize().height来获取屏幕的高度,程序通过(ScreenSize.width-GameFrame.width)/2即(屏幕宽度-窗口宽度)/2的值为窗口最终显示的横坐标x;同理通过(ScreenSize.height-GameFrame.height)/2即(屏幕高度-窗口高度)/2的值为窗口最终显示的纵坐标y,这样窗口在屏幕中就居中显示了。
本部分同时定义了两个int类型的二维数组allChess[]、score[][],allChess用于标记下过棋子的位置,首先将数组中的值全部置为0,表示此时棋盘为空;若下子为黑方则将该位置的值赋为1,白方赋为2。score用于记录棋盘上每个位置的得分,在AI下子时用到该数组,AI自动搜索到得分最大的位置进行下子。本部分同时还声明了若干变量,具体作用注释中以简单介绍,这里就不一一赘述了。
1.GameFrame()构造方法
/**
* 创建一个界面
* Create the frame.
*/
public GameFrame() {
setForeground(Color.WHITE);
setTitle("| 五子棋游戏");//设置标题
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭方式,默认下关闭
setBounds((width-1300)/2,(height-1000)/2, 1300,1000);//设置界面的宽高,并将其初始显示在屏幕*
ImageIcon icon = new ImageIcon("\\Renju_Project\\src\\Renju_Image\\五子棋logo.png");
setIconImage(icon.getImage());
addMouseListener(this);
setResizable(false);
t.start();//启动线程
t.suspend();//将线程挂起
// 创建一个面板
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
setContentPane(contentPane);
// 棋盘标签,显示棋盘图片
JLabel ChessBoard = new JLabel();
ChessBoard.setIcon(new ImageIcon("\\Renju_Project\\src\\Renju_Image\\五子棋盘.png"));
ChessBoard.setBounds(200, 75, 850, 850);
contentPane.add(ChessBoard);
//棋谱标签
JLabel chessManual = new JLabel("棋谱");
chessManual.setFont(new Font("黑体", Font.PLAIN, 25));
chessManual.setBounds(1070, 175, 78, 35);
contentPane.add(chessManual);
//棋谱保存按钮
JButton saveButton = new JButton("保存");
saveButton.setFont(new Font("黑体", Font.PLAIN, 20));
saveButton.setBounds(1180, 170, 78, 35);
contentPane.add(saveButton);
// 用户名标签
JLabel userNameLabel = new JLabel("用户名");
userNameLabel.setFont(new Font("微软雅黑", Font.BOLD, 25));
userNameLabel.setBounds(1080,50, 80,50);
contentPane.add(userNameLabel);
// 用户名显示
userName = new JTextField();
userName.setFont(new Font("微软雅黑", Font.BOLD,18));
userName.setBounds(1080,100, 178, 40);
userName.setHorizontalAlignment(SwingConstants.CENTER);
contentPane.add(userName);
userName.setText(LoginFrame.user_Text.getText());
userName.setColumns(10);
// 棋谱显示文本域
chessManual_textArea = new JTextArea();
chessManual_textArea.setFont(new Font("微软雅黑", Font.BOLD, 20));
chessManual_textArea.setLineWrap(true);//设置文本域自动换行
chessManual_textArea.setBounds(1080, 247, 178, 688);
contentPane.add(chessManual_textArea);
chessManual_textArea.setText("");
chessManual_textArea.setColumns(10);
/*
* 为文本域添加滚动面板,指定滚动显示的视图组件textArea
* 垂直滚动条需要时显示 水平滚动条从不显示
*/
JScrollPane scrollPane = new JScrollPane(
chessManual_textArea,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
);
scrollPane.setBounds(1080, 237, 178, 688);
contentPane.add(scrollPane);
// 对弈时间标签
JLabel TimeLabel = new JLabel("对弈时间:");
TimeLabel.setFont(new Font("微软雅黑", Font.BOLD, 30));
TimeLabel.setBounds(200, 15, 163, 46);
contentPane.add(TimeLabel);
// 对弈时间 黑方标签
JLabel black_Label = new JLabel("黑方");
black_Label.setFont(new Font("微软雅黑", Font.BOLD, 30));
black_Label.setBounds(436, 15, 60, 46);
contentPane.add(black_Label);
// 对弈时间 黑方时间显示文本框
black_TimeText = new JTextField();
black_TimeText.setHorizontalAlignment(SwingConstants.CENTER);
black_TimeText.setFont(new Font("微软雅黑", Font.PLAIN, 20));
black_TimeText.setBounds(516, 22, 96, 35);
contentPane.add(black_TimeText);
black_TimeText.setColumns(10);
// 对弈时间 白方标签
JLabel white_Label = new JLabel("白方");
white_Label.setFont(new Font("微软雅黑", Font.BOLD, 30));
white_Label.setBounds(720, 15, 60, 46);
contentPane.add(white_Label);
// 对弈时间 白方时间显示文本框
white_TimeText = new JTextField();
white_TimeText.setHorizontalAlignment(SwingConstants.CENTER);
white_TimeText.setFont(new Font("微软雅黑", Font.PLAIN, 20));
white_TimeText.setColumns(10);
white_TimeText.setBounds(800, 22, 96, 35);
contentPane.add(white_TimeText);
// 开始游戏按钮
JButton begin_Button = new JButton("开始游戏");
begin_Button.setFont(new Font("微软雅黑", Font.BOLD, 20));
begin_Button.setBounds(30, 800, 133, 46);
contentPane.add(begin_Button);
// 关于游戏
JLabel aboutGame = new JLabel("关于游戏");
aboutGame.setFont(new Font("微软雅黑", Font.BOLD, 20));
aboutGame.setBounds(55, 860, 90, 40);
contentPane.add(aboutGame);
// 对弈模式标签
JLabel game_Label = new JLabel("对弈模式");
game_Label.setFont(new Font("微软雅黑", Font.BOLD, 25));
game_Label.setBounds(15, 75, 133, 35);
contentPane.add(game_Label);
// 对弈模式黑方标签
JLabel black_lable_game = new JLabel("黑 方");
black_lable_game.setFont(new Font("微软雅黑", Font.PLAIN, 20));
black_lable_game.setBounds(15, 137, 81, 21);
contentPane.add(black_lable_game);
/*
* 定义按钮组
* 实现单选按钮只能选择一项
*/
ButtonGroup group1 = new ButtonGroup();
ButtonGroup group2 = new ButtonGroup();
ButtonGroup group3 = new ButtonGroup();
/*
* 对弈模式 黑方电脑单选按钮
*/
JRadioButton black_computer_RadioButton = new JRadioButton("电脑");
black_computer_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
black_computer_RadioButton.setBounds(15, 169, 78, 29);
contentPane.add(black_computer_RadioButton);
// 对弈模式 黑方玩家单选按钮
JRadioButton black_people_RadioButton = new JRadioButton("玩家");
black_people_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
black_people_RadioButton.setBounds(100, 170, 78, 29);
black_people_RadioButton.setSelected(true);//默认选项
contentPane.add(black_people_RadioButton);
group1.add(black_computer_RadioButton);
group1.add(black_people_RadioButton);
// 对弈模式 白方标签
JLabel white_lable_game = new JLabel("白 方");
white_lable_game.setFont(new Font("微软雅黑", Font.PLAIN, 20));
white_lable_game.setBounds(15, 243, 81, 21);
contentPane.add(white_lable_game);
// 对弈模式 白方电脑单选按钮
JRadioButton white_computer_RadioButton = new JRadioButton("电脑");
white_computer_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
white_computer_RadioButton.setBounds(15, 275, 78, 29);
contentPane.add(white_computer_RadioButton);
// 对弈模式 白方玩家单选按钮
JRadioButton white_people_RadioButton = new JRadioButton("玩家");
white_people_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
white_people_RadioButton.setBounds(100, 276, 78, 29);
white_people_RadioButton.setSelected(true);//默认选项
contentPane.add(white_people_RadioButton);
group2.add(white_computer_RadioButton);
group2.add(white_people_RadioButton);
// 对弈先手标签
JLabel game_begin_Label = new JLabel("对弈先手");
game_begin_Label.setFont(new Font("微软雅黑", Font.BOLD, 25));
game_begin_Label.setBounds(15, 350, 133, 35);
contentPane.add(game_begin_Label);
// 对弈先手 黑方单选按钮
JRadioButton black_RadioButton = new JRadioButton("黑方");
black_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
black_RadioButton.setBounds(15, 412, 83, 29);
black_RadioButton.setSelected(true);//默认选项
contentPane.add(black_RadioButton);
// 对弈先手 白方单选按钮
JRadioButton white_RadioButton = new JRadioButton("白方");
white_RadioButton.setFont(new Font("微软雅黑", Font.PLAIN, 20));
white_RadioButton.setBounds(100, 412, 96, 29);
contentPane.add(white_RadioButton);
group3.add(black_RadioButton);
group3.add(white_RadioButton);
// 执行方标签
JLabel performer_Label = new JLabel("执行方");
performer_Label.setFont(new Font("微软雅黑", Font.BOLD, 25));
performer_Label.setBounds(15, 474, 133, 35);
contentPane.add(performer_Label);
//投降按钮
JButton surrender_Button = new JButton("投 降");
surrender_Button.setFont(new Font("微软雅黑", Font.BOLD, 20));
surrender_Button.setBounds(54, 728, 80, 46);
contentPane.add(surrender_Button);
//悔棋按钮
JButton retract_Button = new JButton("悔 棋");
retract_Button.setFont(new Font("微软雅黑", Font.BOLD, 20));
retract_Button.setBounds(54, 660, 80, 46);
contentPane.add(retract_Button);
//游戏设置按钮
JButton timeset_Button = new JButton("时间设置");
timeset_Button.setFont(new Font("微软雅黑", Font.BOLD, 20));
timeset_Button.setBounds(930, 20, 115, 46);
contentPane.add(timeset_Button);
//刷新屏幕,防止开始游戏时出现无法显示情况
this.repaint();
}
此部分通过GameFrame()构造方法,对界面中的所有控件的文本内容、字体、大小、位置等进行了详细设置。
1.标签
包括“对弈时间”、“对弈模式”、“对弈先手”、“执行方”、“黑方”、“白方”、“棋谱”、“用户名”等标签,主要用于显示一些提示性信息,将界面进行模块化划分;
2.按钮组件
包括“开始游戏”、“悔棋”、“投降”、“时间设置”、“保存”、“关于游戏”,开始游戏用于输出相关提示信息并开始一局新的游戏,悔棋用于回到悔棋方之前的一步,同时更新棋谱和游戏时间等,投降用于在游戏过程中任何一方认输,并结束游戏,时间设置用于在游戏开始前设置玩家最大游戏时间,超出时间判该方为输,并结束游戏,保存用于将文本域中的棋谱字符串保存成txt文件,关于游戏弹出界面显示软件版本、开发者基本信息。
3.单选按钮组件
包括设置对弈模式“玩家”、“电脑”以及设置对弈先手“黑方”、“白方”。
4.文本框
包括“black_TimeText”、“white_TimeText”即黑方时间显示文本框和白方时间显示文本框,用于每一方在下子过程中的倒计时显示。“userName”文本框显示用户名信息,以及“chessManual_textArea”文本域,用于实时显示和保存棋谱。
2. paint()构造方法
public void paint(Graphics g) {
super.paint(g);
/**
* 处理屏幕闪烁问题
* 双缓冲技术:手机游戏中常见,原因是手机内存小,屏幕闪烁明显
* 防止屏幕闪烁
*/
//新建一个缓冲图片
BufferedImage bi = new BufferedImage(1300, 1000,
BufferedImage.TYPE_INT_ARGB);//颜色类型
Graphics g2 = bi.createGraphics();//通过g2绘制到缓冲图片上
//输出当前执行方信息
g2.setFont(new Font("微软雅黑", Font.PLAIN, 35));
if(message == "Black") {
g2.setColor(Color.black);
}else if(message == "White") {
g2.setColor(Color.white);
}else if(message == "GameOver") {
g2.setColor(Color.red);
}
g2.drawString(message, 15, 600);
black_TimeText.setText(blackMessage);
white_TimeText.setText(whiteMessage);
//绘制全部棋子
for (int i=0;i<15;i++) {
for (int j=0;j<15;j++) {
if(allChess[i][j] == 1) {
//绘制黑子
int tempX=i*50+278;
//i*每格间距(50)+棋盘最左边线距边框为278
int tempY=j*50+186;
//j*每格间距(50)+棋盘最上边线距边框为186
g2.drawImage(imgB, tempX-25,tempY-25, null);
}
else if(allChess[i][j] == 2) {
//绘制白子
int tempX=i*50+278;
//i*每格间距(50)+棋盘最左边线距边框为278
int tempY=j*50+186;
//j*每格间距(50)+棋盘最上边线距边框为186
g2.drawImage(imgW, tempX-25,tempY-25, null);
}
}
}
g.drawImage(bi,0,0,this);
}
该部分使用了双缓冲技术解决了屏幕闪烁问题,而屏幕闪烁是由于程序先用背景色覆盖了组件再去重绘图案的方式导致了闪烁,虽然时间很短,但是若棋盘中的棋子过多重绘面积很大,闪烁就会很明显。这种情况在手机游戏中常见,原因是手机内存小,屏幕闪烁明显。同时在paint(Graphic g){}方法中,本程序采用了直接在屏幕上绘图,由于执行的语句很多,程序会不断地改变窗体中正在被绘制的图像,会造成绘制的缓慢,这在一定程度上也加剧了闪烁。
解决方法是先定义一个Graphics对象g2,按照窗口大小(1300,1000)建立一个缓冲对象bi给g2,通过g2绘制到缓冲图片上,后台通过g.drawImage
(bi,0,0,this)将缓冲图片中的图像一次性输出到窗口。
message 字符串表示当前执行方:Black或者White以及程序结束是输出GameOver,通过if语句进行判断,不同的情况,设置字体颜色不同,通过drawString()方法将message实时绘制在窗口上。
black_TimeText.setText(blackMessage)用于实时显示黑方倒计时;white_Time
Text.setText(whiteMessage)用于实时显示白方倒计时。
定义了allChess[i][j]二维数组存放棋子,若allChess[i][j]值为0,则表示该点没有棋子;若allChess[i][j]值为1,则表示该点的棋子为黑子;若allChess[i][j]值为2,则表示该点的棋子为白子。通过双重for循环,遍历棋子,定义两个整型变量tempX和tempY,存放棋子的落点坐标。
tempX=i50+278:i表示棋盘横轴1~15,棋盘每隔间距为50,棋盘最左边线距边框为278,通过此算法可以精确求得棋子落点的横坐标;
tempY=j50+186:j表示棋盘纵轴A~O,棋盘每隔间距为50,棋盘最上边线距边框为186,通过此算法可以精确求得棋子落点的纵坐标。通过g2调用drawImage(imgW, tempX-25,tempY-25, null)将黑子的图片绘制在棋盘上,这样黑子就下子完成了,同理可以精确绘制白子。其中tempX-25和tempY-25是由于棋子图片的直径为50px,为了让棋子的中心点能和棋盘横轴与纵轴的交叉点重合。
3.mousePressed(MouseEvent e)鼠标按下方法
//鼠标按下事件
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
if(canPlay == true) {
//将鼠标点击的位置转换为棋盘坐标的位置
x = e.getX();
y = e.getY();
if (x>=278&&x<=978&&y>=186&&y<=886) {
x = Math.round((x-278)/50f);
//使鼠标点击的点距离棋盘最近的交叉点显示棋子
y = Math.round((y-186)/50f);
letter= (char)(x+65);
if(allChess[x][y] == 0) {
//判断当前要下的棋子类型
if(isBlack == true) {
allChess[x][y] = 1;
isBlack = false;
message = "White";
TotalNum++;
System.out.println(TotalNum);
chessManual_textArea.append("[Black]:("+ letter+","+(15-y)+")\n");
}else if(white_sign == 0 && black_sign == 1 && isBlack == false) {
System.out.println("白方电脑操作但黑方先手");
searchLocation();
isBlack = true;
message = "Black";
Computer();
}else if(!(white_sign == 0 && black_sign == 1)){
allChess[x][y] = 2;
isBlack = true;
message = "Black";
chessManual_textArea.append("[White]:("+ letter+","+(15-y)+")\n");
}
//判断该棋子是否与其他棋子连成五个,即判断游戏是否结束
boolean winFlag = this.isWin();
if(winFlag==true) {
JLabel label6 = new JLabel("游戏结束,"
+(allChess[x][y]==1?"黑方获胜!":"白方获胜!"));
label6.setFont(new Font("微软雅黑",Font.PLAIN,18));
JOptionPane.showMessageDialog(this, label6);
if(allChess[x][y]==1) {
WinType = "Black";
f.writeDate("UserInformation.xls", userName.getText(), "WinType", "Black");
}else if(allChess[x][y]==2){
WinType = "White";
f.writeDate("UserInformation.xls", userName.getText(), "WinType", "White");
}
f.writeDate("UserInformation.xls", userName.getText(), "TotalNum",String.valueOf(TotalNum));
canPlay = false;
message = "GameOver";
chessManual_textArea.append("[获胜方]:"+(allChess[x][y]==1?" 黑方":"白方\n"));
}
}else {
JLabel label7 = new JLabel("当前位置有棋子,请重新落子!");
label7.setFont(new Font("微软雅黑",Font.PLAIN,18));
JOptionPane.showMessageDialog(this, label7);
}
this.repaint();
}
}
}
GameFrame实现MouseListener接口后,重写mousePressed(MouseEvent e)方法实现对鼠标按下事件的监听。利用boolean型变量canPlay来判断是否可以下子,其值为true则可以落子,其值为false则无法落子。通过e.getX()方法和e.getY()方法获得鼠标按下点的坐标,if()语句中x>=278&&x<=978&&y>=186
&&y<=886判断鼠标点击的点是否在棋盘范围内,棋盘最左侧竖线距离边框278px,最右侧竖线距离边框978px,最上侧横线距离边框186px,最下侧横线距离边框886px。
x = Math.round((x-285)/50f);y = Math.round((y-195)/50f);计算出使鼠标点击的点距离棋盘最近的交叉点显示棋子。接下来判断将要下子的类型,利用chessManual_textArea.append("[Black]:(A"+","+(15-y)+")\n");按照特定的格式输出下子的点坐标至棋谱中。
每落一子都要调用isWin()方法判断程序是否有五子连线,若有则程序结束,弹出信息显示胜利方,并将胜利方的信息和总局数存入excel文件中;若无,则继续下子。
4.isWin()判断输赢方法
private boolean isWin() {
boolean flag = false;
//保存共有多少棋相连
int count = 1;
int color = allChess[x][y];
/**
* 横向判断输赢
* 判断横向是否有五个棋子相连 特点:纵坐标相同,即allChess[x][y]中 */的y值相同
//通过循环做棋子相连的判断
int i=1;
while(x+i>=0&&x+i<=14&&y+i>=0&&y+i<=14&&color== allChess
[x+i][y]) {
count++;
i++;//下一个
}
i=1;//重置i的值
while(x-i>=0&&x-i<=14&&y-i>=0&&y-i<=14&&color == allChess[x-i][y]) {
count++;
i++;//下一个
}
if(count>=5) {
flag = true;
}
/**
* 纵向判断输赢
* 判断纵向是否有五个棋子相连 特点:横坐标相同,即allChess[x][y]中的x值相同
*/
int i2=1;
int count2=1;
while(x+i2>=0&&x+i2<=14&&y+i2>=0&&y+i2<=14&&color== allChess[x][y+i2]) {
count2++;
i2++;//下一个
}
i2=1;//重置i的值
while(x-i2>=0&&x-i2<=14&&y-i2>=0&&y-i2<=14&&color== allChess[x][y-i2]) {
count2++;
i2++;//下一个
}
if(count2>=5) {
flag = true;
}
/**
* 斜方向变化(右上/左下)
*/
int i3=1;
int count3=1;
while(x+i3>=0&&x+i3<=14&&y-i3>=0&&y-i3<=14&&color== allChess[x+i3][y-i3]) {
count3++;
i3++;//下一个
}
i3=1;//重置i的值
while(x-i3>=0&&x-i3<=14&&y+i3>=0&&y+i3<=14&&color== allChess[x-i3][y+i3]) {
count3++;
i3++;//下一个
}
if(count3>=5) {
flag = true;
}
/**
* 斜方向变化(左上/右下)
*/
int i4=1;
int count4=1;
while(x-i4>=0&&x-i4<=14&&y-i4>=0&&y-i4<=14&&color== allChess
[x-i4][y-i4]) {
count4++;
i4++;//下一个
}
i4=1;//重置i的值
while(x+i4>=0&&x+i4<=14&&y+i4>=0&&y+i4<=14&&color== allChess
[x+i4][y+i4]) {
count4++;
i4++;//下一个
}
if(count4>=5) {
flag = true;
}
return flag;
}
该部分通过将判断函数分为四大块,横向判断、纵向判断、斜方向(左上之右下)判断、斜方向(右上至左下)判断。
横向判断方法找到棋子的共同点为纵坐标相同,即allChess[x][y]中的y值相同。通过while(x+i>=0&&x+i<=14&&y+i>=0&&y+i<=14&&color== allChess
[x+i][y]) {count++;i++;}循环进行判断,判断条件:x+i>=0&&x+i<=14&&y+i>=0
&&y+i<=14是确保在棋盘边界处下子能正常显示,解决数组越界问题;color== allChess[x+i][y]则判断所下的同一颜色棋子的纵坐标是否相同,若相同count+1,接着判断下一个。这样在横向上从左往右的判断就完成了。若要从右往左进行判断则只需要将判断条件修改为x-i>=0&&x-i<=14&&y-i>=0&&y-i<=14&&color == allChess[x-i][y],判断完成后,利用if语句判断count的值是否为5,若是,则将flage的值赋为true,利用鼠标按下事件中的判断方法,弹出消息对话框显示该方胜利。
同理找到纵向、斜向(左上之右下)和斜向(右上至左下)的规律,得出判断条件,运用相同的方法即可完成胜负判断。
5.AI下子评分方法详细设计
五子棋要想赢,必然要有五个棋子在一起相连成线,通过计算棋盘中每一个五格棋连的线(即五元组)。正常情况下,五子棋盘为15*15,那么总共为572个五元组。同时更具期盼中白子和黑子数量的不同给予不同的评分,每一个位置的得分就是包含该位置的所有五元组的得分之和。如图所示,图14表示了红色棋子所在的五元组中其他棋子位置。
评分表
类型 | 局面 | 评分 |
---|---|---|
无 | 既有人类落子也有机器落子 | 0 |
无 | 全部为空,没有棋子 | 7 |
攻击 | 机器落1子 | 35 |
攻击 | 机器落2子 | 800 |
攻击 | 机器落3子 | 15000 |
攻击 | 机器落4子 | 800000 |
防守 | 人类落1子 | 15 |
防守 | 人类落2子 | 400 |
防守 | 人类落3子 | 1800 |
防守 | 人类落4子 | 100000 |
6.评分表方法tupleScore()详细设计
public int tupleScore(int humanChessmanNum, int machineChessmanNum){
//1.既有人类落子,又有机器落子,判分为0
if(humanChessmanNum > 0 && machineChessmanNum > 0){
return 0;
}
//2.全部为空,没有落子,判分为7
if(humanChessmanNum == 0 && machineChessmanNum == 0){
return 7;
}
//3.机器落1子,判分为35
if(machineChessmanNum == 1){
return 35;
}
//4.机器落2子,判分为800
if(machineChessmanNum == 2){
return 800;
}
//5.机器落3子,判分为15000
if(machineChessmanNum == 3){
return 15000;
}
//6.机器落4子,判分为800000
if(machineChessmanNum == 4){
return 800000;
}
//7.人类落1子,判分为15
if(humanChessmanNum == 1){
return 15;
}
//8.人类落2子,判分为400
if(humanChessmanNum == 2){
return 400;
}
//9.人类落3子,判分为1800
if(humanChessmanNum == 3){
return 1800;
}
//10.人类落4子,判分为100000
if(humanChessmanNum == 4){
return 100000;
}
return -1;//若是其他结果肯定出错了。这行代码不会执行
}
该方法中的两个参数 humanChessmanNum和machineChessmanNum分别代表棋盘中人类下子的个数和机器下子的个数,在进行drawImage()方法绘制棋子时,将humanChessmanNum和machineChessmanNum进行自增运算,故可以求得两个数的值,根据棋盘上黑子和白子的数量来确定返回的得分。
6.确定机器落子位置searchLocation()方法详细设计
public void searchLocation(){
//每次都初始化下score评分数组
for(int i = 0; i < 15; i++){
for(int j = 0; j < 15; j++){
score[i][j] = 0;
}
}
//1.扫描纵向的15个列 i表示x轴 j表示y轴
for(int i = 0; i < 15; i++){
for(int j = 0; j < 11; j++){
int k = j;
while(k < j + 5){
if(allChess[i][k] == 2) machineChessmanNum++;
else if(allChess[i][k] == 1)humanChessmanNum++;
k++;
}
//将每一个五元组中的黑子和白子个数放到评分表中计算值
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(k = j; k < j + 5; k++){
score[i][k] += tupleScoreTmp;
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//2.扫描横向15行
for(int i = 0; i < 15; i++){
for(int j = 0; j < 11; j++){
int k = j;
while(k < j + 5){
if(allChess[k][i] == 2) machineChessmanNum++;
else if(allChess[k][i] == 1)humanChessmanNum++;
k++;
}
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(k = j; k < j + 5; k++){
score[k][i] += tupleScoreTmp;
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//3.扫描右上角到左下角上侧部分
for(int i = 14; i >= 4; i--){
for(int k = i, j = 0; j < 15 && k >= 0; j++, k--){
int m = k; //x 14 13
int n = j; //y 0 1
while(m > k - 5 && k - 5 >= -1){//m = 4 k=4 4,0 3,1, 2,2 1,3 0,4
if(allChess[m][n] == 2) machineChessmanNum++;
else if(allChess[m][n] == 1)humanChessmanNum++;
m--;
n++;
}//注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
//System.out.println(m+"==>"+(k-5));
if(m == k-5){
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(m = k, n = j; m > k - 5 ; m--, n++){
score[m][n] += tupleScoreTmp;
}
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//4.扫描右上角到左下角下侧部分
for(int i = 1; i < 15; i++){
for(int k = i, j = 14; j >= 0 && k < 15; j--, k++){
int m = k;//y 1
int n = j;//x 14
while(m < k + 5 && k + 5 <= 15){
if(allChess[n][m] == 2) machineChessmanNum++;
else if(allChess[n][m] == 1)humanChessmanNum++;
m++;
n--;
}
//注意斜向判断的时候,可能构不成五元组(靠近四个角 落),遇到这种情况要忽略掉
if(m == k+5){
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(m = k, n = j; m < k + 5; m++, n--){
score[n][m] += tupleScoreTmp;
}
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//5.扫描左上角到右下角上侧部分
for(int i = 0; i < 11; i++){
for(int k = i, j = 0; j < 15 && k < 15; j++, k++){
int m = k;
int n = j;
while(m < k + 5 && k + 5 <= 15){
if(allChess[m][n] == 2) machineChessmanNum++;
else if(allChess[m][n] == 1)humanChessmanNum++;
m++;
n++;
}
//注意斜向判断的时候,可能构不成五元组(靠近四个角 落),遇到这种情况要忽略掉
if(m == k + 5){
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(m = k, n = j; m < k + 5; m++, n++){
score[m][n] += tupleScoreTmp;
}
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//6.扫描左上角到右下角下侧部分
for(int i = 1; i < 11; i++){
for(int k = i, j = 0; j < 15 && k < 15; j++, k++){
int m = k;
int n = j;
while(m < k + 5 && k + 5 <= 15){
if(allChess[n][m] == 2) machineChessmanNum++;
else if(allChess[n][m] == 1)humanChessmanNum++;
m++;
n++;
} //注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
if(m == k + 5){
tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
//为该五元组的每个位置添加分数
for(m = k, n = j; m < k + 5; m++, n++){
score[n][m] += tupleScoreTmp;
}
}
//置零
humanChessmanNum = 0;//五元组中的黑棋数量
machineChessmanNum = 0;//五元组中的白棋数量
tupleScoreTmp = 0;//五元组得分临时变量
}
}
//从空位置中找到得分最大的位置
for(int i = 0; i < 15; i++){
for(int j = 0; j < 15; j++){
if(allChess[i][j] == 0 && score[i][j] > maxScore){
goalX = i;
goalY = j;
maxScore = score[i][j];
}
}
}
System.out.println("goalX:"+goalX+"goalY:"+goalY);
}
该设通过定义二维数组score[][]用于存放棋盘上每一个位置的得分,首先通过纵向扫描15列,当遇到人类棋子时将humanChessmanNum 加1,遇到机器棋子时将machineChessmanNum 加1,当搜索的范围超过5个后while循环结束,进下一次搜索,以此类推,搜索过程如图所示。其他的横向、斜向搜索方法同理。搜索完成后,调用tupleScore()方法将每个位置的所有五元组得分之和相加得到该位置的评分,取得最大的评分位置调用Computer()方法完成AI落子。
四、项目测试
1.人人对弈
设置对弈模式:“玩家VS玩家”,对弈先手:黑方,棋谱显示相应信息,测试结果如图所示:
设置对弈模式:“玩家VS玩家”,对弈先手:白方,棋谱显示相应信息,测试结果如图所示:下子形成五子连线,弹出消息对话框显示:游戏结束,黑方获胜,执行方标签和棋谱试试更新,测试结果如图所示:
打开对应的文件路径,可查看到保存的文件,测试结果如图所示:
2.人机对弈
3.时间设置
当某一方在时间减少到0前,未形成五子,系统判断这一方输掉比赛,测试结果如图所示:
五、总结
由于篇幅受限,以上之展示部分功能介绍以及功能测试,有不足之处请多指正。感兴趣的可以关注,收藏,点击链接即可下载项目源码和论文~~
https://download.csdn.net/download/qq_45556665/15140088