SGF Tree
试图将SGF文件变成树,简化版本
SGF是一种可以是用于围棋棋谱记录的文本格式,关键的部分大概类似这样:
(;B[cd];W[pq](;B[cq];W[do])(;B[cf];W[pn]))
如果先从简单的模型开始,例如简化成:
(AB(CD)(EF))
要把这样的字符串解析成一棵二叉树(最简单的情况),使用下面的代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def make_node(color='@', left={}, right={}):
node = {
'color': color,
'left': left,
'right': right
}
return node
def make_sgf_tree(sgf_str='', index=[0], head=make_node()):
current_node = head
# 遍历字符串
while True:
index[0] += 1 # 记录字符串被处理的当前位置,技巧:参数修改
# 循环退出条件
if sgf_str[index[0]] == ')':
break
# 递归处理内层的括号对
elif sgf_str[index[0]] == '(':
make_sgf_tree(sgf_str, index, current_node)
# 非括号字符作为节点的color
else:
n = make_node(color=sgf_str[index[0]])
# 尽量插在树的右边
if not current_node['right']:
current_node['right'] = n
elif not current_node['left']:
current_node['left'] = n
else:
print 'should NOT be there....'
current_node = n
# 前序遍历
def tree_pre_trav(head=make_node()):
start = head
if not start:
return
print start['color'],
tree_pre_trav(start['left'])
tree_pre_trav(start['right'])
# 后序遍历
def tree_post_trav(head=make_node()):
start = head
if start['left']:
tree_post_trav(start['left'])
if start['right']:
tree_post_trav(start['right'])
print start['color'],
# 中序遍历
def tree_in_trav(head=make_node()):
start = head
if start['left']:
tree_in_trav(start['left'])
print start['color'],
if start['right']:
tree_in_trav(start['right'])
if __name__=='__main__':
# sgf_str = '(B(W(BW)(BWB))(WBW))'
sgf_str = '(A(B(CD)(EFG))(HIJ))'
print 'SGF String: \"' + sgf_str + '\"'
sgf_str_index = [0]
head = make_node()
make_sgf_tree(sgf_str=sgf_str, index=sgf_str_index, head=head)
print '\nThe SGF Tree:'
print head
print '\nPreorder Traversal:'
tree_pre_trav(head)
print '\n\nPostorder Traversal:'
tree_post_trav(head)
print '\n\nInorder Traversal:'
tree_in_trav(head)
输出:
SGF String: "(A(B(CD)(EFG))(HIJ))"
The SGF Tree:
{'color': '@', 'right': {'color': 'A', 'right': {'color': 'B', 'right': {'color': 'C', 'right': {'color': 'D', 'right': {}, 'left': {}}, 'left': {}}, 'left': {'color': 'E', 'right': {'color': 'F', 'right': {'color': 'G', 'right': {}, 'left': {}}, 'left': {}}, 'left': {}}}, 'left': {'color': 'H', 'right': {'color': 'I', 'right': {'color': 'J', 'right': {}, 'left': {}}, 'left': {}}, 'left': {}}}, 'left': {}}
Preorder Traversal:
@ A H I J B E F G C D
Postorder Traversal:
J I H G F E D C B A @
Inorder Traversal:
@ H I J A E F G B C D
应该就是下图的样子:

其实最后得到的代码,不过是将一个表示二叉树的字符串变成一颗二叉树而已。再稍微扩展一下,应该就能解析SGF文件了。再然后,就可以设计一个围棋打谱程序了。
还有一个我之前写的C语言的版本,指针与二叉树,可能适合C语言的思维:
#include <stdio.h>
#include <stdlib.h>
struct node {
char color;
char x;
char y;
struct node *left;
struct node *right;
};
// record the beginning of sgf string
char *start = NULL;
void make_sgf_tree(struct node *head, char *str) {
start = str;
struct node *current_node = head;
while (*start != '\n') {
start += 1;
if (*start == ')')
break;
else if (*start == '(')
make_sgf_tree(current_node, start); // 递归,我们都喜欢
else {
if (*start == ';') { // 围棋动作的开始,期望类似 ;W[ab]...
struct node *new_node = (struct node*)malloc(sizeof(struct node));
new_node->color = *(start+1);
new_node->x = *(start+3);
new_node->y = *(start+4);
printf("(%c,%c)\n", new_node->x, new_node->y);
start += 5; //FIXME, there is a better way to parse
if (current_node->right == NULL)
current_node->right = new_node;
else if (current_node->left == NULL)
current_node->left = new_node;
else
printf("22not children........\n");
current_node = new_node;
}
}
}
}
// 前序遍历
void show_tree_pre(struct node *head)
{
struct node *start = head;
if (start != NULL)
printf("<%c(%c,%c)>\n", start->color, start->x, start->y);
if (start->left != NULL)
show_tree_pre(start->left);
if (start->right != NULL)
show_tree_pre(start->right);
}
int main()
{
// 简单的测试,sgf文件的子集,且限定成最简单的二叉树(假定某手棋的变化之多有两种)
char *str = "(;W[ab];B[cd](;W[ef];B[ac](;W[ss];B[rr])(;W[gg];B[hh]))(;W[ee];B[bc]))\n";
printf("SGF string: %s",str);
// 我们把它变成一颗树
struct node *head = (struct node*)malloc(sizeof(struct node));
make_sgf_tree(head, str);
// 再前序遍历看看这个树是不是我们想想的样子
printf("\npreorder travelsal:\n");
show_tree_pre(head->right);
return 0;
}