qq围棋棋谱文件是.wgs格式的,用multigo打不开,据说stonebase可以打开,又不想安装太多软件,就想着把wgs转换为sgf棋谱。
一、首先要分析wgs棋谱文件。
用UltraEdit打开一个wgs棋谱,如下:
参考 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] |
Pingback: QQ围棋棋谱转换程序wgs2sgf V1.1发布 | 知行近思