设计思路
- 我们可以把五子棋分为 棋盘界面的绘制、下棋的实现与校正、为鼠标添加动作监听器、黑白棋的输赢判断这四个部分。
- 对于棋盘界面的绘制,我们可以直接继承父类JFrame,创建一个名为GoUI的子类,在里面添加包括显示棋盘、按钮等等的方法。
- 对于下棋的实现与校正,我们需要在指定位置绘制一个棋子,这一步需要用到Graphics并且要与动作监听器配合使用。
- 为鼠标添加动作监听器,是为了让棋盘与鼠标产生连接,我们可以先创建一个GoListener的类,在里面写好鼠标动作的方法,然后将其传至显示界面的GoUI类里
- 最后是黑白棋的输赢判断,我们只需要用两个数字来代表黑白子,再实时获取每个坐标上和它相邻的五个位置是否全为一样的数字,便可以判断输赢。
设计过程
棋盘界面的绘制
我们先创建一个继承了JFrame的GoUI打类,并在里面写好showUI的方法
public class GoUI extends JFrame{
final int X = 50, Y = 75, SIZE = 50, ROWS = 15, COLS = 15;
//使用final修饰变量,设定棋盘的初始值
public void showUI(){
setTitle ("GoBangV1.0"); //设置窗口的名称
setSize (1000, 875); //设置窗口的大小
setDefaultCloseOperation (EXIT_ON_CLOSE); //设置关闭时结束程序
setLayout (null); //设置流式布局为null,自定义组件位置
JButton btn = new JButton ("开始游戏"); //创建一个按钮对象
btn.setBounds (855, 100, 100, 35); //设置按钮对象大小
add (btn); //将按钮对象添加到界面里
setVisible (true);
setResizable (false);// 设置窗体大小不可变
Graphics g = getGraphics ();
gol.g = g;
接着我们写完程序入口,如下
public static void main(String[] args){
GoUI goUI = new GoUI ();
goUI.showUI ();
}
这一部分代码暂时完成,但还需要添加动作监听器,我们后面再做修改。
棋子的绘制
当我们设置好一个棋盘以后 ,我们就应该设置棋子了,为了方便区分,我们添加一个名为chess的类,里面完成对棋子的设定。
先创建类与对象
public class Chess{
int r, c, chessFlag;
int SIZE = 50;
再为之赋值
public Chess(int r, int c, int chessFlag){
this.r = r;
this.c = c;
this.chessFlag = chessFlag;
}
接着我们创建一个名为drawChess的方法来绘制棋子
public void drawChess(Graphics g){
int chessX = c * SIZE + 50 - 25;
int chessY = r * SIZE + 75 - 25;
//设置棋子的坐标
if(chessFlag == 1){
for(int i = 0; i < SIZE; i++){
Color color = new Color (i * 5, i * 5, i * 5);
g.setColor (color);
g.fillOval (chessX + i / 2, chessY + i / 2, SIZE - i, SIZE - i);
//通过循环,用不同颜色的圆面叠加,产生立体棋子的效果
}
} else{
for(int i = 0; i < SIZE; i++){
Color color = new Color (150 + i * 2, 150 + i * 2, 150 + i * 2);
g.setColor (color);
g.fillOval (chessX + i / 2, chessY + i / 2, SIZE - i, SIZE - i);
}
}
这一步完成了棋子坐标、颜色和大小的设定,接下来就是将棋子绘制在棋盘上了。
为鼠标添加动作监听器
初始准备
public class GoListener implements MouseListener, ActionListener{
final int X = 50, Y = 75, SIZE = 50, ROWS = 15, COLS = 15;
Graphics g;
int chessFlag = 0; //代表棋盘的状态,是否开始游戏
int[][] chessArr = new int[16][16]; //新建一个二维数组来存储棋子的状态
Chess[] chessList = new Chess[256]; //创建一个线性的数组来存储每一步的棋子
int chessCount = 0;
接下来我们实现点击开始游戏按钮就启动棋盘的功能。
public void actionPerformed(ActionEvent e){
String btnText = e.getActionCommand (); //创建btnText对象并将按钮的文字赋值
Object source = e.getSource (); //获取按钮的事件源
JButton btn = (JButton) source; //把事件源转换为对象btn
接下来通过比对文字是否为“开始游戏”,来判断按钮是否被点击,并且在被点击以后,改变chessFlag为1,即游戏正在进行,并且将按钮的文字设置为结束游戏。
if(btnText.equals ("开始游戏")){
chessFlag = 1;
btn.setText ("结束游戏");
接下来我们完善“结束游戏” 被点击时要做的事情。首先是清空棋子状态,即将储存棋子状态的数组全部设置为0,再完成棋盘的重绘,使得已经绘制的棋子消失。这里我们先写一个drawGoBack的方法,之后再来填充。最后还要设置按钮的文字为“开始游戏”。
if(btnText.equals ("开始游戏")){
chessFlag = 1;
btn.setText ("结束游戏");
} else if(btnText.equals ("结束游戏")){
chessFlag = 0;
chessArr = new int[16][16];
drawGoBack (g);
btn.setText ("开始游戏");
完成这些以后,我们完善当鼠标被点击后的操作(mousePressed)
首先,如果玩家未点击开始游戏,要求先完成这步操作。接着,我们获取点击的坐标值,并且通过数学计算,算出其对应的棋盘上的行列值,并完成赋值。
public void mousePressed(MouseEvent e){
if(chessFlag == 0){
JOptionPane.showMessageDialog (null, "请点击开始游戏");
return;
}
int x = e.getX ();
int y = e.getY ();
// 计算行列值
int r = (y - 75 + 25) / 50;
int c = (x - 50 + 25) / 50;
System.out.println ("r=" + r + ",c=" + c);
接着我们为了防止玩家再棋盘范围外下棋,我们添加一个提示,并为提示添加弹窗
// 判断下棋的范围,并弹出弹窗提示
if(r < 0 || r > 15 || c < 0 || c > 15){
JOptionPane.showMessageDialog (null, "请在棋盘范围内下棋");
return;
}
if(chessArr[r][c] != 0){
JOptionPane.showMessageDialog (null, "此处已有棋子");
return;
}
我们用1和2代表黑棋白棋,并将它们储存进创建的二维数组,通过矩阵的方式,在电脑上后台模拟出当前棋盘的状态。
chessArr[r][c] = chessFlag;
同时,为了方便之后添加回放和悔棋功能,我们将棋子的状态(行列值、黑白棋)按下棋的顺序添加进刚开始创建的线性数组。并且用chessCount来计数。然后调用chess类中编写的绘制方法来完成棋子的绘制
Chess chess = new Chess (r, c, chessFlag);
chessList[chessCount] = chess;
chessCount++;
chess.drawChess (g);
注意,为了实现黑白棋的交替进行,我们需要判断此时棋子为黑还是白,即是否为1,然后将下一个棋子设置为另一个数字。而在drawChess中,我们已经写好了两种情况的棋子颜色
if(chessFlag == 1){
chessFlag = 2;
} else if(chessFlag == 2){
chessFlag = 1;
}
接着,我们先预创建一个类和对象来分别判断和记录棋子的输赢,并完成比较和结果输出。最后,在判断完输赢并输出以后,我们将棋子状态chessFlag设定为0,即游戏已结束。
boolean winFlag = GoIsWin.isWin (r, c, chessArr);
if(winFlag){
if(chessArr[r][c] == 1){
JOptionPane.showMessageDialog (null, "黑棋赢了");
} else if(chessArr[r][c] == 2){
JOptionPane.showMessageDialog (null, "白棋赢了");
}
chessFlag = 0;
}
棋子与棋盘的绘制方法
完成以上对于动作的设置后,我们需要补全之前设置的两个方法,即绘制棋子与绘制棋盘
对于绘制棋子,我们只需遍历记录在线性数组里的数据,为每一步分别调用drawChess方法即可。
public void drawChess(Graphics g){
for(int i = 0; i < chessCount; i++){
Chess chess = chessList[i];
chess.drawChess (g);
}
}
绘制棋盘,我们需要先绘制一个底座,再绘制一个盘面,然后为盘面画上网格线。
public void drawGoBack(Graphics g){
//绘制底座
Color color = new Color (166, 137, 124);
g.setColor (color);
g.fillRect (0, 0, 845, 870);
//绘制盘面
Color color1 = new Color (219, 207, 202);
g.setColor (color1);
g.fillRect (X - SIZE / 2, Y - SIZE / 2, COLS * SIZE + SIZE, ROWS * SIZE + SIZE);
g.setColor (Color.BLACK);
// 为棋盘划线,来绘制格子
for(int i = 0; i <= 15; i++){
g.drawLine (X, Y + i * SIZE, X + COLS * SIZE, Y + i * SIZE);
g.drawLine (X + i * SIZE, Y, X + i * SIZE, Y + ROWS * SIZE);
}
g.setColor (Color.BLACK);
g.drawRect (X - SIZE / 2, Y - SIZE / 2, COLS * SIZE + SIZE, ROWS * SIZE + SIZE);
}
}
这样,我们就完成了各种动作的设置与棋子棋盘的绘制。
对GoUI类添加设置好的动作监听器
当然,不要忘记将动作监听器传输到GoUI的类中,我们如下修改GoUI的代码。
public class GoUI extends JFrame{
final int X = 50, Y = 75, SIZE = 50, ROWS = 15, COLS = 15;
GoListener gol = new GoListener ();
public void showUI(){
setTitle ("GoBangV1.0");
setSize (1000, 875);
setDefaultCloseOperation (EXIT_ON_CLOSE);
setLayout (null);
JButton btn = new JButton ("开始游戏");
btn.setBounds (855, 100, 100, 35);
add (btn);
btn.addActionListener (gol);
setVisible (true);
setResizable (false);
// 调用 addMouseListener 添加鼠标监听器
addMouseListener (gol);
Graphics g = getGraphics ();
gol.g = g;
}
public void paint(Graphics g){
super.paint (g);
gol.drawGoBack (g);
gol.drawChess (g);
}
public static void main(String[] args){
GoUI goUI = new GoUI ();
goUI.showUI ();
}
}
黑白棋的输赢判断
接下来,我们补全输赢判断的方法,为了方便区分,我们创建一个专门的类
我们已纵向为例,这个时候就需要用到我们创建的二维数组了。
我们创建int count来计数,然后保持列不变,遍历上下的行,如果有相等的便为count加1
private static int col (int r, int c, int[][] chessArr){
int count = 1;
//向下遍历
for(int i = r + 1; i < chessArr[0].length; i++){
if(chessArr[i][c] == chessArr[r][c]){
count++;
} else{
break;
}
}
//向上遍历
for(int i = r - 1; i >= 0; i--){
if(chessArr[i][c] == chessArr[r][c]){
count++;
} else{
break;
}
}
System.out.println ("纵向查找到:" + count);
return count;
同理完成横向的查找
private static int row(int r, int c, int[][] chessArr){
int count = 1;
for(int i = c + 1; i < chessArr[0].length; i++){
if(chessArr[r][i] == chessArr[r][c]){
count++;
} else{
break;
}
}
for(int i = c - 1; i >= 0; i--){
if(chessArr[r][i] == chessArr[r][c]){
count++;
} else{
break;
}
}
System.out.println ("横向查找到:" + count);
return count;
}
但我们还需要完成斜向的判断。
我们可以使用while循环来解决,分别创建 i 和 j 、a 和 b 两组来代表行列,同时进行加减,就能达成斜向遍历的效果,代码如下。
private static int left(int r, int c, int[][] chessArr) {
int count = 1;
int i = c + 1;
int j = r + 1;
while (i < chessArr[0].length & j < chessArr[0].length) {
if (chessArr[j][i] == chessArr[r][c]) {
count++;
i++;
j++;
} else {
break;
}
}
int a = c - 1;
int b = r - 1;
while (a>= 0 & b>=0) {
if (chessArr[b][a] == chessArr[r][c]) {
count++;
a--;
b--;
} else {
break;
}
}
System.out.println ("左斜向查找到:" + count);
return count;
}
private static int right(int r, int c, int[][] chessArr){
int count = 1;
int i = c + 1;
int j = r - 1;
while (i < chessArr[0].length & j >=0) {
if (chessArr[j][i] == chessArr[r][c]) {
count++;
i++;
j--;
} else {
break;
}
}
int a = c - 1;
int b = r + 1;
while (a< chessArr[0].length & b>=0) {
if (chessArr[b][a] == chessArr[r][c]) {
count++;
a--;
b++;
} else {
break;
}
}
System.out.println ("右斜向查找到:" + count);
return count;
}
这样,我们就完成各个方向的计数。
接着我们写全判断的标准,并返回布尔值。之前在动作监听器类里调用的布尔值也就成立了。
public static boolean isWin(int r, int c, int[][] chessArr){
if(row (r, c, chessArr) >= 5 ||
col (r, c, chessArr) >= 5 ||
left (r, c, chessArr) >= 5 ||
right (r, c, chessArr) >= 5){
return true;
}
return false;
}
结语
这样,一个基础的五子棋游戏也就做好了,在下一篇文章中,将会添加悔棋和回放的功能,完成进阶版的五子棋游戏。