\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tikz-catan}[2026/07/30 v1.0.0 Catan board diagrams in TikZ]

\RequirePackage{tikz}
\usetikzlibrary{calc}

% hex circumradius in cm
\newcommand\CatanS{1.05}
% resource glyph toggle
\newif\ifcatanicons \cataniconstrue

% colors
\definecolor{reswood}{HTML}{3D8B4F}
\definecolor{resbrick}{HTML}{C0603C}
\definecolor{ressheep}{HTML}{B7CF7C}
\definecolor{reswheat}{HTML}{E7C34D}
\definecolor{resore}{HTML}{8E9AA6}
\definecolor{resdesert}{HTML}{E7DAB0}
\definecolor{seafill}{HTML}{D7E8F2}
\definecolor{seaborder}{HTML}{8FB8D0}
\definecolor{tokenfill}{HTML}{F6EEDA}
\definecolor{tokenborder}{HTML}{C9B98A}
\definecolor{tokentext}{HTML}{3A3428}
\definecolor{tokenred}{HTML}{B03A2E}
\definecolor{dimfill}{HTML}{ECECEC}
\definecolor{dimborder}{HTML}{BDBDBD}
\definecolor{dimtoken}{HTML}{9A9A9A}
\definecolor{robbergray}{HTML}{4A4A52}
\definecolor{legendtext}{HTML}{3A3428}
\definecolor{pred}{HTML}{C0392B}
\definecolor{pblue}{HTML}{2E5FA3}
\definecolor{porange}{HTML}{E67E22}
\definecolor{pwhite}{HTML}{F5F2EA}

% axial coordinates, pointy-top hexes
% hex center: x = S*sqrt(3)*(q+r/2), y = -1.5*S*r (r grows downward)
\newcommand\hexXY[2]{%
  \pgfmathsetmacro\hx{\CatanS*1.7320508*((#1)+(#2)/2)}%
  \pgfmathsetmacro\hy{-\CatanS*1.5*(#2)}}

% corner name -> polar angle
\def\cornerAngN{90}\def\cornerAngNE{30}\def\cornerAngSE{-30}
\def\cornerAngS{270}\def\cornerAngSW{210}\def\cornerAngNW{150}
\newcommand\vertexXY[3]{%
  \hexXY{#1}{#2}%
  \pgfmathsetmacro\vx{\hx+\CatanS*cos(\csname cornerAng#3\endcsname)}%
  \pgfmathsetmacro\vy{\hy+\CatanS*sin(\csname cornerAng#3\endcsname)}}

% side name -> its two corners and outward normal
\def\sideCaNE{N}\def\sideCbNE{NE}\def\sideNormNE{60}
\def\sideCaE{NE}\def\sideCbE{SE}\def\sideNormE{0}
\def\sideCaSE{SE}\def\sideCbSE{S}\def\sideNormSE{300}
\def\sideCaSW{S}\def\sideCbSW{SW}\def\sideNormSW{240}
\def\sideCaW{SW}\def\sideCbW{NW}\def\sideNormW{180}
\def\sideCaNW{NW}\def\sideCbNW{N}\def\sideNormNW{120}

% resource glyphs
\newcommand\glyphwood{%
  \fill[reswood!55!black] (-0.035,-0.335) rectangle (0.035,-0.245);
  \fill[reswood!45!black] (-0.165,-0.255) -- (0.165,-0.255) -- (0,-0.085) -- cycle;
  \fill[reswood!35!black] (-0.125,-0.16) -- (0.125,-0.16) -- (0,-0.04) -- cycle;}
\newcommand\glyphbrick{%
  \fill[resbrick!55!black,rounded corners=0.6pt] (-0.20,-0.28) rectangle (-0.01,-0.18);
  \fill[resbrick!55!black,rounded corners=0.6pt] (0.03,-0.28) rectangle (0.22,-0.18);
  \fill[resbrick!40!black,rounded corners=0.6pt] (-0.10,-0.15) rectangle (0.12,-0.05);}
\newcommand\glyphsheep{%
  \draw[black!60,line width=1.0pt,line cap=round] (-0.055,-0.22) -- (-0.055,-0.29);
  \draw[black!60,line width=1.0pt,line cap=round] (0.055,-0.22) -- (0.055,-0.29);
  \fill[white] (-0.085,-0.165) circle (0.075);
  \fill[white] (0.005,-0.135) circle (0.08);
  \fill[white] (0.085,-0.17) circle (0.07);
  \fill[white] (0.0,-0.20) circle (0.075);
  \fill[black!65] (0.165,-0.115) circle (0.05);
  \fill[black!65] (0.20,-0.075) ellipse (0.025 and 0.016);}
\newcommand\glyphwheat{%
  \foreach \dx in {-0.11,0,0.11}{
    \draw[reswheat!45!black,line width=1.0pt,line cap=round]
      (\dx,-0.30) -- (\dx,-0.10);
    \fill[reswheat!45!black] (\dx,-0.09) ellipse (0.028 and 0.05);}}
\newcommand\glyphore{%
  \fill[resore!45!black] (-0.18,-0.28) -- (-0.02,-0.28) -- (-0.10,-0.10) -- cycle;
  \fill[resore!30!black] (0.00,-0.28) -- (0.18,-0.28) -- (0.09,-0.06) -- cycle;}
\newcommand\glyphdesert{%
  \fill[resdesert!45!black,rounded corners=1.2pt] (-0.03,-0.30) rectangle (0.03,-0.02);
  \fill[resdesert!45!black,rounded corners=1.2pt] (-0.13,-0.22) rectangle (-0.03,-0.16);
  \fill[resdesert!45!black,rounded corners=1.2pt] (0.03,-0.16) rectangle (0.13,-0.10);}

% number token; n=0 draws nothing (desert)
\newcommand\drawtoken[1]{%
  \ifnum#1>0
    \def\numcol{tokentext}%
    \ifnum#1=6\def\numcol{tokenred}\fi
    \ifnum#1=8\def\numcol{tokenred}\fi
    \fill[tokenfill] (0,0.30) circle (0.30);
    \draw[tokenborder,line width=0.9pt] (0,0.30) circle (0.30);
    \node[font=\bfseries,text=\numcol,scale=\CatanS] at (0,0.345) {#1};
    \pgfmathtruncatemacro\pips{6-abs(#1-7)}%
    \foreach \i in {1,...,\pips}{
      \pgfmathsetmacro\px{(\i-(\pips+1)/2)*0.085}%
      \fill[\numcol] (\px,0.155) circle (0.026);}
  \fi}
\newcommand\drawtokendim[1]{%
  \ifnum#1>0
    \fill[white!92!black] (0,0.30) circle (0.30);
    \draw[dimborder,line width=0.9pt] (0,0.30) circle (0.30);
    \node[font=\bfseries,text=dimtoken,scale=\CatanS] at (0,0.345) {#1};
    \pgfmathtruncatemacro\pips{6-abs(#1-7)}%
    \foreach \i in {1,...,\pips}{
      \pgfmathsetmacro\px{(\i-(\pips+1)/2)*0.085}%
      \fill[dimtoken] (\px,0.155) circle (0.026);}
  \fi}

% hex renderers, callback targets for board lists
\newcommand\hexfull[4]{%
  \hexXY{#1}{#2}%
  \begin{scope}[shift={(\hx,\hy)},scale=\CatanS]
    \draw[draw=res#3!55!black,line width=1.1pt,fill=res#3]
      (30:1)--(90:1)--(150:1)--(210:1)--(270:1)--(330:1)--cycle;
    \ifcatanicons
      \begin{scope}[shift={(0,-0.06)}]\csname glyph#3\endcsname\end{scope}
    \fi
    \drawtoken{#4}
  \end{scope}}
\newcommand\hexdim[4]{%
  \hexXY{#1}{#2}%
  \begin{scope}[shift={(\hx,\hy)},scale=\CatanS]
    \draw[draw=dimborder,line width=1.1pt,fill=dimfill]
      (30:1)--(90:1)--(150:1)--(210:1)--(270:1)--(330:1)--cycle;
    \drawtokendim{#4}
  \end{scope}}
\newcommand\hexshape[4]{%
  \hexXY{#1}{#2}%
  \begin{scope}[shift={(\hx,\hy)},scale=\CatanS]
    \draw[draw=dimborder,line width=1.0pt,fill=white!96!black]
      (30:1)--(90:1)--(150:1)--(210:1)--(270:1)--(330:1)--cycle;
  \end{scope}}
\newcommand\hexprob[4]{%
  \hexXY{#1}{#2}%
  \begin{scope}[shift={(\hx,\hy)},scale=\CatanS]
    \draw[draw=tokenborder!80!black,line width=1.0pt,fill=tokenfill!55!white]
      (30:1)--(90:1)--(150:1)--(210:1)--(270:1)--(330:1)--cycle;
    \drawtoken{#4}
  \end{scope}}
\newcommand\hlhex[2]{\expandafter\def\csname hl@#1@#2\endcsname{}}
\newcommand\hlres[1]{\expandafter\def\csname hlR@#1\endcsname{}}
\newcommand\hlnum[1]{\expandafter\def\csname hlN@#1\endcsname{}}
% full color if coordinate, resource, or number was declared
\newcommand\hexauto[4]{%
  \ifcsname hl@#1@#2\endcsname \hexfull{#1}{#2}{#3}{#4}%
  \else\ifcsname hlR@#3\endcsname \hexfull{#1}{#2}{#3}{#4}%
  \else\ifcsname hlN@#4\endcsname \hexfull{#1}{#2}{#3}{#4}%
  \else \hexdim{#1}{#2}{#3}{#4}\fi\fi\fi}
\newcommand\hexdots[4]{%
  \foreach \c in {N,NE,SE,S,SW,NW}{%
    \vertexXY{#1}{#2}{\c}%
    \fill[dimtoken] (\vx,\vy) circle (1.5pt);}}

% sea frame
\newcommand\catanframe{%
  \pgfmathsetmacro\R{5.15*\CatanS}%
  \draw[fill=seafill,draw=seaborder,line width=1.2pt,rounded corners=8pt]
    (0:\R)--(60:\R)--(120:\R)--(180:\R)--(240:\R)--(300:\R)--cycle;}

% pieces
\newcommand\settlement[4]{%
  \vertexXY{#1}{#2}{#3}%
  \begin{scope}[shift={(\vx,\vy)},scale=\CatanS]
    \draw[fill=#4,draw=#4!50!black,line width=0.9pt]
      (-0.16,-0.13)--(0.16,-0.13)--(0.16,0.03)--(0,0.18)--(-0.16,0.03)--cycle;
  \end{scope}}
\newcommand\city[4]{%
  \vertexXY{#1}{#2}{#3}%
  \begin{scope}[shift={(\vx,\vy)},scale=\CatanS]
    \draw[fill=#4,draw=#4!50!black,line width=0.9pt]
      (-0.21,-0.13)--(0.21,-0.13)--(0.21,0.06)--(0.02,0.06)--
      (0.02,0.15)--(-0.095,0.27)--(-0.21,0.15)--cycle;
  \end{scope}}
\newcommand\road[4]{%
  \vertexXY{#1}{#2}{\csname sideCa#3\endcsname}%
  \pgfmathsetmacro\ax{\vx}\pgfmathsetmacro\ay{\vy}%
  \vertexXY{#1}{#2}{\csname sideCb#3\endcsname}%
  \draw[#4!45!black,line width=4.4pt,line cap=round,shorten <=4.5pt,shorten >=4.5pt]
    (\ax,\ay)--(\vx,\vy);
  \draw[#4,line width=2.6pt,line cap=round,shorten <=5.2pt,shorten >=5.2pt]
    (\ax,\ay)--(\vx,\vy);}
\newcommand\robber[2]{%
  \hexXY{#1}{#2}%
  \begin{scope}[shift={(\hx,\hy)},scale=\CatanS,xshift=-0.52cm,yshift=0.05cm]
    \fill[robbergray] (-0.13,-0.16) arc (180:360:0.13 and 0.05)
       -- (0.10,0.10) arc (0:180:0.10 and 0.12) -- cycle;
    \fill[robbergray] (0,0.26) circle (0.095);
  \end{scope}}

% ports
\def\portgeneric{generic}
\newcommand\port[4]{%
  \hexXY{#1}{#2}%
  \pgfmathsetmacro\nang{\csname sideNorm#3\endcsname}%
  \pgfmathsetmacro\bx{\hx+1.55*\CatanS*cos(\nang)}%
  \pgfmathsetmacro\by{\hy+1.55*\CatanS*sin(\nang)}%
  \vertexXY{#1}{#2}{\csname sideCa#3\endcsname}%
  \draw[tokenborder!70!black,line width=2.2pt,line cap=round,shorten <=3pt,shorten >=8pt]
    (\vx,\vy) -- (\bx,\by);
  \vertexXY{#1}{#2}{\csname sideCb#3\endcsname}%
  \draw[tokenborder!70!black,line width=2.2pt,line cap=round,shorten <=3pt,shorten >=8pt]
    (\vx,\vy) -- (\bx,\by);
  \def\porttype{#4}%
  \begin{scope}[shift={(\bx,\by)},scale=\CatanS]
    \ifx\porttype\portgeneric
      \fill[tokenfill] (0,0) circle (0.34);
      \draw[black!45,line width=1.5pt] (0,0) circle (0.34);
      \node[font=\bfseries\footnotesize,text=tokentext,scale=\CatanS] at (0,0.02) {3:1};
    \else
      \fill[tokenfill] (0,0) circle (0.34);
      \draw[res#4!70!black,line width=1.6pt] (0,0) circle (0.34);
      \node[font=\bfseries\footnotesize,text=tokentext,scale=\CatanS] at (0,0.06) {2:1};
      \fill[res#4] (-0.07,-0.17) rectangle (0.07,-0.06);
      \draw[res#4!55!black,line width=0.5pt] (-0.07,-0.17) rectangle (0.07,-0.06);
    \fi
  \end{scope}}
% desaturated port
\newcommand\portdim[4]{%
  \hexXY{#1}{#2}%
  \pgfmathsetmacro\nang{\csname sideNorm#3\endcsname}%
  \pgfmathsetmacro\bx{\hx+1.55*\CatanS*cos(\nang)}%
  \pgfmathsetmacro\by{\hy+1.55*\CatanS*sin(\nang)}%
  \vertexXY{#1}{#2}{\csname sideCa#3\endcsname}%
  \draw[dimborder,line width=2.2pt,line cap=round,shorten <=3pt,shorten >=8pt]
    (\vx,\vy) -- (\bx,\by);
  \vertexXY{#1}{#2}{\csname sideCb#3\endcsname}%
  \draw[dimborder,line width=2.2pt,line cap=round,shorten <=3pt,shorten >=8pt]
    (\vx,\vy) -- (\bx,\by);
  \def\porttype{#4}%
  \begin{scope}[shift={(\bx,\by)},scale=\CatanS]
    \fill[white!95!black] (0,0) circle (0.34);
    \draw[dimborder,line width=1.5pt] (0,0) circle (0.34);
    \ifx\porttype\portgeneric
      \node[font=\bfseries\footnotesize,text=dimtoken,scale=\CatanS] at (0,0.02) {3:1};
    \else
      \node[font=\bfseries\footnotesize,text=dimtoken,scale=\CatanS] at (0,0.02) {2:1};
    \fi
  \end{scope}}
% full color if the port resource was declared via \hlres, else dimmed
\newcommand\portauto[4]{%
  \ifcsname hlR@#4\endcsname \port{#1}{#2}{#3}{#4}%
  \else \portdim{#1}{#2}{#3}{#4}\fi}

% built-in example board; #1: callback taking {q}{r}{resource}{number}
\newcommand\standardboard[1]{%
  #1{0}{-2}{ore}{10}#1{1}{-2}{sheep}{2}#1{2}{-2}{wood}{9}%
  #1{-1}{-1}{wheat}{12}#1{0}{-1}{brick}{6}#1{1}{-1}{sheep}{4}#1{2}{-1}{brick}{10}%
  #1{-2}{0}{wheat}{9}#1{-1}{0}{wood}{11}#1{0}{0}{desert}{0}#1{1}{0}{wood}{3}#1{2}{0}{ore}{8}%
  #1{-2}{1}{wood}{8}#1{-1}{1}{ore}{3}#1{0}{1}{wheat}{4}#1{1}{1}{sheep}{5}%
  #1{-2}{2}{brick}{5}#1{-1}{2}{sheep}{11}#1{0}{2}{wheat}{6}}
\newcommand\standardports[1]{%
  #1{0}{-2}{NW}{generic}#1{1}{-2}{NE}{sheep}#1{2}{-1}{NE}{generic}%
  #1{2}{0}{E}{ore}#1{1}{1}{E}{wheat}#1{0}{2}{SE}{generic}%
  #1{-1}{2}{SW}{wood}#1{-2}{1}{W}{brick}#1{-1}{-1}{NW}{generic}}

% \catanrows{renderer}{r1; r2; r3; r4; r5}: five comma-separated rows,
% coordinates assigned top row first, left to right; desert number optional
\newcount\catan@q
\def\catan@split#1 #2\relax#3\relax{\def\catan@eres{#1}\def\catan@enum{#2}}
\def\catan@dorow#1#2{%
  \ifnum#2<0 \catan@q=\numexpr-2-(#2)\relax \else \catan@q=-2 \fi
  \foreach \catan@e in {#1}{%
    \edef\catan@etmp{\catan@e\space}%
    \expandafter\catan@split\catan@etmp\relax\relax
    \ifx\catan@enum\empty\def\catan@enum{0}\else
      \pgfmathtruncatemacro\catan@tmp{\catan@enum}%
      \edef\catan@enum{\catan@tmp}\fi
    \catan@cb{\the\catan@q}{#2}{\catan@eres}{\catan@enum}%
    \global\advance\catan@q 1 }}
\newcommand\catanrows[2]{%
  \def\catan@cb{#1}\catan@fiverows#2\catan@end}
\def\catan@fiverows#1;#2;#3;#4;#5\catan@end{%
  \catan@dorow{#1}{-2}\catan@dorow{#2}{-1}\catan@dorow{#3}{0}%
  \catan@dorow{#4}{1}\catan@dorow{#5}{2}}

% legend
\newcommand\catanlegend[1]{%
  \begin{scope}[shift={(0,#1)}]
    \foreach \res/\lbl [count=\i from 0] in
      {wood/Wood,brick/Brick,sheep/Sheep,wheat/Wheat,ore/Ore,desert/Desert}{
      \pgfmathsetmacro\lx{(\i-2.5)*1.85}%
      \draw[draw=res\res!55!black,line width=0.8pt,fill=res\res]
        (\lx-0.62,-0.14) rectangle (\lx-0.18,0.14);
      \node[font=\footnotesize,text=legendtext,anchor=west] at (\lx-0.12,0) {\lbl};}
  \end{scope}}

\endinput
