/* * Copyright (C) 2001-2002 Gary Wong * Copyright (C) 2002-2021 the AUTHORS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * $Id: latex.c,v 1.61 2022/02/13 22:30:16 plm Exp $ */ #include "config.h" #include #include #include "analysis.h" #include "backgammon.h" #include "drawboard.h" #include "export.h" #include "format.h" #include static const char *aszLuckTypeLaTeXAbbr[] = { "$--$", "$-$", "", "$+$", "$++$" }; static void Points1_12(FILE * pf, int y) { int i; if (fClockwise) for (i = 1; i <= 12; i++) fprintf(pf, "\\put(%d,%d){\\makebox(20,10){\\textsl{\\tiny %d}}}\n", 50 + i * 20 + (i > 6) * 20, y, i); else for (i = 1; i <= 12; i++) fprintf(pf, "\\put(%d,%d){\\makebox(20,10){\\textsl{\\tiny %d}}}\n", 330 - i * 20 - (i > 6) * 20, y, i); } static void Points13_24(FILE * pf, int y) { int i; if (fClockwise) for (i = 13; i <= 24; i++) fprintf(pf, "\\put(%d,%d){\\makebox(20,10){\\textsl{\\tiny %d}}}\n", 570 - i * 20 - (i > 18) * 20, y, i); else for (i = 13; i <= 24; i++) fprintf(pf, "\\put(%d,%d){\\makebox(20,10){\\textsl{\\tiny %d}}}\n", i * 20 - 190 + (i > 18) * 20, y, i); } static void LaTeXPrologue(FILE * pf) { fputs("\\documentclass{article}\n" "\\usepackage{epic,eepic,textcomp,ucs}\n" "\\usepackage[utf8x]{inputenc}\n" "\\newcommand{\\board}{\n" "\\shade\\path(70,20)(80,120)(90,20)(110,20)(120,120)(130,20)" "(150,20)(160,120)\n" "(170,20)(70,20)\n" "\\path(70,20)(80,120)(90,20)(100,120)(110,20)(120,120)(130,20)" "(140,120)(150,20)\n" "(160,120)(170,20)(180,120)(190,20)\n" "\\shade\\path(90,240)(100,140)(110,240)(130,240)(140,140)(150,240)" "(170,240)\n" "(180,140)(190,240)(90,240)\n" "\\path(70,240)(80,140)(90,240)(100,140)(110,240)(120,140)(130,240)" "(140,140)\n" "(150,240)(160,140)(170,240)(180,140)(190,240)\n" "\\shade\\path(210,20)(220,120)(230,20)(250,20)(260,120)(270,20)" "(290,20)(300,120)\n" "(310,20)(210,20)\n" "\\path(210,20)(220,120)(230,20)(240,120)(250,20)(260,120)(270,20)" "(280,120)\n" "(290,20)(300,120)(310,20)(320,120)(330,20)\n" "\\shade\\path(230,240)(240,140)(250,240)(270,240)(280,140)" "(290,240)(310,240)\n" "(320,140)(330,240)(230,240)\n" "\\path(210,240)(220,140)(230,240)(240,140)(250,240)(260,140)" "(270,240)(280,140)\n" "(290,240)(300,140)(310,240)(320,140)(330,240)\n" "\\path(60,10)(340,10)(340,250)(60,250)(60,10)\n" "\\path(70,20)(190,20)(190,240)(70,240)(70,20)\n" "\\path(210,20)(330,20)(330,240)(210,240)(210,20)}\n\n", pf); fputs("\\newcommand{\\blackboard}{\n" "\\board\n", pf); Points1_12(pf, 10); Points13_24(pf, 240); fputs("}\n\n", pf); fputs("\\newcommand{\\whiteboard}{\n" "\\board\n", pf); Points1_12(pf, 240); Points13_24(pf, 10); fputs("}\n\n" "\\addtolength\\textwidth{144pt}\n" "\\addtolength\\textheight{144pt}\n" "\\addtolength\\oddsidemargin{-72pt}\n" "\\addtolength\\evensidemargin{-72pt}\n" "\\addtolength\\topmargin{-72pt}\n\n" "\\setlength{\\unitlength}{0.20mm}\n\n" /* user should set size */ "\\begin{document}\n", pf); /* FIXME define commands for \cubetext, \labeltext, etc. here, which * are scaled to a size specified by the user. The other functions * should then use those definitions. */ } static void LaTeXEpilogue(FILE * pf) { fputs("\\end{document}\n", pf); } static void DrawLaTeXPoint(FILE * pf, int i, int fPlayer, unsigned int c) { unsigned int j; int x, y = 0; if (i < 6) x = 320 - 20 * i; else if (i < 12) x = 300 - 20 * i; else if (i < 18) x = 20 * i - 160; else if (i < 24) x = 20 * i - 140; else if (i == 24) /* bar */ x = 200; else /* off */ x = 365; if (fClockwise) x = 400 - x; for (j = 0; j < c; j++) { if (j == 5 || (i == 24 && j == 3)) { fprintf(pf, "\\whiten\\path(%d,%d)(%d,%d)(%d,%d)(%d,%d)(%d,%d)\n" "\\path(%d,%d)(%d,%d)(%d,%d)(%d,%d)(%d,%d)\n" "\\put(%d,%d){\\makebox(10,10){\\textsf{\\tiny %u}}}\n", x - 5, y - 5, x + 5, y - 5, x + 5, y + 5, x - 5, y + 5, x - 5, y - 5, x - 5, y - 5, x + 5, y - 5, x + 5, y + 5, x - 5, y + 5, x - 5, y - 5, x - 5, y - 5, c); break; } if (i == 24) /* bar */ y = fPlayer ? 60 + 20 * j : 200 - 20 * j; else if (fPlayer) y = i < 12 || i == 25 ? 30 + 20 * j : 230 - 20 * j; else y = i >= 12 && i != 25 ? 30 + 20 * j : 230 - 20 * j; fprintf(pf, fPlayer ? "\\put(%d,%d){\\circle{10}}" "\\put(%d,%d){\\blacken\\circle{20}}\n" : "\\put(%d,%d){\\whiten\\circle{20}}" "\\put(%d,%d){\\circle{20}}\n", x, y, x, y); } } static void PrintLaTeXBoard(FILE * pf, matchstate * pms, int fPlayer) { unsigned int anOff[2] = { 15, 15 }; int i, x, y; /* FIXME print position ID and pip count, and the player on roll. * Print player names too? */ fprintf(pf, "\\bigskip\\pagebreak[1]\\begin{center}\\begin{picture}" "(356,240)(22,10)\n" "\\%sboard\n", fPlayer ? "black" : "white"); for (i = 0; i < 25; i++) { anOff[0] -= pms->anBoard[0][i]; anOff[1] -= pms->anBoard[1][i]; DrawLaTeXPoint(pf, i, 0, pms->anBoard[!fPlayer][i]); DrawLaTeXPoint(pf, i, 1, pms->anBoard[fPlayer][i]); } DrawLaTeXPoint(pf, 25, 0, anOff[!fPlayer]); DrawLaTeXPoint(pf, 25, 1, anOff[fPlayer]); if (fClockwise) x = 365; else x = 35; if (pms->fCubeOwner < 0) y = 130; else if (pms->fCubeOwner) y = 30; else y = 230; fprintf(pf, "\\path(%d,%d)(%d,%d)(%d,%d)(%d,%d)(%d,%d)" "\\put(%d,%d){\\makebox(24,24){\\textsf{\\large %d}}}\n", x - 12, y - 12, x + 12, y - 12, x + 12, y + 12, x - 12, y + 12, x - 12, y - 12, x - 12, y - 12, pms->nCube == 1 ? 64 : pms->nCube); fputs("\\end{picture}\\end{center}\\vspace{-4mm}\n\n\\nopagebreak[4]\n", pf); } /* * This was removed at first when unicode encoding was used, but we * still need to escape what could cause problems (like % $ _ * etc...). Just to be safe, we escape everything but simple accented * characters. */ static void LaTeXEscape(FILE * pf, char *pch) { /* Translation table from GNU recode, by Francois Pinard. */ static struct translation { const unsigned char uch; const char *sz; } at[] = { { 10, "\n\n"}, /* \n */ { 35, "\\#"}, /* # */ { 36, "\\$"}, /* $ */ { 37, "\\%"}, /* % */ { 38, "\\&"}, /* & */ { 60, "\\mbox{$<$}"}, /* < */ { 62, "\\mbox{$>$}"}, /* > */ { 92, "\\mbox{$\\backslash{}$}"}, /* \ */ { 94, "\\^{}"}, /* ^ */ { 95, "\\_"}, /* _ */ { 123, "\\{"}, /* { */ { 124, "\\mbox{$|$}"}, /* | */ { 125, "\\}"}, /* } */ { 126, "\\~{}"}, /* ~ */ { 160, "~"}, /* no-break space */ { 161, "!`"}, /* inverted exclamation mark */ { 163, "\\pound{}"}, /* pound sign */ { 167, "\\S{}"}, /* paragraph sign, section sign */ { 168, "\\\"{}"}, /* diaeresis */ { 169, "\\copyright{}"}, /* copyright sign */ { 171, "``"}, /* left angle quotation mark */ { 172, "\\neg{}"}, /* not sign */ { 173, "\\-"}, /* soft hyphen */ { 176, "\\mbox{$^\\circ$}"}, /* degree sign */ { 177, "\\mbox{$\\pm$}"}, /* plus-minus sign */ { 178, "\\mbox{$^2$}"}, /* superscript two */ { 179, "\\mbox{$^3$}"}, /* superscript three */ { 180, "\\'{}"}, /* acute accent */ { 181, "\\mbox{$\\mu$}"}, /* small greek mu, micro sign */ { 183, "\\cdotp"}, /* middle dot */ { 184, "\\,{}"}, /* cedilla */ { 185, "\\mbox{$^1$}"}, /* superscript one */ { 187, "''"}, /* right angle quotation mark */ { 188, "\\frac1/4{}"}, /* vulgar fraction one quarter */ { 189, "\\frac1/2{}"}, /* vulgar fraction one half */ { 190, "\\frac3/4{}"}, /* vulgar fraction three quarters */ { 191, "?`"}, /* inverted question mark */ { 0, NULL} }; int i; if (!pch) return; while (*pch) { for (i = 0; at[i].uch; i++) if (at[i].uch > *pch) { /* no translation required */ putc(*pch, pf); break; } else if (at[i].uch == *pch) { /* translation found */ fputs(at[i].sz, pf); break; } pch++; } } static void PrintLaTeXComment(FILE * pf, char *pch) { LaTeXEscape(pf, pch); if (pch != NULL) fputs(pch, pf); fputs("\n\n", pf); } static void PrintLaTeXCubeAnalysis(FILE * pf, const matchstate * pms, int fPlayer, float aarOutput[2][NUM_ROLLOUT_OUTPUTS], float aarStdDev[2][NUM_ROLLOUT_OUTPUTS], const evalsetup * pes, int fTake) { cubeinfo ci; if (pes->et == EVAL_NONE) return; SetCubeInfo(&ci, pms->nCube, pms->fCubeOwner, fPlayer, pms->nMatchTo, pms->anScore, pms->fCrawford, pms->fJacoby, nBeavers, pms->bgv); /* FIXME use center and tabular environment instead of verbatim */ fputs("{\\begin{quote}\\footnotesize\\begin{verbatim}\n", pf); fputs(OutputCubeAnalysis(aarOutput, aarStdDev, pes, &ci, fTake), pf); fputs("\\end{verbatim}\\end{quote}}\n", pf); } static const char * PlayerSymbol(int fPlayer) { return fPlayer ? "\\textbullet{}" : "\\textopenbullet{}"; } static void ExportGameLaTeX(FILE * pf, listOLD * plGame) { listOLD *pl; moverecord *pmr; matchstate msExport; int fTook = FALSE; unsigned int i; char sz[1024]; doubletype dt = DT_NORMAL; listOLD *pl_hint = NULL; updateStatisticsGame(plGame); if (game_is_last(plGame)) pl_hint = game_add_pmr_hint(plGame); for (pl = plGame->plNext; pl != plGame; pl = pl->plNext) { pmr = pl->p; FixMatchState(&msExport, pmr); switch (pmr->mt) { case MOVE_GAMEINFO: dt = DT_NORMAL; fputs("\\clearpage\n", pf); fputs("\\noindent{\\Large ", pf); if (pmr->g.nMatch) { fprintf(pf, ngettext("%d point match", "%d points match", pmr->g.nMatch), pmr->g.nMatch); fprintf(pf, _(" (game %d)"), pmr->g.i + 1); } else fprintf(pf, _("Money session (game %d)"), pmr->g.i + 1); fputs("}\n\n\\vspace{\\baselineskip}\n\n", pf); fprintf(pf, "\\noindent\n\\makebox[0.5\\textwidth][s]" "{\\large %s ", PlayerSymbol(0)); LaTeXEscape(pf, ap[0].szName); fprintf(pf, " ("); fprintf(pf, ngettext("%d point", "%d points", pmr->g.anScore[0]), pmr->g.anScore[0]); fprintf(pf, ")\\hfill}"); fprintf(pf, "\\makebox[0.5\\textwidth][s]" "{\\large %s ", PlayerSymbol(1)); LaTeXEscape(pf, ap[1].szName); fprintf(pf, " ("); fprintf(pf, ngettext("%d point", "%d points", pmr->g.anScore[1]), pmr->g.anScore[1]); fprintf(pf, ")\\hfill}\n\n"); break; case MOVE_NORMAL: dt = DT_NORMAL; msExport.fTurn = msExport.fMove = pmr->fPlayer; if (fTook) /* no need to print board following a double/take */ fTook = FALSE; else PrintLaTeXBoard(pf, &msExport, pmr->fPlayer); PrintLaTeXCubeAnalysis(pf, &msExport, pmr->fPlayer, pmr->CubeDecPtr->aarOutput, pmr->CubeDecPtr->aarStdDev, &pmr->CubeDecPtr->esDouble, -1); /* FIXME: output cube skill */ sprintf(sz, "%s %u%u%s: ", PlayerSymbol(pmr->fPlayer), pmr->anDice[0], pmr->anDice[1], aszLuckTypeLaTeXAbbr[pmr->lt]); FormatMove(strchr(sz, 0), (ConstTanBoard) msExport.anBoard, pmr->n.anMove); fprintf(pf, "\\begin{center}%s%s\\end{center}\n\n", sz, aszSkillTypeAbbr[pmr->n.stMove]); /* FIXME use center and tabular environment instead of verbatim */ fputs("{\\footnotesize\\begin{verbatim}\n", pf); for (i = 0; i < pmr->ml.cMoves; i++) { if (i >= exsExport.nMoves && i != pmr->n.iMove) continue; putc(i == pmr->n.iMove ? '*' : ' ', pf); FormatMoveHint(sz, &msExport, &pmr->ml, i, i != pmr->n.iMove || i != pmr->ml.cMoves - 1 || i < exsExport.nMoves, TRUE, TRUE); fputs(sz, pf); } fputs("\\end{verbatim}}", pf); PrintLaTeXComment(pf, pmr->sz); break; case MOVE_DOUBLE: dt = DoubleType(msExport.fDoubled, msExport.fMove, msExport.fTurn); if (dt != DT_NORMAL) { fprintf(pf, "\\begin{center}\\emph{%s}\\end{center}", _("Cannot analyse beaver nor raccoons!")); break; } PrintLaTeXBoard(pf, &msExport, pmr->fPlayer); PrintLaTeXCubeAnalysis(pf, &msExport, pmr->fPlayer, pmr->CubeDecPtr->aarOutput, pmr->CubeDecPtr->aarStdDev, &pmr->CubeDecPtr->esDouble, -1); /* FIXME what about beavers? */ fprintf(pf, "\\begin{center}%s %s%s\\end{center}\n\n", PlayerSymbol(pmr->fPlayer), _("Double"), aszSkillTypeAbbr[pmr->stCube]); PrintLaTeXComment(pf, pmr->sz); break; case MOVE_TAKE: if (dt != DT_NORMAL) { dt = DT_NORMAL; fprintf(pf, "\\begin{center}\\emph{%s}\\end{center}", _("Cannot analyse beaver nor raccoons!")); break; } fTook = TRUE; fprintf(pf, "\\begin{center}%s %s%s\\end{center}\n\n", PlayerSymbol(pmr->fPlayer), _("Take"), aszSkillTypeAbbr[pmr->stCube]); PrintLaTeXComment(pf, pmr->sz); break; case MOVE_DROP: if (dt != DT_NORMAL) { dt = DT_NORMAL; fprintf(pf, "\\begin{center}\\emph{%s}\\end{center}", _("Cannot analyse beaver nor raccoons!")); break; } fprintf(pf, "\\begin{center}%s %s%s\\end{center}\n\n", PlayerSymbol(pmr->fPlayer), _("Drop"), aszSkillTypeAbbr[pmr->stCube]); PrintLaTeXComment(pf, pmr->sz); break; case MOVE_RESIGN: fprintf(pf, "\\begin{center}%s %s %s\\end{center}\n\n", PlayerSymbol(pmr->fPlayer), _("Resigns"), gettext(aszGameResult[pmr->r.nResigned - 1])); /* FIXME print resignation analysis, if available */ PrintLaTeXComment(pf, pmr->sz); break; case MOVE_SETDICE: /* ignore */ break; case MOVE_SETBOARD: case MOVE_SETCUBEVAL: case MOVE_SETCUBEPOS: /* FIXME print something? */ break; } ApplyMoveRecord(&msExport, plGame, pmr); } if (pl_hint) game_remove_pmr_hint(pl_hint); /* FIXME print game result */ } extern void CommandExportGameLaTeX(char *sz) { FILE *pf; sz = NextToken(&sz); if (!plGame) { outputl(_("No game in progress (type `new game' to start one).")); return; } if (!confirmOverwrite(sz, fConfirmSave)) return; if (!sz || !*sz) { outputl(_("You must specify a file to export to (see `help export " "game latex').")); return; } if (!strcmp(sz, "-")) pf = stdout; else if ((pf = g_fopen(sz, "w")) == 0) { outputerr(sz); return; } LaTeXPrologue(pf); ExportGameLaTeX(pf, plGame); LaTeXEpilogue(pf); if (pf != stdout) fclose(pf); setDefaultFileName(sz); } extern void CommandExportMatchLaTeX(char *sz) { FILE *pf; listOLD *pl; int fDontClose = FALSE; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a file to export to (see `help export " "match latex').")); return; } if (!confirmOverwrite(sz, fConfirmSave)) return; if (!strcmp(sz, "-")) { pf = stdout; fDontClose = TRUE; } else if ((pf = g_fopen(sz, "w")) == 0) { outputerr(sz); return; } LaTeXPrologue(pf); /* FIXME write match introduction? */ for (pl = lMatch.plNext; pl != &lMatch; pl = pl->plNext) ExportGameLaTeX(pf, pl->p); LaTeXEpilogue(pf); if (!fDontClose) fclose(pf); setDefaultFileName(sz); }