您的位置:首页 > 其它

一维数组建模表示二维的棋盘状态

2016-04-26 01:16 281 查看


当我们想写一个棋类游戏的时候,不难发现,很多棋类游戏的棋盘都可以用一个二维数组表示,比如:

井字棋(3*3的二维数组)、黑白棋(8*8的二维数组)、五子棋(15*15的二维数组)等等

使用二维数组表示棋盘,数组的下标就是棋子的坐标,数组中的值就是棋子的状态。

好处就是数据访问比较直观,可直接根据下标快速找到某个位置的棋子的状态。

但缺点也是很明显的

比如:

首先是遍历棋盘需要用双重循环处理横坐标跟纵坐标;

其次是判断棋子状态,比如以上所说的三种棋子,需要判断行、列以及斜线8个方向上的棋子状态,因为根据行、列和斜线的下标变化特点,加上判断的算法不统一,需要用多套不同的方法来处理。

针对这种情况,我来给大家介绍一种方法,正如标题所示,用一维数组来表示棋盘状态,那么用一维数组该怎样才能表示一个二维数组呢?

我们用井字棋做为例子来说:

首先来看看用二维数组表示井字棋棋盘:

0 1 2

0 x x x

1 x x x

2 x x x

其中X就是代表棋盘的每个位置,0、1、2就是每个位置的坐标,比如第二个棋子的坐标是(0,1),第六个棋子的坐标是(1,2)

假如起点在第一个棋子,即(x = 0,y = 0)这个位置,那向下走就需要执行一次x + 1, y + 0,才能向下走一格。其他方向同理,都需要处理两个值。

好了,用二维数组表示的就不多说了,相信大家都知道怎么做的,下面来讲讲这次的主题:用一维数组表示,用一维数组表示的有两种方法:

第一种:

0 1 2

3 4 5

6 7 8

直接有多少个棋子就开多大的数组,例如:井字棋有3*3 = 9个,所以用长度为9的一维数组表示棋盘,这样在二维数组中的第一行编号为0 1 2,第二行编号为3 4 5,第三行编号为6 7 8.。即一维数组中的6个元素表示二维数组中的第3行第1个。

这样虽然损失了数据访问的直观性,但它处理数据更加简洁,只需一层遍历就能搜索整个棋盘,不需要关注两个下标。那它是怎么向各个方向走的呢?

其实看上面那个图就不难找到个规律,

向左步进量为1,

向下步进量为3,

向右下步进量为4,

向左下步进量为2,

跟以上各自相反方向的步进量则取负;

这样我们就可以用一个一维数组来表示步进量:

int dir[4] = {1,3,4,2};

但是用这种一维数组存储方法有一个缺点,就是比较难判断是否越界了,比如:在3这个位置,减一变成2,在数组里存在,所以程序会判断出不越界,但是,减1的操作是向左走一步,即3这个位置再向左走一步就已经越界了。

所以给大家介绍第二种表示方法:

dddd

dxxx

dxxx

dxxx

ddddd

这是Warren Smith提出的一种建模方法:http://www.radagast.se/othello/endgame.c

如上表所示,一个井字棋棋盘用长度为21的一维数组表示,其中所有的d为标志位,x为棋盘中棋子状态。看到这个棋盘,有些人会觉得右边没有用标志位d包围整个棋盘,不是跟前面那种方法一样不能判断越界,其实并不会,这样就已经能保证任意一个棋子位置向8个方向走都能遇到标志位。我们把字母改为数字来表示就明白了。

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

16 17 18 19 20

其中一维数组中下标为0、1、2、3、4、8、12、16、17、18、19、20的都表示标志位,我们可以用值-1表示,或任何一个不跟棋子状态一样的数值表示,剩下的位置都是表示棋盘。

这样可以看出,

向左步进量为1,

向下步进量为4,

向右下步进量为5,

向左下步进量为3,

跟以上各自相反方向的步进量则取负;

现在我们再来看看越界这个问题,加入13这个位置,向左走一步,即减1操作,变成12,而12这个位置是标志位,所以越界结束搜索。

任意一个棋位向各个方向走都能遇到标志位,大家可以自己算算,这里就不多说了。

下面,来说说这些下标是怎么从二维数组下标中转换过来的:

以下的x都表示纵向方向,y都表示横向方向

先说第一种方法:

0 1 2

3 4 5

6 7 8

棋盘有r(3)行c(3)列

第一个位置便是0 * 3 + 0 = 0

第二个位置是0 * 3 + 1 = 1



第四个位置是1 * 3 + 0 = 3



最后一个是2 * 3 + 2 = 8

按照这个规律不难推出一条公式:p = c * x + y,其中p为一维数组下标,c为棋盘有多少列,x为二维数组的x坐标,y为二维数组的y坐标。即一维数组中的p可以表示二维数组中的(x, y)这个位置。

3*3棋盘各个方向的步进量刚才已经写出,为{1,3,4,2}

那如果是4 * 4、5*5甚至更多呢?别急,下面来算算就知道了:

4*4棋盘:

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

5*5棋盘:

0 1 2 3 4

5 6 7 8 9

10 11 12 13 14

15 16 17 18 19

20 21 22 23 24

不难看出4*4的步进量为{1,4,5,3}

5*5的步进量为{1,5,6,4}

看出规律了把?,没错,向左右的步进量都不变为1,向下都为列数,向右下都为列数 + 1,向左下都为列数– 1;

所以可以推出一条公式:dir[4] = {1,c,c + 1, c - 1};

接下来是从一维下标推出二维下标,根据p = c * x + y,这条公式,

可以看出

y = p % c

x = (p – y) / c;

第二种方法:

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

16 17 18 19 20

同样棋盘有r(3)行c(3)列

棋盘为是5 6 7 9 10 11 13 14 15,那怎么从二维中转过来呢?可以看出:

5 = 3 + 2 + 0 + 0 * 4

6 = 3 + 2 + 1 + 0 * 4



9 = 3 + 2 + 0 + 1 * 4



15 = 3 + 2 + 2 + 2 * 4

同样可以推出一条规律p = c + 2 + y + x * (c + 1) ,其中p为一维数组下标,c为棋盘有多少列,x为二维数组的x坐标,y为二维数组的y坐标。即一维数组中的p可以表示二维数组中的(x,y)这个位置。

接下来看看步进量问题(大家可以自己画出来算算):

3 * 3的为{1,4,5,3}

4 * 4的为{1,5,6,4}

5 * 5的为{1,6,7,5}

同样不难看出其规律,dir[4] = {1, c + 1, c + 2, c},c为棋盘列数

接下来是从一维下标推出二维下标,根据p = c + 2 + y + x * (c + 1),这条公式,

同样可以看出

y = (p – (c + 2)) % (c + 1),

x = (p – (c + 2) - y) / (c + 1)

好了,今天说的就这么多,下面来看看我做的一个例子,是我在写AI五子棋时用到的:

/**
* 设置先手落子的玩家ID
*
* @param playerid 玩家ID
*/
public void InitGameState(int playerid) {
this.m_playerId = playerid;
this.winner = playerid;
this.m_board = new int[15 * 15 + 34 + 15];
this.nullPos = new HashSet<>();
this.whitePos = new HashSet<>();
this.blackPos = new HashSet<>();
this.canPutPos = new ArrayList<>();
for (int i = 0; i < 15; ++i) {
for (int j = 0; j < 15; ++j) {
m_board[17 + i + j * 16] = -1;
}
}
this.len = 0;
this.is = false;
}


我在初始化中把标志位都设为0,把空的棋盘都设为-1,游戏中设定黑棋为1,白棋为2

下面以一个判断是否五子连珠为例子:

//方向步进量
private static int f[] = {1, 16, 17, 15};
/**
* 检查是否有形成五子连珠
*
* @param dis 下棋位置
* @param playid 玩家id,即白方还是黑方
*/
private void check(int dis, int playid) {
for (int i = 0; i < f.length; ++i) {
int c = 1;
int d = dis + f[i];
//向各个方向步进,直到遇到该位置是标志位或已经有对方棋子结束
while (m_board[d] == playid) {
c++;
d += f[i];
}
d = dis - f[i];
Log.d("d", d + "");
//向相反方向步进,直到遇到该位置是标志位或已经有对方棋子结束
while (m_board[d] == playid) {
c++;
d -= f[i];
}
if (c >= 5) {
winner = playid;
Log.d("over", winner + "赢");
is = true;
break;
}
}
}


下面是用一维数组思路做出的二维矩阵旋转的算法(c++):



#include<iostream>
#include<string.h>
using namespace std;

int result[1000];

int main()
{
int n;
while (cin >> n)
{
int num = 1;
int i = 0, j = 0;
memset(result, 0, sizeof(result));
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
result[n + 2 + i + j * (n + 1)] = -1;
}
}
int k = n + 1, f = 1;
int dis[4] = { 1, n + 1, -1, -(n + 1) };
while (num <= n * n)
{
for (int i = 0; i < 4; ++i)
{
while (result[k + dis[i]] == -1)
{
result[k += dis[i]] = num++;
}
}
}
int pos = 0;
for (int i = n + 2; i <= (n + 2) * n; ++i)
{
if (result[i])
{
printf("%5d", result[i]);
pos++;
if (pos % n == 0)
{
cout << endl;
}
}
}
}

return 0;
}


下面附上我写的AI五子棋

Java版AI五子棋/article/7598346.html(用二维数组表示棋盘)

Android版AI五子棋框架http://git.oschina.net/ysx_xx/Chess-frame(用一维数组表示棋盘)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: