2

Following this answer by @StevenB.Segletes, who's also one of the creators of the package listofitems, I'm now trying to use what that package was able to parse from a single-branch SGF string inside a macro.

This is what I would like to be able to achieve, but I think I'm having expansion problems, because I'm getting a Missing \endcsname inserted error:

\long\def\Firstof#1#2\endFirstof{#1}
\ignoreemptyitems
\newcommand{\parseSgf}[1]{
  \setsepchar{;/[||]}
  \readlist*\Z{#1}

% This loop basically splits on ;, which is a node/move delimiter in SGF \foreachitem \i \in \Z[]{ \itemtomacro\Z[\icnt, 1]\stoneColor \itemtomacro\Z[\icnt, 2]\sgfCoords

% These if's test the `key[value]` SGF format for the keys `B` and `W`, which denote Black and White moves, respecitvely
% Other keys typically refer to metadata, or other secondary info.
\if\stoneColor B
  \drawStoneFromSgfCoords{black}{\sgfCoords} % if I hardcode the arguments, it works.
  % \drawStoneFromSgfCoords{black}{Z[\icnt, 2]} % I've also tried stuff like this directly...
\fi
\if\stoneColor AB
  \drawStoneFromSgfCoords{black}{\sgfCoords}
\fi
\if\stoneColor W
  \drawStoneFromSgfCoords{white}{\sgfCoords}
\fi
\if\stoneColor AW
  \drawStoneFromSgfCoords{white}{\sgfCoords}
\fi

} }

Ideally, if anyone is able to find a solution to this, I would like it to find both Black or White moves (B and W) and edited (added) stones (AB and AW), so the if's would look like this:

\if\stoneColor { B \OR AB }
...
\if\stoneColor { W \OR AW }

But, of course, I can save this for another question, if it gets to be too much.


Here's a complete example:

\documentclass{article}

\usepackage{tikz}

% From this answer by @DavidCarlisle. \newcommand\notwhite{black} \newcommand\notblack{white}

% From this answer by @DavidCarlisle. \ExplSyntaxOn \cs_generate_variant:Nn \int_from_alph:n {e}

\NewExpandableDocumentCommand{\stringToCoordX}{ m }{ \int_from_alph:e { \use_i:nn #1 } } \NewExpandableDocumentCommand{\stringToCoordY}{ m }{ \int_from_alph:e { \use_ii:nn #1 } } \ExplSyntaxOff

\newcommand{\drawStoneFromSgfCoords}[2]{ \pgfmathsetmacro{\x}{\stringToCoordX{#2} - 1} \pgfmathsetmacro{\y}{\stringToCoordY{#2} - 1}

\draw[draw = \UseName{not#1}, fill = #1, line width = 0.1mm] (\x * 10cm / 18, \y * 10cm / 18) circle [radius = 0.2575cm]; }

\usepackage{listofitems}

% From this answer by @StevenB.Segletes. \long\def\Firstof#1#2\endFirstof{#1} \ignoreemptyitems \newcommand{\parseSgf}[1]{ \setsepchar{;/[||]} \readlist*\Z{#1}

\foreachitem \i \in \Z[]{ \itemtomacro\Z[\icnt, 1]\color \itemtomacro\Z[\icnt, 2]\sgfCoords

\expandafter\if\expandafter\Firstof\stoneColor\endFirstof B
  \drawStoneFromSgfCoords{black}{ab}
\else\expandafter\if\expandafter\Firstof\stoneColor\endFirstof W
  \drawStoneFromSgfCoords{white}{cd}
\fi\fi

} }

\def\sgfA{;B[ab];W[cd]} % not truly valid SGF, just one simple example \def\sgfB{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs])} \def\sgfC{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs];PL[W]AB[dq]AW[eq])}

\begin{document} \begin{tikzpicture} \pgfmathsetmacro{\step}{10 / 18}

\draw[step=\step] (0, 0) grid (10, 10);

% \drawStoneFromSgfCoords{black}{ab}
% \drawStoneFromSgfCoords{white}{cd}

\parseSgf{\sgfA}

\end{tikzpicture} \end{document}

psygo
  • 438
  • 1
    You are applying \expandafter (once) to \color which will not produce anything useful, and also using \if which comapres character code of character tokens. What do you intent to test here? It;s hard to guess from the code shown. – David Carlisle Feb 09 '24 at 17:05
  • @DavidCarlisle I've added some extra comments to the first code block. Let me know if it needs more clarification. – psygo Feb 09 '24 at 17:14
  • 2
    as I think I mentioned before, using \color as a local variable will break any use of latex color commands in that scope, some but not all tikz color may work but it seems unnecessarily risky – David Carlisle Feb 09 '24 at 17:17
  • Oh, yes. True, I forgot about that, sorry. I wasn't actually using \color in my code, it kinda got mixed up when pasting here. Just fixed it. – psygo Feb 09 '24 at 17:18
  • 1
    athough as it is, since you know it is B or W you can simplify \expandafter\if\expandafter\Firstof\color\endFirstof B to \if\color B – David Carlisle Feb 09 '24 at 17:21
  • Another nice catch, thank you, @DavidCarlisle. I've updated and simplified the first and second code blocks, hopefully. – psygo Feb 09 '24 at 17:32

1 Answers1

3

Several issues:

  1. Don't use \color, since it overwrites an existing macro...I changed it to \Color.

  2. The value of \Color will be B or W, which is not an actual color. Therefore, I introduce \newcommand\thecolorofB{black} and \newcommand\thecolorofW{white} to convert \Color into an actual color.

  3. The parentheses in the SGF definitions are also causing a problem. They were not being parsed, but instead interpreted as SGF commands (but missing the associated [] datum). Therefore, I hardwired the parsing to digest them away. This approach solves the immediate issue, but may not be suitable depending on where you want to take this problem in the future.

  4. The values of \Color and \sgfCoords needed to be expanded, prior to feeding them to your macro \drawStoneFromSgfCoords. I accomplished this by expanding them into \tmp and then expanding \tmp into the macro invocation.

The MWE:

\documentclass{article}

\usepackage{tikz}

% From this answer by @DavidCarlisle. \newcommand\notwhite{black} \newcommand\notblack{white}

% From this answer by @DavidCarlisle. \ExplSyntaxOn \cs_generate_variant:Nn \int_from_alph:n {e}

\NewExpandableDocumentCommand{\stringToCoordX}{ m }{ \int_from_alph:e { \use_i:nn #1 } } \NewExpandableDocumentCommand{\stringToCoordY}{ m }{ \int_from_alph:e { \use_ii:nn #1 } } \ExplSyntaxOff

\newcommand{\drawStoneFromSgfCoords}[2]{% \pgfmathsetmacro{\x}{\stringToCoordX{#2} - 1} \pgfmathsetmacro{\y}{\stringToCoordY{#2} - 1} % \draw[draw = \UseName{not#1}, fill = #1, line width = 0.1mm] (\x * 10cm / 18, \y * 10cm / 18) circle [radius = 0.2575cm]; }

\usepackage{listofitems}

% From this answer by @StevenB.Segletes. \long\def\Firstof#1#2\endFirstof{#1} \newcommand\thecolorofB{black} \newcommand\thecolorofW{white} \ignoreemptyitems \newcommand{\parseSgf}[1]{% \setsepchar{;||(||)/[||]}% \readlist*\Z{#1}% % \foreachitem \i \in \Z[]{% \itemtomacro\Z[\icnt, 1]\Color \itemtomacro\Z[\icnt, 2]\sgfCoords \edef\tmp{{\csname thecolorof\Color\endcsname}{\sgfCoords}}% % \expandafter\if\expandafter\Firstof\Color\endFirstof B \expandafter\drawStoneFromSgfCoords\tmp \else\expandafter\if\expandafter\Firstof\Color\endFirstof W \expandafter\drawStoneFromSgfCoords\tmp \fi\fi }% }

\def\sgfA{;B[ab];W[cd]} \def\sgfB{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs])} \def\sgfC{(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05];B[as];W[bs];B[cs];PL[W]AB[dq]AW[eq])}

\begin{document} \begin{tikzpicture} \pgfmathsetmacro{\step}{10 / 18} \draw[step=\step] (0, 0) grid (10, 10); \parseSgf{\sgfC} \end{tikzpicture} \end{document}

enter image description here

  • Do you then disagree with @DavidCarlisle that these ifs can become \if\Color? His simplification seems to be working with your code (I can create an edit on your answer, but this is also on my first code block). – psygo Feb 09 '24 at 17:59
  • Also, on that note, if I add two more ifs for AB and AW, I don't think they get recognized because it only checks the A, the first letter of them. Is there a way around that? – psygo Feb 09 '24 at 18:00
  • Hmm... Just realized this probably won't work for parsing AB or AW because they are inside the same node (not separated by ;). – psygo Feb 09 '24 at 18:16
  • @PhilippeFanaro my comment about \if was predicated on "you know it is B or W". If it can be AB then you shouldn't be using \if at all. – David Carlisle Feb 09 '24 at 18:35
  • @DavidCarlisle That is where I introduced \Firstof, to expandibly return the first token of an arbitrary-length input string. That input string can be anything between semicolons that doesn't fit the B/W profile, such as GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.52.2]KM[6.5]SZ[19]DT[2024-02-05] – Steven B. Segletes Feb 10 '24 at 00:03
  • @StevenB.Segletes ah well as i said my comment was predicated on it only being B or W but if AB acts like B and AW acts like W taking the first letter doesn't actually do the right thing either. (by the way that command is predefined in latex as \@car....\@nil and \@cdr....\@nil for the remainder, in honour of the lisp tradition, or honor even as it comes from Leslie. – David Carlisle Feb 10 '24 at 00:19
  • @DavidCarlisle Yes, with each reincarnation of his question, the approach that previously was sufficient no longer is. My latest approach to his more recent question is given here: https://tex.stackexchange.com/questions/709176/how-to-parse-a-keyvalue-string-with-the-package-listofitems/709324#709324. I had to increase more depth nesting to my parser. – Steven B. Segletes Feb 12 '24 at 02:25