Java窗体基本使用(五子棋案例)
最近看到有人在讨论窗体,刚好没事就试着写了一个小案例来理解一下
简单来说,窗体是底层容器,用于放各种组件,常见的容器有JFrame和JDialog两种,JPanel是中间容器,可以放在底层容器中,使用中间容器便于基本组件的管理。布局管理是将容器中的组件按照一定的规则和方式放在容器中。常见的布局有这几个
- FlowLayout()流布局(Panel默认的就是这个布局)
- GridLayout(int rows, int cols)网格布局
- BorderLaout()边界布局(JFrame默认就是这个布局)
- CardLayout()卡片布局
- GridBagLayout()网格包布局
今天用到的主要是边界布局,话不多说,看代码吧
- 变量定义
//表示当前下棋的人,-1代表黑棋,0 下棋结束胜负以分, 1代表白棋
private static int dir = -1;
//存放棋盘上的棋子信息,-1代表黑棋,0 代表位子空, 1代表白棋
private static int chess[][] = null;
//默认棋盘行数:19
private static int row = 19;
//默认棋盘每行格子数:19
private static int col = 19;
//这个本来是用来记录鼠标光标位置的,但我比较懒,不想多写别的,
//这里就用它来记录获胜者五个棋子的起始数组所在下标和结束数组所在下标
private Point winerStart = null;
private Point winerEnd = null;
//用来显示获胜方的,初始文字随便写的
public JLabel title = new JLabel("观棋不语,落子无悔");
- 绘制棋盘网格(这里数据类型使用有些要注意的地方)
/**
* 因为这里采用的是JPeanl面板作为棋盘的容器,棋盘内容都是同过Graphcis对象绘制的,
* 重写了Component里的paintComponent(Graphics g)方法,该方法可以通过对象g绘制初始化界面
*/
@Override
protected void paintComponent(Graphics g) {
// TODO Auto-generated method stub
super.paintComponent(g);
//设置画笔颜色为灰色
g.setColor(Color.gray);
//求出当前每个网格的宽间距,不能用整型,不然精度会出问题
double colSpace = (0.0+this.getWidth())/this.col;
//求出当前每个网格的高间距,不能用整型,不然精度会出问题
double rowSpace = (0.0+this.getHeight())/this.row;
//for循环绘制各个位置的经线纬线
for(int i = 0; i < this.col; i++ ) {
g.drawLine((int)(i*colSpace), 0, (int)(i*colSpace), this.getHeight());
}
for(int i = 0; i < this.row ; i++) {
g.drawLine(0, (int)(i*rowSpace), this.getWidth(), (int)(i*rowSpace));
}
}
这是效果图还行吧,
3. 记录棋子,并绘制当前棋盘落子内容
大致思路讲一下,首先我们将棋盘网格画好后,每个网格可以看成一个落子区域,所以我们用一个int类型的二维数组(chess[][])来存储每个区域内的落子信息(-1代表黑棋,0 代表位子空, 1代表白棋,),但我们点击窗体时通过鼠标光标位置计算当前区域位置(第几行第几列),将对应下标的数组值改为单前落子方对应的值就好了,绘制就比较简单,通过repaint();来实现每次落子后棋盘画面的刷新(当然paintComponent(Graphics g)方法内要加上棋子的绘制,相当于绘画棋盘时遍历chess[][]数组,-1画黑棋,1画白棋),至于下棋方交替我们用个变量来判断就好了(dir = -1)代码如下:
这是一些需要的变量
//表示当前下棋的人
private static int dir = -1;
//存放棋盘上的棋子信息,-1代表黑棋,0 代表位子空, 1代表白棋
private static int chess[][] = null;
//默认棋盘行数:19
private static int row = 19;
//默认棋盘每行格子数:19
private static int col = 19;
带参数的构造方法
/**
* 初始化一个自定义大小的棋盘,每行col各格子,共row行
* @param row 一个int类型数据 ,棋盘行数
* @param col 一个int类型数据 ,棋盘每行格子数
*/
public CheckerBoard(int row, int col) {
this.row = row;
this.col = col;
//初始化棋盘信息
chess = new int[row][col];
//定义背景颜色为黑色
this.setBackground(Color.yellow);
//给个默认大小
this.setSize(800,800);
//添加一个鼠标事件的Listener
this.addMouseListener(this);
}
点击时记录落子信息
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
//计算鼠标当前点击的位子是第几列(0代表第一列)
int col = e.getX()*this.col/this.getWidth();
//计算鼠标当前点击的位子是第几行(0代表第一行)
int row = e.getY()*this.row/this.getHeight();
//判定当前位子是否已经落子了,同时棋局是否进行(dir=0表示棋局已经结算了)
//有棋子在的话肯定不能在下了,胜负以分也不能再下了
if(chess[row][col] == 0 && dir != 0) {
//记录当前位置棋子信息
chess[row][col] = dir;
//下一个落子方
dir = -dir;
//重新调用 paintComponent(Graphics g)方法绘制棋盘
repaint();
}
}
下面是paintComponent(Graphics g)补上棋子绘制的代码
protected void paintComponent(Graphics g) {
// TODO Auto-generated method stub
super.paintComponent(g);
g.setColor(Color.gray);
double colSpace = (0.0+this.getWidth())/this.col;
double rowSpace = (0.0+this.getHeight())/this.row;
for(int i = 0; i < this.col; i++ ) {
g.drawLine((int)(i*colSpace), 0, (int)(i*colSpace), this.getHeight());
}
for(int i = 0; i < this.row ; i++) {
g.drawLine(0, (int)(i*rowSpace), this.getWidth(), (int)(i*rowSpace));
}
//加了这一截遍历chess[][]的方法
for (int i = 0; i < this.row; i++) {
for (int j = 0; j < this.col; j++) {
//如果大于0(1嘛)
if (chess[i][j] > 0) {
//绘画颜色为白色
g.setColor(Color.WHITE);
//System.out.println(j*colSpace+","+ i*rowSpace);
//通过数组行列值计算出绘制坐标,画一个白色的圆 (绘制方法以图像区域左上角为零点)
g.fillOval((int)(j*colSpace), (int)(i*rowSpace), (int)colSpace, (int)rowSpace);
}
if(chess[i][j] < 0) {
g.setColor(Color.black);
g.fillOval((int)(j*colSpace), (int)(i*rowSpace), (int)colSpace, (int)rowSpace);
}
}
}
}
代码效果如下:
4. 接下来就是获胜机制的判定了
每次落子时看看有没有获胜情况
//L代表左R代表有,(取名难哪)判定同一行有没有获胜情况
private void judgeLR(int row, int col) {
int coltemp1 = col , coltemp2 = col;
int rowtemp1 = row, rowtemp2 = row;
//从当前位置向左找与当前落子相同的连续的最后一次棋子位置
while(coltemp1 > 0 && chess[rowtemp1][coltemp1-1] == dir)
{
coltemp1--;
}
//从当前位置向左找与当前落子相同的连续的最后一次棋子位置
while(coltemp2 < this.col-1 && chess[rowtemp2][coltemp2+1] == dir)
{
coltemp2++;
}
//如果连续相同的数目大于5个,即胜负以分
if(coltemp2 - coltemp1 >= 4){
//记录连续相同的起始棋子坐标
winerStart = new Point(coltemp1, rowtemp1);
//记录连续相同的结束棋子坐标
winerEnd = new Point(coltemp2, rowtemp2);
}
}
嗯,上面只是一个方位还有其它几个方位这里就不列举了,我上传文件里有源码
- 获胜之后总要有点显示,我们就将就一下,前面不是记录了连续相同的起始棋子坐标和记录连续相同的结束棋子坐标嘛,画条红线将这两个点连起来,加的地方还是。。。。。paintComponent(Graphics g)方法,代码如下:
protected void paintComponent(Graphics g) {
// TODO Auto-generated method stub
super.paintComponent(g);
g.setColor(Color.gray);
double colSpace = (0.0+this.getWidth())/this.col;
double rowSpace = (0.0+this.getHeight())/this.row;
for(int i = 0; i < this.col; i++ ) {
g.drawLine((int)(i*colSpace), 0, (int)(i*colSpace), this.getHeight());
}
for(int i = 0; i < this.row ; i++) {
g.drawLine(0, (int)(i*rowSpace), this.getWidth(), (int)(i*rowSpace));
}
for (int i = 0; i < this.row; i++) {
for (int j = 0; j < this.col; j++) {
if (chess[i][j] > 0) {
g.setColor(Color.WHITE);
//System.out.println(j*colSpace+","+ i*rowSpace);
g.fillOval((int)(j*colSpace), (int)(i*rowSpace), (int)colSpace, (int)rowSpace);
}
if(chess[i][j] < 0) {
g.setColor(Color.black);
g.fillOval((int)(j*colSpace), (int)(i*rowSpace), (int)colSpace, (int)rowSpace);
}
}
}
//多了这段(如果起始和结束都有记录的坐标点,就证明分出胜负了)
if(winerStart!=null && winerEnd != null) {
//划红线嘛,当然要设置成红色,线条的宽度你可以自己调一下,这里不多演示(代码太长了)
g.setColor(Color.RED);
//说出来你别不信,这就是为了画一条直线
g.drawLine(
/*根据起始坐标所在列求出线段开始端点的x坐标,
*(+ (int)colSpace/2主要是为了让线段端点在表格中心)
*/
(int)(winerStart.x*colSpace) + (int)colSpace/2,
/*根据起始坐标所在行求出线段开始端点的y坐标,
*(+ (int)rowSpace/2主要是为了让线段端点在表格中心)
*/
(int)(winerStart.y*rowSpace) + (int)rowSpace/2,
/*根据结束坐标所在列求出线段结束端点的x坐标,
*(+ (int)colSpace/2主要是为了让线段端点在表格中心)
*/
(int)(winerEnd.x*colSpace) + (int)colSpace/2,
/*根据结束坐标所在行求出线段结束端点的y坐标,
*(+ (int)rowSpace/2主要是为了让线段端点在表格中心)
*/
(int)(winerEnd.y*rowSpace) + (int)rowSpace/2
);
if(chess[winerStart.y][winerStart.x] == -1) {
title.setText("黑棋获胜");
}else {
title.setText("白棋获胜");
}
this.dir = 0;
}
}
代码写到这就差不多了,还有一些小问题我相信机智的你肯定看源码后就明白了
这是最后的运行效果(有木有看到那根刺眼的红线):