19

I'm working on a character sheet for the Fate RPG, which rates its skills with a numerical scale that corresponds to an adjective scale. I've been trying to write a macro that will take \skillAdj{4} and turn it into "Great" in a more elegant way than with these conditionals.

Ideally, I'd be able to do lookup in both directions, but I'm more concerned with going from number to adjective.

\usepackage{calc}
\usepackage{ifthen}
%
\newcounter{skillcounter}
\set{skillcounter}{0}
%
\newcommand[1]{\skillAdj}{\ifthenelse{\equal{#1}{8}
Legendary
}
{\ifthenelse{\equal{#1}{7}
Epic
}
{\ifthenelse{\equal{#1}{6}
Fantastic
}
{\ifthenelse{\equal{#1}{5}
Superb
}
{\ifthenelse{\equal{#1}{4}
Great
}
{\ifthenelse{\equal{#1}{3}
Legendary
}
{\ifthenelse{\equal{#1}{2}
Legendary
}
{\ifthenelse{\equal{#1}{1}
Legendary
}
{\ifthenelse{\equal{#1}{0}
Mediocre
}
{\ifthenelse{\equal{#1}{-1}
Poor
}
{\ifthenelse{\equal{#1}{-2}
Terrible
}
{\ifthenelse{\equal{#1}{-3}
Awful
}
{\ifthenelse{\equal{#1}{-4}
Abysmal
}
{Error}}}}}}}}}}}}}

This is the scale.

+8 Legendary
+7 Epic
+6 Fantastic
+5 Superb
+4 Great
+3 Good
+2 Fair
+1 Average
+0 Mediocre
-1 Poor
-2 Terrible
-3 Awful
-4 Abysmal

I'd really appreciate any helpful advice people have. I haven't found anything in the documentation of any of the packages that seem like they might provide this function.

Werner
  • 603,163
JakeO
  • 193

5 Answers5

18

There are several ways to accomplish your need. Here's one with expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\skillAdj}{m}
 {
  \int_case:nnn { #1 }
   {
     {8}{Legendary}
     {7}{Epic}
     {6}{Fantastic}
     {5}{Superb}
     {4}{Great}
     {3}{Good}
     {2}{Fair}
     {1}{Average}
     {0}{Mediocre}
    {-1}{Poor}
    {-2}{Terrible}
    {-3}{Awful}
    {-4}{Abysmal}
   }
   {Error}
 }
\ExplSyntaxOff

\begin{document}

X is \skillAdj{8}

Y is \skillAdj{5}

Z is \skillAdj{-4}

\texttt{egreg} is \skillAdj{10}

Come on! How can it be?

\end{document}

enter image description here

You can also say \skillAdj{\value{skillcounter}}, if you want and have defined the counter.


Notice that in your definition you have some glitches:

\set{skillcounter}{0}

should be

\setcounter{skillcounter}{0}

while

\newcommand[1]{\skillAdj}{\ifthenelse{\equal{#1}{8}...

should be

\newcommand{\skillAdj}[1]{\ifthenelse{\equal{#1}{8}...

A very similar solution using xstring:

\documentclass{article}

\usepackage{xstring}
\newcommand{\skillAdj}[1]{%
  \IfEqCase{#1}{%
     {8}{Legendary}%
     {7}{Epic}%
     {6}{Fantastic}%
     {5}{Superb}%
     {4}{Great}%
     {3}{Legendary}%
     {2}{Legendary}%
     {1}{Legendary}%
     {0}{Mediocre}%
    {-1}{Poor}%
    {-2}{Terrible}%
    {-3}{Awful}%
    {-4}{Abysmal}%
   }[Error]
 }

\newcounter{skillcounter}

\begin{document}

\setcounter{skillcounter}{8}

X is \skillAdj{\theskillcounter}

Y is \skillAdj{5}

Z is \skillAdj{-4}

\texttt{egreg} is \skillAdj{10}

Come on! How can it be?

\end{document}

Notice that here you have to use \theskillcounter as \value{skillcounter} wouldn't work.

egreg
  • 1,121,712
  • Thanks @egreg. Your solution looks like it will be easier to manage. – JakeO May 13 '13 at 17:04
  • @JakeO I added a version using xstring; but I'd prefer the first one that, however, requires a very up-to-date TeX distribution. – egreg May 13 '13 at 17:10
10

You could also use TeX's in-built \ifcase construct as follows- no packages necessary!

screenshot

% arara: pdflatex
\documentclass{article}

\newcommand{\skillAdj}[1]{%
    \ifcase\numexpr#1+4\relax
            Abysmal      % -4
            \or Awful    % -3
            \or Terrible % -2
            \or Poor     % -1
            \or Mediocre  % 0
            \or Average   % 1
            \or Fair      % 2
            \or Good      % 3
            \or Great     % 4
            \or Superb    % 5
            \or Fantastic % 6
            \or Epic      % 7
            \or Legendary % 8
    \else 
       Error
    \fi
}

\newcounter{skillcounter}

\begin{document}


\setcounter{skillcounter}{-5}

\loop
\theskillcounter: \skillAdj{\theskillcounter}
\stepcounter{skillcounter}\par
\ifnum\value{skillcounter}<10 \repeat

\end{document}

If your smallest value (-4 in the above) changes, just update the line

\ifcase\numexpr#1+4\relax

accordingly.

Heiko Oberdiek
  • 271,626
cmhughes
  • 100,947
  • Saver is \ifcase\numexpr(#1)+4\relax. The final \relax is removed automatically by \numexpr and prevents that \numexpr looks for a continuation of the expression in the next tokens. – Heiko Oberdiek May 14 '13 at 15:55
10

enter image description here

One more TeX \ifcase version, without extra ifnum:

\def\skillAdj#1{%
\count255=8\advance\count255by-#1 %
\ifcase\the\count255 %
Legendary%
\or Epic%
\or Fantastic%
\or Superb%
\or Great%
\or Good%
\or Fair%
\or Average%
\or Mediocre%
\or Poor%
\or Terrible%
\or Awful%
\or Abysmal%
\else Error%
\fi%
}

\def\test#1{#1 \skillAdj{#1}}
\obeylines\tt
\test{+8}
\test{+7}
\test{+6}
\test{+5}
\test{+4}
\test{+3}
\test{+2}
\test{+1}
\test{+0}
\test{-1}
\test{-2}
\test{-3}
\test{-4}
\test{-7}
\test{+9}

\newcount\cnt\cnt5

\test{\the\cnt}

\bye
g.kov
  • 21,864
  • 1
  • 58
  • 95
6

Other answers have exploited the numeric character of one of the member of the correspondance. Here is a method which establishes a bidirectional correspondance. It does use the numeric character of the levels to allow some flexibility in the input, like using a count register, or having arithmetic operations on them (uses the e-TeX \numexpr)

The code uses LaTeX and some of its internal macros (with @'s), as I guess the majority of people here do not use Plain TeX.

\documentclass{article}


\makeatletter
% because we will be using \@namedef, \@nameuse, \@ifundefined from the LaTeX
% kernel. 

% but our own private macros are defined *without* @ signs:
%    1. having @ signs everywhere gives headaches
%    2. *not* using @ signs is the safer road to avoid conflicts with packages,
%    as they traditionally do use @ signs in their private macro names. 

\def\gobtilstop #1\stop {}
\def\gobtilz    #1\z {}

\def\SetUpTwoWays #1{\gobtilstop #1\gobtilz\stop\setuptwoways #1\z}

\def\setuptwoways #1,#2\z 
{%
    \@namedef {SkillToAdj\the\numexpr#1}{#2}%
    \@namedef {AdjToSkill#2}{#1}%
    \SetUpTwoWays
}

% we used \the\numexpr to allow the argument of \SkillToAdj to be a count
% register, or something like 2+3 for example.

\def\SkillToAdj #1%
{%
    \@ifundefined{SkillToAdj\the\numexpr#1}
                 {error}
                 {\@nameuse{SkillToAdj\the\numexpr#1}}%
}

\def\AdjToSkill #1%
{%
    \@ifundefined{AdjToSkill#1}
                 {1000000000} % needs to be a number if the output should be
                              % used as input for \SkillToAdj
                 {\@nameuse{AdjToSkill#1}}%
}
\makeatother

% Removing the \the\numexpr above would allow bidirectional correspondances
% between any kind of data.

% Here, the first entry is numeric, but the list given here could be in an
% arbitrary order, doesn't have to be decreasing or increasing, and there could
% be gaps too.

\SetUpTwoWays
    {+8,Legendary}{+7,Epic}{+6,Fantastic}{+5,Superb}
    {+4,Great}{+3,Good}{+2,Fair}{+1,Average}{+0,Mediocre}
    {-1,Poor}{-2,Terrible}{-3,Awful}{-4,Abysmal}\stop

\def\AdjSucc #1{\SkillToAdj{\AdjToSkill{#1}+1}}

\begin{document}

\tt 
\newcount\cnta
\cnta -5
\loop
\the\cnta{} 
   $\longrightarrow$ \SkillToAdj{\cnta}
   $\longrightarrow$ \AdjToSkill{\SkillToAdj{\cnta}}
   $\longrightarrow$ \SkillToAdj{\AdjToSkill{\SkillToAdj{\cnta}}}\endgraf
\ifnum\cnta<10 \advance\cnta 1 \repeat


Terrible (badly input): \AdjToSkill{ Terrible}

Level $3*2$ is \SkillToAdj{3*2}

Level $5-7$ is \SkillToAdj{5-7}

Terrible is worse than \AdjSucc{Terrible} which is worse than \AdjSucc{\AdjSucc{Terrible}}


\end{document}

output

  • Thank you very much for this, the bidirectional hookup is useful. So far all of the answers people have given have been beyond my understanding--I've only used LaTeX in the past to format papers and such. When I have more time I'll try to truly understand how the code works, but until then these solutions are all very helpful. – JakeO May 14 '13 at 17:28
  • Glad it helps! the idea is to automatize the creation of macros such as \SkillToAdj2 which expands to Fair. But to get 2 inside the macro name one uses the \csname primitive, here hidden in LaTeX \@namedef. Conversely \AdjToSkillFair is defined to expand to +2. It could have been done simply by \def\AdjToSkillFair {+2}. But I used also \@namedef as there were various other strings to use, besides Fair. As the +2 may be input in various ways, like 2 or 1+1, it was filtered through \numexpr. Text strings should also be filtered for more robust code allowing spaces. –  May 14 '13 at 17:57
5

The following is a mild modification to PGF/TikZ: How to store strings in array? The adjustment accommodates for negative values (excluding a count of the elements since it may not be required):

enter image description here

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox
\newcounter{listtotal}\newcounter{listcntr}%
\newcommand{\skilladj}[1]{% \skilladj{<number>}
  \setcounter{listcntr}{-5}% Start from -4
  \renewcommand*{\do}[1]{\stepcounter{listcntr}\ifnum\value{listcntr}=#1\relax##1\fi}%
  \expandafter\docsvlist\expandafter{\skilladjarray}% Process list again
}
% Skill Adjustment array
\newcommand{\skilladjarray}{Abysmal,Awful,Terrible,Poor,Mediocre,Average,Fair,Good,Great,Superb,Fantastic,Epic,Legendary}%
\begin{document}
\verb|\skilladj{-2}:|\ \skilladj{-2} \par
\verb|\skilladj{2}:|\ \skilladj{2} \par
\verb|\skilladj{0}:|\ \skilladj{0} \par
\verb|\skilladj{5}:|\ \skilladj{5} \par
\verb|\skilladj{-5}:|\ \skilladj{-5} \par
\verb|\skilladj{9}:|\ \skilladj{9} \par
\verb|\skilladj{8}:|\ \skilladj{8}
\end{document}
Werner
  • 603,163