SGFあれこれ  (2000-12-23)
SGF棋譜形式についてのア・ラ・カルトです。愚痴なども…
トップに戻る  休憩所
SGFの原典
(英語サイト)を参照してください。
  • Textの処理
  • (2000-10-12)追加
  • 持ち時間・残り時間の処理(愚痴)
  • ノードの区切り(ゴミ)
  • Resultの対応
  • (2000-12-23)修正
  • Dateの処理
  • (2000-10-12)追加
  • Handicapの処理
  • (2000-12-23)追加
  • 括弧の処理
  • (2000-12-19)追加
  • コード体系
  • (2000-12-22)
    Textの処理
    原典の引用

    SimpleText

    SimpleText is a simple string. There is no formatting of any kind. White spaces other than space must be converted to space, i.e. there's no newline! Applications must be able to handle SimpleTexts of any size.
    Escaping: "\" is the escape character. Any char following "\" is inserted verbatim (though whitespaces still have to be converted to space!). Following chars have to be escaped, when used in SimpleText: "]", "\" and ":" (only if used in compose datatype).

    Text

    Text is a formatted text. White spaces other than linebreaks are converted to space (e.g. no tab, vertical tab, ..).

    Formatting:
    Soft line break: linebreaks preceeded by a '\' (soft linebreaks are converted to '', i.e. they are removed)
    Hard line breaks: any other linebreaks encountered

    Attention: a single linebreak is represented differently on different systems, e.g. 'LFCR' for DOS, 'LF' on Unix. An application should be able to deal with following linebreaks: LF, CR, LFCR, CRLF.

    Applications must be able to handle Texts of any size. The text should be displayed the way it is, though long lines may be word-wrapped, if they don't fit the display.

    Escaping: "\" is the escape character. Any char following "\" is inserted verbatim (though whitespaces still have to be converted to space!). Following chars have to be escaped, when used in Text: "]", "\" and ":" (only if used in compose datatype).

    Example:

    C[Meijin NR: yeah, k4 is won\
    derful
    sweat NR: thank you! :\)
    dada NR: yup. I like this move too. It's a move only to be expected from a pro. I really like it :)
    jansteen 4d: Can anyone\
     explain [me\] k4?]
    could be rendered as:
    Meijin NR: yeah, k4 is wonderful
    sweat NR: thank you! :)
    dada NR: yup. I like this move too. It's a move only to be expected
    from a pro. I really like it :)
    jansteen 4d: Can anyone explain [me] k4?
    読み込みに関してはSimpleTextもTextも同じ処理でよいと思います。ただし、保存に関しては他のソフトで読み込めるように区別しておいた方がいいかもしれません。
    注目すべきは \ 文字の扱いでしょう。
    まず、処理としてはプロパティに関わりなく [ から ] までの文字列を切り出すことです。 [ の次から切り出し処理を行います。

    途中 \ が現れれば、 \ は読み捨てます。
    その次の文字が改行コードなら、次の行と連結させます。
    一応、コントロールコードでなくなるまで読み捨てていきます。(OSの違いの吸収)
    というか、行単位で読み込む処理をしている場合は文字列がなくなれば、次の行を読み込んで処理を継続します。

    その次の文字が改行コードでなければ(次の文字があれば)、 \ の次の文字は切り出す文字列に含まれます。
     \ の次の文字が ] でも切り出す文字列に含まれます。( \] => ] )
     \ の次の文字が \ でも切り出す文字列に含まれます。( \\ => \ )
     ] が現れれば(1つ前の文字が \ でない場合)、切り出す処理は終了です。
    もちろん、ファイルのデータがなくなっていたりする場合のことも対処できていなくてはなりません。(SGFとしては間違っているのでそこで処理を打ち切ります。さらに読み込んでいたデータをクリアするかどうかはコンセプト次第です。)
    そして、切り出した文字列をプロパティの種類によりさらに処理すれば良い訳です。

    保存は簡単ですね。 ] があれば \] にして、 \ があれば \\ にして保存すればいい訳です。
    棋譜管理ソフトによっては、半角80文字程度のバッファをとって行単位処理しているものもあるかもしれません。それにも読み込めるようにするためには、長い文字列は76文字程度ごとに \ を挟んでいきます。
    この76文字というのは、その直前のプロパティなどの文字列も含みます。要するに出力されるSGFファイルをテキストファイルとして読み込んだ場合、どの行も76文字程度に収まっていれば良いということです。
    ただし、SimpleText、Text以外のプロパティは行をまたがることは禁止されていると思います。

    補足事項
    「プロパティに関わりなく [ から ] までの文字列を切り出すこと」は重要なことです。プロパティのすべてに対応しているなら、その必要性はありませんが、将来拡張されるものにも対応できるようにするのは無理です。
    プロパティの値のタイプが判らない場合にも [ から ] まで正しく読み捨てる必要があるのです。どんなタイプであろうともtextと仮定して読み捨てればSGFの場合は間違いないと考えられます。
    シフトJIS文字列
    漢字コードは2Bytesになっています。そして、2Byte目のコードはアスキーコードと重複しています。つまり、文字列の中のどれが全角でどれが半角であるかは、その前数Bytes分解析しなければ判らないことになります。
    普通は、文字列の先頭から調べる処理をします。そして、各言語、処理系にはライブラリなどが用意されているので、それらを使います。ない場合は自前で組むこともできます。

    半角やコントロールコードであれば1Byteスキップし、そうでなければ全角と仮定して2Bytesスキップするという方法でもいいかと思います。
    別な方法としては、1Byte目が全角のコードであれば2Bytesスキップし、そうでなければ1Byteスキップするという方法です。

     \ を正しく処理しなければなりません。保存では、Soft line breakするとき全角をぶつ切りにしてはいけません。
    持ち時間・残り時間の処理(愚痴)
    原典の引用
    Property:          TM
    Propvalue:         real
    Propertytype:      game-info
    Function:          Provides the time limits of the game.
                        The timelimit is given in seconds.
    
    Property:          BL
    Propvalue:         real
    Propertytype:      move
    Function:          Time left for black, after the move was made.
                        Value is given in seconds.
    
    Property:          WL
    Propvalue:         real
    Propertytype:      move
    Function:          Time left for white after the move was made.
                        Value is given in seconds.
    
    対局が時間制であれば、ルートノードに TM を付け加えます。
    値はrealと定められていますが、実際には整数です。それも多分、0を除く正整数でしょう。
    TM[3000]  なら、持ち時間は50分という訳です。

    [注意] FOST提出用では秒単位ではなく、分単位と定められていて、TM[50]にしなければならない。
    残り時間はそれぞれの手の後に BL WL を付け加えます。
    値はrealと定められていますが、これは0以上で持ち時間以下の正整数となるのでしょう。
    ;B[dd]BL[2930];W[pp]WL[2980]  なら、黒は打った後2930秒残っている、白は打った後2980秒残っているという訳です。
    この仕様は、個人的には宜しくないと考えています。
    仕様を定めた人が、これで十分と考えたのなら、これは汎用性に欠けています。ある使い方をする場合にはSGF以外の棋譜形式を使う必要も出てくることになるでしょう。これはSGFの仕様ではカバーしきれていないからです。

    例えば、持ち時間は定めないが、どの手にどれくらい時間を消費したかを記録したい場合もあるでしょう。しかし、SGFでは記録できないと考えられます。
    個人的に有り得ると考えられる方式はTM[0]にしておき、黒が累計30秒使ったとき、BL[-30]というように負の数で表現していくことは可能かもしれません。
    しかし、この記述方法は不自然ですし、他のソフトでは混乱が起きることでしょう。このような記述をした棋譜から対局を再開しようとすると、直後にその時の手番側を時間切れ負けにするのが普通の処理と考えられますし、あれこれときちんと判断せよと言う方が無理な感じもします。

    私の考える汎用性のあるプロパティとは、 BL WL ではなく、累計消費時間を記録するものです。
    上記の例ですと、
    TM[3000]
    ;B[dd]消費時間[70];W[pp]消費時間[20]
    ということになります。
    TM[]がない場合は、時間制ではないということになります。
    また、棋譜データをどのように表示するかはそれぞれのソフトに依存しますが、
    TM[3000]
    …;B[dd]消費時間[2970];W[pp]消費時間[3020];B[dp]消費時間[3025];W[pd]消費時間[3047]
    となっていれば、
    黒dd--累計消費時間:2970  または (00:49:30)
    白pp--累計消費時間:3020  または (00:50:20)
    黒dp--累計消費時間:3025  または (00:50:25)
    白pd--累計消費時間:3047  または (00:50:47)
    というモードで表示することもできるし、
    黒dd--残り30秒   または (00:00:30)
    白pp--秒読み??秒  または (秒読み??)
    黒dp--秒読み25秒  または (秒読み25)
    白pd--秒読み27秒  または (秒読み27)
    というモードで表示することもできるし、
    黒dd--この手の消費時間:??秒  または (??:??:??)
    白pp--この手の消費時間:??秒  または (??:??:??)
    黒dp--この手の消費時間:55秒  または (00:00:55)
    白pd--この手の消費時間:27秒  または (00:00:27)
    というモードで表示することもできる訳です。
    私は、消費時間プロパティ 時[ ] を使おうかと思う今日この頃でございます。
    ノードの区切り(ゴミ)
    ノードの区切りとはノードを分ける区切り記号で、 ; が使われます。
    ただし、 [ ] の間にある ; は区切り記号ではないと思います。
    (;GM[1]FF[4]RU[Japanese]SZ[19]KM[5.5]PL[B];B[dd]C[左上隅星];W[pp]C[右下隅星])
    最初の ; がルートノードの始まりを示します。
    次からは手のノードの始まりを示していきます。

    さて、もし手のプロパティ(B,W)が手のノードの先頭に来るものだとしたらどうでしょう? ノードの区切りは不要なのではありませんか?
    (GM[1]FF[4]RU[Japanese]SZ[19]KM[5.5]PL[B]B[dd]C[左上隅星]W[pp]C[右下隅星])
    これでも規則性は崩壊しません。(ノードの区切りがないほうがいいといっているわけではありません。あってもなくてもおなじではないかと思っているだけです。) まあ、とりあえずノードの区切りがあることによって、SGFであることの確認をするための記号と考えれば、その冗長性も多いに意味はありますが…


    ところで、ルートノード以外に手のプロパティがないことを許すと少々ややこしいことが考えられます。
    手のないノードがあったとして、手のあるノードが分岐していたら…
    これはその分岐がどういう意味なのか判らなくなります。その分岐した手をどういうルールで再現させればいいのか訳判りません。

    手のないノードの後に手のあるノードが続いていたり、さらにそれらの途中から分岐していたりすると…
    枝分かれ処理は崩壊してしまう感じがします。


    どうであれ、鈍では少々手抜きをしていまして、手のノードの先頭に手のプロパティ(B,W)がないと困るのです。(^^;

    ということで、みなさん、手のノードの先頭に手のプロパティ(B,W)を置くようにしてくださいね。(^^;;
    Resultの対応
    原典の引用
    Property:          RE
    Propvalue:         simpletext
    Propertytype:      game-info
    Function:          Provides the result of the game. It is MANDATORY to use the
                        following format:
                        "0" (zero) or "Draw" for a draw (jigo),
                        "B+" ["score"] for a black win and
                        "W+" ["score"] for a white win
                        Score is optional (some games don't have a score e.g. chess).
                        If the score is given it has to be given as a real value,
                        e.g. "B+0.5", "W+64", "B+12.5"
                        Use "B+R" or "B+Resign" and "W+R" or "W+Resign" for a win by
                        resignation. Applications must not write "Black resigns".
                        Use "B+T" or "B+Time" and "W+T" or "W+Time" for a win on time,
                        "B+F" or "B+Forfeit" and "W+F" or "W+Forfeit" for a win by
                        forfeit,
                        "Void" for no result or suspended play and
                        "?" for an unknown result.
    
    RE[0],RE[Draw]が持碁ということらしいですね。
    RE[Void]は日本囲碁規約に定める無勝負として扱うことにします。

    SGFでは両負けは記録できません。また、囲碁の対局結果には引き分けというのはありません。

    囲碁特有の対局結果を他のゲームの対戦結果の分類に当てはめることには個人的に同意できません。

    鈍では以下のようにします。
    読み込み時表示上の意味出力時
    O両負けO
    W+F黒反則負けW+F
    B+F白反則負けB+F
    W+T黒時間切れ負けW+T
    B+T白時間切れ負けB+T
    W+R白中押し勝ちW+R
    B+R黒中押し勝ちB+R
    W+白勝ちW+数値
    B+黒勝ちB+数値
    V無勝負Void
    0持碁0
    D持碁 
    読み込み処理はテーブルの順に部分的に文字列が一致するかどうかを調べます。
    ?は無視します。
    B+ W+のみ後ろの文字列をrealとみなして数値に変換します。

    なお、SGFではPropertyやそのFunctionで英小文字は無視(読み捨て)してもいいようにしておいた方がいいと思います。
    出力する際には、英小文字は英大文字の前には来ない方がいいかと。
    B+Rsabcdfg・・・これは多くのソフトでOKかもしれない
    Black+R・・・これはダメだろうと思う
    暫定的に鈍の方言として両負けをRE[O](英大文字のオー)としてみました。
    Dateの処理
    原典の引用
    Property:          DT
    Propvalue:         simpletext
    Propertytype:      game-info
    Function:          Provides the date when the game was played.
                        It is MANDATORY to use the ISO-standard format for DT.
                        Note: ISO format implies usage of the Gregorian calender.
                        Syntax:
                        "YYYY-MM-DD" year (4 digits), month (2 digits), day (2 digits)
                        Do not use other separators such as "/", " ", "," or ".".
                        Partial dates are allowed:
                        "YYYY" - game was played in YYYY
                        "YYYY-MM" - game was played in YYYY, month MM
                        For games that last more than one day: separate other dates
                        by a comma (no spaces!); following shortcuts may be used:
                        "MM-DD" - if preceeded by YYYY-MM-DD, YYYY-MM, MM-DD, MM or DD
                        "MM" - if preceeded by YYYY-MM or MM
                        "DD" - if preceeded by YYYY-MM-DD, MM-DD or DD
                        Shortcuts aquire the last preceeding YYYY and MM (if
                        necessary).
                        Note: interpretation is done from left to right.
                        Examples:
                            1996-05,06 = played in May,June 1996
                            1996-05-06,07,08 = played on 6th,7th,8th May 1996
                            1996,1997 = played in 1996 and 1997
                            1996-12-27,28,1997-01-03,04 = played on 27th,28th
                            of December 1996 and on 3rd,4th Januray 1997
                        Note: it's recommended to use shortcuts whenever possible,
                        e.g. 1997-05-05,06 instead of 1997-05-05,1997-05-06
    
    既にある棋譜ファイルから読み込み、上書きするということを考えると日付データは文字列のまま扱った方がいいのかもしれない。

    2000-01,04-01,06,05-01 なら、2000年の1月と4月1日と4月6日と5月1日の34日分
    
    というようになるのでしょう(多分)。

    お城碁などでは月や日などが不明だったりして、年だけを記録したいということもあるでょうが、多量の棋譜を管理する場合には、それらの棋譜で一貫していないと検索などに支障をきたすかもしれない。(ずらずら日付を並べられた場合などには)
    ということで鈍では開始日付と終了日付の2つを扱うようにしています。
    まず、開始日付は文字列の最初で決まります。月や日がない場合は1月、1日とします。
    次に、開始日付を終了日付にコピーしておきます。後は、次々と文字列で決まる日付を終了日付として更新していきます。その処理が終われば出来上がりという訳です。
    上記例だと、開始日付2000-01-01、終了日付2000-5-01となります。
    鈍では、
    "MM-DD" - if preceeded by YYYY-MM-DD, YYYY-MM, MM-DD, MM or DD
    
    の処理が少し怪しい気がしたのでいずれ見直しておきたいと思います。

    日付を2つにすることによって大量の棋譜から日付検索が高速になるメリットはあると思いますし、飛び飛びの日付を記録しなければならないようなことは多くはないだろうとも考えています。

    [注意] FOST提出用ではISO-standard formatではなくて、月/日/年形式と定められていて、DT[1999-09-02]はDT[09/02/99]にしなければならない。(DT[2000-09-03]ならDT[09/03/00]かもしれない)

    修正事項
    日付データ読み込みの処理の記述に誤りがありました。

    開始日付は文字列の最初で決めます。月や日がない場合は1月、1日とします。
    次に、開始日付を終了日付にコピーしておきますが、月や日がない場合は12月、28〜31日とします(それぞれの月に応じて。2月は閏年にも対応)。
    後は、次々と文字列で決まる日付を終了日付として更新していきますが、そのとき月や日がない場合は12月、28〜31日とします。その処理が終われば出来上がりという訳です。

    ただし、これは手作業で日付を変更すればよいというコンセプトならば、特にここまで対応しておく必要はないかもしれません。(鈍では多分対応できていないので気が向いたとき直しておきたいと思います。)
    Handicapの処理
    原典の引用
    Property:          HA
    Propvalue:         number
    Propertytype:      game-info
    Function:          Defines the number of handicap stones (>=2).
                        If there is a handicap, the position should be set up with
                        AB within the same node.
                        HA itself doesn't add any stones to the board, nor does
                        it imply any particular way of placing the handicap stones.
    
    HA[] を使う場合は AB[] も同時に記述しなければならないとしています。
    HA[] だけでは置き石の配置をしてはならないということだろうと思われます。

    では、なぜ HA[] が用意されているのでしょうか?
    これは AB[] や AW[] を調べることなく、置き碁の表示をしたい手抜きソフトのためのものなのでしょう。しかし、 HA[] と AB[]、AW[] が食い違っていたらどうするというのでしょうか? また、 HA[] のみ記録されている棋譜もあるかもしれません。

    個人的には、 HA[] は不要と思います。が、棋譜管理ソフトの中には HA[] のみ処理しているものがあるかもしれません。

    以上のことを考えつつ、鈍では
    保存では AB[] は常に出力します。9子局までは HA[] も出力します。
    読み込みでは、置き石が配置されるまでは HA[] で配石します。 AB[] や AW[] は既に配石されているかどうかにかかわらず、配石していきます。そして、ルートノードが終わるとき、盤上の置き石をパターンマッチして置き碁の名称を決めます。
    例えば、3子局で空き隅の位置が鈍で定めているものと異なれば自由設定扱いとします(これは後々のことも考慮に入れての鈍の仕様です)。
    また、13子局以上では用意されている配石パターンにマッチすれば、その置き碁名称も分類/表示できるようになっています(いろいろと増やしたいがよく知らないので現状は数パターンのみ)。

    まあ、とにかく重要なことは AB[]、AW[] はかならず出力すること。読み込みでは AB[]、AW[] を優先することですね。というか、 HA[] を無視するのが最も良いかもしれません。

    置き石例
    3子  3子 
    10子   ここはないのか?
    11子   ここはないのか?
    12子   ここはないのか?
    13子 13子 井目中四目が正式名?
    井目風鈴 
    14子   ここはないのか?
    15子   ここはないのか?
    16子 16子 バランスの良さから正式にして欲しいものだ
    17子 井目風鈴鉄柱 
    17子 これが井目風鈴中四目が正しいのかな
    18子   ここはないのか?
    19子   ここはないのか?
    20子 20子 GOROで採用されている。
    21子 井目風鈴中四目 これは井目風鈴鉄柱中四目ではないのか?
    22子   ここはないのか?
    23子   ここはないのか?
    24子   ここはないのか?
    25子 25子 
    他に置き碁のパターンと名称をご存知の方がいらっしゃいましたら、メールかまたは掲示板にその名称とテキスト碁盤を投稿してください。
    括弧の処理
    原典の引用
    It contains game trees, with all their nodes and properties, and nothing more. Thus the file format reflects the regular internal structure of a tree of property lists. There are no exceptions; if a game needs to store some information on file with the document, a (game-specific) property must be defined for that purpose.
    [tree (TA1.gif)]
    Example for structure of file format. This tree is written in preorder as:
    (root(ab(c)(de))(f(ghi)(j)))
    (;root(;a;b(;c)(;d;e))(;f(;g;h;i)(;j)))
    これを具体的にSGFの棋譜にしてみると
      ABCDEFGHI
    a┏┯┯┯┯┯┯┯┓  
    b┠┼┼┼┼┼┼┼┨  
    c┠┼a┼・┼f┼┨  
    d┠┼┼┼┼┼┼┼┨  
    e┠┼b┼・g・j┨  
    f┠┼┼┼┼┼┼┼┨  
    g┠c・d・h・┼┨  
    h┠┼┼┼┼┼┼┼┨  
    i┗┷┷e┷i┷┷┛  
    (;GM[1]FF[4]RU[Japanese]SZ[9]KM[5.5]PL[B]
    (;B[cc];W[ce](;B[bg])(;B[dg];W[di]))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
    
    となります。

    下図ならば(;root;a;b(;c)(;d;e))
      ABCDEFGHI
    a┏┯┯┯┯┯┯┯┓  
    b┠a┼b┼c┼┼┨  
    c┠┼・┼・┼・┼┨  
    d┠┼┼┼┼d┼e┨  
    e┠┼・┼・┼・┼┨  
    f┠┼┼┼┼┼┼┼┨  
    g┠┼・┼・┼・┼┨  
    h┠┼┼┼┼┼┼┼┨  
    i┗┷┷┷┷┷┷┷┛  
    (;GM[1]FF[4]RU[Japanese]SZ[9]KM[5.5]PL[B]
    ;B[bb];W[db](;B[fb])(;B[fd];W[hd]))
    
    となります。

    枝分かれ処理について一から解説するとなると膨大な量になるので、ここでは読み込み処理について解説します。(数年前に組んだ部分なのでソースを見て思い出しながら…)

    読み込み時に ( ) の括弧の対応から枝分かれしている個所を見つけます。
    最初と最後の括弧はSGF棋譜データの範囲を示すもので、これは、まあ無視してもいいです。
    ルートノード以外に ( が現れるとその直後の手には枝分かれ手があることになります。そして、 ) が現れるとその直前の手がその枝の終端であることになります。

    さて、これをどのように処理すればいいのか?
    これはスタックを使うのがいいと思います。 ( が現れると、その時の手のデータへのポインタをプッシュします。棋譜の構造によってはその時の手数をプッシュしてもいいです。 ) が現れるとポップします。これは手のデータへのポインタや手数がえられる訳ですが、これでどこから枝分かれしているかが求まります。それが求まれば棋譜データをどのように更新させていくか決まりますよね。

    読み込みデータに括弧の対応が取れていないことがあるかもしれないということを考慮すると、スタックは自前で組むのがいいと思われます。

    class BStack
    {
    public:
    		BStack(int size) { m_data = new int[size]; m_size = size; }
    		~BStack() { delete [] m_data; }	
    	void	init_stack() { m_level = 0; }
    	bool	push(int data);
    	bool	pop(int& data);
    
    private:
    	int	m_level;
    	int	m_size;
    	int*	m_data;
    };
    
    bool BStack::push(int data)
    {
    	if (m_level >= m_size)
    		return false;
    	*(m_data + m_level++) = data;
    	return true;
    }
    
    bool BStack::pop(int& data)
    {
    	if (m_level == 0)
    		return false;
    	data = *(m_data + --m_level);
    	return true;
    }
    
    C++では、こんな感じのクラスを作っておけばいいでしょう。
    sizeは500もあれば十分ですし、1000でもいいでしょう。それを超えるような棋譜はゴミだと思われます。これも完璧にということであれば、m_levelがm_sizeを超えるときに、より大きなメモリを確保し、それにコピーして、元のをデリートする仕組みにすればいいです。
    しかしそこまでしなくても、pushするときにfalseならば「括弧が多すぎて処理できませんでした」と「いい訳」をすれば済むことだろうと思われますし、popするときにfalseならば「括弧の数が合っていませんでした」と「いい訳」をすれば済むことだろうと思われます。
    「いい訳」というのはメッセージボックスを出して、処理を終了するなどのことです。このとき、読み込み処理を打ち切る訳ですが、読み込んだデータをすべてクリアするか、または、そこまでは有効として残すかはそのソフトのコンセプト次第かと思います。

    なお、この処理では、最後に ) が足りなくても関知しません。そもそも、最後の閉じる括弧の数などどうでもいい訳です。これもきっちりしていないと不正なデータとしてクリアしてしまいたいなら、m_levelを読み出せるようにしておいて、読み込みが終わったときにチェックすればいいですが…

    補足事項
     [ から ] までに (  ) があっても、それは枝分かれを示す括弧ではありません。

     [ が現れれば、その直後の文字列はtextと仮定して \] ではない ] まで切り出します。その間、 ( があってもそれを枝分かれの括弧としてプッシュしてはいけませんし、 ) があってもそれを枝分かれの括弧としてポップしてもいけません。
    基本的に1文字ずつ処理する場合、その状態(例えば、 [ ] の処理中かそうでないかとか、ルートノードの処理中かそうでないかなど)をフラグやモードなどを設定して処理することになるでしょう。そうすることによって順に1文字ずつ処理ができます。(漢字の処理もお忘れなく)

     [ ] はON/OFFの2つの状態しかありませんが、 ( ) はそれぞれに対応関係があり、スタックを使うことでその情報が保持できるという訳です。

    少々複雑になるかもしれませんが、Byte単位で見たコードの使いまわしができる(textに使えない文字がないようにする)ための工夫と考えてください。
    具体例
    
    (;GM[1]FF[4]RU[Japanese]SZ[9]KM[5.5]PL[B]
    (;B[cc];W[ce](;B[bg])(;B[dg];W[di]))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
    a            b       c              d      e                   f 
    
    ┯
    |
    push(a)
    stack [a] 
    
    (;B[bg])(;B[dg];W[di]))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
    b       c              d      e                   f 
    
    ┯B[cc]━W[ce]┯
    |            | 
                  push(b)
    stack [a][b] 
    
    )(;B[dg];W[di]))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
     c              d      e                   f 
    
    ┯B[cc]━W[ce]┯B[bg]
    |            |
                  └ pop() == b
    stack [a] 
    
    (;B[dg];W[di]))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
    c              d      e                   f 
    
    ┯B[cc]━W[ce]┳B[bg]
    |            ┃
                  ┣
                  |
                  push(c)
    stack [a][c] 
    
    ))(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
      d      e                   f 
    
    ┯B[cc]━W[ce]┳B[bg]
    |            ┃
                  ┣B[dg]━W[di]
                  |  
                  └ pop() == c
    stack [a] 
    
    )(;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
     d      e                   f 
    
    ┯B[cc]━W[ce]┳B[bg]
    |            ┃
    |            ┗B[dg]━W[di]
    |              
    └ pop() == a
    stack 空 
    
    (;B[gc](;W[fe];B[fg];W[fi])(;W[he])))
    d      e                   f 
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣ 
    |
    push(d)
    stack [d] 
    
    (;W[fe];B[fg];W[fi])(;W[he])))
    e                   f 
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣B[gc]┯ 
    |     |
           push(e)
    stack [d][e] 
    
    )(;W[he])))
     f 
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣B[gc]┯W[fe]━B[fg]━W[fi] 
    |     |
           └ pop() == e
    stack [d] 
    
    (;W[he])))
    f 
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣B[gc]┳W[fe]━B[fg]━W[fi] 
    |     ┃
           ┣
           |
           push(f)
    stack [d][f] 
    
    )))
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣B[gc]┳W[fe]━B[fg]━W[fi] 
    |     ┃
           ┣W[he]
           |
           └ pop() == e
    stack [d] 
    
    ))
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┣B[gc]┳W[fe]━B[fg]━W[fi] 
    |     ┃
    |     ┗W[he]
    |
    └ pop() == d
    stack 空 
    
    )
    
    ┳B[cc]━W[ce]┳B[bg]
    ┃            ┃
    ┃            ┗B[dg]━W[di]
    ┃              
    ┗B[gc]┳W[fe]━B[fg]━W[fi] 
           ┃
           ┗W[he]
    
    さて、これで理解は完璧になったでしょうか?
    コード体系
    この項目は現在の理解でこうしておくということで、正しい理解を得た場合には修正します。

    原典の引用
    Property:          CA
    Propvalue:         simpletext
    Propertytype:      root
    Function:          Provides the used charset for SimpleText and Text type.
                        Default value is 'ISO-8859-1' aka 'Latin1'.
                        Only charset names (or their aliases) as specified in RFC 1345
                        (or updates thereof) are allowed.
                        Basically this field uses the same names as MIME messages in
                        their 'charset=' field (in Content-Type).
                        RFC's can be obtained via FTP from DS.INTERNIC.NET,
                        NIS.NSF.NET, WUARCHIVE.WUSTL.EDU, SRC.DOC.IC.AC.UK
                        or FTP.IMAG.FR.
    
    もはや、読解力不足を感じた。というか、調べるべきことが多すぎる気がする。

    鈍から出力されるSGFデータがシフトJISであることを明記することにします。

    CA[Shift_JIS]

    読み込み時には次のような扱いをすることにします。
    コードデフォルト環境設定
    Shift_JISシフトJIS扱いシフトJIS扱い
    ISO-8859-1Byte単位扱いByte単位扱い
    Latin1Byte単位扱いByte単位扱い
    その他シフトJIS扱いByte単位扱い
    記載なしシフトJIS扱いByte単位扱い
    環境設定で指定することで読み込んだ文字の扱いを変えます。

    おそらくですが、日本語処理をベースにしているソフトから出力されるものを読み込む場合はデフォルトを設定し、英語処理をベースにしているソフトから出力されるものを読み込む場合は環境設定で指定すればうまくいくのではないかと思われます。

    シフトJIS扱いとは、全角文字も1文字として文字単位で処理をする方式です。
    例えば、
    表示
    という文字列は 表 示 という2文字として扱います。
    これはtextでもそのまま
    表示
    の2文字となります。

    表\示
    という文字列は 表 \ 示 という3文字として扱います。
    これはtextでは \示 は  になり、
    表示
    の2文字となります。

    表\\示
    という文字列は 表 \ \ 示 という4文字として扱います。
    これはtextでは \\ は \ になり、
    表\示
    の3文字となります。

    表\

    という文字列は 表 \【改行コード】 示 という3文字として扱います。
    これはtextでは \【改行コード】 は文字列が連結し、
    表示
    の2文字となります。

    C[表]
    は  という1文字として切り出されます。
    これはtextでもそのまま

    の1文字となります。

    C[[表\]2];
    は \] が ] になり、
    [表]2 が切り出されます。

    C[表\];B[dd];W[de];
    は \] が ] になり、
    表];B[dd が切り出されます。
    次の ;W[de] は囲碁のルールチェックにより多分無視されます。

    C[十[分];
    はそのまま十[分 が切り出されます。

    C[十]って];
     が切り出され、 って は多分読み捨てられます。

    C[表\]);B[dd];W[de];
    は \] が ] になり、
    表]);B[dd が切り出されます。以下同様。
    Byte単位扱いとは、全角文字は2BytesとしてByte単位で処理をする方式です。
    例えば、
    表示
    という文字列は 0x95 0x5c 0x8e 0xa6 という4Bytesとして扱います。
    これはtextでは 0x5c  0x8e は 0x8e となって、
    0x95 0x8e 0xa6 の3Bytesになり、
    侮ヲ
    となります。

    表\示
    という文字列は 0x95 0x5c 0x5c 0x8e 0xa6 という5Bytesとして扱います。
    これはtextでは 0x5c  0x5c は 0x5c となって、
    0x95 0x5c 0x8e 0xa6 の4Bytesになり、
    表示
    となります。

    表\\示
    という文字列は 0x95 0x5c 0x5c 0x5c 0x8e 0xa6 という6Bytesとして扱います。
    これはtextでは 0x5c  0x5c は 0x5c となり、 0x5c  0x8e は 0x8e となって、
    0x95 0x5c 0x8e 0xa6 の4Bytesになり、
    表示
    となります。

    表\

    という文字列は 0x95 0x5c 0x5c【改行コード】 0x8e 0xa6 という5Bytesとして扱います。
    これはtextでは 0x5c  0x5c は 0x5c となって
    0x95 0x5c【改行コード】 0x8e 0xa6 の4Bytesになり、


    となります。

    C[表];B[dd];W[de]
    は 0x95 0x5c 0x5d という3Bytesはtextでは 0x5c  0x5d は 0x5d となって、
    0x95 0x5d の2Bytesはになり、
    評;B[dd が切り出されます。
    次の ;W[de] は囲碁のルールチェックにより多分無視されます。

    C[[表\]2];
    は 0x95 0x5c 0x5c という3Bytesはtextでは 0x5c  0x5d は 0x5d となって、
    0x95 0x5c の2Bytesはになり、
    [表 が切り出され、 ]2] は、多分読み捨てられます。

    C[表\];
    は 0x95 0x5c 0x5c という3Bytesはtextでは 0x5c  0x5c は 0x5c となって、
    0x95 0x5c の2Bytesはになり、
     がそのまま切り出されます。

    C[十[分];
    は 0x8f 0x5c 0x5b という3Bytesはtextでは 0x5c  0x5b は 0x5b となって、
    0x8f 0x5b の2Bytesはになり、
    充分 が切り出されます。

    C[十]って];
    は 0x8f 0x5c 0x5d という3Bytesはtextでは 0x5c  0x5d は 0x5d となって、
    0x8f 0x5d の2Bytesはになり、
    従って が切り出されます。

    C[表]);B[dd];W[de]
    は 0x95 0x5c 0x5d という3Bytesはtextでは 0x5c  0x5d は 0x5d となって、
    0x95 0x5d の2Bytesはになり、
    評);B[dd が切り出されます。以下同様。

    ただ、JAVAアプレットで作る場合は、いったいどういうことになるのか…

      (「鈍」のホームページ)
    トップに戻る 
    最初へ