\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