用 JAVA 写五子棋游戏——双人对局部分

一、用一只脚就能想到的代码

  • 窗体——JFrame 对象
  • 棋盘——用 Graphics 对象绘制 30 条纵横交错的正方形网格线,其中纵横各 15 条也可以改用棋盘图片。
  • 记录回合数的整型变量。我创建了一个变量 turn 来记录回合数。 可以利用回合数来判断当前哪一方执棋,也可以创建另外一个变量来记录当前哪一方执棋。
  • 棋子——用 Graphics 对象绘制大小合适的。注意圆的颜色。如果画出来的圆看不清楚,注意给圆描边。也可以改用棋子图片。
  • MouseListener或者MouseAdapter来获取鼠标点击的坐标。
  • 二维数组来记录对局情况。二维数组的索引可以与棋盘上的坐标对应起来,便于判断胜负。我创建了类型为 char[15][15] 的数组 array。我把它的元素的初始值设为 0。如果第 x 行第 y 列是白棋,就令 array[x][y] = ‘w’;如果是黑棋,就令 array[x][y] = ‘b’

二、用一双脚才能想到的代码

● 定义 JFrame 的子类,并重写其 paint() 方法。保证窗体重绘时棋盘与棋子都不会消失。

● 创建一个数组,记录棋子的下棋顺序和位置。便于实现悔棋功能:

先下的棋子先进数组,后下的棋子后进数组。最后下的棋子在数组的最后一个。

我们希望数组中的每个元素都记录了棋子的坐标信息,这样才能实现悔棋功能。怎么记录坐标信息呢?有两种方法:

  • 可以定义一个类,让这个类的两个属性分别记下棋子的横纵坐标:
class Chess {
	int x, y;
	char player

	Chess(int x, int y, int turn) {
		this.x = x;
		this.y = y;
		player = turn % 2 == 0 ? 'w' : 'b';
	}
}

//...

Vector<Chess> history = new Vector<>();
history.add(new Chess(x, y, turn));  //记录

//...

//移除最后一个元素
//remove 方法会返回被移除的对象
Chess t = history.remove(history.lastElement());  
  • 考虑到棋子的横、纵坐标都是小于 15 的正整数,也就是说,横坐标和纵坐标都只占用 4 bit。再加上棋子的颜色(1 bit),可以利用一个 short 变量(16 bit)存储棋子的信息:
    用 JAVA 写五子棋游戏——双人对局部分_第1张图片

由于种种原因,我最后决定不用 short 而是用 int 来存储,代码实现如下:

int x = 横坐标;
int y = 纵坐标;
Integer chess = ((turn % 2) << 8) | (x << 4) | y;  //编码
history.add(chess);  //存入

//...

chess = history.remove(history.lastElement());  //取出
char player = chess >> 8 == 0 ? 'w' : 'b';  //解码
int x = chess >> 4;
int y = chess & 0xF;  

● 将鼠标坐标转换为行序号和列序号:

我们希望画出来的圆都在网格线的交叉点上;下棋的时候不想先瞄准再去点击:

class Frame {
	//...
	//注 1
	static final int WIDTH = 600,  //窗体宽度
	  HEIGHT = 600,  //窗体高度
	  LINE_GAP = 50,  //网格线间距
	  FIRST_HORIZONTAL_LINE = 80,  //第一水平线纵坐标
	  FIRST_VERTICAL_LINE = 80;  //第一竖直线横坐标
}

//...

int mouseX = 鼠标横坐标;
int mouseY = 鼠标纵坐标;
int realX = -1, realY = -1;   //棋子在第 realY 行,第 realX 列。
     
for (int i = 0; i < 15; i++) {
	//注 2
	int v = Frame.FIRST_VERTICAL_LINE + i * Frame.LINE_GAP;
	if (Math.abs(mouseX - t) < Frame.LINE_GAP * 0.3) {
		realX = i;
		break;
	}
}
  • 注 1:
    将变量声明为 final 可以防止它的值被改变。将变量声明为 static 可以通过类名直接访问之。我们常常把常用的常量声明为 static final 变量。 常常把 static final 变量名写为全大写、并用下划线来作为单词的分隔,这样我们就能一眼看出来哪个是 static final 变量。

  • 注 2:
    一个数学问题。v 表示是i 条竖直线的横坐标。如果鼠标点击的位置某条线的距离在一定范围内,那么就可以认为棋子被放在这条线上。如果鼠标点在方格的中间位置,那么将没有符合条件的竖直线,realX 的值会保持初始的 -1 。如果 realX 是 -1,那么我们就知道,鼠标没有点在靠近线的地方。对 realY 也是相似的处理方法。
    这里我选用的范围是 0.3 倍的网格线间距。应该选用合适的范围,不然下棋的时候会很难受。

● 定义一个依据棋盘坐标绘制棋子的方法:

我们会重复地使用这段代码,所以最好把它定义为方法:

static final int RADIUS = 圆的半径;

void drawChess(int realX, int realY, Graphics graphics) {

	//注 3
	int x = Frame.FIRST_VERTICAL_LINE + realX * Frame.LINE_GAP - RADIUS,
	  y = Frame.FIRST_HORIZONTAL_LINE + realY * Frame.LINE_GAP - RADIUS;

	graphics.setColor(turn % 2 == 1 ? Color.BLACK : Color.WHITE);
	graphics.fillOval(x, y, RADIUS, RADIUS);
}
  • 注 3:
    又一个数学问题。 xy 是圆左上角的坐标。realXrealY 都是上面出现过的变量。
    注意要判断当前位置是不是已经有棋子了。

三、判断胜负

有一个很蠢但是很有效的办法:

每下一个棋子,就以这个棋子作为起点,向八个方向看。如果有连续的五个棋子颜色相 同,那么对局就要结束了。

int x = 棋子的横坐标;
int y = 棋子的纵坐标;
char p = 棋子的颜色 ['w' 或者 'b'];

int counter = 1; //记录有多少个相同颜色的棋子
	
while (true) {
	if (counter == 5) {
		gameOver();
	}
	if (x + 1 > 14 || x + 1 < 0 || y + 1 > 14 || y + 1 < 0) {
		//如果下一个位置超出了棋盘,终止循环
		break;
	}
	x += 1;
	y += 1;
	if (array[x][y] != p) {
		//如果棋子颜色不同,终止循环
		break;
	}
	counter++;
}
  • 注 4:
    这个循环是以当前棋子为起点,向右下方搜索相同颜色的棋子。
    循环终止的条件是下一个位置超出棋盘或者下一个棋子颜色不同。每循环一次,counter 就增加 1,当 counter 为 5 时,说明已经有 5 个连续的棋子了。
    这个循环只做了向右下方向搜索。还有另外 7 个方向与这段代码类似。

对八个方向的搜索的代码类似。可以只用一个方法来实现:

boolean five(int x, int y, char player, int dx, int dy) { 
	//注 5
	
	int counter = 1; //记录有多少个相同颜色的棋子
	
	while (counter++ < 5) {
	//如果 counter == 5,终止循环,并返回 true
		if (x + dx > 14 || x + dx < 0 || y + dy > 14 || y + dy < 0) {
			//如果下一个位置超出了棋盘,返回 false
			return false;
		}
		x += dx;
		y += dy;
		if (array[x][y] != player) {
			//如果棋子颜色不同,返回 false
			return false;
		}
	}
	
	return true;
}
  • 注 5:
    参数说明:
    xy 是棋子的坐标。player‘b’ 或者 ‘w’dxdy 其实就是 delta x (Δx)delta y (Δy),也就是横、纵坐标的变化量,也就是搜索的方向。向上就是 dy = 1,向下就是 dy = -1,向左就是 dx = -1, 向右就是 dx = 1。每一次进行搜索,x 就变成 x + dxy 就变成 y + dy
    返回值说明:如果找到连续的 5 个棋子,就返回 true ,否则返回 false。

然后,我们通过下面的代码,从八个不同的方向进行搜索:

if(five(x, y, player, -1, -1) || five(x, y, player, -1, 0)
	|| five(x, y, player, -1, 1) || five(x, y, player, 0, -1)
	|| five(x, y, player, 0, 1) || five(x, y, player, 1, -1)
	|| five(x, y, player, 1, 0) || five(x, y, player, 1, 1)) {
		gameOver();
}

也可以改用下面的代码段。下面的代码段与上面的代码段效果相同,不过下面的代码好看一点(大概):

for (int i = -1; i <= 1; i++) {
   for (int j = -1; j <= 1; j++) {
   
   	if (i == 0 && j == 0) {
    		continue;
    	}
    	
   	if (five(x, y, player, i, j)) {
   		gameOver();
   		break;
   	}
   }
}

你可能感兴趣的:(小作品)