C++实现qq围棋.wgs棋谱文件转换为.sgf通用棋谱

qq围棋棋谱文件是.wgs格式的,用multigo打不开,据说stonebase可以打开,又不想安装太多软件,就想着把wgs转换为sgf棋谱。

一、首先要分析wgs棋谱文件。

用UltraEdit打开一个wgs棋谱,如下:

image

参考 kiseigo的博文(http://blog.sina.com.cn/s/blog_4c8bb86b010009cd.html),已经知道从 70h行10列开始是对局,70h行8列开始2个字节保存的是总手数,由低位到高位,如上图 70行8列开始是 32 00,则总手数16进制为 00 32,转换为10进制则为:3x16+2=50手。

剩下的信息就得自己分析了,忙了一天,仍然只能分析出部分信息,对局时间,胜负信息没有找到。下面是我分析的结果:

qq棋谱文件(.wgs)分析(用UltraEdit打开,(xxh,x)表示位置):
1、(00h,a)~(10h,f)表示执黑棋手姓名,共22字节;
2、(30h,a)~(40h,f)表示执白棋手姓名,共22字节;
3、棋手姓名前面六个字节,保存的是qq号信息,从低位到高位,直接转换为十进制;
比如黑棋qq号保存在(00h,4)~(00h,9)字节内,信息为
E9 03 E4 41 00 00
则表示的qq号是 00 00 41 E4 03 E9 所表示的十进制数,即 1105462249
4、棋盘大小 在wgs文件中,棋盘路数信息保存在 60h行,5列,第101字节处
5、手数保存在 (70h,8)~(70h,9);
6、对局从(70h,a)开始;

7、wgs用 01 00表示 脱先  pass一手;

8、wgs用坐标表示棋盘上的点,以左上角为原点,横向坐标为列坐标,纵向坐标为行坐标;

9、每一手棋用两个字节表示其坐标,第一个(high)表示列,第二个(low)表示行。表示列的字节,棋盘坐标从1开始计算,则列计算公式为:列=(dec)high/4+1;行=low+1;其中dec表示将high转换为10进制,加1是因为wgs中坐标从0开始;

 

二、分析sgf棋谱

打开Multigo,和Gnu Go随便下一盘棋,然后用notepad++打开sgf棋谱:

 

(;CA[gb2312]SZ[9]AP[MultiGo:4.4.4]DT[2012-07-14 19\:36\:03]PB[black]BR[4]PW[GNU Go]
WR[2]HA[0]RE[百胜4.25子]US[MultiGo]MULTIGOGM[1]
;B[gc];W[df];B[cg];W[cf];B[dg];W[eg];B[eh];W[fh];B[fg];W[gg];B[ef];W[cc];B[de];W[gh]
;B[gf];W[hf];B[he];W[ge];B[hd];W[ff];B[eg];W[fi];B[ei];W[ce];B[fe];W[gf];B[fd];W[ih]
;B[bf];W[be];B[bg];W[db];B[eb];W[dd];B[ee];W[ec];B[fb];W[af];B[ag];W[ae];B[fc];W[ed]
;B[da];W[ca];B[ea];W[ie];B[id];W[gd];B[hc];W[if];B[bb];W[cb];B[bc];W[ba];B[bd];W[cd]
;B[ab];W[ad];B[];W[];B[];W[])

 

对照棋谱复盘,很容易发现如下信息:

1、sgf也是以坐标表示棋盘,左上角为原点,向右向下依26个英文字母的顺序,即a,b,c,d…,共19个字母。

2、CA[gb2312]表示字符集,编程的时候直接写入文件就好;

3、SZ[9]表示棋盘大小,这里是9路;

4、AP[MultiGo:4.4.4]表示的是打谱软件,可以不用管;

5、DT[2012-07-14 19\:36\:03]表示的是对弈时间,wgs中没有找到对应信息;

6、PB[black]表示黑棋(Black)姓名;

7、PW[white]表示白棋(White)姓名;

8、RE[白胜4.25子]表示对局结果,wgs中没有找到对应信息;

9、B[gc]表示黑棋行棋的坐标;

10、W[cg]表示白棋行棋的坐标;

更加详细的说明可以在http://floss.zoomquiet.org/data/20051105182812/index.html

http://www.red-bean.com/sgf/index.html找到。

基于以上分析,开始写代码。

三、代码实现

 

/*
	qq棋谱文件(.wgs)分析(用UltraEdit打开,(xxh,x)表示位置):
	(00h,a)~(10h,f)表示执黑棋手姓名,共22字节;
	(30h,a)~(40h,f)表示执白棋手姓名,共22字节;
	棋手姓名前面六个字节,保存的是qq号信息,从低位到高位,直接转换为十进制;
		比如黑棋qq号保存在(00h,4)~(00h,9)字节内,信息为
				E9 03 E4 41 00 00
		则表示的qq号是 00 00 41 E4 03 E9 所表示的十进制数,即 1105462249
	棋盘大小 在wgs文件中,棋盘路数信息保存在 60h行,5列,第101字节处
	手数保存在 (70h,8)~(70h,9);
	对局从(70h,a)开始;

*/

#include<iostream>
#include<fstream>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;

int main()
{
	ifstream inFile;
	ofstream gofile;
	ofstream sgffile;

	int lu;		//棋盘大小 在wgs文件中,棋盘路数信息保存在 60h行,5列,第101字节处
	int filesize;		//棋谱文件大小
	char* filepath;	//棋谱文件路径
	filepath=new char[100];
	char* newfilepath;	//sgf文件路径
	newfilepath=new char[100];
	cout<<"请输入棋谱文件路径:";
	cin.get(filepath,100);
	cout<<"棋谱路径:"<<filepath<<endl;

	/*
		得到要输出的sgf文件的路径,首先获得filepath的长度,然后截断原扩展名,在连接新扩展名,截断扩展名时
		应把 .wgs 截去,然后接上 .sgf,不能截 wgs 接 sgf,那样输入相对路径时不能得到正确结果,具体原因未明。
		#include <string.h>
		void *memcpy( void *to, const void *from, size_t count );
		功能:函数从from中复制count 个字符到to中,并返回to指针。 如果to 和 from 重叠,则函数行为不确定。
	*/
	memmove(newfilepath,filepath,strlen(filepath)-4);	//截断 .wgs
	cout<<"newfilepath="<<newfilepath<<endl;
	strcat(newfilepath,".sgf");		//新扩展名 .sgf
	cout<<"新文件路径:"<<newfilepath<<endl;

	inFile.open(filepath);
	inFile.seekg(10,ios::beg);	//黑棋棋手姓名保存在距文件开始10字节处
	char* pb;	//黑棋棋手姓名
	char* pw;	//白棋棋手姓名
	pb=new char[22];
	pw=new char[22];
	inFile.get(pb,22);	//棋手姓名信息占用22字节
	cout<<"黑棋:"<<pb<<endl;
	inFile.seekg(58,ios::beg);	//白棋棋手姓名保存在距文件开始58字节处
	inFile.get(pw,22);	//棋手姓名信息占用22字节
	cout<<"白棋:"<<pw<<endl;

	inFile.close();

	inFile.open(filepath,ios::in|ios::binary);	//打开棋谱
	if(!inFile)
	{
		cerr<<"File Open Error!"<<endl;
		system("pause");
		exit(0);
	}
	else
	{
		inFile.seekg(0,ios::end);
		filesize=inFile.tellg();
		cout<<"文件字节数:"<<filesize<<endl;
	}

	unsigned char b[filesize]; 	//棋谱二进制文件缓冲区

	//如:00 00 41 E4 03 E9,bqq[0]=00,bqq[1]=0,..,计算个位置后计算 bqq[]的和即为qq号
	int bqq[6];	//黑棋qq号
	int wqq[6];	//白棋qq号
	long long  blackqq;	//黑棋qq号,long long 类型以便保存10位数及以上的qq号
	long long whiteqq;	//白棋qq号
	inFile.seekg(4,ios::beg);	//定位到第4字节处读取黑棋qq号
	for(int i=0;i<6;i++)
	{
		inFile.read[1]char*)&b[i],sizeof(char;
	}
	int k=0;
	for(int i=5;i>=0;i--)
	{
		bqq[k]=(int)b[i];
		cout<<"bqq["<<k<<"]="<<bqq[k]<<endl;
		k++;
	}
	cout<<endl;
	/*
		计算qq号 ,若某qq号16进制形式数组为:qq[6]={00 00 41 E4 03 E9},则qq号计算过程: 
			(4*16^7+1*16^6) + (14*16^5+4*16^4)+ ...  =(4*16+1)*16^6 + (14*16+4)*16^4 + ......
		而41表示的十进制数为 4*16+1 ,E4表示的数为 14*16+4,则可见规律为 :
		qq号= qq[0]*16^10 + qq[1]*16^8 + qq[2]*16^6 + qq[3]*16^4 + +qq[4]*16^2 + qq[5]*16^0;
	*/
	blackqq=bqq[0]*1099511627776+bqq[1]*4294967296+bqq[2]*16777216+bqq[3]*65536+bqq[4]*256+bqq[5];
	cout<<"blackqq="<<blackqq<<endl;

	inFile.seekg(52,ios::beg);	//定位到第52字节处读取白棋qq号
	for(int i=0;i<6;i++)
	{
		inFile.read[2]char*)&b[i],sizeof(char;
	}
	k=0;
	for(int i=5;i>=0;i--)
	{
		wqq[k]=(int)b[i];
		cout<<"wqq["<<k<<"]="<<wqq[k]<<endl;
		k++;
	}
	cout<<endl;
	/*
		计算qq号 ,若某qq号16进制形式数组为:qq[6]={00 00 41 E4 03 E9},则qq号计算过程: 
			(4*16^7+1*16^6) + (14*16^5+4*16^4)+ ...  =(4*16+1)*16^6 + (14*16+4)*16^4 + ......
		而41表示的十进制数为 4*16+1 ,E4表示的数为 14*16+4,则可见规律为 :
		qq号= qq[0]*16^10 + qq[1]*16^8 + qq[2]*16^6 + qq[3]*16^4 + +qq[4]*16^2 + qq[5]*16^0;
	*/
	whiteqq=wqq[0]*1099511627776+wqq[1]*4294967296+wqq[2]*16777216+wqq[3]*65536+wqq[4]*256+wqq[5];
	cout<<"whiteqq="<<whiteqq<<endl;

	inFile.seekg(101,ios::beg);	//定位到据文件开始101字节处读取棋盘大小信息
	inFile.read[3]char*)&b[101],sizeof(char;
	lu=(unsigned int)b[101];	//棋盘路数
	inFile.seekg(122,ios::beg);	//指针定位到据文件首部122字节处
	int shoushu=filesize-122;		//2倍手数
	int t[shoushu];		//保存手数信息
	gofile.open("qqgo.txt");
	for(int i=122;i<filesize;i++)
	{
		inFile.read[4]char*)&b[i],sizeof(char;
		gofile<<hex<<(unsigned int)b[i]<<"     ";
		t[i-122]=(unsigned int)b[i];
		cout<<t[i-122]<<" ";
	}
	sgffile.open(newfilepath,ios::out);	//先写入打开,确保重新写入时先清空文件内容
	sgffile.close();					//需要先关闭才能再次以追加方式打开
	sgffile.open(newfilepath,ios::app);	//ios:app 添加输入

	sgffile<<"(;CA[gb2312]SZ["<<lu<<"]"<<"PB["<<pb<<blackqq<<"]PW["<<pw<<whiteqq<<"]AP[WGS2SGF1.0]MULTIGOGM[1]\n";

	/*
	  97~122对应字母a~z, 列:32对应i,0对应a,4对应b,8对应c,....;97+t[i]/4可以实现0->97,4->98,8->99...的映射,
	  行:0->a,1->b,2->c... t[j]+97实现映射;
	*/

	int tmp=0;	//sgf文件换行

	for(int j=0;j<shoushu;j++)
	{
		if[5]j/2)%2==0)		//判断黑白,奇数手为黑,偶数手为白
		{
			if(t[j]==1&&t[j+1]==0)		//脱先,在wgs对应16进制 01 … Continue reading<<[6]char)(97+t[j+1]<<"];";
				j++;			//读取了两个字节,需加1
			}
		}
		else
		{
			if(t[j]==1&&t[j+1]==0)			//脱先,在wgs对应16进制 01 00,在sgf中对应B[]或者W[]
			{
				sgffile<<"W[]";
				j++;
			}
			else
			{
				sgffile<<"W["<<[7]unsigned char)(97+t[j]/4<<[8]unsigned char)(97+t[j+1]<<"]";
				j++;
			}
		}

		tmp++;
		if(tmp%14==0)		//14手换一行
			sgffile<<endl;

	}
	sgffile<<")";

	delete [] filepath;
	delete [] pb;
	delete [] pw;
    inFile.close();
	gofile.close();
	sgffile.close();
	system("pause");
	return 0;
}

注:代码未简化,会输出调试信息。

 

四、总结

通过这个小程序,熟悉了一些文件操作方法。但程序还存在很多问题,不能完全解读wgs文件是一大障碍,sgf还好,都不用找资料,直接多打几个谱就看出来了。还有代码没有使用函数或者面向对象的方法,导致代码有点乱,以后的目标就是要解读wgs文件,完善程序。有可能的话加上界面,时这个小工具更加实用。

附:写代码时用到的一些小工具
1、由16进制机内码得到汉字

#include 
#include
int main(int argc,char* argv[]) 
{     
	unsigned   char*   buffer;
	buffer=new unsigned char[100];
	while(1)
	{
		printf("请输入内码:");
		scanf("%X%X",&buffer[0],&buffer[1]);
		printf("%s\n",buffer);
	}
	delete [] buffer;
	system("pause");
    return   0; 
 }

2、由wgs文件16进制坐标计算10进制坐标

#include
#include
using namespace std;

int main()
{
	int x=0,y=0;
	int f=1;
	while(f=1)
	{
		cout< <"请输入两个16进制数:";
		cin>>hex>>x>>y;
		cout<

3、十六进制转十进制

#include
#include
using namespace std;

int main()
{
	long int x=0;
	int f=1;
	while(f=1)
	{
		cout< <"请输入一个16进制数:";
		cin>>hex>>x;
		cout< <"十进制为"<<x<<endl; if(x="=0)" exit(0);="" }="" system("pause");="" return="" 0;="" }<="" pre="">

4、十进制转十六进制

#include
#include
using namespace std;

int main()
{
	int x=0;
	int f=1;
	while(f=1)
	{
		cout< <"请输入一个10进制数:";
		cin>>x;
		cout< <"十六进制为"<<hex<<x<<endl; if(x="=0)" exit(0);="" }="" system("pause");="" return="" 0;="" }<="" pre="">

项目主页:https://github.com/annProg/wgs2sgf

参考资料

参考资料
1, 2, 4 char*)&b[i],sizeof(char
3 char*)&b[101],sizeof(char
5 j/2)%2==0) //判断黑白,奇数手为黑,偶数手为白 { if(t[j]==1&&t[j+1]==0) //脱先,在wgs对应16进制 01 00,在sgf中对应B[]或者W[] { sgffile<<";B[];"; j++; } else { sgffile<<";B["<<((char)(97+t[j]/4
6 char)(97+t[j+1]
7 unsigned char)(97+t[j]/4
8 unsigned char)(97+t[j+1]

One thought on “C++实现qq围棋.wgs棋谱文件转换为.sgf通用棋谱

  1. Pingback: QQ围棋棋谱转换程序wgs2sgf V1.1发布 | 知行近思

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注