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发布 | 知行近思