By the way, if you care about optimization, you could consider using a 64 bit zobrist hash as a first-pass filter. Maintain a map/hashtable/dictionary from zobrist hash -> list of turn numbers with that hash. Every new board position, add the (hash,turnnumber) entry to the map. Every new move, to check for legality, simply query the map with the new hash. If it exists, only then do you do the expensive stone-by-stone check whether the new position is the same as the one on the turn number(s) you retrieved from the table.
If you need undo as well, then maintain a second map from turn number -> zobrist hash as well. When undoing a move, use this table to look up the hashes for the turn number(s) being undone (while also removing them), so you know what entries to remove from the first map.
This would remove the 30 turn limit and might act as a further optimization, since almost always a single map probe would find no matching zobrist hash and would enable you to skip all comparison of board states.
(The thing that makes zobrist hashing fast is that it it can be updated entirely incrementally - you only need to xor one or two values per each stone that changes. Optionally also include the player to move depending on positional/situational).