赞
踩
目录
黑白棋,又叫反棋、奥赛罗棋、苹果棋或翻转棋。黑白棋在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。它的游戏规则简单,因此上手很容易,但是它的变化又非常复杂。有一种说法是:只需要几分钟学会它,却需要一生的时间去精通它。
1,棋盘
黑白棋的棋盘是一个有8*8方格的棋盘。下棋时将棋下在空格中间,而不是像围棋一样下在交叉点上。开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子。
2,落子
把自己颜色的棋子放在棋盘的空格上,而当自己放下的棋子在横、竖、斜八个方向内有一个自己的棋子,则被夹在中间的全部翻转会成为自己的棋子。夹住的位置上必须全部是对手的棋子,不能有空格。
一步棋可以在数个方向上翻棋,任何被夹住的棋子都必须被翻转过来,棋手无权选择不去翻某个棋子。除非至少翻转了对手的一个棋子,否则就不能落子。如果一方没有合法棋步,也就是说不管他下到哪里,都不能至少翻转对手的一个棋子,那他这一轮只能弃权,而由他的对手继续落子直到他有合法棋步可下。
3,胜负规则
如果一方至少有一步合法棋步可下,他就必须落子,不得弃权。棋局持续下去,直到棋盘填满或者双方都无合法棋步可下,棋子多的胜利,一样多就平局。
我在玩黑白棋的时候,出现了一些比较有趣的局面:
apk下载:资源分享汇总_nameofcsdn的博客-CSDN博客
里面收录了很多残局,来源有:
首先给出残局,都是6个空格的。
比如:
而且都是黑方先走,而且轮到黑方走,而且最后黑方会胜利。
虽然没有明说,但是根据我的感觉,应该是每一步都不能下错,否则就会被AI干掉。
当然,有的时候一步有不止一个最佳选择,但是第一步应该不会。
我会指出应该下哪里,都是在电脑上面试过的,应该没错。
最后我会给出后面的截图,至于和历史上的真实局面是不是一样就不知道了。
现在黑方只能下B7、G2、H2
H1和H8肯定是白方的,黑方要想胜利,就必须得到H2-H7这6个格子。
这里面有涉及到轮空规则,就是说,B7这个地方只有黑方可以下,白方是没法下的。
所以,这一步应该黑方下G2
然后白方、黑方、白方、黑方分别下在H列,只剩下B7
这个时候,白方没法下,还是归黑方下,于是黑方就赢了。
B7、A8是白方的一大杀招,所以黑方应该抢B7,A8会被白方得到。
A7肯定是白方的,所以要保住A3-A6,黑方必须要得到A1、A2,B1会被白方得到。
为了使得白方下A7的时候获利少,必须在白方下了A7之后才能下A1
所以,这一步应该下A2
最后的结果是
和(2)一样,这6个位置的分配其实是明显的。
三个角落是黑方的,另外三个位置是白方的。
关键在于顺序,所以这一步黑方应该下A8
最后结果:
H7只有黑方可以下,在(1)里面的B7也是这样的。
所以H7肯定是留到最后下。
如果下B1的话,白方下A1,貌似不太好。
如果下A7的话,B7就变成了黑方无法下的地方了,肯定是不行的。
所以,这一步应该下B7
最后:
黑方下完B1之后还可以下H7,然后就赢了。
这一局,黑方应该抢夺H8,所以应该下H6
最后下A7就赢了
黑方要抢夺A1-A7以及A1-G1,所以第一步应该下A7
最后白方无子可下,轮到黑方下。
这个局面还是比较好分析的,A6和A7是白方无法下的,所以黑方应该控制好,最后连续2步下这里。
这一步只能下A1或者G1
为了使得最后一步白方被轮空,黑方可以多下一步,黑方D4、E3、F2需要全部获得
所以应该先下A1再下G1
最后就是这样,黑方下了A6之后还可以下A7,于是就胜利了。
这里的A8和B7都对白方非常重要,但是黑方这一步是没法下的。
所以说,要想在这2个位置能落下一字,这一步必须下H1
黑方下了H1之后,白方无法同时得到A8和B7,这一步反而2个位置都不下,下H8
H8对白方其实也很重要,但是黑方最开始是不可能下这个地方的,但是现在的话,白方必须先把H8得到了。
这一步,黑方很明显是下H2了
G8其实是白方无法下的地方,好好利用这一点就可以吃掉B8-G8
B7和G7白方如果下了,除非顶点已经被占了,否则黑方就可以吃掉对角线。
所以,这一步黑方应该下B7,然后白方会下G7
吃掉对角线,白方会下A8
到了这个时候,G7仍然是白方无法下的,所以黑方可以下2个子,然后胜利。
A1是白方和黑方都无法下的,H1和H2是白方无法下的,虽然黑方可以下这2个地方,不过显然没什么好处。
所以黑方应该下B7,然后白方只能下G7
黑方这一步应该下H8,然后白方无子可下
黑方此时可以下H1和H2,显然应该下H1,然后白方只能下H2
最后黑方下A1,就胜利了。
这个局面非常有意思,6个格子都是白方无法下的地方。
无论黑方下哪里,白方都将有地方可以下。
但是,仔细思考的话可以发现,黑方是有办法让白方轮空的。
这一步黑方应该下A8,然后白方就只能下A7
只有下H8才能使白方轮空,而且H8对黑方本来就非常重要。
所以这一步黑方下H8。
现在,黑方下H6就胜利了。
如果下G7的话,反而被白方轮空了,结果自然是失败。
A2-A7最后肯定是白方的,副对角线A8-H1是黑方的。
所以黑方这一步,不仅要考虑如何得到副对角线A8-H1,还要保住C7-E7
也就是说,要先等白方下了A7之后,黑方才能下A8
在这之前,黑方要先占领H1
H1这一步不能直接下,要先下G1或者H2,下完之后下一步就是下H1
所以,这一步应该下H5,否则如果直接下G1或者H2的话,就连H1都占领不了。
黑方下H5之后,白方只能下A7
因为要占领H1,所以只能下G1
黑方下H1之后,白方被轮空,黑方继续下A8就胜利了。
这个局面实在是非常有趣啊!
黑方可以下A2不能下A1,只有等白方下了A2之后才能下A1。
反过来,白方可以下A2不能下A1,只有等黑方下了A2之后才能下A1。
对于H1和G2,情况是一样的。
所以这一步黑方应该下H7,然后白方没法下H8,就只能下A2或者G2
这2种情况,结果其实是一样的。
最后一步白方被轮空。
为了保住第8行,这一步黑方当然应该下F8
黑方下了F8之后,白方可以下G8、B7、A7
下G8没什么意义,为了得到第7行,这一步白方应该下A7,因为下了A7之后黑方肯定是不会下B7的
黑方自然是不会下B7。
这一步有个诀窍,如果白方下B7之后,黑方再下G8就可以得到莫大的好处。
所以这一步应该下A8
这一步白方应该下B7,不然C7-F7就得不到了。
这里,黑方下G7就可以得到莫大的好处了。
最后白方只能下H8
黑方胜
首先,第1列肯定是黑方的,H列肯定是白方的。
所以,G1是黑方下,H1和H8是白方下,这毋庸置疑。
所以第一步只能下G7
这里白方肯定是下H8
如果黑方下G1的话,白方只要下G8黑方就无子可下,所以这里黑方只能下G8
最后结果:
黑方胜,33:31
这一章,我介绍一下用c++写一个黑白棋游戏的时候,需要注意的几个难点。
我的这个代码只支持单机双人对战,所以双方必须在一起运行这个程序,交替利用键盘输入下棋的位置。
支持人机对战的代码在下一章,那里面还加入了一点新功能,而且指出了本章中代码的bug并且改正过来了。
游戏开始界面:
功能:提示双方棋子数,提示该睡下棋,提示并在棋盘中显示哪些位置可以下棋
容错性:输入的列不区分大小写,如果输入错误则重新输入
缺陷:不支持悔棋
开发黑白棋游戏要注意三种情况:
(1)如果甲方下棋之后乙方无法下棋,则甲方可以继续下棋。
也就是说,有的时候一方可以连续下很多步。
我的测试:
白方下完4A之后,还是该白方下:
测试通过
(2)如果棋盘还没下满,但是双方都无法下子,那么游戏结束
我的测试:
白方下8E之后游戏结束:
测试通过
(3)如果一方的棋子全部被吃掉,则游戏结束
我的测试:
黑方下1G之后:
测试通过
所有的代码都在一个cpp文件中,只有120行
完整代码:
- #include<iostream>
- #include <stdlib.h>
- using namespace std;
-
- int list[8][8] = { 0 }; //棋盘状态,1是黑子,2是白子,0是空的
- int p=1; //现在该谁下,1是黑方,2是白方
- const int dr[8] = { 0, 0, 1, 1, 1, -1, -1, -1 },dc[8] = { 1, -1, 0, 1, -1, 0, 1, -1 };//8个方向向量
-
- bool playOK(int r, int c, int dr, int dc) //判断某个格子的某个方向能否下子
- {
- if (list[r][c] != 0)return false;
- int tr = r, tc = c; //tr和tc分别表示该点通过行和列往特定方向移动后的坐标
- while (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == 3 - p)
- { //循环遍历,未到达边界或者右边的棋子是对方的则循环继续,否则循环退出
- tr += dr, tc += dc; //移动坐标
- }
- //若使循环退出的那一格里,是对方的棋子,则(r,c)可落子,否则不可落子
- if (tr == r && tc == c)return false; //难点,这一句不可少
- if (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == p)return true;
- return false;
- }
-
- bool OK(int r, int c) //判断某个格子能否下子
- {
- if (list[r][c])return false;
- for (int i = 0; i < 8; i++) //只要一个方向满足可以下的条件,就可以下
- if (playOK(r, c, dr[i], dc[i]))return true; //调用
- return false;
- }
-
- int num(int k) //统计棋子数目,1是黑子,2是白子
- {
- int s = 0;
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (list[i][j] == k)s++;
- return s;
- }
-
- void display() //显示棋盘和棋子
- {
- system("cls");
- for (int i = 0; i < 8; i++)
- {
- if (!i)
- {
- cout << " ";
- for (int j = 0; j < 8; j++)cout << char('A' + j) << " ";
- cout << endl;
- }
- cout << i + 1 << " ";
- for (int j = 0; j < 8; j++)
- {
- if (list[i][j] == 2)cout << "○";else if (list[i][j] == 1)cout << "●";
- else if (OK(i, j))cout <<"?";else cout << " ."; //调用
- cout << " ";
- }
- cout << endl<<endl;
- }
- cout << "黑方:" << num(1) << " 白方:" << num(2)<<" 轮到"; //调用
- if (p == 1)cout << "黑方下\n"; else cout << "白方下\n";
- cout << "候选项:";
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j)) //调用
- cout << " " << char('1' + i) << char('A' + j);
- }
-
- void init() //初始化
- {
- list[3][3] = list[4][4] = 2;
- list[3][4] = list[4][3] = 1;
- display(); //调用
- }
-
- bool end_() //判断游戏是否结束
- {
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j))return false; //调用
- p = 3 - p; //改变p的2个地方之一
- display(); //难点,这一句不可少 //调用
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j))return false; //调用
- cout << "\n游戏结束\n";
- if (num(1) < num(2))cout << "白方胜利";
- else if (num(1)>num(2))cout << "黑方胜利";
- else cout << "平局";
- return true;
- }
-
- void turn(int tr, int tc, int dr, int dc) //吃子函数play()的一个方向
- {
- if (!playOK(tr, tc, dr, dc))return; //难点,这一句不可少 //调用
- while (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == 3 - p)
- {
- list[tr + dr][tc + dc] = p; //在该处换掉棋子的颜色
- tr += dr, tc += dc;
- }
- }
-
- void play() //落下一个子
- {
- cout << "\n输入行(1-8)和列(A-H)" << endl;
- char x, y;
- cin >> x >> y;
- int r = x - '1', c = y - 'a';
- if (y>'A' && y < 'Z')c = y - 'A';
- if (!OK(r, c)) //调用
- {
- cout << "ERROR!";
- return;
- }
- for (int i = 0; i < 8; i++)turn(r, c, dr[i], dc[i]);
- list[r][c] = p;
- p = 3 - p; //改变p的2个地方之一
- display(); //调用
- }
-
- int main()
- {
- system("color f0");//白底黑字
- init();
- while (!end_())play();
- system("pause>nul");
- return 0;
- }
在本章中,我加入了人机对战的功能,并完善了代码。
完整代码:
- #include "stdafx.h"
- #include<iostream>
- #include <stdlib.h>
- #include<windows.h>
- using namespace std;
-
- int list[8][8] = { 0 }; //棋盘状态,1是黑子,2是白子,0是空的
- int p = 1, aip = 0, air, aic, lastr = 0, lastc = 0; //p是现在该谁下,1是黑方,2是白方
- int p1, p2, p3, p4, temp1[8][8], temp2[8][8], temp3[8][8], temp4[8][8];//4层博弈对应的AI分数和棋局
- const int dr[8] = { 0, 0, 1, 1, 1, -1, -1, -1 }, dc[8] = { 1, -1, 0, 1, -1, 0, 1, -1 };//8个方向向量
- int book[65]; //棋谱
- const int no = 1000;//AI的分数不可能达到的上界值
- const int prio[8][8] =
- { //prio是每个位置的优先级,数值越高越好
- { 80, -20, 8, 6, 6, 8, -20, 80 },
- { -20, -20, 0, -1, -1, 0, -20, -20 },
- { 8, 0, 3, 5, 5, 3, 0, 8 },
- { 6, -1, 5, 1, 1, 5, -1, 6 },
- { 6, -1, 5, 1, 1, 5, -1, 6 },
- { 8, 0, 3, 5, 5, 3, 0, 8 },
- { -20, -20, 0, -1, -1, 0, -20, -20 },
- { 80, -20, 8, 6, 6, 8, -20, 80 }
- };
-
- bool playOK(int r, int c, int dr, int dc) //判断某个格子的某个方向能否下子
- {
- if (list[r][c] != 0)return false;
- int tr = r, tc = c; //tr和tc分别表示该点通过行和列往特定方向移动后的坐标
- while (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == 3 - p)
- { //循环遍历,未到达边界或者右边的棋子是对方的则循环继续,否则循环退出
- tr += dr, tc += dc; //移动坐标
- }
- //若使循环退出的那一格里,是对方的棋子,则(r,c)可落子,否则不可落子
- if (tr == r && tc == c)return false; //难点,这一句不可少
- if (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == p)return true;
- return false;
- }
-
- bool OK(int r, int c) //判断某个格子能否下子
- {
- if (list[r][c])return false;
- for (int i = 0; i < 8; i++) //只要一个方向满足可以下的条件,就可以下
- if (playOK(r, c, dr[i], dc[i]))return true; //调用
- return false;
- }
-
- int num(int k) //统计棋子数目,1是黑子,2是白子
- {
- int s = 0;
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (list[i][j] == k)s++;
- return s;
- }
-
- void display() //显示棋盘和棋子
- {
- system("cls");
- for (int i = 0; i < 8; i++)
- {
- if (!i)
- {
- cout << " ";
- for (int j = 0; j < 8; j++)cout << char('A' + j) << " ";
- cout << endl;
- }
- cout << i + 1 << " ";
- for (int j = 0; j < 8; j++)
- {
- if (list[i][j] == 2)cout << "○"; else if (list[i][j] == 1)cout << "●";
- else if (OK(i, j))cout << "?"; else cout << " ."; //调用
- cout << " ";
- }
- cout << endl << endl;
- }
- cout << "黑方:" << num(1) << " 白方:" << num(2) << " 轮到"; //调用
- if (p == 1)cout << "黑方下\n"; else cout << "白方下\n";
- cout << "候选项:";
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j)) //调用
- cout << " " << char('1' + i) << char('A' + j);
- cout << "\n最后一个落子位置是" << lastr + 1 << char(lastc + 'A');
- }
-
- void init() //初始化
- {
- list[3][3] = list[4][4] = 2;
- list[3][4] = list[4][3] = 1;
- while (aip != 1 && aip != 2 && aip != 3)
- {
- cout << "选择\n1,人机对战,AI先\n2,人机对战,玩家先\n3,双人对战\n";
- cin >> aip;
- }
- display(); //调用
- }
-
- bool end_() //判断游戏是否结束
- {
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j))return false; //调用
- p = 3 - p; //改变p的2个地方之一
- for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)if (OK(i, j))return false; //调用
- display();
- cout << "\n游戏结束\n";
- if (num(1) < num(2))cout << "白方胜利"; //调用
- else if (num(1)>num(2))cout << "黑方胜利"; //调用
- else cout << "平局";
- return true;
- }
-
-
- void turn(int tr, int tc, int dr, int dc) //吃子函数play()的一个方向
- {
- if (!playOK(tr, tc, dr, dc))return; //难点,这一句不可少 //调用
- while (tr + dr >= 0 && tr + dr < 8 && tc + dc >= 0 && tc + dc < 8 && list[tr + dr][tc + dc] == 3 - p)
- {
- list[tr + dr][tc + dc] = p; //在该处换掉棋子的颜色
- tr += dr, tc += dc;
- }
- }
-
- void play(int r, int c)
- {
- lastr = r, lastc = c;
- for (int i = 0; i < 8; i++)turn(r, c, dr[i], dc[i]); //调用
- list[r][c] = p;
- book[num(1) + num(2)] = r * 8 + c; //调用
- p = 3 - p; //改变p的2个地方之一
- }
-
- int getPoint()
- {
- int point = 0;
- for (int i1 = 0; i1<8; i1++)for (int j1 = 0; j1<8; j1++)
- {
- if (list[i1][j1] == aip)point += prio[i1][j1]; //若是ai的子,则加上该位置的积分
- if (list[i1][j1] == 3 - aip) point -= prio[i1][j1]; //若是玩家的子,则减去该位置的积分
- }
- return point;
- }
-
- void AI3()//函数里面会改变p,但是调用函数不会改变p
- {
- p4 = no;
- for (int iiii = 0; iiii < 8; iiii++)for (int jjjj = 0; jjjj < 8; jjjj++) //第四层
- {
- if (!OK(iiii, jjjj))continue; //调用
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)temp4[i][j] = list[i][j];
- play(iiii, jjjj);//调用
- p = 3 - p;
- if (p4 > getPoint())p4 = getPoint();//调用
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)list[i][j] = temp4[i][j];
- }
- if (p4 == no)p4 = getPoint(); //调用
- if (p3 < p4)p3 = p4;
- }
- void AI2()//函数里面会改变p,但是调用函数不会改变p
- {
- p3 = -no;
- for (int iii = 0; iii < 8; iii++)for (int jjj = 0; jjj < 8; jjj++) //第三层
- {
- if (!OK(iii, jjj))continue; //调用
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)temp3[i][j] = list[i][j];
- play(iii, jjj);//调用
- AI3();
- p = 3 - p;
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)list[i][j] = temp3[i][j];
- }
- if (p3 == -no)
- {
- p = 3 - p;
- AI3();
- p = 3 - p;
- }
- if (p2 > p3)p2 = p3;
- }
- void AI() //函数里面会改变p,但是调用函数不会改变p
- {
- p1 = -no;
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++) //第一层
- {
- if (!OK(i, j))continue; //调用
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)temp1[i][j] = list[i][j];
- play(i, j);//调用
- p2 = no;
- for (int ii = 0; ii < 8; ii++)for (int jj = 0; jj < 8; jj++) //第二层
- {
- if (!OK(ii, jj))continue; //调用
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)temp2[i][j] = list[i][j];
- play(ii, jj);//调用
- AI3(); //调用
- p = 3 - p;
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)list[i][j] = temp2[i][j];
- }
- if (p2 == no)
- {
- p = 3 - p;
- AI2(); //调用
- p = 3 - p;
- }
- if (p1 < p2)p1 = p2, air = i, aic = j; //难点,如果调用AI函数却1次都没有执行这条语句,那么AI就会下错位置
- p = 3 - p;
- for (int i = 0; i<8; i++)for (int j = 0; j<8; j++)list[i][j] = temp1[i][j];
- }
- }
-
- void go() //落下一个子
- {
- display(); //调用
- if (p == aip)
- {
- AI();//调用
- play(air, aic);//调用
- Sleep(1000);
- return;
- }
- cout << "\n输入落子位置:行(1-8)和列(A-H) 输入00查看棋谱" << endl;
- char x, y;
- cin >> x >> y;
- if (x == '0')
- {
- for (int i = 5; i <= num(1) + num(2); i++)cout << " " << book[i] / 8 + 1 << char(book[i] % 8 + 'A');
- cout << "\n按任意键退出";
- system("pause>nul");
- return;
- }
- int r = x - '1', c = y - 'a';
- if (y >= 'A' && y < 'Z')c = y - 'A';
- if (!OK(r, c)) //调用
- {
- cout << "ERROR!";
- return;
- }
- play(r, c); //调用
- }
-
- int main()
- {
- system("color f0");//白底黑字
- init();
- while (!end_())go();
- system("pause>nul");
- return 0;
- }
AI的算法是分析四层的博弈树,即考虑 AI-玩家-AI-玩家 轮流下完4步之后可能出现的所有情况。
对每个局面赋予一个值point,用来表示该局面对AI的有利程度,玩家总是尽量使得point小,而AI总是尽量使得point大。至于如何计算point,有一个专门完成该工作的函数getPoint()
计算的方法比较简陋,直接对每个格子赋予一个权值prio,然后用AI的所有格子权值之和减掉玩家的所有格子权值之和即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。