Java课程设计——五子棋游戏(个人)
目录
1、重写paint方法,定义一个Graphics2D,来消除棋子锯齿,使棋子更加圆润。
一、个人负责模块
界面设置中paint方法,功能中的双人对战功能和人机对战功能,以及判断游戏输赢的算法。
二、功能框架图
三、个人任务简述
1、重写paint方法,定义一个Graphics2D,来消除棋子锯齿,使棋子更加圆润。
public void paint(Graphics g) {
//定义一个Graphics2D消除棋子锯齿,使棋子更加圆润
Graphics2D gg = (Graphics2D) g;
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
//棋盘
initPaint(g, gg);
//棋子
ovalPaint(gg);
//提示框
sidePaint(gg);
}
其中,Graphics g
参数表示画笔对象,通过它可以对画布进行绘制操作。
在方法中,首先使用 Graphics2D
对象将画笔转换为 2D 画笔(因为它具有更丰富的绘图功能),然后使用以下两个渲染提示设置来消除棋子锯齿:
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
:开启抗锯齿效果,使棋子边缘更加平滑;gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE)
:对线条进行归一化,以消除由浮点数的不精确表示而导致的锯齿和其他视觉伪影。
2、双人对战功能判断输赢以及人机对战功能
1.双人对战判断输赢
当双人对战模式时,我的任务主要是通过算法来判断输赢。
private void judge(int type, int x, int y) {
//传入参数,来判断是黑(2)或白(1)子
int sum=0;
//判断四个方向
//1)左 右
{
int k = x - 1;
while (k >= 0) {
if (table[k][y] == type) {//二维数字记录棋盘上每个位置上的棋子 (0无棋子 1白子 2黑子)
sum++;
} else {
break;
}
k--;
}
}
for (int k = x + 1; k < NUM; k++) {
if (table[k][y] == type) {
sum++;
} else {
break;
}
}
if (sum >= 4) {
isWin = true;
return;
}
//2)上 下
sum = 0;
for (int k = y - 1; k >= 0; k--) {
if (table[x][k] == type) {
sum++;
} else {
break;
}
}
for (int k = y + 1; k < NUM; k++) {
if (table[x][k] == type) {
sum++;
} else {
break;
}
}
if (sum >= 4) {
isWin = true;
return;
}
//3)左上 右下
sum = 0;
for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {
if (table[i][j] == type) {
sum++;
} else {
break;
}
}
for (int i = x + 1, j = y + 1; i < NUM && j < NUM; i++, j++) {
if (table[i][j] == type)
sum++;
else {
break;
}
}
if (sum >= 4) {
isWin = true;
return;
}
//4)右上 左下
sum = 0;
for (int i = x - 1, j = y + 1; i >= 0 && j < NUM; i--, j++) {
if (table[i][j] == type) {
sum++;
} else {
break;
}
}
for (int i = x + 1, j = y - 1; i < NUM && j >= 0; i++, j--) {
if (table[i][j] == type)
sum++;
else {
break;
}
}
if (sum >= 4) {
isWin = true;
//return;
}
}
这段代码是五子棋游戏中的胜利判断。传入参数 type 表示要判断的是黑子(2)还是白子(1), x 和 y 表示当前落子位置的横纵坐标。通过对当前角色棋子在四个方向上是否连续出现 5 颗棋子,来判断这个角色是否胜利。其中,具体实现的方式是通过遍历分别对应四个方向的二维棋盘数组 table,从当前位置开始往左、右、上、下、斜线方向查找同色的棋子数量。如果某方向上达到了 5 颗或以上同色棋子,则判断该角色已经胜利并将标记变量 isWin 设为 true。
2、人机对战
而人机对战功能,作为此游戏精华所在,我主要参考了五元组评价算法实现简易五子棋【人工智能】_五子棋五元组算法_YouthUpward的博客-CSDN博客的内容。
private void machine() {
//传入棋子种类,判断颜色
int[][] ts = new int[NUM][NUM]; //来记录每个点上的得分
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
ts[i][j] = 0;
}
}
int wn; //白色个数
int bn; //黑色个数
//分4种情况
//横向
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM - 4; j++) {
wn = 0;
bn = 0;
//5个
for (int k = j; k < j + 5; k++) {
if (table[i][k] == 1) {
wn++;
} else if (table[i][k] == 2) {
bn++;
}
}
for (int k = j; k < j + 5; k++) {
if (table[i][k] == 0) {
ts[i][k] += score(wn, bn);
}
}
}
}
//纵向
for (int j = 0; j < NUM; j++) {
for (int i = 0; i < NUM - 4; i++) {
wn = 0;
bn = 0;
for (int k = i; k < i + 5; k++) {
if (table[k][j] == 1) {
wn++;
} else if (table[k][i] == 2) {
bn++;
}
}
for (int k = i; k < i + 5; k++) {
if (table[k][i] == 0) {
ts[k][i] += score(wn, bn);
}
}
}
}
//左上 右下
for (int i = 0; i < NUM - 4; i++) {
for (int j = 0; j < NUM - 4; j++) {
wn = 0;
bn = 0;
for (int ki = i, kj = j; ki < i + 5; ki++, kj++) {
if (table[ki][kj] == 1) {
wn++;
} else if (table[ki][kj] == 2) {
bn++;
}
}
for (int ki = i, kj = j; ki < i + 5; ki++, kj++) {
if (table[ki][kj] == 0) {
ts[ki][kj] += score(wn, bn);
}
}
}
}
//右上 左下
for (int i = 4; i < NUM; i++) {
for (int j = 0; j < NUM - 4; j++) {
wn = 0;
bn = 0;
for (int ki = i, kj = j; kj < j + 5; ki--, kj++) {
if (table[ki][kj] == 1) {
wn++;
} else if (table[ki][kj] == 2) {
bn++;
}
}
for (int ki = i, kj = j; kj < j + 5; ki--, kj++) {
if (table[ki][kj] == 0) {
ts[ki][kj] += score(wn, bn);
}
}
}
}
Vector<Integer> vv = new Vector<>();
int max = Integer.MIN_VALUE;
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (ts[i][j] > max) {
max = ts[i][j];
}
}
}
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (ts[i][j] == max) {
vv.add(i);
vv.add(j);
}
}
}
Random random = new Random();
int r = random.nextInt(vv.size() / 2);
robot_x = vv.get(r * 2);
robot_y = vv.get(r * 2 + 1);
vv.clear();
}
private int score(int wh, int bl) {
if (wh > 0 && bl > 0) {
return 0;
}
if (wh == 0 && bl == 0) {
return 7;
}
if (wh == 1) {
return 35;
}
if (wh == 2) {
return 800;
}
if (wh == 3) {
return 15000;
}
if (wh == 4) {
return 800000;
}
if (bl == 1) {
return 15;
}
if (bl == 2) {
return 400;
}
if (bl == 3) {
return 1800;
}
if (bl == 4) {
return 100000;
}
return -1;
}
}
这段代码是五子棋游戏的 AI 实现方法,通过 machine() 方法来计算机器该下在哪个位置。代码实现了根据当前棋盘上黑白棋子分布情况来为每个空位打分的功能。
具体来说,代码首先定义了一个二维数组 ts 来记录每个点上的得分,初始值为 0。然后对于每种可能出现胜利的情况(包括横向、纵向、左上至右下的斜线和右上至左下的斜线),它会计算出这 5 个位置中黑色和白色棋子的个数。接下来,通过调用 score() 方法计算这个空位的得分,并将其累加到 ts 数组相应位置上。最后,从 ts 数组中选择得分最高的空位作为机器下棋的落点。
score() 方法基于五子棋规则赋予每种情况不同的权值,例如在以下情况下可获得相应分数的奖励:无黑白两种颜色的棋子时可以获得 7 分,一侧一枚棋子可以获得 15 分,三颗连续的棋子可以获得 1800 分等等。这样,在机器每次要下棋时,就会选择得分最高的空位下棋,从而比较有可能获得胜利。
当然,这种算法也存在缺点,就是它会选择最高得分的空位下棋而不会主动堵截你的棋子。例如,当你连下三个能连起来的棋子时,它可能并不会去阻止你下第四个棋子使4个棋子连起来,这样你就直接获得了胜利。
如图,在这种情况下,白棋(人机)并没有选择堵截,而是下在了红圈处,对于算法来说没有问题,但这种情况下已经确定黑棋(人)获得了胜利。
四、课程设计感想
通过团队十几天的努力,感觉收获颇丰,加强了对所学知识的巩固。学习借鉴并实践出人机互动功能是我最大的进步和收获。不足之处在于上述小bug,但能力有限,以后我会继续学习相关算法,做出更加智能的AI人机对战模式。