MLDN 五子棋。笔记

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])

你可能感兴趣的:(游戏,swing,string,360,thread,图形)