goboard.py の中の GameState のところのデータがどうなっているかが、ゾブリストハッシュの
理解のポイントだと思う。
そこで、GameStateクラスに処理がわたったとき、プロパティがどうなるかを追いかけてみた。

コンストラクタの各プロパティに値がセットされた時を調べてみた。

"goboard.py" 
245 if self.previous_state is None:
246     self.previous_states = frozenset()
247 else:
248     self.previous_states = frozenset(       # 以下の3行の式の値を調べる
249         previous.previous_states |          # <1>
250        {( previous.next_player, previous.board.zobrist_hash())})
# <1> | -- 論理和 -- 0と0は0、1と0は1、0と1は1、1と1は1。

previous_states に格納されるデータを調べるために、以下を挿入した。

251 if previous is not None:
252     print("previous.previous_states:")
253     explain(previous.previous_states)
254     print("previous.next_player")
255     explain(previous.next_player)
256     print("previous.board.zobrist_hash()")
257     explain(previous.board.zobrist_hash())

1) まず、黒が第1手をC3にうったとき、previousの値(盤面状態)は以下のようになる。

    previous.previous_states -- frozenset()     # 空の集合
    previous.next_player -- Player.black
    previous.board.zobrist_hash() -- 0

ここから、以下の式はどうなるか?

    248 self.previous_states = frozenset(
    249     previous.previous_states |
    250    {( previous.next_player, previous.board.zobrist_hash())})

このように、previous_satesは空集合とハッシュ値0の和集合になる。

    self.previous_states = frozenset(
        {()} | {( Player.black, 0 )})

2) 次に白が G5 と打った時、previous の値(盤面状態)は以下である。

    previous.previous_states -- frozenset({(<Player.black: 1>, 0)})
    previous.next_player -- Player.white
    previous.board.zobrist_hash() -- 7685528180565542275

ここから、以下の式はどうなるか?

    248 self.previous_states = frozenset(
    249     previous.previous_states |
    250    {( previous.next_player, previous.board.zobrist_hash())})
    self.previous_states = frozenset(
        {(<Player.black: 1>, 0)} | {( <Player.white>, 7685528180565542275 )})
        = ({(<Player.black: 1>, 0), ( <Player.white>, 7685528180565542275 )})

つまり、{(, 0)} と {( , 7685528180565542275 )}の和集合である

3) 次に黒が D3 と打った時、previous の盤面状態は以下である。

    previous.previous_states -- frozenset(
        {(<Player.black: 1>, 0), (<Player.white: 2>, 7685528180565542275)})
    previous.next_player -- Player.black
    previous.board.zobrist_hash() -- 8139394106448984692

ここから、以下の式はどうなるか?

    self.previous_states = frozenset(
        previous.previous_states |
       {( previous.next_player, previous.board.zobrist_hash())})  # <1>
    self.previous_states = frozenset(
        {(<Player.black: 1>, 0), (<Player.white: 2>, 7685528180565542275)} |
        {( <Player.black>, 8139394106448984692 )})
        = {(<Player.black: 1>, 0),
            (<Player.white>, 7685528180565542275),
            (<Player.black>, 8139394106448984692)}

4) 次に白が J9 と打った時、previous の盤面状態は以下である。

    previous.previous_states -- frozenset(
        {(<Player.black: 1>, 8139394106448984692),
         (<Player.black: 1>, 0),
         (<Player.white: 2>, 7685528180565542275)})
    previous.next_player -- Player.white
    previous.board.zobrist_hash() -- 8684821617119420740

ここから、以下の式はどうなるか?

    248 self.previous_states = frozenset(
    249     previous.previous_states |
    250    {( previous.next_player, previous.board.zobrist_hash())})
    self.previous_states = frozenset(
        {(<Player.black: 1>, 8139394106448984692),
         (<Player.black: 1>, 0),
         (<Player.white: 2>, 7685528180565542275)}) |
        {( <Player.white>, 8684821617119420740 )})
        = {(<Player.black: 1>, 0),
            (<Player.white>, 7685528180565542275),
            (<Player.black>, 8139394106448984692),
            (<Player.white>, 8684821617119420740)}

結論

GameStateでは、盤の情報を (Player, hash) というタプルでまとめ、差し手が進むたびに そのタプルを集合として追加している。
コウの判定では、現在の盤面状況を表わすタプルと、過去の指し手の盤面情報のタプルと 比べ、同じであればコウと判定している。

『囲碁ディープラーニングプログラミング』

Max Pumperia、Kevin Ferguson 著
山岡忠夫 訳
マイナビ出版
2019年4月22日 初版第1刷