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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a249900679/article/details/51247294


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

井字棋(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五子棋http://blog.csdn.net/a249900679/article/details/51052103(用二维数组表示棋盘)

Android版AI五子棋框架http://git.oschina.net/ysx_xx/Chess-frame(用一维数组表示棋盘)




猜你喜欢

转载自blog.csdn.net/a249900679/article/details/51247294