From d13c6032fd52e43de870895cd581fcbd3a5acfa4 Mon Sep 17 00:00:00 2001 From: Alexander Stahl Date: Tue, 23 Sep 2025 19:15:58 +0200 Subject: [PATCH 1/2] Initial processing of sgf files (metadata and moves) --- example.typ | 18 +++++- lib.typ | 119 +++++++++++++++++++++++++++++++++++ pietsch-vs-norimoto-1997.sgf | 42 +++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100755 pietsch-vs-norimoto-1997.sgf diff --git a/example.typ b/example.typ index 05d713e..e9ffb22 100644 --- a/example.typ +++ b/example.typ @@ -1,4 +1,4 @@ -#import "lib.typ": go-board, go-board-9, go-board-19 +#import "lib.typ": go-board, go-board-19, go-board-9, sgf_metadata_table, sgf_moves_board #set page(margin: 1cm, height: auto) @@ -46,3 +46,19 @@ kind: "goban", supplement: [Goban], ) + +#pagebreak() += Use `SGF` files +== Metadata Table +#sgf_metadata_table("./pietsch-vs-norimoto-1997.sgf") + +== Moves on a Board +#go-board( + size: 19, + stones: ( + sgf_moves_board("./pietsch-vs-norimoto-1997.sgf") + ), + stone-diameter-ratio: 1, + board-fill: none, + add-played-number: true, +) diff --git a/lib.typ b/lib.typ index 561d2b8..3283391 100644 --- a/lib.typ +++ b/lib.typ @@ -192,3 +192,122 @@ marks: ((2, 2), (6, 2), (2, 6), (6, 6), (4, 4)), ) +#let sgf_parse(file_path) = { + let file_contents = read(file_path) + let lines = file_contents.split(";") + + let metadata_raw = lines.at(1).replace("\n", "").split("]") + if metadata_raw.at(-1) == "" { + metadata_raw.pop() + } + let metadata = () + + for m_raw in metadata_raw { + metadata.insert(metadata.len(), m_raw.split("[")) + } + + let entries = () + let moves = () + let entries = lines.slice(2) + + for entry in entries { + let entry_list = entry.split("[") + + let player = "" + if entry_list.at(0) == "B" { + player = "Black" + } else if entry_list.at(0) == "W" { + player = "White" + } + + let move_pre = entry_list.at(1).split("]").at(0) + let move = move_pre.at(1) + move_pre.at(0) + + let commentary = "" + if entry_list.len() > 2 { + commentary = entry_list.at(2).replace("])", "") + } + + moves.insert(moves.len(), (player, move, commentary)) + } + + return (metadata, moves) +} + +#let sgf_metadata_dict(file_path) = { + let metadata = sgf_parse(file_path).at(0) + let metadata_dict = metadata.to-dict() + return metadata_dict +} + +#let sgf_metadata_table(file_path) = { + let mapping = ( + AB: "Player - Black – Initial Locations of Stones", + AW: "Player - White – Initial Locations of Stones", + AN: "Misc - Annotations", + AP: "File - Creating Application", + B: "Player - Black – Move", + BR: "Player - Black – Rank", + BT: "Player - Black – Name of Team", + C: "Misc - Comment", + CP: "Misc - Copyright", + DT: "Game - Date", + EV: "Event - Name", + FF: "File - Format – SGF Version", + GM: "Game - Type", + GN: "Game - Name", + HA: "Game - Handicap", + KM: "Game - Komi", + ON: "Game - Opening", + OT: "Game - Overtime", + PB: "Player - Black – Name", + PC: "Game - Place", + PL: "Game - Color of starting Player", + PW: "Player - White – Name", + RE: "Game - Result", + RO: "Game - Round", + RU: "Game - Ruleset", + SO: "File - Source", + SZ: "Board - Size", + TM: "Game - Time limit [s]", + US: "File - Creator", + W: "Player - White – Move", + WR: "Player - White – Rank", + WT: "Player - White – Name of Team", + ) + + let metadata_dict = sgf_metadata_dict(file_path) + let metadata = () + let cat-key = () + for m in metadata_dict { + if m.at(0) in mapping { + cat-key = mapping.at(m.at(0)).split(" - ") + metadata.insert(metadata.len(), (cat-key.at(0), cat-key.at(1), m.at(1))) + } + } + + let metadata_sorted = metadata.sorted(key: it => (it.at(0), it.at(1))) + + return [ + #table( + columns: (auto, auto, auto), + inset: 6pt, + align: horizon, + table.header([*Category*], [*Key*], [*Value*]), + ..metadata_sorted.flatten(), + )] +} + +#let sgf_moves(file_path) = { + let data = sgf_parse(file_path) + return data.at(1) +} + +#let sgf_moves_board(filepath) = { + let moves = sgf_moves(filepath) + let moves_board = () + for move in moves { + moves_board.insert(moves_board.len(), move.at(1)) + } + return moves_board +} diff --git a/pietsch-vs-norimoto-1997.sgf b/pietsch-vs-norimoto-1997.sgf new file mode 100755 index 0000000..2a750b3 --- /dev/null +++ b/pietsch-vs-norimoto-1997.sgf @@ -0,0 +1,42 @@ +( +; +FF[4] +EV[LG Cup,2,Korea,LG] +RO[1] +PB[Hans Pietsch] +BR[1 dan] +PW[Yoda Norimoto] +WR[9 dan] +DT[1997-06-27] +PC[Seoul, Korea] +RE[B+0.5] + +;B[pd];W[dp];B[pp];W[ed];B[cd];W[cc];B[bc];W[dc];B[be];W[ic] +;B[df];W[cm];B[kc];W[ie];B[qf];W[ke];B[mc];W[nq];B[kp];W[no] +;B[pn];W[pr];B[qq];W[kr];B[nm];W[pj];B[mp];W[np];B[pl];W[nj] +;B[lj];W[eq];B[jd];W[id];B[ph];W[mk];B[lm];W[lk];B[jj];W[kj] +;B[ki];W[kk];B[ji];W[jc];B[jm];W[jk];B[ik];W[il];B[jl];W[ol] +;B[om];W[mn];B[mm];W[ql];B[qm];W[pk];B[rl];W[pm];B[jr];W[kq] +;B[jq];W[rm];B[pl];W[hk];B[ij];W[pm];B[qn];W[rk];B[qr];W[mq] +;B[lp];W[lq];B[io];W[oq];B[ck];W[dk];B[cl];W[dl];B[bm];W[cn] +;B[bn];W[bo];B[cj];W[dj];B[dm];W[dn];B[bp];W[ao];B[di];W[ci] +;B[bi];W[pc];B[qc];W[oc];B[qd];W[nd];B[le];W[nf];B[me];W[ne] +;B[nh];W[mg];B[kf];W[mh];B[je];W[jf];B[kd];W[jg];B[kg];W[lh] +;B[em];W[en];B[mf];W[ng];B[fm];W[ei];B[dh];W[fn];B[gm];W[gi] +;B[gg];W[hh];B[hl];W[eg];B[fe];W[dg];B[ch];W[eh];B[ee];W[fd] +;B[ge];W[dd];B[cf];W[hg];B[gr];W[fr];B[dr];W[er];B[gq];W[fp] +;B[ri];W[qb];B[rb];W[bb];B[ab];W[ba];B[hf];W[if];B[fj];W[gl] +;B[im];W[ej];B[fk];W[gh];B[ps];W[os];B[qs];W[ms];B[jb];W[ib] +;B[kb];W[ho];B[hp];W[hn];B[hj];W[in];B[jn];W[gp];B[hq];W[de] +;B[ef];W[pb];B[gd];W[gc];B[ia];W[ha];B[ja];W[hc];B[nb];W[ra] +;B[sb];W[oh];B[oi];W[ni];B[pi];W[pg];B[qg];W[ln];B[kn];W[pf] +;B[sn];W[pe];B[qe];W[rn];B[ro];W[sl];B[fi];W[fg];B[gf];W[fh] +;B[gn];W[go];B[pq];W[or];B[qj];W[rj];B[sj];W[ce];B[bd];W[cg] +;B[bg];W[js];B[is];W[ks];B[ga];W[hb];B[hs];W[li];B[nl];W[oj] +;B[pl];W[bl];B[bk];W[pm];B[co];W[cp];B[pl];W[qk];B[qi];W[pm] +;B[al];W[md];B[nc];W[od];B[ld];W[oo];B[pl];W[po];B[qo];W[pm] +;B[an];W[do];B[pl];W[hm];B[gk];W[pm];B[bq];W[cr];B[pl];W[sk] +;B[si];W[pm];B[br];W[cq];B[pl];W[rc];B[rd];W[pm];B[ap];W[co] +;B[pl];W[sm];B[ok];W[so];B[rp];W[nk];B[ol];W[lo];B[ko];W[fs] +;B[jh];W[oa];B[na];W[ob];B[sp];W[gs];B[kl];W[ih];B[lg];W[sn] +) From 5d00c76b0f36d9b7ea834700771ae125bf5a3b87 Mon Sep 17 00:00:00 2001 From: Alexander Stahl Date: Tue, 23 Sep 2025 19:24:25 +0200 Subject: [PATCH 2/2] Little Consolidation --- lib.typ | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib.typ b/lib.typ index 3283391..b479475 100644 --- a/lib.typ +++ b/lib.typ @@ -240,6 +240,11 @@ return metadata_dict } +#let sgf_moves(file_path) = { + let data = sgf_parse(file_path) + return data.at(1) +} + #let sgf_metadata_table(file_path) = { let mapping = ( AB: "Player - Black – Initial Locations of Stones", @@ -298,13 +303,8 @@ )] } -#let sgf_moves(file_path) = { - let data = sgf_parse(file_path) - return data.at(1) -} - -#let sgf_moves_board(filepath) = { - let moves = sgf_moves(filepath) +#let sgf_moves_board(file_path) = { + let moves = sgf_moves(file_path) let moves_board = () for move in moves { moves_board.insert(moves_board.len(), move.at(1))