MLDN 五子棋。
───────────────────────────────────────
———— Swing 知识准备。
(一)、Swing 的概念
Swing 是在java 的基础图形化用户界面AWT 的基础上扩展的API 集。
Swing 的功能:通过java 代码编写窗口程序,创建图形化用户界面(GUI)。
Swing 可以创建窗体,面版,在窗口中导入或绘制图片,或输入文本信息,
结合java 的JDBC或IO操作可以实现数据的保存。(记事本的保存功能)
(二)、JFrame 类。
JFrame 是创建窗体的swing 类,用来创建一个图形界面的原始窗口,并设置其大小,
位置等属性,是swing 编程的基础类之一。
1.JFrame ————主要方法。
setTitle() //窗体标题。
setSize(200, 100) //窗体大小。
setLocation() //窗体位置。
setResizable() //窗体缩放。
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
//窗体关闭,同时结束程序。
setVisible(true) //窗体可见。
2.取得屏幕大小:
Toolkit.getDefaultToolkit().getScreenSize().width
Toolkit.getDefaultToolkit().getScreenSize().height
3.实现窗体显示在屏幕正中间:
JFrame jf = new JFrame();
int w = Toolkit.getDefaultToolkit().getScreenSize().width
int h = Toolkit.getDefaultToolkit().getScreenSize().height
jf.setLocation((w - 200)/2, (h - 100)/2);
4.创建一个类继承JFrame
创建一个构造方法,由构造方法初始化窗体。
this.setXXX
5.在main 方法中直接 new 一个继承JFrame 的类就ok。
(三)、JOptionPane 类。
JOptionPane 用来弹出提示信息框,确认框和信息输入框。
不用new,直接调用静态方法就ok。
1.弹出提示信息框:
JOptionPane.showMessageDialog(jf, "要提示的信息。");
2.弹出确认框:
int result = JOptionPane.showConfirmDialog
(jf, "要确认的信息:正的要删除?");
是:0,否:1,取消:2 。
if(result == 0) {
//显示一个提示框。已经删除。
}
if...
if...
3.弹出信息输入框:
String message = JOptionPane.showInputDialog("请输入你的信息:");
if(message != null) {
JOptionPane.showMessageDialog(jf, "信息:" + message);
} else {
JOptionPane.showMessageDialog(jf, "请输入信息。");
}
点取消的时候 message = null;
(四)、MouseListener 鼠标监听器类。
MouseListener 接收用户通过鼠标所做的操作。可以取得用户点击鼠标的坐标。
MouseListener 的使用方法:
需要调用JFrame 的 addMouseListener 方法加入监听。
MouseListener————主要方法:
1.mouseClicked(MouseEvent e) 监听鼠标点击事件。(鼠标不移动)
2.mousePressed(MouseEvent e) 监听鼠标按下事件。(使用频繁)
3.mouseReleased(MouseEvent e) 监听鼠标抬起事件。
4.mouseEntered(MouseEvent e) 监听鼠标进入事件。
5.mouseExited(MouseEvent e) 监听鼠标离开事件。
鼠标点击时执行的顺序:
mousePressed->mouseReleased->mouseClicked(按下与抬起在同一位置)
得到鼠标点击的坐标: MouseEvent 类。
public void mousePressed(MouseEvent e) {
System.out.println("点击位置:X-->" + e.getX());
System.out.println("点击位置:Y-->" + e.getY());
}
(五)、Graphics 类
Graphics 类似画笔,用来在窗口中绘制文字和图象。
通过重写JFrame 的 paint() 方法来使用,通过repaint() 方法来调用。
Graphics————主要方法:
1.drawString() 绘制字符串
//字符串, x轴位置, y轴位置:3参数
2.drawOval() 绘制一个空心圆
//x轴位置, y轴位置, 宽, 高:4参数
3.fillOval() 绘制一个实心圆
//参数同上。
4.drawLine() 绘制一条线
//起点(x1, y1), 终点(x2, y2) 画棋盘
5.drawRect() 绘制一个空心矩形
6.fillRect() 绘制一个实心矩形
7.drawImage() 绘制一个已经存在的图片,
将一个图片直接显示到窗体中。
将硬盘中的图片导入到窗口中:
通过ImageIO 输入一个BufferedImage
通过drawImage() 方法将一个BufferedImage 对象绘制到窗口中。
实现导入一张图片:
把硬盘中图片读到内存,程序把图片从内存中取出,显示到窗体中。
BufferedImage image = ImageIO.read(new File("文件路径+文件"));
//IO要try,可能读不到文件。
g.drawImage(image, 0, 0, this); //this 绘制到当前窗体下。
//没有背,景窗体是透明的。
其他会用到的方法:
setColor() 设置画笔的颜色
setFont() 设置文字的字体
//g.setFont(new Font("黑体", 20, 20)); //字体,大小
───────────────────────────────────────
(一)、五子棋游戏的功能:
1.在点击鼠标时,可以在相应的位置显示棋子。
2.可以自动判断游戏是否结束,是否黑方或白方已经胜利。
3.对游戏时间进行设置,判断是否超出规定时间。
───────────────────────────────────────
(二)、开发出游戏界面:
1.创建FiveChessFrame 类,继承JFrame 实现MouseListener。
属性:
屏幕大小:
int w = Toolkit.getDefaultToolkit().getScreenSize().width
int h = Toolkit.getDefaultToolkit().getScreenSize().height
背景图片:
BufferedImage bgImage = null;
FiveChessFrame 构造方法。
pubic FiveChessFrame() {
this.setTitle("五子棋");
this.setSize(500,500); //背景图片大小。
this.setLocation((w-500)/2, (h-500)/2);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//加入鼠标监听。
this.addMouseListener(this);
this.setVisible(true);
bgImage = ImageIO.read(new File("文件路径/文件"));
//要try 。
}
//重写paint 方法。显示图片。
public void paint (Graphics g) {
//绘制背景图片。
g.drawImage(bgImage, 1, 20, this);
//游戏信息。
g.setFont(new Font("黑体", Font.BOLD, 20));
g.drawString("游戏信息:", 130, 60);
//时间信息。
g.setFont(new Font("宋体", 0, 14));
g.drawString("黑方时间:无限制", 30, 470);
g.drawString("白方时间:无限制", 260, 470);
//绘制棋盘。
//在mousePressed(MouseEvent e) 方法中打印坐标。
//System.out.println("X:" + e.getX());
//System.out.println("Y:" + e.getY());
计算棋盘中每一条线的间距:
使用19X19 的围棋棋盘。
横19 条线,纵19 条线。
总宽度:360 像素,分成:18 份,每份:20 像素。
总高度:360 像素......
//手绘棋盘。
for(int i=0; i<19; i++) {
g.drawLine(10,70+20*i,370,70+20*i);
g.drawLine(10+20*i,70,10+20*i,430);
}
//标注点位。加粗。 x, y 不是圆心哦!3X3 位置。
g.fillOval(68,128,4,4); //四角
g.fillOval(308,128,4,4);
g.fillOval(308,368,4,4);
g.fillOval(68,368,4,4);
g.fillOval(308,248,4,4); //四边
g.fillOval(188,128,4,4);
g.fillOval(68,248,4,4);
g.fillOval(188,368,4,4);
g.fillOval(188,248,4,4); //中心
}
public static void main(String [] args) {
FiveChessFrame ff = new FiveChessFrame();
}
───────────────────────────────────────
(三)、实现下棋功能:
在棋盘上的鼠标点击位置,显示一个棋子。
黑子:一个实心的黑圆。
白子:一个空心的黑圆 + 一个实心的白圆。
repaint() 方法:重新执行一次paint() 方法。
1.定义一个成员变量保存棋子的坐标。
int x = 0;
int y = 0;
2.通过二维数组保存之前下过的所有棋子的坐标。
//如果其中数据内容 0:表示这个点没有棋子;
1:表示这个点是黑子;
2:表示这个点是白子。
int[][] allChess = new int[19][19];
3.判断下一步下黑子,还是下白子。 开局黑子先下。
boolean isBlack = true;
3.//在paint 方法中绘制全部棋子。
public void paint() {
......
//输出二维数组中所有的数值。一重循环,循环行,二重循环,循环列。
//画线的时候,已经循环了坐标。
//输出二维数组中全部棋子。
for(int i=0; i<19; i++) {
for(int j=0; j<19; j++) {
if(allChess[i][j] == 1) {
//黑子
int tempX = i*20+10; //10 是棋盘外空了10 像素。
int tempY = j*20+70;
g.fillOval(tempX-7, tempY-7, 14, 14);
}
if(allChess[i][j] == 2) {
//白子
int tempX = i*20+10;
int tempY = j*20+70;
g.setColor(Color.white);
g.fillOval(tempX-7, tempY-7, 14, 14);
g.setColor(Color.blach);
g.drawOval(tempX-7, tempY-7, 14, 14);
}
}
}
}
//把棋子保存到相应的位置。
2.public void mousePressed(MouseEvent e) {
x = e.getX();
y = e.getY();
if( x>= 10 && x <= 370 && y >= 70 && y <= 430) {
//判断x, y 距离哪个交叉点最近。把坐标保存到二维数组中。
//一大难点。要下棋子的坐标。
x = (x - 10) / 20; //10 是棋盘外面多余部分。
y = (y - 70) / 20; //除后取整。不准确。
//位置是空的才能下,不然会覆盖原来下的棋子颜色。
if(allChess[x][y] == 0) {
//判断当前下什么颜色的棋子。
if(isBlack == true) {
allChess[x][y] = 1;
isBlack = false;
} else {
allChess[x][y] = 2;
isBlack = true;
}
} else {
JOptionPane.showMessageDialog
(this,"当前位置已经有棋子, 请重新落子");
}
this.repaint();
}
}
分析 画棋子部分:
显示棋子分两步,输入(mousePressed)+输出(paint)。
x = (x-10)/20 * 20 + 10;
(x-10)/20 保存
* 20 + 10 输出
int tempX = i*20 + 10;
───────────────────────────────────────
(四)、判断输赢:
五子棋规则:判断是否有同一颜色的棋子连成5 个。
判断当前下的棋子,跟周围相连的相同的棋子是否有5 个。
1.在mousePressed 方法中第一个if 最后面添加判断。(保证能落子)
//判断这个棋子是否和其他的棋子连成5连,即判断游戏是否结束。
boolean winFlag = this.checkWin();
if(winFlag == true) {
JOptionPane.showMessageDialog(this, "游戏结束," +
(allChess[x][y]==1?"黑方":"白方") + "获胜!"
);
canPlay = false;
}
2.//单独写成一个方法。要判断4 个方向。
private boolean checkWin() {
boolean flag = false;
//保存共有相同颜色多少棋子相连
int count = 1;
/* 分析。。。。
//判断横向是否有5 个棋子相连,
//特点:纵坐标是相同的,即allChess[x][y]中y 值是相同的。
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 {
//判断右上,左下,x 加,y 减。
count = this.checkCount(1, -1, color);
if(count >= 5) {
flag = true;
} else {
//判断右下,左上,x 加, y 加;x 减, y 减。
count = this.checkCount(1, 1, color);
if(count >= 5) {
flag = true;
}
}
}
}
//这种算法,经常使用(横,竖,斜),如扫雷。
return flag;
}
3.添加一个成员变量:标识当前游戏是否可以继续
boolean canPlay = true;
4.mousePressed 所有的代码加一个判断
if(canPlay == ture) {
...所有代码。
}
//也可以if(!canPlay) return; 马士兵的风格。
到此为止,已经完成了五子棋游戏的核心算法。
───────────────────────────────────────
(五)、将判断输赢的方法整合成一个方法:
//判断棋子连接的数量。参数:x轴,y轴变化的数字。
//重复的代码总结成一个方法。
private int checkCount(int xChange, int yChange, int color) {
int count = 1;
int tempX = xChange;
int tempY = yChange;
while(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(color == allchess[x-xChange][y-yChange]) {
count++;
if(xChange != 0) xChange++;
if(yChange != 0) {
if(yChange > 0) {
yChange++;
//yChange 如果是-1 加加就是0 了。多加了一个bug。
} else {
yChange--;
}
}
}
return count;
}
───────────────────────────────────────
(六)、显示游戏信息:轮到谁下了。
1.定义一个成员变量,保存显示的提示信息。
String message = "黑方先落子 ";
2.paint 方法中 游戏信息字符串后面加上 提示信息。
g.drawString("游戏信息:" + message, 130, 60);
3.根据走子,改变提示信息。
public void mousePressed(MouseEvent e) 方法中
if(isBlack == true) {
allChess[x][y] = 1;
isBlack = false;
message = "轮到白方";
} else {
allChess[x][y] = 2;
isBlack = true;
message = "轮到黑方";
}
───────────────────────────────────────
(七)、处理屏幕闪烁问题:
双缓冲技术:用在手机游戏中用的是最多的。
原因是手机的内存相对较小,屏幕闪烁问题比较明显。
把所有要显示到屏幕上的信息,先缓冲到内存中的图片上,
再统一把内存中的图片显示到窗体中。
paint 方法中添加双缓冲。
BufferedImage bi = new BufferedImage
(500,500,BufferedImage.TYPE_INT_ARGB);
//传入一个颜色类型
Graphics g2 = bi.createGraphics(); //为内存中的bi 图片创建画笔。
下面的都用g2 画到缓冲图片上,g只画bi。
....
g.drawImage(bi,0,0,this);
一次性绘制的东西太多,可以会先绘制一些,后绘制一些。
程序很快执行,就会出现闪烁。
双缓冲,只画一个,就把上面的内容一次性绘制到程序中。
───────────────────────────────────────
(八)、加入几个按钮功能:
1.打印出坐标。
System.out.println(e.getX() + "--" e.getY());
2.按钮区域。 x 轴坐标不变。
public void mousePressed(MouseEvent e) 方法最后:
//点击 开始游戏 按钮
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.将下一步下棋的人改为黑方。
1. for(int i=0; i<19; i++) {
for(int j=0; j<19; j++) {
allChess[i][j] = 0;
}
}
//另一种清空二维数组方式 allChess = new int[19][19];
2. message = "黑方先落子";
3. isBlack = 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; //添加try 块。
if(maxTime < 0) {
JOptionPane.showMessageDialog(this,
"请输入正确信息,不允许输入负数!");
}
if(maxTime == 0) {
int result = JOptionPane.showConfirmDialog(this, "设置完成,
是否重新开始游戏?");
if(result == 0) {
//代码和游戏开始相同。
......
blackTime = maxTime;
whiteTime = maxTime;
//给重新开始游戏那也加上2 行代码
blackMessage = "无限制";
whiteMessage = "无限制";
this.canPlay = true;
this.repaint();
}
}
if(maxTime > 0) {
int result = JOptionPane.showConfirmDialog(this, "设置完成,
是否重新开始游戏?");
if(result == 0) {
//代码和游戏开始相同。
......
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;
//开始游戏代码中判断,if(maxTime > 0) {} else {}
//有时间限制用这一段,没有时间限制用maxTime == 0 那段。
this.repaint();
}
}
} catch(NumberFormatException e) {
JOptionPane.showMessageDialog(this,"请正确输入信息!") ;
}
}
//点击 游戏说明 按钮
if(e.getX() >= 400 && e.getX() <= 470 &&
e.getY() >=170 && e.getY() <= 200) {
JOptionPane.showMessageDialog(this,
"这是一个五子棋游戏,黑白双方轮流下棋,当某一方连到5 子时,
游戏结束。");
}
//点击 认输 按钮
if(e.getX() >= 400 && e.getX() <= 470 &&
e.getY() >=270 && e.getY() <= 300) {
int result = JOption.showConfirmDialog(this, "是否确认认输?");
if(result == 0) {
if(isBlack) {
JOptionPane.showMessageDialog(this,"黑方已经认输,游戏结束") ;
} else {
JOptionPane.showMessageDialog(this,"白方已经认输,游戏结束") ;
}
}
canPlay = false;
}
//点击 关于 按钮
if(e.getX() >= 400 && e.getX() <= 470 &&
e.getY() >=320 && e.getY() <= 350) {
JOptionPane.showMessageDialog(this,
"本游戏由MLDN制作,有相关问题可以访问www.mldn.cn");
}
//点击 退出 按钮
if(e.getX() >= 400 && e.getX() <= 470 &&
e.getY() >=370 && e.getY() <= 400) {
JOptionPane.showMessageDialog(this, "游戏结束");
System.exit(0);
}
3.实现各个按钮的功能:
3.1.开始游戏:重新开始新的游戏。
3.2.游戏设置:设置倒计时。
3.3.游戏说明:说明游戏规则和操作。
3.4.认输: 某一方放弃游戏,认输。
3.5.关于: 显示程序的作者或编写单位的相关信息。
3.6.退出: 结束程序。
───────────────────────────────────────
(九)、游戏设置,倒计时,线程。
1.添加一个成员变量。
//保存最多拥有多少时间(秒)。
int maxTime = 0;
//保存黑方和白方的剩余时间。
int blackTime = 0;
int whiteTime = 0;
//保存双方剩余时间的显示信息。
String blackMessage = "无限制";
String whiteMessage = "无限制";
2.定义一个倒计时线程类。
Thread t = new Thread(this); //对当前窗体做线程控制。
在构造方法中启动线程。
t.start();
t.suspend(); //挂起线程先。
让窗体实现 Runnable 接口。实现run() 方法。
public void run() {
//判断是否有时间限制
if(maxTime > 0) {
while(true) {
if(isBlack) {
blackTime--;
//超时判断。
if (blackTime == 0) {
JOptionPane.showMessageDialog(this, "黑方超时,游戏结束!");
}
} else {
whiteTime--;
//超时判断。
if (whiteTime == 0) {
JOptionPane.showMessageDialog(this, "白方超时,游戏结束!");
}
}
blackMessage = blackTime / 3600 + ":"
+ (blackTime / 60 - blackTime / 3600 * 60) + ":"
+ (blackTime - blackTime / 60 * 60);
whiteMessage = whiteTime / 3600 + ":"
+ (whiteTime / 60 - whiteTime / 3600 * 60) + ":"
+ (whiteTime - whiteTime / 60 * 60);
this.repaint(); //刷新屏幕,重新调用paint 方法。
Thread.sleep(1000); //1000毫秒,即1秒。要try 。
//cacth (InterruptException e)
}
}
}
3.显示倒计时。修改paint 方法。
g2.drawString("黑方时间:"+blackMessage,30,470);
g2.drawString("白方时间:"+whiteMessage,260,470);
───────────────────────────────────────
(十)、第一次运行出现黑屏,
原因:paint 方法不是最后执行的。
解决:在构造方法最后手工调用 repaint() 方法。刷新屏幕。
───────────────────────────────────────
(十一)、棋子下到最边缘的位置,报数组下标越界错误。
原因:在checkCount 连接数量判断的时候,没有考虑位置为0。
位置为0 再减 1 就是 -1 下标越界了。
解决:多加一个判断。
while(x+xChange>=0 && x+xChange<=18 && y+yChange>=0 &&
y+yChange<=18 && color == allChess[x+xChange][y+yChange])
下半部分判断:加变成减去。
while(x-xChange>=0 && x-xChange<=18 && y-yChange>=0 &&
y-yChange<=18 && color == allChess[x-xChange][y-yChange])