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棋谱:
1 2 3 4 5 6 7 |
(;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找到。
基于以上分析,开始写代码。
三、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
/* 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((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((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((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((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((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))<<((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["<<((unsigned char)(97+t[j]/4))<<((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进制机内码得到汉字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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进制坐标
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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、十六进制转十进制
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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、十进制转十六进制
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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=""> |
Pingback: QQ围棋棋谱转换程序wgs2sgf V1.1发布 | 知行近思