texlive[45832] Master/texmf-dist: exam (16nov17)

commits+karl at tug.org commits+karl at tug.org
Thu Nov 16 23:14:35 CET 2017


Revision: 45832
          http://tug.org/svn/texlive?view=revision&revision=45832
Author:   karl
Date:     2017-11-16 23:14:35 +0100 (Thu, 16 Nov 2017)
Log Message:
-----------
exam (16nov17)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/exam/README
    trunk/Master/texmf-dist/doc/latex/exam/exam.md5
    trunk/Master/texmf-dist/doc/latex/exam/examdoc.pdf
    trunk/Master/texmf-dist/doc/latex/exam/examdoc.tex
    trunk/Master/texmf-dist/tex/latex/exam/exam.cls

Modified: trunk/Master/texmf-dist/doc/latex/exam/README
===================================================================
--- trunk/Master/texmf-dist/doc/latex/exam/README	2017-11-16 22:14:19 UTC (rev 45831)
+++ trunk/Master/texmf-dist/doc/latex/exam/README	2017-11-16 22:14:35 UTC (rev 45832)
@@ -1,4 +1,5 @@
-This is version 2.5 of the exam document class, dated May 7, 2015.
+This is version 2.6 of the exam document class, dated October 26,
+2017.
 
 The exam document class, together with its user's guide examdoc.tex,
 attempts to make it easy for even a LaTeX novice to prepare exams.
@@ -11,7 +12,8 @@
 each question and the total possible points for that question) or by
 page number (listing each page with points and the total possible
 points for that page).  A grading table can cover the entire exam or
-just a part of the exam.
+just a part of the exam.  Grading tables can have multiple rows or
+multiple columns.
 
 3. Create headers and footers that are each specified in three parts:
 One part to be left justified, one part to be centered, and one part
@@ -38,9 +40,8 @@
 e.g., point values, or table headings), so that they can be adapted to
 personal taste and/or languages other than English.
 
-
 The user's guide examdoc.tex attempts to explain all of the
-possibilities in a readable way, with many specific examples.
+possibilities in a readable way, with many examples.
 
 --------------------------------------------------------------------
 

Modified: trunk/Master/texmf-dist/doc/latex/exam/exam.md5
===================================================================
--- trunk/Master/texmf-dist/doc/latex/exam/exam.md5	2017-11-16 22:14:19 UTC (rev 45831)
+++ trunk/Master/texmf-dist/doc/latex/exam/exam.md5	2017-11-16 22:14:35 UTC (rev 45832)
@@ -1,3 +1,3 @@
-3d7b2787a32cf31f94fce30dbebe6a6d  exam.cls
-0b79dabd1e74952a1f70abe1909330c2  examdoc.tex
-6c10f29d36dd8e009e854721b391fb50  examdoc.pdf
+01d4eea329c471b4295296dff587c23a  exam.cls
+831e1841882d3e05abdae653595862d6  examdoc.pdf
+a9a481f4f36ec3dbf90ffe63f57ce803  examdoc.tex

Modified: trunk/Master/texmf-dist/doc/latex/exam/examdoc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/exam/examdoc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/exam/examdoc.tex	2017-11-16 22:14:19 UTC (rev 45831)
+++ trunk/Master/texmf-dist/doc/latex/exam/examdoc.tex	2017-11-16 22:14:35 UTC (rev 45832)
@@ -8,7 +8,8 @@
 % The exam documentclass itself is in the file exam.cls.
 
 
-%%% Copyright (c) 1997, 2000, 2004, 2008, 2011, 2015 Philip S. Hirschhorn
+%%% Copyright (c) 1997, 2000, 2004, 2008, 2011, 2015, 2017
+% Philip S. Hirschhorn
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3
@@ -49,9 +50,9 @@
 % without generating pdf warnings:
 \newcommand{\bs}{\texorpdfstring{\char`\\}{}}
 
-\newcommand{\docversion}{2.5}
-\newcommand{\docdate}{May 7, 2015}
-%\newcommand{\docdate}{\today}
+\newcommand{\docversion}{2.6}
+\newcommand{\docdate}{November 5, 2017}
+%\newcommand{\docdate}{Draft: \today}
 
 %--------------------------------------------------------------------
 %
@@ -128,8 +129,8 @@
   Wellesley College\\
   Wellesley, MA 02481\\
   psh at math.mit.edu\\[\bigskipamount]
-  Copyright \copyright~1994, 1997, 2000, 2004, 2008, 2011, 2015 Philip
-  Hirschhorn\\
+  Copyright \copyright~1994, 1997, 2000, 2004, 2008, 2011, 2015, 2017\\
+  Philip Hirschhorn\\
   All rights reserved}
 
 \date{\docdate}
@@ -509,8 +510,8 @@
 space allocated by \verb"\vspace{\stretch{1}}".)
 
 In addition to leaving blank space, it's also possible to leave lined
-space, dotted lined space, or an empty box.  For the full story, see
-section~\ref{sec:LeaveSpace}.
+space, dotted lined space, space printed with a grid, or an empty box.
+For the full story, see section~\ref{sec:LeaveSpace}.
 
 %--------------------------------------------------------------------
 \subsection{Headers and footers}
@@ -687,19 +688,24 @@
 of the exam (see section~\ref{sec:solutions}).  The effect of this is
 that
 \begin{itemize}
-\item the contents of the
-  environments\indt{solution}\indt{solutionorbox}%
-  \indt{solutionorlines}\indt{solutionordottedlines}
+\item the contents of the environments
+  %
+  \index{solution environment@\texttt{solution} environment}%
+  %
+  \indt{solutionorbox}%
+  \indt{solutionorlines}\indt{solutionordottedlines}%
+  \indt{solutionorgrid}\indt{solutionbox}
   \begin{center}
     \begin{tabular}{l}
       \verb"solution",\\
       \verb"solutionorbox",\\
-      \verb"solutionorlines", and\\
-      \verb"solutionordottedlines"
+      \verb"solutionorlines",\\
+      \verb"solutionordottedlines",\\
+      \verb"solutionorgrid", and\\
+      \verb"solutionbox"
     \end{tabular}
   \end{center}
-  (see section~\ref{sec:solutions}) will be printed on the next run of
-  \LaTeX,
+  (see section~\ref{sec:solutions}) will be printed,
 \item any choices of a \texttt{choices}\indt{choices},
   \texttt{oneparchoices}\indt{oneparchoices},
   \texttt{checkboxes}\indt{checkboxes}, or
@@ -1072,11 +1078,27 @@
 of the question (or part, or subpart, or subsubpart) in parentheses,
 but
 \begin{itemize}
-\item the command \verb"\pointsinmargin"\indc{pointsinmargin} will
-  cause the point values to be set in the left margin,
+\item the command \verb"\pointsinmargin"\indc{pointsinmargin} (or,
+  equivalently, the command
+  \verb"\pointsinleftmargin"\indc{pointsinleftmargin}) will cause the
+  point values to be set in the left margin,
 \item the command
   \verb"\pointsinrightmargin"\indc{pointsinrightmargin} will cause the
-  point values to be set in the right margin, and
+  point values to be set in the right margin,
+\item the command
+  \verb"\pointstwosided"\indc{pointstwosided} will cause the
+  point values to be set
+  \begin{itemize}
+  \item in the right margin on odd numbered pages and
+  \item in the left margin on even numbered pages,
+  \end{itemize}
+\item the command
+  \verb"\pointstwosidedreversed"\indc{pointstwosidedreversed} will
+  cause the point values to be set
+  \begin{itemize}
+  \item in the left margin on odd numbered pages and
+  \item in the right margin on even numbered pages, and
+  \end{itemize}
 \item the commands \verb"\nopointsinmargin"\indc{nopointsinmargin} and
   \verb"\nopointsinrightmargin"\indc{nopointsinrightmargin} are
   equivalent, and either of them will revert to the default situation.
@@ -1131,8 +1153,9 @@
 section~\ref{sec:pointname}.)
 
 \bigskip If you give the command
-\verb"\pointsinmargin"\indc{pointsinmargin}, then the above input will
-produce instead \pointsinmargin
+\verb"\pointsinmargin"\indc{pointsinmargin} (or, equivalently,
+\verb"\pointsinleftmargin"\indc{pointsinleftmargin}), then the above input
+will produce instead \pointsinmargin
 \begin{questions}
 \question[20]
 Why is there air?
@@ -1489,8 +1512,9 @@
 
 If you want some text (e.g., `` points'', or ``\%'') automatically
 inserted along with the point values when using either
-\verb"\pointsinmargin" or \verb"\pointsinrightmargin", see the command
-\verb"\marginpointname" in section~\ref{sec:pointname}.
+\verb"\pointsinmargin", \verb"\pointsinrightmargin",
+\verb"\pointstwosided", or \verb"\pointstwosidedreversed", see the
+command \verb"\marginpointname" in section~\ref{sec:pointname}.
 
 
 
@@ -3611,7 +3635,8 @@
 \index{customization!multiple choice questions|)}
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
-\section{Escaping the indentation: \texttt{\bs uplevel} and \texttt{\bs fullwidth}}
+\section{Escaping the indentation: \texttt{\bs uplevel} and
+  \texttt{\bs fullwidth}}
 \label{sec:uplevel}
 \index{indentation|(}
 
@@ -3708,9 +3733,9 @@
 \end{verbatim}
 by
 \begin{verbatim}
-    \begin{uplevel}
+    \begin{EnvUplevel}
       The following two parts should be answered in classical Greek:
-    \end{uplevel}
+    \end{EnvUplevel}
 \end{verbatim}
 
 If you want to give instructions for a group of questions, then the
@@ -4081,7 +4106,10 @@
 that of the current line.  That is, the left edge is at the current
 left margin (which depends on whether we're in a question, part,
 subpart, or subsubpart) and the right edge is at the right margin.
+(The box printed by the \verb"\makeemptybox" command can be printed in
+color; for that, see section~\ref{sec:colorboxes}.)
 
+
 \medskip
 
 For example, if you type
@@ -4157,6 +4185,7 @@
 by including an optional argument with the \verb"solutionoremptybox"
 environment; see section~\ref{sec:SolSpace}.
 
+
 \indcstop{makeemptybox}
 \index{empty box|)}
 \index{space!empty box|)}
@@ -4178,6 +4207,24 @@
 from the current left margin (which depends on whether we're in a
 question, part, subpart, or subsubpart) to the right margin.
 
+By default, the lines are in black.  However, if you give the
+commands\indc{colorfillwithlines}\index{color package}
+\begin{center}
+  \begin{tabular}{l}
+    \verb"\usepackage{color}"\\
+    \verb"\colorfillwithlines"
+  \end{tabular}
+\end{center}
+then the lines will be in color, by default a light gray.  That
+default color was defined by the
+command\indc{definecolor}\indt{FillWithLinesColor}
+\begin{center}
+  \verb"\definecolor{FillWithLinesColor}{gray}{0.8}"
+\end{center}
+\index{customization!\texttt{\char`\\fillwithlines}} You can change
+the color by giving a new \verb"\definecolor" command to redefine the
+color \texttt{FillWithLinesColor}.
+
 \medskip
 
 For example, if you type
@@ -4301,6 +4348,26 @@
 and that value can be changed by giving a new \verb"\setlength"
 command.
 
+By default, the dotted lines are in black.  However, if you give the
+commands\indc{colorfillwithdottedlines}\index{color package}
+\begin{center}
+  \begin{tabular}{l}
+    \verb"\usepackage{color}"\\
+    \verb"\colorfillwithdottedlines"
+  \end{tabular}
+\end{center}
+then the dotted lines will be in color, by default a light gray.  That
+default color was defined by the
+command\indc{definecolor}\indt{FillWithDottedLinesColor}
+\begin{center}
+  \verb"\definecolor{FillWithDottedLinesColor}{gray}{0.8}"
+\end{center}
+\index{customization!\texttt{\char`\\fillwithdottedlines}} You can
+change the color by giving a new \verb"\definecolor" command to
+redefine the color \texttt{FillWithDottedLinesColor}.
+
+\medskip
+
 For example, if you type
 \begin{verbatim}
 \begin{questions}
@@ -4412,7 +4479,7 @@
 \verb"\gridsize+\gridlinewidth".
 
 By default, the created grids are in black.  However, if you give the
-commands\indc{colorgrids}
+commands\indc{colorgrids}\index{color package}
 \begin{center}
   \begin{tabular}{l}
     \verb"\usepackage{color}"\\
@@ -4514,7 +4581,7 @@
 or \verb"\pagestyle{foot}" (see section~\ref{sec:pagestyle}), and put
 the following into your preamble (i.e., after the
 \verb"\documentclass" command and before the \verb"\begin{document}"
-  command):
+  command):\index{color package}\indc{gridfromfoot}
 \begin{verbatim}
 \usepackage{color}
 \colorgrids
@@ -4736,14 +4803,13 @@
 \subsection{Solution environments}
 \label{sec:SolEnv}
 \index{solutions|(}
-\index{solution environment|(}
+\index{solution environment@\texttt{solution} environment|(}
 \index{answers|(}
-\index{environment!solution|(}
-\index{environment!solutionorbox|(}
-\index{environment!solutionorlines|(}
-\index{environment!solutionordottedlines|(}
-\index{environment!solutionorgrid|(}
-\index{solution environment|(}
+\index{environment!solution@\texttt{solution}|(}
+\index{environment!solutionorbox@\texttt{solutionorbox}|(}
+\index{environment!solutionorlines@\texttt{solutionorlines}|(}
+\index{environment!solutionordottedlines@\texttt{solutionordottedlines}|(}
+\index{environment!solutionorgrid@\texttt{solutionorgrid}|(}
 \index{solutionorbox environment|(}
 \index{solutionorlines environment|(}
 \index{solutionordottedlines environment|(}
@@ -4774,7 +4840,7 @@
 
 The six environments for solutions are divided into two types.
 \begin{itemize}
-\item The \texttt{solutionorbox} environment (see
+\item The \texttt{solutionbox} environment (see
   section~\ref{sec:solutionbox}) always prints a box, of whatever size
   you choose, and if solutions are being printed it prints the
   solution inside of the box.
@@ -4796,10 +4862,11 @@
   differ only in the type of space they leave when you do include the
   optional argument and solutions are not being printed.
 
-
   The differences between these five environments are that if the
-  optional argument is used and solutions are not being printed,
-  then\index{solution environment!optional argument}
+  optional argument is used and solutions are not being printed, then
+  %
+  \index{solution environment@\texttt{solution} environment!optional argument}
+  %
   \begin{itemize}
   \item the \verb"solution" enviroment inserts that amount of blank
     space, as if you had given a \verb"\vspace*"\indc{vspace*} command
@@ -4831,15 +4898,14 @@
 the page, and do not affect any gradetables or pointtables.
 
 
-\index{solution environment|)}
 \index{solutionorbox environment|)}
 \index{solutionorlines environment|)}
 \index{solutionordottedlines environment|)}
-\index{environment!solution|)}
-\index{environment!solutionorbox|)}
-\index{environment!solutionorlines|)}
-\index{environment!solutionordottedlines|)}
-\index{environment!solutionorgrid|)}
+\index{environment!solution@\texttt{solution}|)}
+\index{environment!solutionorbox@\texttt{solutionorbox}|)}
+\index{environment!solutionorlines@\texttt{solutionorlines}|)}
+\index{environment!solutionordottedlines@\texttt{solutionordottedlines}|)}
+\index{environment!solutionorgrid@\texttt{solutionorgrid}|)}
 
 %--------------------------------------------------------------------
 \subsection{The appearance of the solution}
@@ -4864,7 +4930,10 @@
 \item with no framing or shading (see section~\ref{sec:SolPlain}).
 \end{itemize}
 In all of these cases, solutions can be broken across pages, and the
-part on each page will be boxed or shaded as appropriate.
+part on each page will be boxed or shaded as appropriate.  In
+addition, the \verb"\SolutionEmphasis" command can be used to
+highlight the text of the solution (using, e.g., boldface, or italics,
+or color); see section~\ref{sec:SolEmph}.
 
 
 %--------------------------------------------------------------------
@@ -4874,7 +4943,8 @@
 
 By default, the solution is printed inside of a box (i.e., an
 \verb"\fbox"\indc{fbox}), and if the solution is broken across pages,
-then each piece is enclosed in a box.  There is also a
+then each piece is enclosed in a box.  (The box can be printed in
+color; for that, see section~\ref{sec:colorboxes}.)  There is also a
 \verb"\shadedsolutions"\indc{shadedsolutions} command to instead have
 the solution printed in a \verb"\colorbox"\indc{colorbox} (i.e.,
 printed on a shaded background); for this, you must load the
@@ -5040,6 +5110,56 @@
 \index{solution!no framing or shading|)}
 
 %--------------------------------------------------------------------
+\subsection{Imported graphics in solutions}
+\label{sec:graphics}
+\index{graphics|(}
+\indc{includegraphics}
+
+Graphics can be included inside solution environments using the
+\verb"\includegraphics" command, as long as you've given the command
+\verb"\usepackage{graphicx}" in the preamble (i.e., after the
+\verb"\documentclass" command and before the \verb"\begin{document}"
+  command).
+
+A problem sometimes arises because many \LaTeX{} manuals say that you
+should put an \verb"\includegraphics" command inside of a
+\texttt{figure} environment, so that it will float to a convenient
+place on a page.  The problem with this is that floating environments
+can't appear inside of a solution environment.
+
+There are (at least) two solutions to this problem, depending on
+whether you really want the graphic to float away from the location at
+which you've typed it.
+\begin{itemize}
+\item If you want the graphic to float, put the \texttt{figure}
+  environment \emph{outside} of the solution environment, using
+  \verb"\ifprintanswers" to ensure that it's printed only when
+  solutions are being printed, as in
+\begin{verbatim}
+\ifprintanswers
+  \begin{figure}
+    \includegraphics[width=7cm]{myfigure.pdf}
+    \caption{This is a lovely figure}
+    \label{fig:lovely}
+  \end{figure}
+\fi
+\end{verbatim}
+\item If you \emph{don't} want the graphic to float, then omit the
+  \texttt{figure} environment and just put the \verb"\includegraphics"
+  command wherever you want the graphic to appear.  If you want the
+  graphic to have a caption, then put the command
+  \verb"\usepackage{caption}" in your preamble and then use the
+  \verb"\captionof" command, as in
+\begin{verbatim}
+\includegraphics[width=7cm]{myfigure.pdf}
+\captionof{figure}{This is a lovely figure}
+\label{fig:lovely}
+\end{verbatim}
+\end{itemize}
+
+\index{graphics|)}
+
+%--------------------------------------------------------------------
 \subsection{Customizing the solution}
 \label{sec:SolCust}
 \index{customization!solution|(}
@@ -5162,7 +5282,7 @@
 \label{sec:SolSpace}
 \index{space!for answers|(}
 \index{answer space|(}
-\index{solution environment!optional argument|(}
+\index{solution environment@\texttt{solution} environment!optional argument|(}
 
 Each of the \texttt{solution}, \texttt{solutionorbox},
 \texttt{solutionorlines}, \texttt{solutionordottedlines}, and
@@ -5170,8 +5290,10 @@
 \begin{itemize}
 \item In a
   %
-  \verb"solution"\indtsub{solution}{optional argument}
+  \verb"solution"
   %
+  \index{solution environment@\texttt{solution} environment!optional argument}
+  %
   environment this is an amount of blank space to be left (just as if
   you had used a \verb"\vspace*"\indc{vspace*} command; see
   section~\ref{sec:BlankSpace}) when solutions are not being printed,
@@ -5313,13 +5435,16 @@
 
 \emph{Note:} The document class option \texttt{cancelspace} and the
 command \verb"\cancelspace" only affect the optional arguments of of
-the five solution environments; they do \emph{not} affect any
-\verb"\vspace" commands that you put into the file.
+the five solution environments \texttt{solution},
+\texttt{solutionorbox}, \texttt{solutionorlines},
+\texttt{solutionordottedlines}, and \texttt{solutionorgrid}; they do
+\emph{not} affect any \verb"\vspace" commands that you put into the
+file.
 
 \indcstop{nocancelspace}
 \indcstop{cancelspace}
 
-\index{solution environment!optional argument|)}
+\index{solution environment@\texttt{solution} environment!optional argument|)}
 
 %--------------------------------------------------------------------
 \subsection{The solutionbox environment}
@@ -5348,8 +5473,12 @@
 \end{enumerate}
 If answers are not being printed then only the box is printed, with
 nothing in it.  If answers are being printed, then the solution is
-printed inside of the box.
+printed inside of the box.  (The box printed by the
+\texttt{solutionbox} environment can be printed in color; for that,
+see section~\ref{sec:colorboxes}.)
 
+
+
 For example, if you type
 \begin{verbatim}
 \begin{solutionbox}{2in}
@@ -5375,12 +5504,70 @@
 If solutions \emph{are} being printed, then the exact same box would
 be printed, but it would have the solution printed inside of it.
 
-
-
 \index{answer space|)}
 \index{space!for answers|)}
 
+%--------------------------------------------------------------------
+\subsection{Solution box frames in color}
+\label{sec:colorboxes}
 
+The frames that are printed by the commands that create solution boxes
+can be printed in color.  (By ``frame'' we mean the four lines that
+constitute the box, not any solution that may appear inside that box.)
+This includes
+\begin{itemize}
+\item the box printed by the \texttt{solutionbox}
+  environment\index{customization!\texttt{solutionbox}}
+  (see section~\ref{sec:solutionbox}),
+\item the box printed by the \verb"\makeemptybox"
+  command\index{customization!\texttt{\char`\\makeemptybox}}
+  (see section~\ref{sec:EmptyBox}),
+\item the box printed by the optional argument of the
+  \texttt{solutionorbox}
+  environment\index{customization!\texttt{solutionorbox}} (see
+  section~\ref{sec:SolEnv}), and
+\item the box printed around the solution by
+  the\index{customization!\texttt{solution} environments}
+  \begin{itemize}
+  \item \texttt{solution},
+  \item \texttt{solutionorbox},
+  \item \texttt{solutionorlines},
+  \item \texttt{solutionordottedlines}, and
+  \item \texttt{solutionorgrid}
+  \end{itemize}
+  environments when solutions are being printed and the default
+  solution appearance (\verb"\framedsolutions") is being used (see
+  section~\ref{sec:SolBox}).
+\end{itemize}
+
+If you've given the command
+\begin{center}
+  \verb"\usepackage{color}"\index{color package}
+\end{center}
+in the preamble of your document (i.e., after the
+\verb"\documentclass" command and before the \verb"\begin{document}"
+command), then you can give the command\indc{colorsolutionboxes}
+\begin{center}
+  \verb"\colorsolutionboxes"
+\end{center}
+to have the frame printed in color.  The color of the frame will be
+\texttt{SolutionBoxColor}, the default value of which was created by
+the command\indc{definecolor}\indt{SolutionBoxColor}
+\begin{center}
+  \verb"\definecolor{SolutionBoxColor}{gray}{0.8}"
+\end{center}
+You can change the color by giving a new \verb"\definecolor" command,
+but you must do this \emph{after} the \verb"\colorsolutionboxes"
+command.
+
+To cancel color \texttt{solutionbox} frames and return to black, give
+the command\indc{nocolorsolutionboxes}
+\begin{center}
+  \verb"\nocolorsolutionboxes"
+\end{center}
+
+
+
 %--------------------------------------------------------------------
 \subsection{Changes depending on whether or not solutions are
   being printed}
@@ -5473,7 +5660,7 @@
 \indcstop{ifprintanswers}
 
 \index{answers|)}
-\index{solution environment|)}
+\index{solution environment@\texttt{solution} environment|)}
 \index{solutions|)}
 
 
@@ -5721,15 +5908,194 @@
 \item create grading ranges (see section~\ref{sec:PartialTable}) and
   print several partial grading tables, making each one small enough
   to fit on the page, or
-\item use the \verb"\pointsofquestion" command (see
-  section~\ref{sec:pointsofq}) or the \verb"\pointsonpage" command
-  (see section~\ref{sec:pointsonp}) to create a custom \verb"tabular"
-  environment that has more rows (or columns) than the tables produced
-  by the \verb"gradingtable" command.
+\item use the \verb"\multirowgradetable" or
+  \verb"\multicolumngradetable" command (see section~\ref{sec:MulRows}
+  and section~\ref{sec:MulCols}) to create a grading table with
+  multiple rows or multiple columns.
 \end{itemize}
 \index{grading table!by question number or page number|)}
 
 %--------------------------------------------------------------------
+\subsubsection{Grading tables with multiple rows}
+\label{sec:MulRows}
+
+If you want to print a horizontal grading table but the table would be
+too large for the page, you can print a grading table with multiple
+rows by giving the command
+\begin{center}
+  \verb"\multirowgradetable{numrows}[questions or pages]"
+\end{center}
+\indc{multirowgradetable}Note that the first argument
+(\texttt{numrows}) is \emph{required}, and so it is enclosed in
+braces, but the second argument (\texttt{questions} or \texttt{pages})
+is optional, and is enclosed in square brackets, just as in the
+\verb"\gradetable" command (see section~\ref{sec:GradeTables}).  For
+example, to print a grading table with 3 rows indexed by questions,
+you would give the command
+\begin{center}
+  \verb"\multirowgradetable{3}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\gradetable[h][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multirowgradetable{1}[questions]"
+\end{center}
+
+The distance between the rows of a multirow table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+For example, if an exam has 16 questions, each worth a total of 15
+points, and you type
+\begin{verbatim}
+\begin{center}
+  \setlength{\doublerulesep}{0.25in}
+  \multirowgradetable{2}[questions]  
+\end{center}
+\end{verbatim}
+then you'll get
+\begin{center}
+  \setlength{\doublerulesep}{0.25in}
+  \cwidth=2em
+  \renewcommand{\arraystretch}{1.5}
+  \begin{tabular}{|l|c|c|c|c|c|c|c|c|c|}
+    \hline
+    Question:& 1& 2& 3& 4& 5& 6& 7& 8& 9\\
+    \hline
+    Points:&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15\\
+    \hline
+    Score:&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \\
+    \hline
+    \hline
+    Question:& 10& 11& 12& 13& 14& 15& 16&& Total\\
+    \hline
+    Points:&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15&
+    15&
+    &
+    240\\
+    \hline
+    Score:&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \hbox to \cwidth{\hfill}&
+    \\
+    \hline
+  \end{tabular}
+\end{center}
+
+
+%--------------------------------------------------------------------
+\subsubsection{Grading tables with multiple columns}
+\label{sec:MulCols}
+
+If you want to print a vertical grading table but the table would be
+too large for the page, you can print a grading table with multiple
+columns by giving the command
+\begin{center}
+  \verb"\multicolumngradetable{numcols}[questions or pages]"
+\end{center}
+\indc{multicolumngradetable}Note that the first argument
+(\texttt{numcols}) is \emph{required}, and so it is enclosed in
+braces, but the second argument (\texttt{questions} or \texttt{pages})
+is optional, and is enclosed in square brackets, just as in the
+\verb"\gradetable" command (see section~\ref{sec:GradeTables}).  For
+example, to print a grading table with 3 columns indexed by questions,
+you would give the command
+\begin{center}
+  \verb"\multicolumngradetable{3}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\gradetable[v][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multicolumngradetable{1}[questions]"
+\end{center}
+
+The distance between the columns of a multicolumn table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+For example, if an exam has 10 questions, each worth a total of 15
+points, and you type
+\begin{verbatim}
+\begin{center}
+  \setlength{\doublerulesep}{0.5in}
+  \multicolumngradetable{2}[questions]
+\end{center}
+\end{verbatim}
+then you'll get
+\begin{center}
+  \cwidth=2em
+  \setlength{\doublerulesep}{0.5in}
+  \renewcommand{\arraystretch}{1.5}
+  \begin{tabular}{|c|c|c|c|c|c|c|}
+    \cline{1-3}\cline{5-7}
+    Question& Points& Score&
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}&
+    Question& Points& Score\\
+    \cline{1-3}\cline{5-7}
+    1& 15& \hbox to \cwidth{\hfill}&&7& 15& \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+    2& 15& \hbox to \cwidth{\hfill}&&8& 15& \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+    3& 15& \hbox to \cwidth{\hfill}&&9& 15& \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+    4& 15& \hbox to \cwidth{\hfill}&&10& 15& \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+    5& 15& \hbox to \cwidth{\hfill}&& &  & \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+    6& 15& \hbox to \cwidth{\hfill}&&Total:& 150& \hbox to \cwidth{\hfill}\\
+    \cline{1-3}\cline{5-7}
+
+    \cline{1-3}\cline{5-7}
+  \end{tabular}
+\end{center}
+
+
+
+%--------------------------------------------------------------------
 \subsection{Point tables indexed by question number or by page
   number}
 \label{sec:PointTables}
@@ -5893,15 +6259,88 @@
 \item create grading ranges (see section~\ref{sec:PartialTable}) and
   print several partial point tables, making each one small enough
   to fit on the page, or
-\item use the \verb"\pointsofquestion" command (see
-  section~\ref{sec:pointsofq}) or the \verb"\pointsonpage" command
-  (see section~\ref{sec:pointsonp}) to create a custom \verb"tabular"
-  environment that has more rows (or columns) than the tables produced
-  by the \verb"pointtable" command.
+\item use the \verb"\multirowpointtable" or
+  \verb"\multicolumnpointtable" command (see
+  section~\ref{sec:PtMulRows} and section~\ref{sec:PtMulCols}) to
+  create a point table with multiple rows or multiple columns.
 \end{itemize}
 \index{point table!by question number or page number|)}
 
 %--------------------------------------------------------------------
+\subsubsection{Point tables with multiple rows}
+\label{sec:PtMulRows}
+
+If you want to print a horizontal point table but the table would be
+too large for the page, you can print a point table with multiple
+rows by giving the command
+\begin{center}
+  \verb"\multirowpointtable{numrows}[questions or pages]"
+\end{center}
+\indc{multirowpointtable}Note that the first argument
+(\texttt{numrows}) is \emph{required}, and so it is enclosed in
+braces, but the second argument (\texttt{questions} or \texttt{pages})
+is optional, and is enclosed in square brackets, just as in the
+\verb"\pointtable" command (see section~\ref{sec:PointTables}).  For
+example, to print a point table with 3 rows indexed by questions, you
+would give the command
+\begin{center}
+  \verb"\multirowpointtable{3}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\pointtable[h][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multirowpointtable{1}[questions]"
+\end{center}
+
+The distance between the rows of a multirow table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+%--------------------------------------------------------------------
+\subsubsection{Point tables with multiple columns}
+\label{sec:PtMulCols}
+
+If you want to print a vertical point table but the table would be
+too large for the page, you can print a point table with multiple
+columns by giving the command
+\begin{center}
+  \verb"\multicolumnpointtable{numcols}[questions or pages]"
+\end{center}
+\indc{multicolumnpointtable}Note that the first argument
+(\texttt{numcols}) is \emph{required}, and so it is enclosed in
+braces, but the second argument (\texttt{questions} or \texttt{pages})
+is optional, and is enclosed in square brackets, just as in the
+\verb"\pointtable" command (see section~\ref{sec:PointTables}).  For
+example, to print a point table with 3 columns indexed by questions,
+you would give the command
+\begin{center}
+  \verb"\multicolumnpointtable{3}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\pointtable[v][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multicolumnpointtable{1}[questions]"
+\end{center}
+
+The distance between the columns of a multicolumn table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+%--------------------------------------------------------------------
 \subsection{Table entries as clickable links}
 \label{sec:Clickable}
 \index{grading table!clickable links}
@@ -5932,7 +6371,8 @@
 will be clickable links that take you to the corresponding question.
 
 %--------------------------------------------------------------------
-\subsection{Grading ranges, partial grading tables, and partial point tables}
+\subsection{Grading ranges, partial grading tables, and partial point
+  tables}
 \label{sec:PartialTable}
 \index{grading range|(}
 \indcstart{begingradingrange}
@@ -6006,7 +6446,92 @@
 \indcstop{begingradingrange}
 \index{grading range|)}
 
+
 %--------------------------------------------------------------------
+\subsubsection{Partial grading tables and partial point tables with
+  multiple rows}
+\label{sec:PartMulRows}
+
+If you want to print a horizontal partial grading table or partial
+point table but the table would be too large for the page, you can
+print a partial grading table or partial point table with multiple
+rows by giving the one of the commands
+\begin{center}
+  \verb"\multirowpartialgradetable{numrows}{rangename}[questions or pages]"
+  \verb"\multirowpartialpointtable{numrows}{rangename}[questions or pages]"
+\end{center}
+\indc{multirowpartialgradetable}\indc{multirowpartialpointtable}Note
+that the first two arguments (\texttt{numrows} and \texttt{rangename})
+are \emph{required}, and so they are enclosed in braces, but the third
+argument (\texttt{questions} or \texttt{pages}) is optional, and is
+enclosed in square brackets, just as in the \verb"\partialgradetable"
+and \verb"\partialpointtable" commands (see
+section~\ref{sec:PartialTable}).  For example, to print a partial
+grading table with 3 rows covering the range \texttt{myrange} indexed
+by questions, you would give the command
+\begin{center}
+  \verb"\multirowpartialgradetable{3}{myrange}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\partialgradetable{myrange}[h][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multirowpartialgradetable{1}{myrange}[questions]"
+\end{center}
+
+The distance between the rows of a multirow table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+%--------------------------------------------------------------------
+\subsubsection{Partial grading tables and partial point tables with
+  multiple columns}
+\label{sec:PartMulCols}
+
+If you want to print a vertical partial grading table or partial point
+table but the table would be too large for the page, you can print a
+partial grading table or partial point table with multiple columns by
+giving one of the commands
+\begin{center}
+\verb"\multicolumnpartialgradetable{numcols}{rangename}[questions or pages]"\\
+\verb"\multicolumnpartialpointtable{numcols}{rangename}[questions or pages]"
+\end{center}
+\indc{multicolumnpartialgradetable}\indc{multicolumnpartialpointtable}Note
+that the first two arguments (\texttt{numcols} and
+\texttt{rangename}) are \emph{required}, and so they are enclosed in
+braces, but the third argument (\texttt{questions} or \texttt{pages})
+is optional, and is enclosed in square brackets, just as in the
+\verb"\partialgradetable" and \verb"\partialpointtable" commands (see
+section~\ref{sec:PartialTable}).  For example, to print a partial
+grading table with 3 columns covering the range \texttt{myrange}
+indexed by questions, you would give the command
+\begin{center}
+  \verb"\multicolumnpartialgradetable{3}{myrange}[questions]"
+\end{center}
+The command
+\begin{center}
+  \verb"\partialgradetable{myrange}[v][questions]"
+\end{center}
+is equivalent to
+\begin{center}
+  \verb"\multicolumnpartialgradetable{1}{myrange}[questions]"
+\end{center}
+
+The distance between the columns of a multicolumn table is
+\verb"\doublerulesep", the default value of which is 2.0pt.  You can
+change that using a \verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+%--------------------------------------------------------------------
 \subsection{\texttt{\bs pointsofquestion}}
 \label{sec:pointsofq}
 
@@ -6114,29 +6639,59 @@
 points.  (There are also commands that create tables listing both
 non-bonus and bonus points; see section~\ref{sec:CombinedTables}.)
 These commands, and the non-bonus commands to which they correspond,
-are as follows:\indc{bonusgradetable}\indc{bonuspointtable}%
-\indc{bonuspointsinrange}\indc{partialbonusgradetable}%
-\indc{partialbonuspointtable}\indc{bonuspointsofquestion}%
-\indc{bonuspointsonpage}
+are as follows:
 \begin{center}
   \begin{tabular}{l@{\qquad\qquad}l}
     Bonus points& Non-bonus points\\[\bigskipamount]
     \verb"\bonusgradetable"&        \verb"\gradetable"\\
-    \verb"\bonuspointtable"& \verb"\pointtable"\\[\medskipamount]
-    \verb"\bonuspointsinrange"& \verb"\pointsinrange"\\
+    \verb"\multirowbonusgradetable"& \verb"\multirowgradetable"\\
+    \verb"\multicolumnbonusgradetable"& \verb"\multicolumngradetable"\\
+    \verb"\bonuspointtable"& \verb"\pointtable"\\
+    \verb"\multirowbonuspointtable"& \verb"\multirowpointtable"\\
+    \verb"\multicolumnbonuspointtable"&
+        \verb"\multicolumnpointtable"\\[\medskipamount]
     \verb"\partialbonusgradetable"& \verb"\partialgradetable"\\
+    \verb"\multirowpartialbonusgradetable"&
+        \verb"\multirowpartialgradetable"\\
+    \verb"\multicolumnpartialbonusgradetable"&
+        \verb"\multicolumnpartialgradetable"\\
     \verb"\partialbonuspointtable"&
-                       \verb"\partialpointtable"\\[\medskipamount] 
+        \verb"\partialpointtable"\\
+    \verb"\multirowpartialbonuspointtable"&
+        \verb"\multirowpartialpointtable"\\
+    \verb"\multicolumnpartialbonuspointtable"&
+        \verb"\multicolumnpartialpointtable"\\[\medskipamount] 
+    \verb"\bonuspointsinrange"& \verb"\pointsinrange"\\
     \verb"\bonuspointsofquestion"&  \verb"\pointsofquestion"\\
     \verb"\bonuspointsonpage"&      \verb"\pointsonpage"
   \end{tabular}
 \end{center}
+\indc{bonusgradetable}\indc{bonuspointtable}%
+\indc{bonuspointsinrange}\indc{partialbonusgradetable}%
+\indc{partialbonuspointtable}\indc{bonuspointsofquestion}%
+\indc{bonuspointsonpage}%
+\indc{multirowbonusgradetable}%
+\indc{multicolumnbonusgradetable}%
+\indc{multirowbonuspointtable}%
+\indc{multicolumnbonuspointtable}%
+\indc{multirowpartialbonusgradetable}%
+\indc{multicolumnpartialbonusgradetable}%
+\indc{multirowpartialbonuspointtable}%
+\indc{multicolumnpartialbonuspointtable}%
 All of the the commands in the first column count bonus points and
 ignore non-bonus points, while all of the commands in the second
-column count non-bonus points and ignore bonus points.  The
-\verb"\partialbonusgradetable" and \verb"\partialbonuspointtable"
-commands use the same
-\verb"\begingradingrange"\indc{begingradingrange} and
+column count non-bonus points and ignore bonus points.  The commands
+\begin{center}
+  \begin{tabular}{l}
+    \verb"\partialbonusgradetable",\\
+    \verb"\multirowpartialbonusgradetable",\\
+    \verb"\multicolumnpartialbonusgradetable",\\
+    \verb"\partialbonuspointtable",\\
+    \verb"\multirowpartialbonuspointtable", and\\
+    \verb"\multicolumnpartialbonuspointtable"
+  \end{tabular}
+\end{center}
+use the same \verb"\begingradingrange"\indc{begingradingrange} and
 \verb"\endgradingrange"\indc{endgradingrange} commands that are used
 by the corresponding non-bonus versions (see
 section~\ref{sec:PartialTable}).  Just as is described in
@@ -6214,14 +6769,15 @@
 
 There are commands for producing \emph{combined} grading and point
 tables, both partial and full, that tabulate both regular points and
-bonus points (see section~\ref{sec:Bonus}).  These commands, and the
+bonus points (see section~\ref{sec:Bonus}).  The single-row and
+single-column versions of these commands, and the
 non-bonus and bonus commands to which they correspond, are
 \indc{combinedgradetable}\indc{combinedpointtable}%
 \indc{partialcombinedgradetable}\indc{partialcombinedpointtable}%
 \begin{center}
   \begin{tabular}{l@{\enskip}l@{\enskip}l}
-    Non-Bonus points only& Bonus points only&
-                           Combined\\[\medskipamount]
+    Non-Bonus points only& \multicolumn{1}{c}{Bonus points only}&
+        \multicolumn{1}{c}{Combined}\\[\medskipamount]
     \verb"\gradetable"& \verb"\bonusgradetable"&
                         \verb"\combinedgradetable"\\
     \verb"\pointtable"& \verb"\bonuspointtable"&
@@ -6242,6 +6798,37 @@
 first being either \verb"[v]" or \verb"[h]" and the second being
 either \verb"[questions]" or \verb"[pages]").
 
+The multirow and multicolumn versions of combined grading tables and
+combined point tables are produced by the commands
+\indc{multirowcombinedgradetable}%
+\indc{multirowcombinedpointtable}%
+\indc{multirowpartialcombinedgradetable}%
+\indc{multirowpartialcombinedpointtable}%
+\indc{multicolumncombinedgradetable}%
+\indc{multicolumncombinedpointtable}%
+\indc{multicolumnpartialcombinedgradetable}%
+\indc{multicolumnpartialcombinedpointtable}%
+\begin{verbatim}
+\multirowcombinedgradetable{numrows}[questions or pages]
+\multirowcombinedpointtable{numrows}[questions or pages]
+\multirowpartialcombinedgradetable{numrows}{rangename}[questions or pages]
+\multirowpartialcombinedpointtable{numrows}{rangename}[questions or pages]
+\multicolumncombinedgradetable{numcols}[questions or pages]
+\multicolumncombinedpointtable{numcols}[questions or pages]
+\multicolumnpartialcombinedgradetable{numcols}{rangename}[questions or pages]
+\multicolumnpartialcombinedpointtable{numcols}{rangename}[questions or pages]
+\end{verbatim}
+The distance between the rows of a multirow table and between the
+columns of a multicolumn table is \verb"\doublerulesep", the default
+value of which is 2.0pt.  You can change that using a
+\verb"\setlength" command, as in
+\begin{center}
+  \verb"\setlength{\doublerulesep}{0.5in}"
+\end{center}
+(see section~\ref{sec:CustTable}).
+
+
+
 Just as is described in section~\ref{sec:Clickable} for their regular
 points and section~\ref{sec:BonusTables} for bonus points, if you put
 the
@@ -6412,7 +6999,7 @@
 \index{grade table!customization|(}
 \index{point table!customization|(}
 
-There are three ways in which you can customize the appearance of
+There are four ways in which you can customize the appearance of
 grading tables and point tables:
 \begin{itemize}
 \item You can change the words (and the fonts) that appear in the
@@ -6421,20 +7008,30 @@
   you will write the scores.
 \item You can change the value of
   \verb"\baselinestretch"\indc{baselinestretch} used for the table.
+\item You can change the distance between the rows in a multirow table
+  and the distance between the columns in a multicolumn table.
 \end{itemize}
 For all grading and point tables:\indc{gradetablestretch}\indc{cellwidth}
+\indc{doublerulesep}%
 \begin{center}
-  \begin{tabular}{l@{\qquad}l}
+  \begin{tabular}{l@{\qquad}p{3in}}
     \verb"\cellwidth{length}"& sets the minimum width of the blank cells to
-    \verb"length"\\
+    \verb"length"\\[\medskipamount]
     \verb"\gradetablestretch{number}"& uses \verb"number" as the
-    \verb"\baselinestretch"
+    \verb"\baselinestretch"\\[\medskipamount]
+    \verb"\setlength{\doublerulesep}{length}"& sets the distance
+    between the columns of a multicolumn table and the distance
+    between the rows of a multirow table to \verb"length".
   \end{tabular}
 \end{center}
-For vertical non-bonus tables (i.e., tables created by
-\verb"\gradetable[v]", \verb"\pointtable[v]",
-\verb"\partialgradetable{rangename}[v]", or
-\verb"\partialpointtable{rangename}[v]"):%
+For vertical non-bonus tables:
+% (i.e., tables created by
+% \verb"\gradetable[v]", \verb"\multicolumngradetable",
+% \verb"\pointtable[v]", \verb"\multicolumnpointtable",
+% \verb"\partialgradetable{rangename}[v]",
+% \verb"\multicolumnpartialgradetable",
+% \verb"\partialpointtable{rangename}[v]", or
+% \verb"\multicolumnpartialpointtable"):%
 \indc{vqword}\indc{vpgword}\indc{vpword}\indc{vsword}\indc{vtword}
 \begin{center}
   \begin{tabular}{l@{\qquad}l}
@@ -6446,10 +7043,11 @@
     \verb"\vtword{text}"& substitutes \verb"text" for ``Total:''
   \end{tabular}
 \end{center}
-For horizontal non-bonus tables (i.e., tables created by
-\verb"\gradetable[h]", \verb"\pointtable[h]",
-\verb"\partialgradetable{rangename}[h]", or
-\verb"\partialpointtable{rangename}[h]"):%
+For horizontal non-bonus tables:
+%  (i.e., tables created by
+% \verb"\gradetable[h]", \verb"\pointtable[h]",
+% \verb"\partialgradetable{rangename}[h]", or
+% \verb"\partialpointtable{rangename}[h]"):%
 \indc{hqword}\indc{hpgword}\indc{hpword}\indc{hsword}\indc{htword}
 \begin{center}
   \begin{tabular}{l@{\qquad}l}
@@ -6461,11 +7059,13 @@
     \verb"\htword{text}"& substitutes \verb"text" for ``Total''
   \end{tabular}
 \end{center}
-For vertical bonus tables (i.e., tables created by
-\verb"\bonusgradetable[v]", \verb"\bonuspointtable[v]",
-\verb"\partialbonusgradetable{rangename}[v]", or
-\verb"\partialbonuspointtable{rangename}[v]"):\indc{bvqword}%
-\indc{bvpgword}\indc{bvpword}\indc{bvsword}\indc{bvtword}
+For vertical bonus tables:
+%  (i.e., tables created by
+% \verb"\bonusgradetable[v]", \verb"\bonuspointtable[v]",
+% \verb"\partialbonusgradetable{rangename}[v]", or
+% \verb"\partialbonuspointtable{rangename}[v]"):
+\indc{bvqword}\indc{bvpgword}\indc{bvpword}\indc{bvsword}%
+\indc{bvtword}
 \begin{center}
   \begin{tabular}{l@{\qquad}l}
     Command& \multicolumn{1}{c}{Effect}\\[\medskipamount]
@@ -6476,11 +7076,13 @@
     \verb"\bvtword{text}"& substitutes \verb"text" for ``Total:''
   \end{tabular}
 \end{center}
-For horizontal bonus tables (i.e., tables created by
-\verb"\bonusgradetable[h]", \\
-\verb"\bonuspointtable[h]",
-\verb"\partialbonusgradetable{rangename}[h]", or\\
-\verb"\partialbonuspointtable{rangename}[h]"):\indc{bhqword}%
+For horizontal bonus tables:
+%  (i.e., tables created by
+% \verb"\bonusgradetable[h]", \\
+% \verb"\bonuspointtable[h]",
+% \verb"\partialbonusgradetable{rangename}[h]", or\\
+% \verb"\partialbonuspointtable{rangename}[h]"):
+\indc{bhqword}%
 \indc{bhpgword}\indc{bhpword}\indc{bhsword}\indc{bhtword}
 \begin{center}
   \begin{tabular}{l@{\qquad}l}
@@ -6492,11 +7094,13 @@
     \verb"\bhtword{text}"& substitutes \verb"text" for ``Total''
   \end{tabular}
 \end{center}
-For vertical combined tables (i.e., tables created by
-\verb"\combinedgradetable[v]",\\
-\verb"\combinedpointtable[v]",
-\verb"\partialcombinedgradetable{rangename}[v]", or\\
-\verb"\partialcombinedpointtable{rangename}[v]"):\indc{cvqword}%
+For vertical combined tables:
+%  (i.e., tables created by
+% \verb"\combinedgradetable[v]",\\
+% \verb"\combinedpointtable[v]",
+% \verb"\partialcombinedgradetable{rangename}[v]", or\\
+% \verb"\partialcombinedpointtable{rangename}[v]"):
+\indc{cvqword}%
 \indc{cvpgword}\indc{cvpword}\indc{cvbpword}\indc{cvsword}%
 \indc{cvtword}
 \begin{center}
@@ -6510,11 +7114,13 @@
     \verb"\cvtword{text}"& substitutes \verb"text" for ``Total:''
   \end{tabular}
 \end{center}
-For horizontal combined tables (i.e., tables created by
-\verb"\combinedgradetable[h]", \\
-\verb"\combinedpointtable[h]",
-\verb"\partialcombinedgradetable{rangename}[h]", or\\
-\verb"\partialcombinedpointtable{rangename}[h]"):\indc{chqword}%
+For horizontal combined tables:
+%  (i.e., tables created by
+% \verb"\combinedgradetable[h]", \\
+% \verb"\combinedpointtable[h]",
+% \verb"\partialcombinedgradetable{rangename}[h]", or\\
+% \verb"\partialcombinedpointtable{rangename}[h]"):
+\indc{chqword}%
 \indc{chpgword}\indc{chpword}\indc{chbpword}\indc{chsword}%
 \indc{chtword}
 \begin{center}
@@ -6529,16 +7135,15 @@
   \end{tabular}
 \end{center}
 If you don't use any of these commands then you get the default
-values, which are defined by the commands in the following table:
+values, which are defined by the commands in the following
+table:\indc{doublerulesep}\indc{gradetablestretch}\indc{cellwidth}
 \begin{center}
-  \begin{tabular}{l@{\quad}l@{\quad}l}
-    \multicolumn{3}{c}{\bfseries Defaults for grading and point
+  \begin{tabular}{l@{\quad}l}
+    \multicolumn{2}{c}{\bfseries Defaults for grading and point
                                  tables}\\*[\bigskipamount]
-    \verb"\hqword{Question:}"& \verb"\vqword{Question}"&
-                               \verb"\cellwidth{2em}"\\
-    \verb"\hpgword{Page:}"&    \verb"\vpgword{Page}"&
-                               \verb"\gradetablestretch{1.5}"\\
-    \verb"\hpword{Points:}"& \verb"\vpword{Points}"\\
+    \verb"\hqword{Question:}"& \verb"\vqword{Question}"\\
+    \verb"\hpgword{Page:}"&    \verb"\vpgword{Page}"\\
+    \verb"\hpword{Points:}"&    \verb"\vpword{Points}"\\
     \verb"\hsword{Score:}"& \verb"\vsword{Score}"\\
     \verb"\htword{Total}"& \verb"\vtword{Total:}"\\[\medskipamount]
 %
@@ -6553,7 +7158,10 @@
     \verb"\chpword{Points:}"& \verb"\cvpword{Points}"\\
     \verb"\chbpword{Bonus Points:}"& \verb"\cvbpword{Bonus Points}"\\
     \verb"\chsword{Score:}"& \verb"\cvsword{Score}"\\
-    \verb"\chtword{Total}"& \verb"\cvtword{Total:}"
+    \verb"\chtword{Total}"& \verb"\cvtword{Total:}"\\[\medskipamount]
+    \verb"\cellwidth{2em}"\\
+    \verb"\gradetablestretch{1.5}"\\
+    \verb"\setlength{\doublerulesep}{2.0pt}"
   \end{tabular}
 \end{center}
 
@@ -6888,7 +7496,8 @@
 \indcstop{runningheader}
 
 %--------------------------------------------------------------------
-\subsubsection{Using \texttt{\bs lhead}, \texttt{\bs chead} and \texttt{\bs rhead}}
+\subsubsection{Using \texttt{\bs lhead}, \texttt{\bs chead} and
+  \texttt{\bs rhead}}
 \label{sec:lhead}
 \indcstart{lhead}
 \indcstart{chead}
@@ -7022,7 +7631,8 @@
 \indcstop{runningfooter}
 
 %--------------------------------------------------------------------
-\subsubsection{Using \texttt{\bs lfoot}, \texttt{\bs cfoot}, and \texttt{\bs rfoot}}
+\subsubsection{Using \texttt{\bs lfoot}, \texttt{\bs cfoot}, and
+  \texttt{\bs rfoot}}
 \label{sec:lfoot}
 \indcstart{lfoot}
 \indcstart{cfoot}
@@ -7102,15 +7712,6 @@
 \end{center}
 gives a half inch of extra head height on the first page and a
 quarter inch of extra head height on all pages after the first. 
-If you say
-\begin{center}
-  \verb"\extraheadheight[.5in]{}"
-\end{center}
-then this will be interpreted as if it was
-\begin{center}
-  \verb"\extraheadheight[.5in]{0in}"
-\end{center}
-Note that the braces \emph{must} appear.
 
 If you give an \verb"\extraheadheight"\indc{extraheadheight} command,
 it should be in the preamble, i.e., after the \verb"\documentclass"
@@ -7151,15 +7752,6 @@
 \end{center}
 gives a half inch of extra foot height on the first page and a
 quarter inch of extra foot height on all pages after the first. 
-If you say
-\begin{center}
-  \verb"\extrafootheight[.5in]{}"
-\end{center}
-then this will be interpreted as if it was
-\begin{center}
-  \verb"\extrafootheight[.5in]{0in}"
-\end{center}
-Note that the braces \emph{must} appear.
 
 If you give an \verb"\extrafootheight"\indc{extrafootheight} command,
 it should be in the preamble, i.e., after the \verb"\documentclass"

Modified: trunk/Master/texmf-dist/tex/latex/exam/exam.cls
===================================================================
--- trunk/Master/texmf-dist/tex/latex/exam/exam.cls	2017-11-16 22:14:19 UTC (rev 45831)
+++ trunk/Master/texmf-dist/tex/latex/exam/exam.cls	2017-11-16 22:14:35 UTC (rev 45832)
@@ -3,7 +3,8 @@
 % A LaTeX2e document class for preparing exams.
 
 %% exam.cls
-%% Copyright (c) 1994, 1997, 2000, 2004, 2008, 2011, 2015 Philip S. Hirschhorn
+%% Copyright (c) 1994, 1997, 2000, 2004, 2008, 2011,
+%% 2015, 2017 Philip S. Hirschhorn
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3
@@ -31,8 +32,8 @@
 % from my web page: http://www-math.mit.edu/~psh/
 
 
-\def\fileversion{2.5}
-\def\filedate{2015/05/07}
+\def\fileversion{2.6}
+\def\filedate{2017/10/26}
 %---------------------------------------------------------------------
 %---------------------------------------------------------------------
 % 
@@ -85,6 +86,263 @@
 %                    Changelog since version 2.4:
 
 %--------------------------------------------------------------------
+% Version 2.6, 2017/09/19
+
+% No longer betatest.
+
+%--------------------------------------------------------------------
+% Version 2.510$\beta$, 2016/10/11
+
+% Bugfix: We changed \@setheadheight and \@setfootheight to fix a bug
+% that was introduced by the bugfix in version 2.306beta, 2009/03/28:
+% If the second page has a different \textheight (because of a change
+% in either headheight or footheight between pages 1 and 2), then page
+% 2 would use the \textheight of page 1.  Pages 3 and beyond would get
+% the correct \textheight.  The original version of this set \@colroom
+% and \vsize to the new \textheight, but that had a bug in that if a
+% float appeared at the top of a page, there would be no notice taken
+% of the space lost to the float, and so the text would overrun the
+% bottom of the page.  The bugfix in version 2.306beta eliminated the
+% changes to \@colht and \vsize.  In this bugfix, we adjust \@colroom,
+% \@colht, and \vsize in the same way that we adjust \textheight.
+
+%--------------------------------------------------------------------
+% Version 2.509$\beta$, 2016/09/12
+
+% Multicolumn grade and point tables, and a new syntax for multirow
+% grade and point tables (which were introduced in version
+% 2.508beta).
+
+% Multicolumn tables are vertically oriented, while multirow tables
+% are horizontally oriented, and so they do not take the optional
+% argument choosing between horizontal and vertical.  They all take
+% one required argument specifying the number of columns (for
+% multicolumn) or the number of rows (for multirow).
+
+% The tables can be:
+
+%   grade tables or point tables,
+
+%   plain, bonus, or combined,
+
+%   indexed by questions or by pages,
+
+%   complete or partial.
+
+% As usual, if you omit the optional argument that chooses between
+% questions and pages, you get questions.
+
+% The new commands are:
+
+% \def\multirowgradetable{numrows}[questions or pages]
+% \def\multirowpointtable{numrows}[questions or pages]
+% \def\multirowbonusgradetable{numrows}[questions or pages]
+% \def\multirowbonuspointtable{numrows}[questions or pages]
+% \def\multirowcombinedgradetable{numrows}[questions or pages]
+% \def\multirowcombinedpointtable{numrows}[questions or pages]
+
+% \def\multirowpartialgradetable{numrows}{rangename}[questions or pages]
+% \def\multirowpartialpointtable{numrows}{rangename}[questions or pages]
+% \def\multirowpartialbonusgradetable{numrows}{rangename}[questions or pages]
+% \def\multirowpartialbonuspointtable{numrows}{rangename}[questions or pages]
+% \def\multirowpartialcombinedgradetable{numrows}{rangename}[questions or pages]
+% \def\multirowpartialcombinedpointtable{numrows}{rangename}[questions or pages]
+
+% \def\multicolumngradetable{numcols}[questions or pages]
+% \def\multicolumnpointtable{numcols}[questions or pages]
+% \def\multicolumnbonusgradetable{numcols}[questions or pages]
+% \def\multicolumnbonuspointtable{numcols}[questions or pages]
+% \def\multicolumncombinedgradetable{numcols}[questions or pages]
+% \def\multicolumncombinedpointtable{numcols}[questions or pages]
+
+% \def\multicolumnpartialgradetable{numcols}{rangename}[questions or pages]
+% \def\multicolumnpartialpointtable{numcols}{rangename}[questions or pages]
+% \def\multicolumnpartialbonusgradetable{numcols}{rangename}[questions or pages]
+% \def\multicolumnpartialbonuspointtable{numcols}{rangename}[questions or pages]
+% \def\multicolumnpartialcombinedgradetable{numcols}{rangename}[questions or pages]
+% \def\multicolumnpartialcombinedpointtable{numcols}{rangename}[questions or pages]
+
+% The older grade and point table commands can still be used.  For
+% example, the commands
+
+%   \gradetable[h][questions]
+%   \multirowgradetable{1}[questions]
+
+% are equivalent.
+
+% The distance between the rows of a multirow table and between the
+% columns of a multicolumn table is \doublerulesep, the default value
+% of which is 2.0pt.  You can change that using a \setlength command,
+% as in
+
+%   \setlength{\doublerulesep}{0.5in}
+
+%--------------------------------------------------------------------
+% Version 2.508$\beta$, 2016/08/06
+
+% New commands: Multirow grade and point tables.
+
+% These are all horizontally oriented tables, and so do not take the
+% optional argument choosing between horizontal and vertical.  They all
+% take one required argument specifying the number of columns, which is
+% the number of columns used for the point values (including the total),
+% but not counting the column of row headings.
+
+% Note: The syntax was changed in version 2.509beta, so that you now
+% specify the number of *rows* rather than the number of *columns*!  For
+% example, the first command below should now be
+
+% \multirowgradetable{numrows}[questions or pages]
+
+% The tables can be:
+
+%   grade tables or point tables,
+
+%   plain, bonus, or combined,
+
+%   indexed by questions or by pages,
+
+%   complete or partial.
+
+% The new commands are:
+
+
+% \multirowgradetable{numcols}[questions or pages]
+% \multirowpointtable{numcols}[questions or pages]
+% \multirowbonusgradetable{numcols}[questions or pages]
+% \multirowbonuspointtable{numcols}[questions or pages]
+% \multirowcombinedgradetable{numcols}[questions or pages]
+% \multirowcombinedpointtable{numcols}[questions or pages]
+
+% \multirowpartialgradetable{numcols}{rangename}[questions or pages]
+% \multirowpartialpointtable{numcols}{rangename}[questions or pages]
+% \multirowpartialbonusgradetable{numcols}{rangename}[questions or pages]
+% \multirowpartialbonuspointtable{numcols}{rangename}[questions or pages]
+% \multirowpartialcombinedgradetable{numcols}{rangename}[questions or pages]
+% \multirowpartialcombinedpointtable{numcols}{rangename}[questions or pages]
+
+%--------------------------------------------------------------------
+% Version 2.507$\beta$, 2016/07/14
+
+% New commands:
+
+%   \pointstwosided
+%   \pointstwosidedreversed
+
+% The first causes points to be in the right margin on odd numbered
+% pages and in the left margin on even numbered pages.
+
+% The second causes points to be in the left margin on odd numbered
+% pages and in the right margin on even numbered pages.
+
+% Also: Some minor edits (e.g., deleting the unused \thebonuspoints).
+
+%--------------------------------------------------------------------
+% Version 2.506$\beta$, 2016/05/12
+
+% Fixed an obscure bug that arose only when \CorrectChoiceEmphasis
+% used color and a \CorrectChoice (in a choices or checkboxes
+% environment) followed a \choice whose text completely filled its
+% last line, and which was not separated from the \CorrectChoice by a
+% blank line, in which case an extra (blank) line was inserted by that
+% \choice.  We fixed this by adding an improvised "\leavehmode"
+% (styled after \leavevmode) to the \CorrectChoice command in both the
+% choices and checkboxes environments, which caused the text of the
+% previous \choice to be broken into lines *before* the \special
+% inserted by the \color command was added.
+
+%--------------------------------------------------------------------
+% Version 2.505$\beta$, 2016/05/10
+
+% We fixed a bug in the choices and checkboxes environments that arose
+% only when \CorrectChoiceEmphasis used color.  If it did, and if the
+% text of a correct choice exactly filled a line, and if there was no
+% blank line in the latex file separating this correct choice from the
+% following choice, there would be an extra blank line inserted after
+% the correct choice.  We did this by inserting \color at begingroup and
+% \color at endgroup as needed.  (We're pretty sure the actual fix was
+% the \endgraf in the expansion of \color at endgroup.)
+
+%--------------------------------------------------------------------
+% Version 2.504$\beta$, 2016/05/09
+
+% We fixed a bug in the solutionbox environment that caused enumerate,
+% itemize, or description environments to have their text stick into
+% the right margin.  We did this by resetting \@totalleftmargin and
+% \linewidth in the box containing the solution.
+
+%--------------------------------------------------------------------
+% Version 2.503$\beta$, 2016/03/25
+
+% New commands:
+
+%      \colorfillwithlines
+%      \colorfillwithdottedlines
+
+% The first causes the lines drawn by the \fillwithlines command to be
+% drawn in color.  The default color is set by the command 
+
+%      \definecolor{FillWithLinesColor}{gray}{0.8}
+
+% and the color can be changed by giving a new \definecolor command.
+% You can return to black lines by giving the command
+
+%      \nocolorfillwithlines
+
+% \colorfillwithdottedlines causes the lines drawn by the
+% \fillwithdottedlines command to be drawn in color.  The default
+% color is set by the command 
+
+%      \definecolor{FillWithDottedLinesColor}{gray}{0.8}
+
+% and the color can be changed by giving a new \definecolor command.
+% You can return to black dotted lines by giving the command
+
+%      \nocolorfillwithdottedlines
+
+%--------------------------------------------------------------------
+% Version 2.502$\beta$, 2016/03/23
+
+% The command
+
+%   \colorsolutionboxes
+
+% that was created in version 2.501beta now affects not only the boxes
+% created by \solutionbox, but also by \makeemptybox, \solutionorbox,
+% and all of the boxes printed by all of the various solution
+% environments when solutions are being printed surrounded by a box.
+
+%--------------------------------------------------------------------
+% Version 2.501$\beta$, 2016/02/08
+
+% Changed the \solutionbox environment so that it works correctly
+% inside a tabular.
+
+% Also: The \solutionbox frame can now be printed in color, as long as
+% you load color.sty in the preamble.
+%
+%  Usage: Say
+%
+%   \usepackage{color}
+%
+% in the preamble, and then give the command
+%
+%   \colorsolutionboxes
+%
+% to have the frame around a solutionbox in color.  The default color
+% was created by the command
+%
+%   \definecolor{SolutionBoxColor}{gray}{0.8}
+%
+% and you can change the color by giving a new \definecolor command
+% (which must be done *after* the \colorsolutionboxes command).
+%
+% To cancel color solutionbox frames and return to black, give the
+% command
+%
+%   \nocolorsolutionboxes
+
+%--------------------------------------------------------------------
 % Version 2.5 2015/05/07
 
 % No longer betatest.
@@ -552,6 +810,11 @@
     % Reset the effect of the most recent change:
     \global\advance\topmargin by -\@extrahead
     \global\advance\textheight by \@extrahead
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    \global\advance\@colroom by \@extrahead
+    \global\advance\@colht by \@extrahead
+    \global\advance\vsize by \@extrahead
+    %
     % Save the newly set value:
     \def\@temp{#1}
     \def\@spaces{ }
@@ -567,6 +830,26 @@
     % Set the new values:
     \global\advance\topmargin by \@extrahead
     \global\advance\textheight by -\@extrahead
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    \global\advance\@colroom by -\@extrahead
+    \global\advance\@colht by -\@extrahead
+    \global\advance\vsize by -\@extrahead
+    %
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    % We're fixing a bug that was introduced by the bugfix in version
+    % 2.306beta, 2009/03/28: If the second page has a different
+    % \textheight (because of a change in either headheight or
+    % footheight between pages 1 and 2), then page 2 would use the
+    % \textheight of page 1.  Pages 3 and beyond would get the correct
+    % \textheight.
+    % The original version of this set \@colroom and \vsize to the new
+    % \textheight, but that had a bug in that if a float appeared at
+    % the top of a page, there would be no notice taken of the space
+    % lost to the float, and so the text would overrun the bottom of
+    % the page.
+    % In this bugfix, we adjust \@colroom, \@colht, and \vsize in the
+    % same way that we adjust \textheight.
+    %
     % Make it take effect RIGHT NOW!:
     % (The following stuff isn't necessary if \@setheadheight is
     % executed only in the preamble or as we return from the output
@@ -584,17 +867,23 @@
     % We *do* need to put \@colht at the correct new value, though,
     % apparently because \@colht is set near the end of the
     % output routine.
-    \global\@colht=\textheight
+%     \global\@colht=\textheight
+%
 %     \global\@colroom=\textheight
 %     \global\vsize=\textheight
 %     \global\pagegoal=\textheight
   \endgroup
-}
+}% @setheadheight
 
 \def\@setfootheight#1{%
   \begingroup % Avoid trouble from using \@temp and \@spaces
     % Reset the effect of the most recent change:
     \global\advance\textheight by \@extrafoot
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    \global\advance\@colroom by \@extrafoot
+    \global\advance\@colht by \@extrafoot
+    \global\advance\vsize by \@extrafoot
+    %
     % Save the newly set value:
     \def\@temp{#1}
     \def\@spaces{ }
@@ -609,6 +898,26 @@
     \fi
     % Set the new values:
     \global\advance\textheight by -\@extrafoot
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    \global\advance\@colroom by -\@extrafoot
+    \global\advance\@colht by -\@extrafoot
+    \global\advance\vsize by -\@extrafoot
+    %
+    % Bugfix, Version 2.510beta, 2016/10/11:
+    % We're fixing a bug that was introduced by the bugfix in version
+    % 2.306beta, 2009/03/28: If the second page has a different
+    % \textheight (because of a change in either headheight or
+    % footheight between pages 1 and 2), then page 2 would use the
+    % \textheight of page 1.  Pages 3 and beyond would get the correct
+    % \textheight.
+    % The original version of this set \@colroom and \vsize to the new
+    % \textheight, but that had a bug in that if a float appeared at
+    % the top of a page, there would be no notice taken of the space
+    % lost to the float, and so the text would overrun the bottom of
+    % the page.
+    % In this bugfix, we adjust \@colroom, \@colht, and \vsize in the
+    % same way that we adjust \textheight.
+    %
     % Make it take effect RIGHT NOW!:
     % (The following stuff isn't necessary if \@setfootheight is
     % executed only in the preamble or as we return from the output
@@ -626,16 +935,15 @@
     % We *do* need to put \@colht at the correct new value, though,
     % apparently because \@colht is set near the end of the
     % output routine.
-    \global\@colht=\textheight
+%     \global\@colht=\textheight
+%
 %     \global\@colroom=\textheight
 %     \global\vsize=\textheight
 %     \global\pagegoal=\textheight
   \endgroup
-}
+}% @setfootheight
 
 
-
-
 %---------------------------------------------------------------------
 %
 %                      *************************
@@ -1246,7 +1554,7 @@
 % the last such page with AtEndDocument.
 
 \def\numpages{\@ifundefined{exam at lastpage}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at lastpage
 }% numpages
 
@@ -1255,50 +1563,50 @@
 % so that the construction \romannumeral\numcoverpages
 % won't generate an error on the first run of latex.
 \def\numcoverpages{\@ifundefined{exam at lastcoverpage}%
-  {0\mbox{\normalfont\bf ??}}%
+  {0\mbox{\normalfont\bfseries ??}}%
   \exam at lastcoverpage
 }% numpages
 
 \def\totalnumpages{\@ifundefined{exam at totalpages}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at totalpages
 }% numpages
 
 \def\numpoints{\@ifundefined{exam at numpoints}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numpoints
 }% numpoints
 \def\numbonuspoints{\@ifundefined{exam at numbonuspoints}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numbonuspoints
 }% numbonuspoints
 
 \def\numquestions{\@ifundefined{exam at numquestions}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numquestions
 }% numquestions
 
 \def\numparts{\@ifundefined{exam at numparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numparts
 }% numparts
 
 \def\numsubparts{\@ifundefined{exam at numsubparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numsubparts
 }% numsubparts
 
 \def\numsubsubparts{\@ifundefined{exam at numsubsubparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam at numsubsubparts
 }% numsubsubparts
 
 \def\pointsofquestion#1{\@ifundefined{pointsofq@\romannumeral #1}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   {\csname pointsofq@\romannumeral #1\endcsname}%
 }% pointsofquestion
 \def\bonuspointsofquestion#1{\@ifundefined{bonuspointsofq@\romannumeral #1}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   {\csname bonuspointsofq@\romannumeral #1\endcsname}%
 }% bonuspointsofquestion
 
@@ -1311,11 +1619,11 @@
 % for that last page, and one of those will be undefined.
 %
 % \def\pointsonpage#1{\@ifundefined{pointsonpage@\romannumeral #1}%
-%   {\mbox{\normalfont\bf ??}}%
+%   {\mbox{\normalfont\bfseries ??}}%
 %   {\csname pointsonpage@\romannumeral #1\endcsname}%
 % }% pointsonpage
 % \def\bonuspointsonpage#1{\@ifundefined{bonuspointsonpage@\romannumeral #1}%
-%   {\mbox{\normalfont\bf ??}}%
+%   {\mbox{\normalfont\bfseries ??}}%
 %   {\csname bonuspointsonpage@\romannumeral #1\endcsname}%
 % }% bonuspointsonpage
 %
@@ -2125,6 +2433,18 @@
   \fi
 }% add at half
 
+% Important reminder about \ifhlfcntr at pos: Do not use it inside
+% another conditional!  The construction
+%  \ifhlfcntr at pos{somecounter}
+%    do some stuff...
+%  \fi
+% is perfectly fine as long as it's expanded, but: If it's inside
+% another conditional, and the condition is not satisfied, then it's
+% read through without expansion.  In that case, TeX sees the
+% \ifhlfcntr at pos but does *not* recognize it as being part of a
+% conditional, but when it sees the concluding \fi it does recognize
+% that, and so TeX completes the outer conditional at that \fi, which
+% causes an error.
 \newcounter{ifpos at cntr}
 \def\ifhlfcntr at pos#1{%
   % The argument must be a hlfcntr (which, of course,
@@ -3132,8 +3452,8 @@
 
 \def\@checkqueslevel#1{%
   \begingroup
-    \def\@temp{#1}%
-    \ifx\@temp\@queslevel
+    \def\exam at temp{#1}%
+    \ifx\exam at temp\@queslevel
       % Everything's fine; do nothing.
     \else
       \ClassError{exam}{%
@@ -3339,10 +3659,10 @@
 
 \def\setup at point@toks{%
 % We set the token list \point at toks equal to the sequence of commands
-% needed to put \padded at point@block at the correct location, followed by the
-% tokens ``\global \point at toks={}''.  The \question, \part, \subpart,
-% or \subsubpart command then adds the two tokens ``\the\point at toks''
-% to \everypar.
+% needed to put \padded at point@block at the correct location, followed
+% by the tokens ``\global \point at toks={}''.  The \question, \part,
+% \subpart, or \subsubpart command then adds the two tokens
+% ``\the\point at toks'' to \everypar.
 %
 % Note: It is not the *contents* of \point at toks that is added to
 % \everypar; just the two tokens ``\the\point at toks''.  This difference
@@ -3362,6 +3682,66 @@
 % tokens ``\the\point at toks'' will insert an *empty* token list, which
 % will do no harm.
 %
+  \if at pointstwosided
+    % Set \csname \q at label \endcsname equal to the thing
+    % that expands to the page number of the current (question or
+    % part or subpart or subsubpar; whatever it is), but do it
+    % carefully because, if we don't yet have page info, then it won't
+    % be defined:
+    \ifx\@queslevel\ques at ref
+      \def\q at label{Pg at question@\arabic{question}}
+    \else
+      \ifx\@queslevel\part at ref
+        \def\q at label{Pg at part@\arabic{question}@\arabic{partno}}
+      \else
+        \ifx\@queslevel\subpart at ref
+          \def\q at label{Pg at subpart@\arabic{question}%
+            @\arabic{partno}@\arabic{subpart}}
+        \else
+          \ifx\@queslevel\subsubpart at ref
+            \def\q at label{Pg at subsubpart@\arabic{question}%
+              @\arabic{partno}@\arabic{subpart}@\arabic{subsubpart}}
+          \else
+            \ClassError{exam}{%
+              This can't happen in function \protect\setup at point@toks
+              \MessageBreak
+            }{%
+              An unexplained error occurred in exam.cls;\MessageBreak
+              please inform the package maintainer, and send along
+              \MessageBreak
+              the LaTeX file that shows the error.\MessageBreak
+            }%
+          \fi
+        \fi
+      \fi
+    \fi
+    % 
+    \expandafter\ifx \csname \q at label \endcsname\relax
+      % No page info yet; put it into the right margin
+      \@pointsinrightmargintrue
+      \@pointsinleftmarginfalse
+    \else
+      \ifodd \csname \q at label \endcsname\relax
+        \if at pointsinoutsidemargin
+          \@pointsinrightmargintrue
+          \@pointsinleftmarginfalse
+        \else
+          \@pointsinrightmarginfalse
+          \@pointsinleftmargintrue
+        \fi
+      \else
+        \if at pointsinoutsidemargin
+          \@pointsinrightmarginfalse
+          \@pointsinleftmargintrue
+        \else
+          \@pointsinrightmargintrue
+          \@pointsinleftmarginfalse
+        \fi
+      \fi
+    \fi
+  \fi
+  % That ends the \if at pointstwosided.
+  % Now we actually setup \point at toks:
   \if at pointsinleftmargin
     \point at toks={%
           \llap{\padded at point@block 
@@ -3392,8 +3772,6 @@
   \fi
 }% setup at point@toks
 
-
-
 \def\droppoints{%
   \leavevmode\unskip\nobreak\hfill
   \rlap{\hskip\rightmargin  % Defined by the list environment
@@ -3404,7 +3782,6 @@
   \par
 }
 
-
 \def\droptotalpoints{%
   \leavevmode\unskip\nobreak\hfill
   \rlap{\hskip\rightmargin  % Defined by the list environment
@@ -3476,29 +3853,55 @@
 \newif\if at pointsdropped
 \newif\if at pointsinleftmargin
 \newif\if at pointsinrightmargin
+\newif\if at pointstwosided
+\newif\if at pointsinoutsidemargin
+
+% If we have \@pointstwosidedtrue and \@pointsinoutsidemarginfalse,
+% then the points will be printed on the inside margin (left on odd
+% numbered pages, right on even numbered pages).  If we have
+% \@pointstwosidedfalse, then \if at pointsinoutsidemargin is ignored.
+
+% If we have \@pointstwosidedtrue, then both \@pointsinleftmargin and
+% \@pointsinrightmargin will be flipped back and forth, as needed, in
+% \setup at point@toks.
+
 \def\pointsinleftmargin{\global\@pointsinleftmargintrue 
                     \global\@pointsinrightmarginfalse
                     \global\@pointsdroppedfalse
+                    \global\@pointstwosidedfalse
                     \gdef\pt at name{\@marginpointname}%
                     \gdef\bnspt at name{\@marginbonuspointname}}
 \def\pointsinrightmargin{\global\@pointsinrightmargintrue
                          \global\@pointsinleftmarginfalse
                          \global\@pointsdroppedfalse
+                         \global\@pointstwosidedfalse
                          \gdef\pt at name{\@marginpointname}%
                          \gdef\bnspt at name{\@marginbonuspointname}}
 \def\nopointsinmargin{\global\@pointsinleftmarginfalse
                       \global\@pointsinrightmarginfalse
                       \global\@pointsdroppedfalse
+                      \global\@pointstwosidedfalse
                       \gdef\pt at name{\@pointname}%
                       \gdef\bnspt at name{\@bonuspointname}}
 \def\pointsdroppedatright{\global\@pointsdroppedtrue
                           \global\@pointsinleftmarginfalse
                           \global\@pointsinrightmarginfalse
+                          \global\@pointstwosidedfalse
                           \gdef\pt at name{\@marginpointname}%
                           \gdef\bnspt at name{\@marginbonuspointname}}
-\let\pointsinmargin\pointsinleftmargin
-\let\nopointsinrightmargin\nopointsinmargin
-\let\nopointsinleftmargin\nopointsinmargin
+\def\pointstwosided{\global\@pointstwosidedtrue
+                    \global\@pointsinoutsidemargintrue
+                    \global\@pointsdroppedfalse
+                    \gdef\pt at name{\@marginpointname}%
+                    \gdef\bnspt at name{\@marginbonuspointname}}
+\def\pointstwosidedreversed{\global\@pointstwosidedtrue
+                            \global\@pointsinoutsidemarginfalse
+                            \global\@pointsdroppedfalse
+                            \gdef\pt at name{\@marginpointname}%
+                            \gdef\bnspt at name{\@marginbonuspointname}}
+\let\pointsinmargin=\pointsinleftmargin
+\let\nopointsinrightmargin=\nopointsinmargin
+\let\nopointsinleftmargin=\nopointsinmargin
 
 \nopointsinmargin
 
@@ -3534,7 +3937,6 @@
   % which confines the effects of anything here:
   \gdef\bonuspoint at block{#1}%
 }
-\def\thebonuspoints{\@points \@bonuspointname}
 
 %Initialize:
 \noboxedpoints
@@ -3688,6 +4090,7 @@
        \settowidth{\leftmargin}{W.\hskip\labelsep\hskip 2.5em}%
        \def\choice{%
          \if at correctchoice
+           \color at endgroup
            \endgroup
          \fi
          \item
@@ -3695,12 +4098,30 @@
        } % choice
        \def\CorrectChoice{%
          \if at correctchoice
+           \color at endgroup
            \endgroup
          \fi
          \ifprintanswers
            % We can't say \choice here, because that would
            % insert an \endgroup:
-           \begingroup \@correctchoicetrue
+           % 2016/05/10: We say \color at begingroup in addition to
+           % \begingroup in case \CorrectChoiceEmphasis involves color
+           % and the text exactly fills the line (which would
+           % otherwise create a blank line after this choice):
+           % 2016/05/11: We leave hmode if we're in it,
+           % i.e., if there's no blank line preceding this
+           % \CorrectChoice command.  (Without this, the
+           % \special created by a \color{whatever} command that might
+           % be inserted by \CorrectChoice at Emphasis would be appended 
+           % to the previous \choice, which could cause an extra
+           % (blank) line to be inserted before this \CorrectChoice.)
+           % Since \par and \endgraf seem to cancel \@totalleftmargin
+           % (for reasons I don't understand), we'll do the following:
+           % Motivated by  the def of \leavevmode, 
+           %      \def\leavevmode{\unhbox\voidb at x}
+           % we will now leave hmode (if we're in hmode):
+           \ifhmode \unskip\unskip\unvbox\voidb at x \fi
+           \begingroup \color at begingroup \@correctchoicetrue
            \CorrectChoice at Emphasis
          \fi
          \item
@@ -3713,7 +4134,7 @@
        \choiceshook
      }%
   }%
-  {\if at correctchoice \endgroup \fi \endlist}
+  {\if at correctchoice \color at endgroup \endgroup \fi \endlist}
 
 \newenvironment{oneparchoices}%
   {%
@@ -3768,7 +4189,7 @@
        \settowidth{\leftmargin}{W.\hskip\labelsep\hskip 2.5em}%
        \def\choice{%
          \if at correctchoice
-           \endgroup
+           \color at endgroup \endgroup
          \fi
          \item
          \do at choice@pageinfo
@@ -3775,12 +4196,29 @@
        } % choice
        \def\CorrectChoice{%
          \if at correctchoice
-           \endgroup
+           \color at endgroup \endgroup
          \fi
          \ifprintanswers
            % We can't say \choice here, because that would
-           % insert an \endgroup:
-           \begingroup \@correctchoicetrue
+           % insert an \endgroup.
+           % 2016/05/10: We say \color at begingroup in addition to
+           % \begingroup in case \CorrectChoiceEmphasis involves color
+           % and the text exactly fills the line (which would
+           % otherwise create a blank line after this choice):
+           % 2016/05/11: We leave hmode if we're in it,
+           % i.e., if there's no blank line preceding this
+           % \CorrectChoice command.  (Without this, the
+           % \special created by a \color{whatever} command that might
+           % be inserted by \CorrectChoice at Emphasis would be appended 
+           % to the previous \choice, which could cause an extra
+           % (blank) line to be inserted before this \CorrectChoice.)
+           % Since \par and \endgraf seem to cancel \@totalleftmargin
+           % (for reasons I don't understand), we'll do the following:
+           % Motivated by  the def of \leavevmode, 
+           %      \def\leavevmode{\unhbox\voidb at x}
+           % we will now leave hmode (if we're in hmode):
+           \ifhmode \unskip\unskip\unvbox\voidb at x \fi
+           \begingroup \color at begingroup \@correctchoicetrue
            \CorrectChoice at Emphasis
            \item[\checked at char]
          \else
@@ -3795,7 +4233,7 @@
        \checkboxeshook
      }%
   }%
-  {\if at correctchoice \endgroup \fi \endlist}
+  {\if at correctchoice \color at endgroup \endgroup \fi \endlist}
 
 \newenvironment{oneparcheckboxes}%
   {%
@@ -3851,7 +4289,8 @@
 %--------------------------------------------------------------------
 %             Answer Lines (for short answer questions)
 
-% Note: \ques at ref is also used in \item at points@pageinfo
+% Note: \ques at ref is also used in \item at points@pageinfo, and all four
+% of the following are used in \setup at point@toks
 
 \def\ques at ref{question}
 \def\part at ref{part}
@@ -3894,7 +4333,10 @@
   \ifprintanswers
     \ans at l~\hbox to 0pt{\hbox to \answerlinelength{\hrulefill}\hss}%
     \raise \answerclearance\hbox to \answerlinelength{%
-      \CorrectChoice at Emphasis \hfil #1\hss}%
+      % 2016/05/10: Added \color at begingroup and \color at endgroup:
+      \color at begingroup
+      \CorrectChoice at Emphasis \hfil #1\hss
+      \color at endgroup}%
   \else
     \ans at l~\hbox to \answerlinelength{\hrulefill}%
   \fi
@@ -3961,9 +4403,11 @@
       \setbox0 \hbox{\color at begingroup
              \CorrectChoice at Emphasis \fillin at ans \color at endgroup}%
       \ifdim\wd0 > #1\relax
-        \hbox{\CorrectChoice at Emphasis \fillin at ans}%
+        \hbox{\color at begingroup\CorrectChoice at Emphasis \fillin at ans
+              \color at endgroup}%
       \else
-        \hbox to #1{\CorrectChoice at Emphasis \hfil \fillin at ans \hfil}%
+        \hbox to #1{\color at begingroup\CorrectChoice at Emphasis 
+                    \hfil \fillin at ans \hfil\color at endgroup}%
       \fi
     \endgroup
   \else
@@ -4043,18 +4487,71 @@
 % \setlength\linefillthickness{.1pt}
 %
 % This value can be changed by giving a new \setlength command.
+%
+% As of version 2.503, 2016/03/25, the lines drawn by the
+% \fillwithlines command will be drawn in color if the user has given
+% the command
+%
+%      \colorfillwithlines.
+%
+% The actual drawing of the lines is now done by the command
+% \do at fillwithlines, after the \fillwithlines command decides whether
+% they will be in color.  The default color is set by the command
+%
+%      \definecolor{FillWithLinesColor}{gray}{0.8}
+%
+% and the color can be changed by giving a new \definecolor command.
+% You can return to black lines by giving the command
+%
+%      \nocolorfillwithlines
 
-
 \newlength\linefillheight
 \newlength\linefillthickness
 \setlength\linefillheight{.25in}
 \setlength\linefillthickness{0.1pt}
 
+
+
+
+\newif\if at colorfillwithlines
+\@colorfillwithlinesfalse
+\def\colorfillwithlines{%
+  \@ifundefined{definecolor}
+  {%
+    \ClassError{exam}{%
+      You must load the color package with the command\MessageBreak
+      \space\space\protect\usepackage{color}\MessageBreak
+      in order to use the command \protect\colorfillwithlines
+      \MessageBreak
+      }{%
+      This command makes use of the package color.sty,\MessageBreak
+      and so you have to load color.sty before your\MessageBreak
+      \protect\begin{document} command.\MessageBreak
+      }%
+  }%
+  {%
+    \definecolor{FillWithLinesColor}{gray}{0.8}
+    \@colorfillwithlinestrue
+  }%
+}% \colorfillwithlines
+\def\nocolorfillwithlines{\@colorfillwithlinesfalse}
+
+\newcommand\fillwithlines[1]{%
+  \if at colorfillwithlines
+    \color at begingroup
+      \color{FillWithLinesColor}%
+      \do at fillwithlines{#1}%
+    \color at endgroup
+  \else
+    \do at fillwithlines{#1}%
+  \fi
+}% \fillwithlines
+
 \newcommand\linefill{\leavevmode
     \leaders\hrule height \linefillthickness \hfill\kern\z@}
 
-
-\def\fillwithlines#1{%
+% \do at fillwithlines is called only by \fillwithlines
+\def\do at fillwithlines#1{%
   \begingroup
   \ifhmode
     \par
@@ -4069,7 +4566,7 @@
   % no matter where on the page it happens to start:
   \cleaders \copy0 \vskip #1 \hbox{}%
   \endgroup
-}
+}% \do at fillwithlines
 
 %--------------------------------------------------------------------
 %                         \fillwithdottedlines
@@ -4093,10 +4590,64 @@
 %
 % This value can be changed by giving a new \setlength command.
 
+% As of version 2.503, 2016/03/25, the dotted lines drawn by the
+% \fillwithdottedlines command will be drawn in color if the user has
+% given the command
+%
+%      \colorfillwithdottedlines.
+%
+% The actual drawing of the lines is now done by the command
+% \do at fillwithdottedlines, after the \fillwithdottedlines command
+% decides whether they will be in color.  The default color is set by
+% the command 
+%
+%      \definecolor{FillWithDottedLinesColor}{gray}{0.8}
+%
+% and the color can be changed by giving a new \definecolor command.
+% You can return to black lines by giving the command
+%
+%      \nocolorfillwithdottedlines
+
+
 \newlength\dottedlinefillheight
 \setlength\dottedlinefillheight{.25in}
 
-\def\fillwithdottedlines#1{%
+\newif\if at colorfillwithdottedlines
+\@colorfillwithdottedlinesfalse
+\def\colorfillwithdottedlines{%
+  \@ifundefined{definecolor}
+  {%
+    \ClassError{exam}{%
+      You must load the color package with the command\MessageBreak
+      \space\space\protect\usepackage{color}\MessageBreak
+      in order to use the command \protect\colorfillwithdottedlines
+      \MessageBreak
+      }{%
+      This command makes use of the package color.sty,\MessageBreak
+      and so you have to load color.sty before your\MessageBreak
+      \protect\begin{document} command.\MessageBreak
+      }%
+  }%
+  {%
+    \definecolor{FillWithDottedLinesColor}{gray}{0.8}
+    \@colorfillwithdottedlinestrue
+  }%
+}% \colorfillwithdottedlines
+\def\nocolorfillwithdottedlines{\@colorfillwithdottedlinesfalse}
+
+\newcommand\fillwithdottedlines[1]{%
+  \if at colorfillwithdottedlines
+    \color at begingroup
+      \color{FillWithDottedLinesColor}%
+      \do at fillwithdottedlines{#1}%
+    \color at endgroup
+  \else
+    \do at fillwithdottedlines{#1}%
+  \fi
+}% \fillwithdottedlines
+
+% \do at fillwithdottedlines is called only by \fillwithdottedlines
+\def\do at fillwithdottedlines#1{%
   \begingroup
   \ifhmode
     \par
@@ -4111,7 +4662,7 @@
   % no matter where on the page it happens to start:
   \cleaders \copy0 \vskip #1 \hbox{}%
   \endgroup
-}% fillwithdottedlines
+}% \do at fillwithdottedlines
 
 %--------------------------------------------------------------------
 %                            \fillwithgrid
@@ -4261,7 +4812,26 @@
 \newlength\minboxheight
 \setlength\minboxheight{.1in}
 
+
+% As of version 2.502, 2016/03/23, the frame drawn by the
+% \makeemptybox command will be drawn in color if the user has given
+% the command \colorsolutionboxes.  The actual drawing of the box is
+% now done by the command \do at emptybox, after the \makeemptybox
+% command decides whether it will be in color.
+
 \newcommand\makeemptybox[1]{%
+  \if at colorsolutionboxes
+    \color at begingroup
+      \color{SolutionBoxColor}%
+      \do at emptybox{#1}%
+    \color at endgroup
+  \else
+    \do at emptybox{#1}%
+  \fi
+}
+
+% The command \do at emptybox is called only by \makeemptybox.
+\newcommand\do at emptybox[1]{%
   \par
   \hbox to \hsize{\hskip\@totalleftmargin \leaders\hrule\hfill}%
   \nointerlineskip
@@ -4476,16 +5046,45 @@
 \cvsword{Score}
 \cvtword{Total:}
 
-
-% The only commands here accessible to the user are \gradetable,
-% \bonusgradetable, \combinedgradetable, \pointtable,
-% \bonuspointtable, \combinedpointtable, \partialgradetable,
+% Before we created multirow and multicolumn tables, he only commands
+% here accessible to the user were \gradetable, \bonusgradetable,
+% \combinedgradetable, \pointtable, \bonuspointtable,
+% \combinedpointtable, \partialgradetable,
 % \partialbonusgradetable, \partialcombinedtable, \partialpointtable,
 % \partialbonuspointtable, \partialcombinedpointtable,
 % \begingradingrange, \endgradingrange, \pointsinrange,
 % \bonuspointsinrange, \firstqinrange, \lastqinrange, and
-% \numqinrange.
+% \numqinrange.  The new user commands are
+% 
+% \def\multirowgradetable
+% \def\multirowpointtable
+% \def\multirowbonusgradetable
+% \def\multirowbonuspointtable
+% \def\multirowcombinedgradetable
+% \def\multirowcombinedpointtable
+% 
+% \def\multirowpartialgradetable
+% \def\multirowpartialpointtable
+% \def\multirowpartialbonusgradetable
+% \def\multirowpartialbonuspointtable
+% \def\multirowpartialcombinedgradetable
+% \def\multirowpartialcombinedpointtable
+% 
+% \def\multicolumngradetable
+% \def\multicolumnpointtable
+% \def\multicolumnbonusgradetable
+% \def\multicolumnbonuspointtable
+% \def\multicolumncombinedgradetable
+% \def\multicolumncombinedpointtable
+% 
+% \def\multicolumnpartialgradetable
+% \def\multicolumnpartialpointtable
+% \def\multicolumnpartialbonusgradetable
+% \def\multicolumnpartialbonuspointtable
+% \def\multicolumnpartialcombinedgradetable
+% \def\multicolumnpartialcombinedpointtable
 
+
 % The possibilities are
 
 %   \gradetable[v][questions]
@@ -4559,7 +5158,36 @@
 %    \numqinrange{whatever}
 %
 % where ``whatever'' is a label chosen by the user.
+% 
+% \def\multirowgradetable{numcols}[questions or pages]
+% \def\multirowpointtable{numcols}[questions or pages]
+% \def\multirowbonusgradetable{numcols}[questions or pages]
+% \def\multirowbonuspointtable{numcols}[questions or pages]
+% \def\multirowcombinedgradetable{numcols}[questions or pages]
+% \def\multirowcombinedpointtable{numcols}[questions or pages]
+% 
+% \def\multirowpartialgradetable{numcols}{rangename}[questions or pages]
+% \def\multirowpartialpointtable{numcols}{rangename}[questions or pages]
+% \def\multirowpartialbonusgradetable{numcols}{rangename}[questions or pages]
+% \def\multirowpartialbonuspointtable{numcols}{rangename}[questions or pages]
+% \def\multirowpartialcombinedgradetable{numcols}{rangename}[questions or pages]
+% \def\multirowpartialcombinedpointtable{numcols}{rangename}[questions or pages]
+% 
+% \def\multicolumngradetable{numrows}[questions or pages]
+% \def\multicolumnpointtable{numrows}[questions or pages]
+% \def\multicolumnbonusgradetable{numrows}[questions or pages]
+% \def\multicolumnbonuspointtable{numrows}[questions or pages]
+% \def\multicolumncombinedgradetable{numrows}[questions or pages]
+% \def\multicolumncombinedpointtable{numrows}[questions or pages]
 
+% \def\multicolumnpartialgradetable{numrows}{rangename}[questions or pages]
+% \def\multicolumnpartialpointtable{numrows}{rangename}[questions or pages]
+% \def\multicolumnpartialbonusgradetable{numrows}{rangename}[questions or pages]
+% \def\multicolumnpartialbonuspointtable{numrows}{rangename}[questions or pages]
+% \def\multicolumnpartialcombinedgradetable{numrows}{rangename}[questions or pages]
+% \def\multicolumnpartialcombinedpointtable{numrows}{rangename}[questions or pages]
+
+
 % If one or both optional arguments are omitted, the defaults are
 % `[v]' and `[questions]'.
 
@@ -4573,8 +5201,9 @@
 % \partialcombinedpointtable:
 \newif\if at partial
 
-% \@combinedtrue means we're doing \combinedgradetable
-% or \combinedpointtable:
+% \@combinedtrue means we're doing \combinedgradetable,
+% \combinedpointtable, \partialcombinedgradetable, or
+% \partialcombinedpointtable:
 \newif\if at combined
 
 % It's OK to use the counter num at cols as a scratch counter
@@ -4606,8 +5235,6 @@
 }% endgradingrange
 
 
-
-
 % Now that grading tables may be for only part of the exam,
 % we need the counter tbl at points to add up the total points
 % for the questions (or pages) that appear on the table:
@@ -4617,7 +5244,249 @@
 % points for the questions (or pages) that appear on the table:
 \new at hlfcntr{tbl at bonuspoints}
 
+%--------------------------------------------------------------------
+% multirow tables, non-partial:
 
+\def\multirowgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowbonusgradetable#1{%
+  \@scorestrue
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowbonuspointtable#1{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowcombinedgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowcombinedpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+%--------------------------------------------------------------------
+% multirow tables, partial:
+
+\def\multirowpartialgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpartialpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpartialbonusgradetable#1#2{%
+  \@scorestrue
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpartialbonuspointtable#1#2{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpartialcombinedgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+\def\multirowpartialcombinedpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl at range{#2}%
+  \setcounter{num at rows}{#1}%
+  \i at gtable[h]%
+}
+
+%--------------------------------------------------------------------
+%  multicolumn tables, non-partial:
+
+\def\multicolumngradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnbonusgradetable#1{%
+  \@scorestrue
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnbonuspointtable#1{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumncombinedgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumncombinedpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+%--------------------------------------------------------------------
+% multicolumn tables, partial:
+
+\def\multicolumnpartialgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpartialpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpartialbonusgradetable#1#2{%
+  \@scorestrue
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpartialbonuspointtable#1#2{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpartialcombinedgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+\def\multicolumnpartialcombinedpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl at range{#2}%
+  \setcounter{num at cols}{#1}%
+  \i at gtable[v]%
+}
+
+%--------------------------------------------------------------------
+% partial single row (and column) tables:
+
 \def\partialgradetable#1{%
   \@scorestrue
   \@bonusfalse
@@ -4624,6 +5493,12 @@
   \@partialtrue
   \@combinedfalse
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4636,6 +5511,12 @@
   \@partialtrue
   \@combinedfalse
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4648,6 +5529,12 @@
   \@partialtrue
   \@combinedtrue
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4660,6 +5547,12 @@
   \@partialtrue
   \@combinedfalse
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4672,6 +5565,12 @@
   \@partialtrue
   \@combinedfalse
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4684,6 +5583,12 @@
   \@partialtrue
   \@combinedtrue
   \def\tbl at range{#1}%
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4691,6 +5596,7 @@
 }% partialcombinedpointtable
 
 %--------------------------------------------------------------------
+% single row (and column) tables, non-partial:
 
 \def\gradetable{%
   \@scorestrue
@@ -4697,6 +5603,12 @@
   \@bonusfalse
   \@partialfalse
   \@combinedfalse
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4708,6 +5620,12 @@
   \@bonustrue
   \@partialfalse
   \@combinedfalse
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4719,6 +5637,12 @@
   \@bonusfalse
   \@partialfalse
   \@combinedtrue
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4730,6 +5654,12 @@
   \@bonusfalse
   \@partialfalse
   \@combinedfalse
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4741,6 +5671,12 @@
   \@bonustrue
   \@partialfalse
   \@combinedfalse
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4752,6 +5688,12 @@
   \@bonusfalse
   \@partialfalse
   \@combinedtrue
+  % We don't yet know if the table is vertical or horizontal, and so
+  % we don't know if we need to set num at cols or num at rows.  We'll set
+  % them both, and we'll later on just ignore the value of the one
+  % that we didn't need to set here:
+  \setcounter{num at cols}{1}%
+  \setcounter{num at rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
@@ -4764,7 +5706,7 @@
 % (the defaults being [v] and [questions]) and then make sure
 % that the user said \addpoints and that this isn't the
 % first run of LaTeX.
-% \do at table then branches, depending on whether the user
+% \find at p@or at q@range then branches, depending on whether the user
 % selected [questions] or [pages].
 
 \def\i at gtable[#1]{%
@@ -4774,10 +5716,11 @@
   \@ifnextchar[{\ii at gtable{#1}}{\ii at gtable{#1}[questions]}%
 }
 \def\ii at gtable#1[#2]{%
+  % We get here from \i at gtable.
   % We make sure the user said \addpoints, and then make sure
   % that this isn't the first run of LaTeX (by checking that
   % \exam at numpoints is defined).  If both of those are OK,
-  % we go on to \do at table to see whether we're doing a table
+  % we go to \find at p@or at q@range to see whether we're doing a table
   % indexed by questions or by pages.
   \if at addpoints
     \@ifundefined{exam at numpoints}%
@@ -4788,7 +5731,7 @@
         }%
         \fbox{Run \LaTeX{} again to produce the table}%
       }%
-      {\do at table{#1}{#2}}%
+      {\find at p@or at q@range{#1}{#2}}%
   \else
     \ClassError{exam}{%
       You must give the command \protect\addpoints\MessageBreak
@@ -4801,46 +5744,50 @@
   \fi
 }% ii at gtable
 
-
 \def\@questionsref{questions}
 \def\@pagesref{pages}
-\def\do at table#1#2{%
-  % The first argument is ``v'' or ``h'';
-  % the second argument is ``questions'' or ``pages''.
+\def\find at p@or at q@range#1#2{%
+  % We get here from \ii at gtable.
+  % The first argument should be ``v'' or ``h'';
+  % the second argument should be ``questions'' or ``pages''.
   % See whether we're doing a table indexed by
-  % questions or by pages:
-  \begingroup % avoid trouble from using \@temp
-    \def\@temp{#2}%
-    \ifx\@temp\@questionsref
+  % questions (in which case we go to \find at qrange) or by pages (in
+  % which case we go to \find at prange):
+  \begingroup
+    % We've begun a group that will contain the construction of the
+    % table, to confine the effect of any \def's that we use.
+    \def\exam at temp{#2}%
+    \ifx\exam at temp\@questionsref
+      \tbl at pgsfalse
       \find at qrange{#1}%
     \else
-      \ifx\@temp\@pagesref
+      \ifx\exam at temp\@pagesref
+        \tbl at pgstrue
         \find at prange{#1}%
       \else
         \ClassError{exam}{%
-          The second optional argument to a\MessageBreak
-           \space\space  grade table or point table command\MessageBreak 
-            \space \space must be either `questions' or
-            `pages',\MessageBreak
-            \space\space not `#2'.\MessageBreak
+          Grade and point tables can be indexed\MessageBreak
+          \space\space by either `questions' or `pages',\MessageBreak
+          \space\space but not by `#2'.\MessageBreak
         }{%
           Grade tables and point tables can be indexed by questions or
           pages;\MessageBreak
           \space\space for others, you're on your own.\MessageBreak
         }%
-        \fbox{Error: grade or point table: Invalid second
-                          optional argument `#2'.}%
+        \fbox{\textbf{Error:} grade or point table: Invalid argument
+              `#2' must be `questions' or `pages'.}%
       \fi
     \fi
   \endgroup
-}% do at table
+}% find at p@or at q@range
 
-
+% \range at undefined can be called from either \find at qrange or
+% \find at prange
 \def\range at undefined{%
   \fbox{Warning: grading range `\tbl at range ' not defined;
                                run \LaTeX{} again.}%
   \ClassWarning{exam}{%
-    Grading range `\tbl at range ' not defined.\MessageBreak
+    Grading range `\tbl at range' not defined.\MessageBreak
     \space\space Run LaTeX again to produce the table.\MessageBreak
   }%
 }% range at undefined
@@ -4850,16 +5797,18 @@
 %--------------------------------------------------------------------
 % Grade and point tables indexed by question numbers:
 
-% We get to \find at qrange if we know we're doing a table indexed
-% by question numbers.  The argument is either ``v'' or ``h''.
-% If we're not doing a partial table, then \find at qrange sets
-% \tbl at firstq to 1 and \tbl at lastq to \numquestions.  Otherwise,
-% \find at qrange makes sure the grading range is defined and that
-% its last question isn't before its first question.
-% \find at qrange then calls \@tblquestions, passing along the argument
-% that is either ``v'' or ``h''.
+% When we get to \find at qrange, we know we're doing a table indexed by
+% question numbers and that this is not the first run of latex.  The
+% argument is either ``v'' or ``h''.  If we're not doing a partial
+% table, then \find at qrange sets \tbl at firstq and \first at pq@index to 1
+% and \tbl at lastq and \last at pq@index to \numquestions.  Otherwise,
+% \find at qrange makes sure the grading range is defined and that its
+% last question isn't before its first question.  \find at qrange then
+% calls \tbl at v@or at h, passing along the argument that is either ``v''
+% or ``h''.
 
 \def\find at qrange#1{%
+  % We get here from \find at p@or at q@range.
   % We're doing a table indexed by question numbers.
   \if at partial
     \@ifundefined{range@\tbl at range @firstq}%
@@ -4874,12 +5823,14 @@
           {%
             \edef\tbl at firstq{\csname range@\tbl at range @firstq\endcsname}%
             \edef\tbl at lastq{\csname range@\tbl at range @lastq\endcsname}%
+            \let\first at pq@index=\tbl at firstq
+            \let\last at pq@index=\tbl at lastq
             % Check that firstq precedes or equals lastq:
             \ifnum \tbl at firstq > \tbl at lastq\relax
-              \fbox{Error: Grading Range `\tbl at range ':
+              \fbox{\textbf{Error:} Grading Range `\tbl at range':
                       Last question precedes first question.}%
               \ClassError{exam}{%
-                In grading range `\tbl at range ',
+                In grading range `\tbl at range',
                                 the last question\MessageBreak
                 \space\space comes before the first question.\MessageBreak
                 }{%
@@ -4888,466 +5839,54 @@
                              least one question.\MessageBreak
                 }%
             \else
-              \@tblquestions{#1}%
+              \tbl at v@or at h{#1}%
             \fi
           }%
       }%
   \else
     \def\tbl at firstq{1}%
+    \let\first at pq@index=\tbl at firstq
     % \numquestions is always defined, even if this is the first
     % run of LaTeX and \exam at numquestions isn't defined.
     % If it's the first run of LaTeX, then its value isn't useful,
     % but it's never used until a later run (when its value is useful).
     \def\tbl at lastq{\numquestions}%
-    \@tblquestions{#1}%
+    \let\last at pq@index=\tbl at lastq
+    \tbl at v@or at h{#1}%
   \fi
 }% find at qrange
 
-\def\@tblquestions#1{%
-  % \tbl at firstq and \tbl at lastq have already been set.
-  % The argument is either ``v'' or ``h', and we branch accordingly.
-  \if v#1%
-    \@vtblquestions
+\def\@vref{v}
+\def\@href{h}
+\def\tbl at v@or at h#1{%
+  % \first at pq@index=\tbl at firstq or \tbl at firstp and
+  % \last at pq@index=\tbl at lastq or \tbl at lastp have already been set.
+  % The argument should be either `v' or `h', and we branch
+  % accordingly.
+  \def\exam at temp{#1}%
+  \ifx\exam at temp\@vref
+    \check at num@cols at v
   \else
-    \if h#1%
-      \@htblquestions
+    \ifx\exam at temp\@href
+      \check at num@rows at h
     \else
       \ClassError{exam}{%
-        The first optional argument to \protect\gradetable,\MessageBreak
-        \space\space \protect\bonusgradetable,
-        \protect\pointtable, or\MessageBreak
-        \space\space  \protect\bonuspointtable
-        \space must be either `h' or `v'\MessageBreak
+        Grade or point table: the argument `#1'\MessageBreak
+        \space\space must be `v' or `h'.
+        \MessageBreak
       }{%
-        Grade tables and point tables can be either horizontal or
-        vertical;\MessageBreak
+        Grade tables and point tables can be either vertical or
+        horizontal;\MessageBreak
         \space\space no diagonals allowed.\MessageBreak
       }%
-      \if at scores
-        \if at bonus
-          \fbox{Error: bonusgradetable: Invalid first optional argument
-                                                        `#1'.}%
-        \else
-          \fbox{Error: gradetable: Invalid first optional argument
-                                                        `#1'.}%
-        \fi
-      \else
-        \if at bonus
-          \fbox{Error: bonuspointtable: Invalid first optional argument
-                                                    `#1'.}%
-        \else
-          \fbox{Error: pointtable: Invalid first optional argument
-                                                    `#1'.}%
-        \fi
-      \fi
+      \fbox{\textbf{Error:} grade or point table: Invalid argument
+            `#1' must be `v' or `h'.}%
     \fi
   \fi
-}% @tblquestions
+}% tbl at v@or at h
 
 %--------------------------------------------------------------------
-% Vertical, indexed by question numbers:
-
-\def\@vtblquestions{%
-  % The table is vertical and indexed by question numbers.
-  % The question range has already been determined.
-  % Branch according to whether it's bonus, non-bonus, or combined,
-  % and gradetable or pointtable:
-  \begingroup
-    % Save the current value of question in @iterator, so that
-    % we can restore it after doing the table:
-    \setcounter{@iterator}{\value{question}}%
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \if at bonus
-      \if at scores
-        \@bvgrdtblquestions
-      \else
-        \@bvpttblquestions
-      \fi
-    \else
-      \if at combined
-        \if at scores
-          \@cvgrdtblquestions
-        \else
-          \@cvpttblquestions
-        \fi
-      \else
-        \if at scores
-          \@vgrdtblquestions
-        \else
-          \@vpttblquestions
-        \fi
-      \fi
-    \fi
-    % Restore the saved value of question:
-    \setcounter{question}{\value{@iterator}}%
-  \endgroup
-}% @vtblquestions
-
-\def\@cvgrdtblquestions{%
-  % Vertical combined gradetable, indexed by questions
-  \set at hlfcntr{tbl at points}{0}%
-  \set at hlfcntr{tbl at bonuspoints}{0}%
-  \begin{tabular}{|c|c|c|c|}
-    \hline
-    {\@cvqword}& {\@cvpword}& {\@cvbpword}& {\@cvsword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at cvloop
-    {\@cvtword}& \prt at tablepoints&
-      \prt at tablebonuspoints&
-      \hbox to \@cellwidth{\hfill}\\
-    \hline
-  \end{tabular}%
-}
-\def\do at cvloop{%
-  % \do at cvloop is used by \@cvgrdtblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-    \pointsofquestion{\arabic{question}}&
-    \bonuspointsofquestion{\arabic{question}}&\\
-  \hline
-  \@ifundefined{pointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at points}
-       {\csname pointsofq@\romannumeral \c at question\endcsname}}%
-  \@ifundefined{bonuspointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at bonuspoints}
-       {\csname bonuspointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at cvloop=\do at cvloop
-  \else
-    \let\next at cvloop=\relax
-  \fi
-  \next at cvloop
-}% do at cvloop
-
-
-\def\@cvpttblquestions{%
-  % Vertical combined pointtable, indexed by questions
-  \set at hlfcntr{tbl at points}{0}%
-  \set at hlfcntr{tbl at bonuspoints}{0}%
-  \begin{tabular}{|c|c|c|}
-    \hline
-    {\@cvqword}& {\@cvpword}& {\@cvbpword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at cvptloop
-    {\@cvtword}& \prt at tablepoints&
-      \prt at tablebonuspoints\\
-    \hline
-  \end{tabular}%
-}
-\def\do at cvptloop{%
-  % \do at cvptloop is used by \@cvpttblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-    \pointsofquestion{\arabic{question}}&
-    \bonuspointsofquestion{\arabic{question}}\\
-  \hline
-  \@ifundefined{pointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at points}
-       {\csname pointsofq@\romannumeral \c at question\endcsname}}%
-  \@ifundefined{bonuspointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at bonuspoints}
-       {\csname bonuspointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at cvptloop=\do at cvptloop
-  \else
-    \let\next at cvptloop=\relax
-  \fi
-  \next at cvptloop
-}% do at cvptloop
-
-
-\def\@bvgrdtblquestions{%
-  % Vertical bonus gradetable, indexed by questions:
-  \set at hlfcntr{tbl at bonuspoints}{0}%
-  \begin{tabular}{|c|c|c|}
-    \hline
-    {\@bvqword}& {\@bvpword}& {\@bvsword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at bvloop
-    {\@bvtword}& \prt at tablebonuspoints&\hbox to \@cellwidth{\hfill}\\
-    \hline
-  \end{tabular}%
-}% @bvgrdtblquestions
-\def\do at bvloop{%
-  % \do at bvloop is used by \@bvgrdtblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-        \bonuspointsofquestion{\arabic{question}}&\\
-  \hline
-  \@ifundefined{bonuspointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at bonuspoints}
-       {\csname bonuspointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at bvloop=\do at bvloop
-  \else
-    \let\next at bvloop=\relax
-  \fi
-  \next at bvloop
-}% do at bvloop
-
-
-\def\@bvpttblquestions{%
-  % Vertical bonus point table, indexed by questions:
-  \set at hlfcntr{tbl at bonuspoints}{0}%
-  \begin{tabular}{|c|c|}
-    \hline
-    {\@bvqword}& {\@bvpword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at bvptloop
-    {\@bvtword}& \prt at tablebonuspoints\\
-    \hline
-  \end{tabular}%
-}% @bvpttblquestions
-\def\do at bvptloop{%
-  % \do at bvptloop is used by \@bvpttblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-        \bonuspointsofquestion{\arabic{question}}\\
-  \hline
-  \@ifundefined{bonuspointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at bonuspoints}
-       {\csname bonuspointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at bvptloop=\do at bvptloop
-  \else
-    \let\next at bvptloop=\relax
-  \fi
-  \next at bvptloop
-}% do at bvptloop
-
-\def\@vgrdtblquestions{%
-  % Vertical non-bonus grade table, indexed by questions:
-  \set at hlfcntr{tbl at points}{0}%
-  \begin{tabular}{|c|c|c|}
-    \hline
-    {\@vqword}& {\@vpword}& {\@vsword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at vloop
-    {\@vtword}& \prt at tablepoints&\hbox to \@cellwidth{\hfill}\\
-    \hline
-  \end{tabular}%
-}% @vgrdtblquestions
-\def\do at vloop{%
-  % \do at vloop is used by \@vgrdtblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-        \pointsofquestion{\arabic{question}}&\\
-  \hline
-  \@ifundefined{pointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at points}
-       {\csname pointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at vloop=\do at vloop
-  \else
-    \let\next at vloop=\relax
-  \fi
-  \next at vloop
-}% do at vloop
-
-
-\def\@vpttblquestions{%
-  % Vertical non-bonus point table, indexed by questions:
-  \set at hlfcntr{tbl at points}{0}%
-  \begin{tabular}{|c|c|}
-    \hline
-    {\@vqword}& {\@vpword}\\
-    \hline
-    \setcounter{question}{\tbl at firstq}%
-    \addtocounter{question}{-1}\do at vptloop
-    {\@vtword}& \prt at tablepoints\\
-    \hline
-  \end{tabular}%
-}% @vpttblquestions
-\def\do at vptloop{%
-  % \do at vptloop is used by \@vpttblquestions
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}} &
-        \pointsofquestion{\arabic{question}}\\
-  \hline
-  \@ifundefined{pointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at points}
-       {\csname pointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at vptloop=\do at vptloop
-  \else
-    \let\next at vptloop=\relax
-  \fi
-  \next at vptloop
-}% do at vptloop
-
-
 %--------------------------------------------------------------------
-% Horizontal, indexed by question numbers:
-
-\def\@htblquestions{%
-  % We get here from \@tblquestions.
-  % The table is horizontal and indexed by question numbers.
-  % The question range has already been determined.
-  % Set num at cols equal to the number of questions on the table, and
-  % do either bonus, combined, or non-bonus table, in each case
-  % putting in a line for scores only if it's a gradetable:
-  \@ifundefined{exam at numquestions}%
-  {}%
-  {%
-    \setcounter{num at cols}{\tbl at lastq}%
-    \addtocounter{num at cols}{-\tbl at firstq}%
-    \addtocounter{num at cols}{1}%
-  }%
-  % Do either bonus, combined, or non-bonus table.  In each case, put
-  % in a line for scores if it's a gradetable:
-  \begingroup
-    % Save the current value of question in @iterator, so that
-    % we can restore it after doing the table:
-    \setcounter{@iterator}{\value{question}}%
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \if at bonus
-      % It's a horizontal bonus table, by questions:
-      \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-        \hline
-        {\@bhqword}& \setcounter{question}{\tbl at firstq}%
-        \addtocounter{question}{-1}\do at qnumloop
-        {\@bhtword}\\
-        \hline
-        \set at hlfcntr{tbl at bonuspoints}{0}%
-        {\@bhpword}& \setcounter{question}{\tbl at firstq}%
-        \addtocounter{question}{-1}\do at bptloop
-        \prt at tablebonuspoints\\
-        \hline
-        % If it's a grade table, add in the score line:
-        \if at scores
-          {\@bhsword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at sloop
-          \\
-          \hline
-        \fi
-      \end{tabular}%
-    \else
-      \if at combined
-        % It's a horizontal combined table, by questions:
-        \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-          \hline
-          {\@chqword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at qnumloop
-          {\@chtword}\\
-          \hline
-          \set at hlfcntr{tbl at points}{0}%
-          {\@chpword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at ptloop
-          \prt at tablepoints\\
-          \hline
-          \set at hlfcntr{tbl at bonuspoints}{0}%
-          {\@chbpword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at bptloop
-          \prt at tablebonuspoints\\
-          \hline
-          % If it's a grade table, add in the score line:
-          \if at scores
-            {\@chsword}& \setcounter{question}{\tbl at firstq}%
-            \addtocounter{question}{-1}\do at sloop
-            \\
-            \hline
-          \fi
-        \end{tabular}%
-      \else
-        % Horizontal non-bonus table, indexed by question number:
-        \set at hlfcntr{tbl at points}{0}%
-        \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-          \hline
-          {\@hqword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at qnumloop
-          {\@htword}\\
-          \hline
-          {\@hpword}& \setcounter{question}{\tbl at firstq}%
-          \addtocounter{question}{-1}\do at ptloop
-          \prt at tablepoints\\
-          \hline
-          % If it's a grade table, add in the score line:
-          \if at scores
-            {\@hsword}& \setcounter{question}{\tbl at firstq}%
-            \addtocounter{question}{-1}\do at sloop
-            \\
-            \hline
-          \fi
-        \end{tabular}%
-      \fi
-    \fi
-    % Restore the saved value of question:
-    \setcounter{question}{\value{@iterator}}%
-  \endgroup
-}% @htblquestions
-
-
-% \do at qnumloop and \do at sloop are used by non-bonus,
-% combined, and bonus tables:
-\def\do at qnumloop{%
-  \addtocounter{question}{1}%
-  \ref{question@\arabic{question}}&
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at qnloop=\do at qnumloop
-  \else
-    \let\next at qnloop=\relax
-  \fi
-  \next at qnloop
-}% do at qnumloop
-\def\do at sloop{%
-  \addtocounter{question}{1}%
-  \hbox to \@cellwidth{\hfill}&
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at sloop=\do at sloop
-  \else
-    \let\next at sloop=\relax
-  \fi
-  \next at sloop
-}% do at sloop
-
-%  \do at ptloop is used by combined and non-bonus tables:
-\def\do at ptloop{%
-  \addtocounter{question}{1}%
-  \pointsofquestion{\arabic{question}}&
-  \@ifundefined{pointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at points}
-       {\csname pointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at ptloop=\do at ptloop
-  \else
-    \let\next at ptloop=\relax
-  \fi
-  \next at ptloop
-}% do at ptloop
-
-%  \do at bptloop is used by bonus and combined tables:
-\def\do at bptloop{%
-  \addtocounter{question}{1}%
-  \bonuspointsofquestion{\arabic{question}}&
-  \@ifundefined{bonuspointsofq@\romannumeral \c at question}%
-    {}%
-    {\addto at hlfcntr{tbl at bonuspoints}
-       {\csname bonuspointsofq@\romannumeral \c at question\endcsname}}%
-  \ifnum \value{question} < \tbl at lastq\relax
-    \let\next at bptloop=\do at bptloop
-  \else
-    \let\next at bptloop=\relax
-  \fi
-  \next at bptloop
-}% do at bptloop
-
-
-%--------------------------------------------------------------------
-%--------------------------------------------------------------------
 % Grade and point tables indexed by page numbers:
 
 
@@ -5374,18 +5913,23 @@
 % \find at prange makes sure the grading range is defined and that its
 % last page isn't before its first page (if it's a partial table).  In
 % any case, it then sets \tbl at firstp and \tbl at lastp, and calls
-% \@tblpages.
+% \check at secondrun.
 
 \def\find at prange#1{%
+  % We get here from \find at p@or at q@range.
   % We're doing a table indexed by pages.
   % The argument is either ``v'' or ``h''.
-  % After determining the first and last page of the range, we
-  % call \@tblpages, passing it that argument (i.e., we say
-  % \@tblpages{#1}).
-  % If not a partial table, we set \tbl at firstp to 1 and \tbl at lastp to
+  % We first determine the first and last page of the range, storing
+  % those in \first at pq@index=\tbl at firstp and
+  % \last at pq@index=\tbl at lastp. If not a partial table, we set
+  % \first at pq@index=\tbl at firstp to 1 and \last at pq@index=\tbl at lastp to
   % the last page with the appropriate points (and so if it's a
   % combined table, it's the last page to have either bonus or
   % non-bonus points).
+  % We then call \check at secondrun, passing it the argument that we
+  % received (i.e., we say \check at secondrun{#1}) to make sure 
+  % we've done at least two runs of latex (so that we'll have the
+  % information we need about which pages have points on them).
   \if at partial
     \@ifundefined{range@\tbl at range @firstp}%
       {%
@@ -5399,12 +5943,14 @@
           {%
             \edef\tbl at firstp{\csname range@\tbl at range @firstp\endcsname}%
             \edef\tbl at lastp{\csname range@\tbl at range @lastp\endcsname}%
+            \let\first at pq@index=\tbl at firstp
+            \let\last at pq@index=\tbl at lastp
             % Check that firstp precedes or equals lastp:
             \ifnum \tbl at firstp > \tbl at lastp\relax
-              \fbox{Error: Grading Range `\tbl at range ':
+              \fbox{\textbf{Error:} Grading Range `\tbl at range ':
                       Last page precedes first page.}%
               \ClassError{exam}{%
-                In grading range `\tbl at range ', the last page\MessageBreak
+                In grading range `\tbl at range', the last page\MessageBreak
                 \space\space comes before the first page.\MessageBreak
                 }{%
                   \string\begingradingrange \space must precede
@@ -5411,792 +5957,1503 @@
                   \string\endgradingrange.\MessageBreak
                 }%
             \else
-              \@tblpages{#1}%
+              \check at secondrun{#1}%
             \fi
           }%
       }%
   \else
+    % It's not a partial table:
     \def\tbl at firstp{1}%
-    % \tbl at lastp isn't used on the first run of LaTeX, and
+    \let\first at pq@index=\tbl at firstp
+    % We never get here on the first run of LaTeX, and
     % \lastpage at withbonuspoints is defined on the second and later runs.
     \def\tbl at lastp{\lastpage at withpoints}%
+    \let\last at pq@index=\tbl at lastp
     \if at bonus
       \def\tbl at lastp{\lastpage at withbonuspoints}%
+      \let\last at pq@index=\tbl at lastp
     \fi
     \if at combined
       \ifnum \lastpage at withbonuspoints > \lastpage at withpoints\relax
         \def\tbl at lastp{\lastpage at withbonuspoints}%
+        \let\last at pq@index=\tbl at lastp
       \fi        
     \fi
-    \@tblpages{#1}%
+    \check at secondrun{#1}%
   \fi
 }% find at prange
 
-
-\def\@tblpages#1{%
+\def\check at secondrun#1{%
+  % The function \ii at gtable already made sure that this isn't the
+  % first run of latex.  To do a table indexed by pages, though, we
+  % have to also make sure it's not the second run of latex.
   % We get here from \find at prange; the argument is either ``v'' or
   % ``h''.
   % Check that there's enough info from the .aux file to do a page
-  % indexed grade table.  If so, call \@whichtblpgs{#1}:
-  \@ifundefined{lastpage at withpoints}%
-    {\ClassWarning{exam}{%
-       You must run LaTeX twice more\MessageBreak
-       \space\space to produce the table.\MessageBreak}%
-       \fbox{Run \LaTeX{} twice more to produce the table}%
-    }%
-    {%
-      \@ifundefined{pointsonpage@\romannumeral
+  % indexed grade table.  If so, call \tbl at v@or at h{#1}:
+  \@ifundefined{pointsonpage@\romannumeral
                \csname lastpage at withpoints\endcsname}%
+    {\@ifundefined{bonuspointsonpage@\romannumeral
+               \csname lastpage at withbonuspoints\endcsname}%
         {\ClassWarning{exam}{%
-           You must run LaTeX again\MessageBreak
-           \space\space to produce the table.\MessageBreak}%
+           You must run LaTeX again to produce the table.\MessageBreak}%
            \fbox{Run \LaTeX{} again to produce the table}%
         }%
-        {%
-          \@whchtblpgs#1
+        {\tbl at v@or at h{#1}%
         }%
     }%
-}% @tblpages
+    {\tbl at v@or at h{#1}%
+    }%
+}% check at secondrun
 
-\def\@whchtblpgs#1{%
-  % At this point, we know the table is indexed by pages.
-  % It can be vertical or horizontal, 
-  % grade or point, and bonus or non-bonus.
-  % The argument is either ``v'' or ``h''; test it, and branch:
-  \if v#1%
-    \@vtblpages
+%--------------------------------------------------------------------
+% Indexed by pages:
+
+% For a table indexed by pages, we need to know how many pages there
+% are with points on them.  The argument to \count at pgswpts should be
+% the name of a counter; we set that counter equal to the number of
+% pages with the appropriate kind of points.
+
+\def\count at pgswpts#1{%
+  % Set the counter #1 equal to the number of pages in the range with
+  % the appropriate type of points.
+  % We're called by \@computenumcols at h and \@computenumrows at v.
+  \setcounter{#1}{0}%
+  \setcounter{@iterator}{\tbl at firstp}%
+  \addtocounter{@iterator}{-1}%
+  \if at bonus
+    \docount at pgswbpts{#1}%
   \else
-    \if h#1%
-      \@htblpages
+    \if at combined
+      \docount at pgswcpts{#1}%
     \else
-      \ClassError{exam}{%
-        The first optional argument to
-          \protect\gradetable,\MessageBreak 
-          \space\space \protect\bonusgradetable,
-            \protect\pointtable, or\MessageBreak
-          \space\space
-          \protect\bonuspointtable \space must be either `h' or
-          `v'\MessageBreak
-      }{%
-        Grade tables and point tables can be either horizontal or
-        vertical;\MessageBreak
-        \space\space no diagonals allowed.\MessageBreak
-      }%
-      \if at scores
-        \if at bonus
-          \fbox{Error: bonusgradetable: Invalid first optional argument
-                                                  `#1'.}%
-        \else
-          \fbox{Error: gradetable: Invalid first optional argument
-                                                  `#1'.}%
-        \fi
-      \else
-        \if at bonus
-          \fbox{Error: bonuspointtable: Invalid first optional argument
-                                                  `#1'.}%
-        \else
-          \fbox{Error: pointtable: Invalid first optional argument
-                                                  `#1'.}%
-        \fi
-      \fi
+      \docount at pgswpts{#1}%
     \fi
   \fi
-}% \@whchtblpgs
+}% count at pgswpts
 
-%--------------------------------------------------------------------
-% Vertical, indexed by pages:
-
-\def\@vtblpages{%
-  % We get here from \@whchtblpgs.
-  % At this point, we know it's a vertical table indexed by pages.
-  % It can be grade or point, and bonus, non-bonus, or combined.
-  \begingroup
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \if at bonus
-      % It's vertical, bonus, by pages, either point or grade:
-      \set at hlfcntr{tbl at bonuspoints}{0}%
-      \if at scores
-        \begin{tabular}{|c|c|c|}
-          \hline
-          {\@bvpgword}& {\@bvpword}& {\@bvsword}\\
-          \hline
-          \setcounter{@iterator}{\tbl at firstp}%
-          \addtocounter{@iterator}{-1}\pg at bvloop
-          {\@bvtword}& \prt at tablebonuspoints&
-                              \hbox to \@cellwidth{\hfill}\\
-          \hline
-        \end{tabular}%
-      \else
-        \begin{tabular}{|c|c|}
-          \hline
-          {\@bvpgword}& {\@bvpword}\\
-          \hline
-          \setcounter{@iterator}{\tbl at firstp}%
-          \addtocounter{@iterator}{-1}\pg at ptbvloop
-          {\@bvtword}& \prt at tablebonuspoints\\
-          \hline
-        \end{tabular}%
-      \fi
-    \else
-      \if at combined
-        % It's vertical, combined, by pages, either point or grade:
-        \set at hlfcntr{tbl at points}{0}%
-        \set at hlfcntr{tbl at bonuspoints}{0}%
-        \if at scores
-          % Combined gradetable, vertical, by pages:
-          \begin{tabular}{|c|c|c|c|}
-            \hline
-            {\@cvpgword}& {\@cvpword}& {\@cvbpword}& {\@cvsword}\\
-            \hline
-            \setcounter{@iterator}{\tbl at firstp}%
-            \addtocounter{@iterator}{-1}\pg at cvloop
-            {\@cvtword}& \prt at tablepoints&
-              \prt at tablebonuspoints&
-              \hbox to \@cellwidth{\hfill}\\
-            \hline
-          \end{tabular}%
-        \else
-          % Combined pointtable, vertical, by pages:
-          \begin{tabular}{|c|c|c|}
-            \hline
-            {\@cvpgword}& {\@cvpword}& {\@cvbpword}\\
-            \hline
-            \setcounter{@iterator}{\tbl at firstp}%
-            \addtocounter{@iterator}{-1}\pg at ptcvloop
-            {\@cvtword}& \prt at tablepoints&
-              \prt at tablebonuspoints\\
-            \hline
-          \end{tabular}%
-        \fi
-      \else
-        % It's non-bonus, either point or grade:
-        \set at hlfcntr{tbl at points}{0}%
-        \if at scores
-          \begin{tabular}{|c|c|c|}
-            \hline
-            {\@vpgword}& {\@vpword}& {\@vsword}\\
-            \hline
-            \setcounter{@iterator}{\tbl at firstp}%
-            \addtocounter{@iterator}{-1}\pg at vloop
-            {\@vtword}& \prt at tablepoints&\hbox to \@cellwidth{\hfill}\\
-            \hline
-          \end{tabular}%
-        \else
-          \begin{tabular}{|c|c|}
-            \hline
-            {\@vpgword}& {\@vpword}\\
-            \hline
-            \setcounter{@iterator}{\tbl at firstp}%
-            \addtocounter{@iterator}{-1}\pg at ptvloop
-            {\@vtword}& \prt at tablepoints\\
-            \hline
-          \end{tabular}%
-        \fi
-      \fi
-    \fi
-  \endgroup
-}% @vtblpages
-
-
-% \pg at ptvloop and \pg at ptvloopline are used only by non-bonus pointtable:
-\def\pg at ptvloop{%
+\def\docount at pgswcpts#1{%
+  % Called by \count at pgswpts
+  % Count the number of pages in range with any kind of point (bonus
+  % or non-bonus):
   \addtocounter{@iterator}{1}%
   \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
   \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-    \pg at ptvloopline
+    \addtocounter{#1}{1}%
+  \else
+    \check at bnsptpage{#1}%
   \fi
   \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@ptvloop=\pg at ptvloop
+    \def\nextdocount at pgswcpts{\docount at pgswcpts{#1}}%
   \else
-    \let\next at pg@ptvloop=\relax
+    \let\nextdocount at pgswcpts=\relax
   \fi
-  \next at pg@ptvloop
-}% pg at ptvloop
-\def\pg at ptvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstpoints at onpage@\arabic{@iterator}} &
-                         \pointsonpage{\the at iterator}\\
-  \hline
-}% pg at ptvloopline
+  \nextdocount at pgswcpts
+}% docount at pgswcpts
+\def\check at bnsptpage#1{%
+  % We need to hide this inside of a macro because if \ifhlfcntr at pos
+  % isn't expanded (because this stuff is being skipped in the outer
+  % conditional), then TeX doesn't see the \ifnum hidden inside the
+  % \ifhlfcntr at pos, but it does see the \fi, and so it get confused.
+  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
+  \ifhlfcntr at pos{tmp at hlfcntr}%
+    \addtocounter{#1}{1}%
+  \fi
+}% check at bnsptpage
 
-% \pg at vloop and \pg at vloopline are used only by non-bonus gradetable:
-\def\pg at vloop{%
+\def\docount at pgswpts#1{%
+  % Called by \count at pgswpts.
+  % Count the number of pages in range with regular points.
   \addtocounter{@iterator}{1}%
   \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
   \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-    \pg at vloopline
+    \addtocounter{#1}{1}%
   \fi
   \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@vloop=\pg at vloop
+    \def\nextdocount at pgswpts{\docount at pgswpts{#1}}%
   \else
-    \let\next at pg@vloop=\relax
+    \let\nextdocount at pgswpts=\relax
   \fi
-  \next at pg@vloop
-}% pg at vloop
-\def\pg at vloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstpoints at onpage@\arabic{@iterator}} &
-                         \pointsonpage{\the at iterator}&\\
-  \hline
-}% pg at vloopline
+  \nextdocount at pgswpts
+}% docount at pgswpts
 
-% \pg at cvloop, \pg at noncvloopline, \check at bnspts, and
-% \pg at cvloopline are used only by combined gradetable:
-\def\pg at cvloop{%
+\def\docount at pgswbpts#1{%
+  % Called by \count at pgswpts
+  % Count the number of pages in range with bonus points.
   \addtocounter{@iterator}{1}%
-  % Add the bonus points:
   \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-  % Add the regular points:
-  \set at hlfcntr{tmp at hlfcntr}{\csname pointsonpage@\romannumeral
-         \csname c@@iterator\endcsname\endcsname}%
-  \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-  % Print a line of the table, if there are points on this page:
   \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at noncvloopline
-  \else
-    \check at bnspts
+    \addtocounter{#1}{1}%
   \fi
-  % Do the tail recursion bit:
   \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@cvloop=\pg at cvloop
+    \def\nextdocount at pgswbpts{\docount at pgswbpts{#1}}%
   \else
-    \let\next at pg@cvloop=\relax
+    \let\nextdocount at pgswbpts=\relax
   \fi
-  \next at pg@cvloop
-}% pg at cvloop
-\def\pg at noncvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstpoints at onpage@\arabic{@iterator}}&
-           \pointsonpage{\the at iterator}&
-           \bonuspointsonpage{\the at iterator}&\\
-  \hline
-}% pg at noncvloopline
-\def\check at bnspts{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at cvloopline
+  \nextdocount at pgswbpts
+}% docount at pgswbpts
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multirow horizontal tables, indexed by question numbers:
+
+\newcounter{pq at index}% In tables indexed by page numbers, it holds a
+% page number. In tables indexed by question numbers, it holds a
+% question number.
+
+\newcounter{pq at index@pts}% In horizontal tables, this holds either the
+% current page number or the current question number as we put the
+% point values for that page or question number into the table.  In
+% vertical tables, this holds the index for the first column of the
+% current row.
+
+\newcounter{pq at index@bpts}% used to set bonus point values in
+% horizontal tables.  Often used as scratch elsewhere.
+
+\def\hidden at ampersand{&}% Needed because an ampersand can't appear in
+% the replacement text of a conditional.
+
+\newif\iftbl at pgs
+% \tbl at pgstrue means a table indexed by page numbers
+% \tbl at pgsfalse means a table indexed by question numbers
+
+\newcounter{num at cols}
+\newcounter{num at rows}
+\newcounter{current at row}
+\newcounter{cols at done}% Holds the number of columns done in the
+% current row.
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Stuff to unify tables indexed by questions and tables indexed by
+% pages:
+
+% \first at pq@index and \last at pq@index will hold either \tbl at firstq and
+% \tbl at lastq or \tbl at firstp and \tbl at lastp.
+
+\def\increment at index#1{%
+  % If we're doing a table indexed by question numbers, we increment
+  % the counter #1.
+  % If we're doing a table indexed by page numbers,
+  % we increase the counter #1 by at least 1 to either the number of the
+  % next page containing the appropriate kind of points, or to
+  % something greater than \tbl at lastp.
+  \iftbl at pgs
+    \find at nextpagewithpoints{#1}%
+  \else
+    \addtocounter{#1}{1}%
   \fi
-}% check at bnspts
-\def\pg at cvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstbonuspoints at onpage@\arabic{@iterator}}&
-           \pointsonpage{\the at iterator}&
-           \bonuspointsonpage{\the at iterator}&\\
-  \hline
-}% pg at cvloopline
+}% increment at index
 
-% \pg at ptcvloop, \pg at ptnoncvloopline, \check at ptbnspts, and
-% \pg at ptcvloopline are used only by combined pointtable:
-\def\pg at ptcvloop{%
-  \addtocounter{@iterator}{1}%
-  % Add the bonus points:
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-  % Add the regular points:
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-  % Print a line of the table, if there are points on this page:
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at ptnoncvloopline
+\def\nextcolumn at index@v#1{%
+  % Used only for multicolumn tables.
+  % If we're doing a table indexed by question numbers, we increase
+  % the counter #1 by num at cols.
+  % If we're doing a table indexed by page numbers,
+  % we use \find at nextcolumnpage@v to increment the counter #1 to either
+  % the (num at rows)'th page number after #1 that contains the
+  % appropriate kind of points or to a value greater than \tbl at lastp.
+  \iftbl at pgs
+    \find at nextcolumnpage@v{#1}%
   \else
-    \check at ptbnspts
+    \addtocounter{#1}{\value{num at rows}}%
   \fi
-  % Do the tail recursion bit:
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@ptcvloop=\pg at ptcvloop
+}% nextcolumn at index@v
+
+\def\pointsof at index#1{%
+  \iftbl at pgs
+    \pointsonpage{\arabic{#1}}%
   \else
-    \let\next at pg@ptcvloop=\relax
+    \pointsofquestion{\arabic{#1}}%
   \fi
-  \next at pg@ptcvloop
-}% pg at ptcvloop
-\def\pg at ptnoncvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstpoints at onpage@\arabic{@iterator}}&
-           \pointsonpage{\the at iterator}&
-           \bonuspointsonpage{\the at iterator}\\
-  \hline
-}% pg at ptnoncvloopline
-\def\check at ptbnspts{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at ptcvloopline
+}% pointsof at index
+
+\def\bonuspointsof at index#1{%
+  \iftbl at pgs
+    \bonuspointsonpage{\arabic{#1}}%
+  \else
+    \bonuspointsofquestion{\arabic{#1}}%
   \fi
-}% check at ptbnspts
-\def\pg at ptcvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstbonuspoints at onpage@\arabic{@iterator}}&
-           \pointsonpage{\the at iterator}&
-           \bonuspointsonpage{\the at iterator}\\
-  \hline
-}% pg at ptcvloopline
+}% bonuspointsof at index
 
-% \pg at bvloop and \pg at bvloopline are used only by bonus gradetable:
-\def\pg at bvloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-    \pg at bvloopline
-  \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@bvloop=\pg at bvloop
+\def\refto at index#1{%
+  \iftbl at pgs
+    \if at combined
+      % Need to hide this inside of a macro:
+      \refto at comb@index{#1}%
+    \else
+      \if at bonus
+        \pageref{firstbonuspoints at onpage@\arabic{#1}}%
+      \else
+        \pageref{firstpoints at onpage@\arabic{#1}}%
+      \fi
+    \fi
   \else
-    \let\next at pg@bvloop=\relax
+    \ref{question@\arabic{#1}}%
   \fi
-  \next at pg@bvloop
-}% pg at bvloop
-\def\pg at bvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstbonuspoints at onpage@\arabic{@iterator}} &
-                         \bonuspointsonpage{\the at iterator}&\\
-  \hline
-}% pg at bvloopline
+}% refto at index
 
-% \pg at ptbvloop and \pg at ptbvloopline are used only by bonus pointtable:
-\def\pg at ptbvloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
+\def\refto at comb@index#1{%
+  % We're called only by \refto at index.
+  % We can't have the \ifhlfcntr at pos...\fi inside of another
+  % conditional, so we're hiding it in this macro.
+  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\arabic{#1}}}%
   \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-    \pg at ptbvloopline
+    \pageref{firstpoints at onpage@\arabic{#1}}%
+  \else
+    % In theory, there *must* be bonus points on this page, because
+    % there aren't any plain points, but there are allegedly *some*
+    % points.  We're being brave and assuming that's correct, and not
+    % checking (which we'd have to hide inside a macro, because it
+    % would use \ifhlfcntr at pos):
+    \pageref{firstbonuspoints at onpage@\arabic{#1}}%
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pg@ptbvloop=\pg at ptbvloop
+}% refto at comb@index
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multirow tables:
+
+
+%--------------------------------------------------------------------
+% Check that the number of rows is OK, and compute the number of
+% columns:
+
+\def\check at num@rows at h{%
+  % We get here from \tbl at v@or at h.
+  % We make sure the number of rows is a positive integer.  If it
+  % is, we go on to \@computenumcols at h
+  \ifnum \value{num at rows} < 1\relax
+    \ClassError{exam}{%
+      The number of rows in a table must be positive.\MessageBreak
+    }{%
+      The number of rows must be a positive integer.\MessageBreak
+    }%
+    \fbox{\textbf{Error:} Multirow table with no rows!}%
   \else
-    \let\next at pg@ptbvloop=\relax
+    \@computenumcols at h
   \fi
-  \next at pg@ptbvloop
-}% pg at ptbvloop
-\def\pg at ptbvloopline{%
-  % We still don't understand why we need to hide this inside of a
-  % macro; there's some weird interaction with the \ifnum checking to
-  % see if something is ``>0''; checking that something is ``=0''
-  % doesn't cause the ``\ifnum incomplete'' (or whatever) error.
-  \pageref{firstbonuspoints at onpage@\arabic{@iterator}} &
-                         \bonuspointsonpage{\the at iterator}\\
-  \hline
-}% pg at ptbvloopline
+}% check at num@rows at h
 
+\def\@computenumcols at h{%
+  % We get here from \check at num@rows at h.
+  % Compute the number of columns.
+  % First: set num at cols to one more than either (the number of pages
+  % with the appropriate type of points) or (the number of questions),
+  % to have slots for the total along with the questions:
+  \iftbl at pgs
+    \count at pgswpts{num at cols}%
+    \addtocounter{num at cols}{1}%
+  \else
+    \setcounter{num at cols}{\tbl at lastq}%
+    \addtocounter{num at cols}{-\tbl at firstq}%
+    \addtocounter{num at cols}{2}%
+  \fi
+  % Save the number of slots needed in pq at index (used for scratch), to
+  % check for truncation:
+  \setcounter{pq at index}{\value{num at cols}}%
+  % Divide the number of slots needed by num at rows:
+  \divide \csname c at num@cols\endcsname by
+    \csname c at num@rows\endcsname
+  % Division truncates: See if there was truncation.
+  % Use @iterator as a scratch counter.
+  \setcounter{@iterator}{\value{num at cols}}%
+  \multiply \csname c@@iterator\endcsname by
+    \csname c at num@rows\endcsname
+  \ifnum \value{@iterator} < \value{pq at index}\relax
+    % There was truncation; add a column to num at cols:
+    \addtocounter{num at cols}{1}%
+  \fi
+  \@multirowtable
+}% @computenumcols at h
+
 %--------------------------------------------------------------------
-% Horizontal, indexed by pages:
+% Construct the actual table:
 
-% For a horizontal table, before we begin we need to know how many
-% pages there are with points on them:
-\newcounter{num at cols}
-\def\count at pgswpts{%
-  % We're called by \@htblpages.
-  % \count at pgswpts is used for horizontal
-  % grade tables and point tables,
-  % bonus, non-bonus, and combined.
-  \setcounter{num at cols}{0}%
-  \setcounter{@iterator}{\tbl at firstp}%
-  \addtocounter{@iterator}{-1}%
+\def\@multirowtable{%
+  % We get here from \@computenumcols at h.
+  % All multirow tables!
+  \renewcommand\arraystretch{\@gtblstretch}%
+  \set at hlfcntr{tbl at points}{0}%
+  \set at hlfcntr{tbl at bonuspoints}{0}%
+  \setcounter{pq at index}{\first at pq@index}%
+  \addtocounter{pq at index}{-1}%
+  \setcounter{pq at index@pts}{\value{pq at index}}%
+  \setcounter{pq at index@bpts}{\value{pq at index}}%
+  \setcounter{current at row}{0}%
+  \begin{tabular}{|l|*{\value{num at cols}}{c|}}
+    \hline
+    \if at combined
+      \do at comblines@h
+    \else
+      \do at lines@h
+    \fi
+}% @multirowtable
+
+
+\def\do at lines@h{%
+  % Called only by \@multirowtable.
+  % It's either bonus or regular, but not combined:
+  \addtocounter{current at row}{1}% Set to the number of the current row
+  \iftbl at pgs
+    \if at bonus
+      \@bhpgword
+    \else
+      \@hpgword
+    \fi
+  \else
+    \if at bonus
+      \@bhqword
+    \else
+      \@hqword
+    \fi
+  \fi
+  \setcounter{cols at done}{0}%
+  \do at pq@indexloop at h
+  % When we finish \do at pq@indexloop at h, either we've finished a
+  % complete row of page numbers (or questions), or we've done all 
+  % the page numbers (or questions) through \last at pq@index, or both:
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    % We've inserted all the page or question numbers, and there's
+    % room remaining on the current line for \@htword (or \@bhtword):
+    \ifnum \value{current at row} = \value{num at rows}\relax
+      % This is the last row; put in the total:
+      \do at htword@h
+    \else
+      % This isn't the last row.  We insert (\value{num at cols} -
+      % \value{cols at done}) ampersands.
+      \setcounter{@iterator}{\value{num at cols}}%
+      \addtocounter{@iterator}{-\value{cols at done}}%
+      \do at emptycols@h
+    \fi
+  \fi
+  \\
+  \hline
+  % Point values go here!
+  \setcounter{cols at done}{0}%
   \if at bonus
-    \docount at pgswbpts
+    \@bhpword
+    \do at bptloop@h
   \else
-    \if at combined
-      \docount at pgswcpts
+    \@hpword
+    \do at ptloop@h
+  \fi
+  % When we finish \do at ptloop@h or \do at bptloop@h, either
+  % we've finished a complete row of point values, or we've done all
+  % the question (or page) numbers through \last at pq@index, or both:  
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current at row} = \value{num at rows}\relax
+      % This is the last row; put in the total:
+      \if at bonus
+        \do at totalbpts@h
+      \else
+        \do at totalpts@h
+      \fi
     \else
-      \docount at pgswpts
+      % This isn't the last row.  We insert (\value{num at cols} -
+      % \value{cols at done}) ampersands.
+      \setcounter{@iterator}{\value{num at cols}}%
+      \addtocounter{@iterator}{-\value{cols at done}}%
+      \do at emptycols@h
     \fi
   \fi
-}% count at pgswpts
+  % We hold off on putting in the "\\ \hline" because we may want to
+  % immediately follow it with either an "\end{tabular}" or another
+  % "\hline".
+  % Scores?
+  \if at scores
+    \\
+    \hline
+    \if at bonus
+      \@bhsword \hidden at ampersand
+    \else
+      \@hsword \hidden at ampersand
+    \fi
+    \setcounter{cols at done}{0}%
+    \do at sloop@h
+  \fi
+  \ifnum \value{current at row} = \value{num at rows}\relax
+    % This is the last line!  End the tabular:
+    \\
+    \hline
+    \end{tabular}%
+  \else
+    % Don't end the tabular:
+    \\
+    \hline\hline
+  \fi
+  % Check if we should repeat:
+  \ifnum \value{current at row} < \value{num at rows}\relax
+    \let\nextdo at lines@h=\do at lines@h
+  \else
+    \let\nextdo at lines@h=\relax
+  \fi
+  \nextdo at lines@h
+}% do at lines@h
 
-\def\docount at pgswcpts{%
-  % Called by \count at pgswpts
-  % \docount at pgswcpts is used for combined gradetable.
-  % Count the number of pages in range with any kind of point (bonus
-  % or non-bonus):
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \addtocounter{num at cols}{1}%
+\def\do at comblines@h{%
+  % Called only by \@multirowtable.
+  % Combined tables.
+  \addtocounter{current at row}{1}% Set to the number of the current row
+  \iftbl at pgs
+    \@chpgword
   \else
-    \check at bnsptpage
+    \@chqword
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at docount=\docount at pgswcpts
+  \setcounter{cols at done}{0}%
+  \do at pq@indexloop at h
+  % When we finish \do at pq@indexloop at h, either we've finished a
+  % complete row of page (or question) numbers, or we've done all
+  % the page (or question) numbers through \last at pq@index, or both: 
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    % We've inserted all the question (or page) numbers, and there's
+    % room remaining on the current line for \@chtword:
+    \ifnum \value{current at row} = \value{num at rows}\relax
+      % This is the last row; put in the total:
+      \do at htword@h
+    \else
+      % This isn't the last row.  We insert (\value{num at cols} -
+      % \value{cols at done}) ampersands.
+      \setcounter{@iterator}{\value{num at cols}}%
+      \addtocounter{@iterator}{-\value{cols at done}}%
+      \do at emptycols@h
+    \fi
+  \fi
+  \\
+  \hline
+  % Point values go here!
+  \@chpword
+  \setcounter{cols at done}{0}%
+  \do at ptloop@h
+  % When we finish \do at ptloop@h, either we've finished a complete
+  % row of point values, or we've done all the question (or page)
+  % numbers through \last at pq@index, or both:   
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current at row} = \value{num at rows}\relax
+      % This is the last row; put in the total:
+      \do at totalpts@h
+    \else
+      % This isn't the last row.  We insert (\value{num at cols} -
+      % \value{cols at done}) ampersands.
+      \setcounter{@iterator}{\value{num at cols}}%
+      \addtocounter{@iterator}{-\value{cols at done}}%
+      \do at emptycols@h
+    \fi
+  \fi
+  \\
+  \hline
+  % Bonus point values go here!
+  \@chbpword
+  \setcounter{cols at done}{0}%
+  \do at bptloop@h
+  % When we finish \do at bptloop@h, either
+  % we've finished a complete row of point values, or we've done all
+  % the question (or page) numbers through \last at pq@index, or both:  
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current at row} = \value{num at rows}\relax
+      % This is the last row; put in the total:
+      \do at totalbpts@h
+    \else
+      % This isn't the last row.  We insert (\value{num at cols} -
+      % \value{cols at done}) ampersands.
+      \setcounter{@iterator}{\value{num at cols}}%
+      \addtocounter{@iterator}{-\value{cols at done}}%
+      \do at emptycols@h
+    \fi
+  \fi
+  % We hold off on putting in the "\\ \hline" because we may want to
+  % immediately follow it with either an "\end{tabular}" or another
+  % "\hline".
+  % Scores?
+  \if at scores
+    \\
+    \hline
+    \@chsword \hidden at ampersand
+    \setcounter{cols at done}{0}%
+    \do at sloop@h
+  \fi
+  \ifnum \value{current at row} = \value{num at rows}\relax
+    % This is the last line!  End the tabular:
+    \\
+    \hline
+    \end{tabular}%
   \else
-    \let\next at docount=\relax
+    % Don't end the tabular:
+    \\
+    \hline\hline
   \fi
-  \next at docount
-}% docount at pgswcpts
-\def\check at bnsptpage{%
-  % We don't understand why we need to hide this inside of a macro:
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \addtocounter{num at cols}{1}%
+  % Check if we should repeat:
+  \ifnum \value{current at row} < \value{num at rows}\relax
+    \let\nextdo at comblines@h=\do at comblines@h
+  \else
+    \let\nextdo at comblines@h=\relax
   \fi
-}% check at bnsptpage
+  \nextdo at comblines@h
+}% do at comblines@h
 
-\def\docount at pgswpts{%
-  % Called by \count at pgswpts
-  % \docount at pgswpts is used for non-bonus gradetable:
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \addtocounter{num at cols}{1}%
+\def\do at pq@indexloop at h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert at most one row of pq at index:
+  \increment at index{pq at index}%
+  \ifnum \value{pq at index} > \last at pq@index\relax
+    % Do nothing!
+  \else
+    \hidden at ampersand
+    \refto at index{pq at index}%
+    \addtocounter{cols at done}{1}%
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at docount=\docount at pgswpts
+  \ifnum \value{pq at index} < \last at pq@index\relax
+    \ifnum \value{cols at done} < \value{num at cols}\relax
+      \let\nextdo at pq@indexloop at h=\do at pq@indexloop at h
+    \else
+      \let\nextdo at pq@indexloop at h=\relax
+    \fi
   \else
-    \let\next at docount=\relax
+    \let\nextdo at pq@indexloop at h=\relax
   \fi
-  \next at docount
-}% docount at pgswpts
+  \nextdo at pq@indexloop at h
+}% do at pq@indexloop at h
 
-\def\docount at pgswbpts{%
-  % Called by \count at pgswpts
-  % \docount at pgswbpts is used for bonusgradetable:
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \addtocounter{num at cols}{1}%
+\def\do at ptloop@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert at most one row of non-bonus point values:
+  \increment at index{pq at index@pts}%
+  \ifnum \value{pq at index@pts} > \last at pq@index\relax
+    % Do nothing!
+  \else
+    \hidden at ampersand
+    \addtocounter{cols at done}{1}%
+    \pointsof at index{pq at index@pts}%
+    \addto at hlfcntr{tbl at points}{\pointsof at index{pq at index@pts}}%
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at docount=\docount at pgswbpts
+  \ifnum \value{pq at index@pts} < \last at pq@index\relax
+    \ifnum \value{cols at done} < \value{num at cols}\relax
+      \let\nextdo at ptloop@h=\do at ptloop@h
+    \else
+      \let\nextdo at ptloop@h=\relax
+    \fi
   \else
-    \let\next at docount=\relax
+    \let\nextdo at ptloop@h=\relax
   \fi
-  \next at docount
-}% docount at pgswbpts
+  \nextdo at ptloop@h
+}% do at ptloop@h
 
-%--------------------------------------------------------------------
+\def\do at bptloop@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert at most one row of bonus point values:
+  \increment at index{pq at index@bpts}%
+  \ifnum \value{pq at index@bpts} > \last at pq@index\relax
+    % Do nothing!
+  \else
+    \hidden at ampersand
+    \addtocounter{cols at done}{1}%
+    \bonuspointsof at index{pq at index@bpts}%
+    \addto at hlfcntr{tbl at bonuspoints}{\bonuspointsof at index{pq at index@bpts}}%
+  \fi
+  \ifnum \value{pq at index@bpts} < \last at pq@index\relax
+    \ifnum \value{cols at done} < \value{num at cols}\relax
+      \let\nextdo at bptloop@h=\do at bptloop@h
+    \else
+      \let\nextdo at bptloop@h=\relax
+    \fi
+  \else
+    \let\nextdo at bptloop@h=\relax
+  \fi
+  \nextdo at bptloop@h
+}% do at bptloop@h
 
-\def\@htblpages{%
-  % We get here from \@whchtblpgs.
-  % Horizontal grade or point table, by pages:
-  \begingroup
-    \renewcommand\arraystretch{\@gtblstretch}%
-    % Set num at cols equal to the number of pages with points (of
-    % correct type): 
-    \count at pgswpts
-    % Suppress ``extra alignment tab'' errors if \thenum at cols = 0:
-    \ifnum \thenum at cols = 0\relax
-      \setcounter{num at cols}{1}%
-    \fi
-    \set at hlfcntr{tbl at points}{0}%
-    \set at hlfcntr{tbl at bonuspoints}{0}%
+\def\do at htword@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert (\value{num at cols} - \value{cols at done}) ampersands,
+  % and then either \@htword or \@bhtword or \@chtword:
+  \setcounter{@iterator}{\value{num at cols}}%
+  \addtocounter{@iterator}{-\value{cols at done}}%
+  \do at emptycols@h
+  \if at combined
+    \@chtword
+  \else
     \if at bonus
-      % Horizontal bonus grade or point table, by pages:
-      \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-        \hline
-        {\@bhpgword}& \setcounter{@iterator}{\tbl at firstp}%
-                      \addtocounter{@iterator}{-1}\do at bpgnumloop
-        {\@bhtword}\\
-        \hline
-        {\@bhpword}& \setcounter{@iterator}{\tbl at firstp}%
-                     \addtocounter{@iterator}{-1}\do at bpgptloop
-        \prt at tablebonuspoints\\
-        \hline
-        % If it's a grade table, do the line for scores:
-        \if at scores
-          {\@bhsword}& \setcounter{@iterator}{\tbl at firstp}%
-                       \addtocounter{@iterator}{-1}\do at bpgsloop
-          \\
-          \hline
-        \fi
-      \end{tabular}%
+      \@bhtword
     \else
-      \if at combined
-        % Horizontal combined grade or point table, by pages:
-        \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-          \hline
-          {\@chpgword}& \setcounter{@iterator}{\tbl at firstp}%
-                        \addtocounter{@iterator}{-1}\do at cpgnumloop
-          {\@chtword}\\
-          \hline
-          {\@chpword}& \setcounter{@iterator}{\tbl at firstp}%
-                       \addtocounter{@iterator}{-1}\do at cpgptloop
-          \prt at tablepoints\\
-          \hline
-          {\@chbpword}& \setcounter{@iterator}{\tbl at firstp}%
-                       \addtocounter{@iterator}{-1}\do at cbpgptloop
-          \prt at tablebonuspoints\\
-          \hline
-          % If it's a grade table, do the line for scores:
-          \if at scores
-            {\@chsword}& \setcounter{@iterator}{\tbl at firstp}%
-                         \addtocounter{@iterator}{-1}\do at cpgsloop
-            \\
-            \hline
-          \fi
-        \end{tabular}%
-      \else
-        % Horizontal non-bonus grade or point table, by pages:
-        \begin{tabular}{|l|*{\thenum at cols}{c|}c|}
-          \hline
-          {\@hpgword}& \setcounter{@iterator}{\tbl at firstp}%
-                       \addtocounter{@iterator}{-1}\do at pgnumloop
-          {\@htword}\\
-          \hline
-          {\@hpword}& \setcounter{@iterator}{\tbl at firstp}%
-                      \addtocounter{@iterator}{-1}\do at pgptloop
-          \prt at tablepoints\\
-          \hline
-          % If it's a grade table, do the line for scores:
-          \if at scores
-            {\@hsword}& \setcounter{@iterator}{\tbl at firstp}%
-                        \addtocounter{@iterator}{-1}\do at pgsloop
-            \\
-            \hline
-          \fi
-        \end{tabular}%
-      \fi
+      \@htword
     \fi
-  \endgroup
-}% @htblpages
+  \fi
+}% do at htword@h
 
-%--------------------------------------------------------------------
-% \do at pgnumloop, \do at pgptloop, and \do at pgsloop
-%  are only for non-bonus gradetable.
-% \pg at line is also for combinedgradetable.
-\def\do at pgnumloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at line
+\def\do at totalpts@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert (\value{num at cols} - \value{cols at done}) ampersands
+  % and then the total points:
+  \setcounter{@iterator}{\value{num at cols}}%
+  \addtocounter{@iterator}{-\value{cols at done}}%
+  \do at emptycols@h
+  \prt at tablepoints
+}% do at totalpts@h
+
+\def\do at totalbpts@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We insert (\value{num at cols} - \value{cols at done}) ampersands,
+  % and then the total bonus points:
+  \setcounter{@iterator}{\value{num at cols}}%
+  \addtocounter{@iterator}{-\value{cols at done}}%
+  \do at emptycols@h
+  \prt at tablebonuspoints
+}% do at totalbpts@h
+
+\def\do at emptycols@h{%
+  % Called by \do at lines@h, \do at comblines@h, \do at htword@h,
+  % \do at totalpts@h, and \do at totalbpts@h.
+  % We insert \value{@iterator} ampersands:
+  \ifnum \value{@iterator} > 0\relax
+    \hidden at ampersand
+    \addtocounter{@iterator}{-1}%
+    \let\nextdo at emptycols@h=\do at emptycols@h
+  \else
+    \let\nextdo at emptycols@h=\relax
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pgnumloop=\do at pgnumloop
+  \nextdo at emptycols@h
+}% do at emptycols@h
+
+\def\do at sloop@h{%
+  % Called by both \do at lines@h and \do at comblines@h.
+  % We assume that cols at done has been set to zero.
+  % We insert num at cols \hbox to \@cellwidth,
+  % separated by ampersands.
+  \addtocounter{cols at done}{1}%
+  \hbox to \@cellwidth{\hfill}%
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    \hidden at ampersand
+    \let\nextdo at sloop@h=\do at sloop@h
   \else
-    \let\next at pgnumloop=\relax
+    \let\nextdo at sloop@h=\relax
   \fi
-  \next at pgnumloop
-}% \do at pgnumloop
-\def\pg at line{%
-  % We still don't know why we need to hide this inside of a macro:
-  \pageref{firstpoints at onpage@\arabic{@iterator}}&
-}% \pg at line
+  \nextdo at sloop@h
+}% do at sloop@h
 
-\def\do at pgptloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-    \pgpt at entry
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multicolumn tables
+
+
+%--------------------------------------------------------------------
+% Here's an example of a multicolumn grade table indexed by questions,
+% to show how we're getting the \cline's to link up correctly with the
+% \vline's.
+
+% Every line of \cline's is followed by a
+% \noalign{\vskip\arrayrulewidth} to cancel the
+% \noalign{\vskip-\arrayrulewidth} that ends the definition of
+% \cline.
+
+% \begin{tabular}{*2{@{}c|c|c@{}c}}
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+%     Question%
+%     \hspace*{\fill}%
+%   & Points%
+%   & \hspace*{\fill}%
+%     Score%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+%     Question%
+%     \hspace*{\fill}%
+%   & Points%
+%   & \hspace*{\fill}%
+%     Score%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline\\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+%     1\hspace*{\fill}%
+%   & 5%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & \vline\hspace*{\tabcolsep}\hspace*{\fill}%
+%     4%
+%     \hspace*{\fill}%
+%   & 20%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline\\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+%     2%
+%     \hspace*{\fill}%
+%   & 10%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & \vline\hspace*{\tabcolsep}\hspace*{\fill}%
+%     5%
+%     \hspace*{\fill}%
+%   & 25%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline\\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+%     3%
+%     \hspace*{\fill}%
+%   & 15%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%     \hspace*{\tabcolsep}\hspace*{\fill}\vline
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & \vline\hspace*{\tabcolsep}\hspace*{\fill}%
+%     Total:%
+%     \hspace*{\fill}%
+%   & 75%
+%   & \hspace*{\fill}%
+%   \hbox to \@cellwidth{\hfill}%
+%   \hspace*{\tabcolsep}\hspace*{\fill}\vline\\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+% \end{tabular}
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Check that the number of cols is OK, and compute the number of rows:
+
+\def\check at num@cols at v{%
+  % We get here from \tbl at v@or at h.
+  % We make sure the number of cols is between 1 and 10 (since we
+  % can't handle more than 10 cols in a multicolumn table).
+  % If it is, we go on to \@computenumrows at v
+  \ifnum \value{num at cols} < 1\relax
+    \ClassError{exam}{%
+      The number of columns in a table must be positive.\MessageBreak
+    }{%
+      The number of columns must be a positive integer.\MessageBreak
+    }%
+    \fbox{\textbf{Error:} Multicolumn table with no columns!}%
+  \else
+    \ifnum \value{num at cols} > 10\relax
+      \ClassError{exam}{%
+        Multicolumn tables can have at most 10 columns.\MessageBreak
+      }{%
+        Multicolumn tables can have at most 10 columns.\MessageBreak
+      }%
+      \fbox{\textbf{Error:} Multicolumn table with more than 10 columns!}%
+    \else
+      \@computenumrows at v
+    \fi
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pgptloop=\do at pgptloop
+}% check at num@cols at v
+
+\def\@computenumrows at v{%
+  % We get here from \check at num@cols at v.
+  % Compute the number of rows.
+  % First: set num at rows to one more than the number of either
+  % (questions) or (pages with the appropriate type of points), to
+  % have slots for the total along with the questions or page numbers:
+  \iftbl at pgs
+    \count at pgswpts{num at rows}%
+    \addtocounter{num at rows}{1}%
   \else
-    \let\next at pgptloop=\relax
+    \setcounter{num at rows}{\last at pq@index}%
+    \addtocounter{num at rows}{-\first at pq@index}%
+    \addtocounter{num at rows}{2}%
   \fi
-  \next at pgptloop
-}% \do at pgptloop
-% \pgpt at entry is used by different macros:
-\def\pgpt at entry{%
-  % We still don't know why we need to hide this inside of a macro:
-  \pointsonpage{\the at iterator}&
-}% \pgpt at entry
+  % Save the number of slots needed, using pq at index@bpts as a scratch
+  % counter, to check for truncation on division:
+  \setcounter{pq at index@bpts}{\value{num at rows}}%
+  % Divide the number of slots needed by num at cols:
+  \divide \csname c at num@rows\endcsname by
+    \csname c at num@cols\endcsname
+  % Division truncates: See if there was truncation.
+  % Use the counter @iterator as a scratch counter:
+  \setcounter{@iterator}{\value{num at rows}}%
+  \multiply \csname c@@iterator\endcsname by
+    \csname c at num@cols\endcsname
+  \ifnum \value{@iterator} < \value{pq at index@bpts}\relax
+    % There was truncation; add one to num at rows:
+    \addtocounter{num at rows}{1}%
+  \fi
+  \@multicolumntable
+}% @computenumrows at v
 
-\def\do at pgsloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at sentry
+%--------------------------------------------------------------------
+% Construct the actual table:
+
+\def\@multicolumntable{%
+  % We get here from \@computenumrows at v.
+  % Set \cline at stuff@v equal to the line of \cline's:
+  \create at cline@stuff at v
+  \renewcommand\arraystretch{\@gtblstretch}%
+  \set at hlfcntr{tbl at points}{0}%
+  \set at hlfcntr{tbl at bonuspoints}{0}%
+  \if at combined
+    \if at scores
+      % combinedgradetable, possibly partial.
+      % Note: We'll never use the final "c" in the format of the
+      % tabular, but there's no harm in that. 
+      \begin{tabular}{*{\value{num at cols}}{@{}c|c|c|c@{}c}}
+      % We need to make sure that the \cline at stuff@v commands come
+      % *immediately* following the \\ or \begin{tabular} (with no
+      % conditionals evaluated, even if those conditionals expand to
+      % the empty string)! 
+      % Put in the row of column headings, with \cline at stuff@v above and
+      % below:
+      \cline at stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn at heads@comb at v
+      \\
+      \cline at stuff@v
+    \else
+      % combinedpointtable, possibly partial.
+      % Note: We'll never use the final "c" in the format of the
+      % tabular, but there's no harm in that. 
+      \begin{tabular}{*{\value{num at cols}}{@{}c|c|c@{}c}}
+      % We need to make sure that the \cline at stuff@v commands come
+      % *immediately* following the \\ or \begin{tabular} (with no
+      % conditionals evaluated, even if those conditionals expand to
+      % the empty string)! 
+      % Put in the row of column headings, with \cline at stuff@v above and
+      % below:
+      \cline at stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn at heads@comb at noscores@v
+      \\
+      \cline at stuff@v
+    \fi
+    % pq at index@pts will hold the question number (or page number) in
+    % the first column of the row. 
+    \setcounter{pq at index@pts}{\first at pq@index}%
+    \iftbl at pgs
+      % If we're indexed by pages, we need to make sure there are
+      % points of the appropriate type on the first page listed:
+      \addtocounter{pq at index@pts}{-1}%
+      \find at nextpagewithpoints{pq at index@pts}%
+    \fi
+    \setcounter{current at row}{0}%
+    \do at lines@v
+  \else
+    % It's not combined:
+    \if at scores
+      % Note: We'll never use the final "c" in the format of the
+      % tabular, but there's no harm in that. 
+      \begin{tabular}{*{\value{num at cols}}{@{}c|c|c@{}c}}
+      % We need to make sure that the \cline at stuff@v commands come
+      % *immediately* following the \\ or \begin{tabular} (with no
+      % conditionals evaluated, even if those conditionals expand to
+      % the empty string)! 
+      % Put in the row of column headings, with \cline at stuff@v above and
+      % below:
+      \cline at stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn at heads@v
+      \\
+      \cline at stuff@v
+    \else
+      % Note: We'll never use the final "c" in the format of the
+      % tabular, but there's no harm in that. 
+      \begin{tabular}{*{\value{num at cols}}{@{}c|c@{}c}}
+      % We need to make sure that the \cline at stuff@v commands come
+      % *immediately* following the \\ or \begin{tabular} (with no
+      % conditionals evaluated, even if those conditionals expand to
+      % the empty string)! 
+      % Put in the row of column headings, with \cline at stuff@v above and
+      % below:
+      \cline at stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn at heads@noscores at v
+      \\
+      \cline at stuff@v
+    \fi
+    % pq at index@pts will hold the question number (or page number) in
+    % the first column of the row. 
+    \setcounter{pq at index@pts}{\first at pq@index}%
+    \iftbl at pgs
+      % If we're indexed by pages, we need to make sure there are
+      % points of the appropriate type on the first page listed:
+      \addtocounter{pq at index@pts}{-1}%
+      \find at nextpagewithpoints{pq at index@pts}%
+    \fi
+    \setcounter{current at row}{0}%
+    \do at lines@v
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at pgsloop=\do at pgsloop
+}% @multicolumntable
+
+%--------------------------------------------------------------------
+% \create at cline@stuff at v
+
+% The function \create at cline@stuff at v defines \cline at stuff@v to be whatever's
+% appropriate given the values of num at cols, \if at bonus, \if at combined, and
+% \if at scores.
+
+% We wimped out of generating \cline at stuff@v on the fly because we didn't
+% see how to get the correct expansions/nonexpansions without using a
+% primitive of e-TeX.
+
+% \clines at ii@whatever is for tables in which a  logical column consists
+% of two columns; it's used for pointtable and bonuspointtable.
+
+\def\clines at ii@i{\cline{1-2}}
+\def\clines at ii@ii{\cline{1-2} \cline{4-5}}
+\def\clines at ii@iii{\cline{1-2} \cline{4-5} \cline{7-8}}
+\def\clines at ii@iv{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}}
+\def\clines at ii@v{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14}}
+\def\clines at ii@vi{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17}}
+\def\clines at ii@vii{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17} \cline{19-20}}
+\def\clines at ii@viii{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17} \cline{19-20} \cline{22-23}}
+\def\clines at ii@vix{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17} \cline{19-20} \cline{22-23}
+  \cline{25-26}}
+\def\clines at ii@x{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17} \cline{19-20} \cline{22-23}
+  \cline{25-26} \cline{28-29}}
+
+% \clines at iii@whatever is for tables in which a  logical column consists
+% of three columns; it's are used for gradetable, bonusgradetable, and
+% combinedpointtable:
+
+\def\clines at iii@i{\cline{1-3}}
+\def\clines at iii@ii{\cline{1-3} \cline{5-7}}
+\def\clines at iii@iii{\cline{1-3} \cline{5-7} \cline{9-11}}
+\def\clines at iii@iv{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}}
+\def\clines at iii@v{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19}}
+\def\clines at iii@vi{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23}}
+\def\clines at iii@vii{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23} \cline{25-27}}
+\def\clines at iii@viii{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23} \cline{25-27} \cline{29-31}}
+\def\clines at iii@ix{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23} \cline{25-27} \cline{29-31}
+  \cline{33-35}}
+\def\clines at iii@x{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23} \cline{25-27} \cline{29-31}
+  \cline{33-35} \cline{37-39}}
+
+
+% \clines at iv@whatever is for tables in which a  logical column
+% consists of four columns; it's used for combinedgradetable.
+
+\def\clines at iv@i{\cline{1-4}}
+\def\clines at iv@ii{\cline{1-4} \cline{6-9}}
+\def\clines at iv@iii{\cline{1-4} \cline{6-9} \cline{11-14}}
+\def\clines at iv@iv{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}}
+\def\clines at iv@v{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24}}
+\def\clines at iv@vi{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29}}
+\def\clines at iv@vii{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29} \cline{31-34}}
+\def\clines at iv@viii{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29} \cline{31-34} \cline{36-39}}
+\def\clines at iv@ix{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29} \cline{31-34} \cline{36-39}
+  \cline{41-44}}
+\def\clines at iv@x{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29} \cline{31-34} \cline{36-39}
+  \cline{41-44} \cline{46-49}}
+
+% The definition of \cline ends with \noalign{\vskip-\arrayrulewidth},
+% and so we want to throw in a \noalign{\vskip\arrayrulewidth} to
+% cancel that.
+\def\cline at correction{\noalign{\vskip\arrayrulewidth}}
+
+\def\create at cline@stuff at v{%
+  % Called by \@multicolumntable.
+  \if at combined
+    \if at scores
+      \edef\cline at stuff@v{\expandafter\noexpand\csname
+        clines at iv@\romannumeral \c at num@cols\endcsname
+        \noexpand\cline at correction}%
+    \else
+      \edef\cline at stuff@v{\expandafter\noexpand\csname
+        clines at iii@\romannumeral \c at num@cols\endcsname
+        \noexpand\cline at correction}%
+    \fi
   \else
-    \let\next at pgsloop=\relax
+    \if at scores
+      \edef\cline at stuff@v{\expandafter\noexpand\csname
+        clines at iii@\romannumeral \c at num@cols\endcsname
+        \noexpand\cline at correction}%
+    \else
+      \edef\cline at stuff@v{\expandafter\noexpand\csname
+        clines at ii@\romannumeral \c at num@cols\endcsname
+        \noexpand\cline at correction}%
+    \fi
   \fi
-  \next at pgsloop
-}% \do at pgsloop
-% \pg at sentry is called by several different macros,
-% to create different tables:
-\def\pg at sentry{%
-  % We still don't know why we need to hide this inside of a macro:
-  \hbox to \@cellwidth{\hfill}&
-}% \pg at sentry
+}% create at cline@stuff at v
 
 %--------------------------------------------------------------------
-% These things are for bonusgradetable:
+% The various \docolumn at heads@something at v
 
-% \do at bpgnumloop, \do at bpgptloop, \do at bpgsloop,
-% and \bpg at sline are only for bonusgradetable.
-% \bpg at line is also used for combinedgradetable.
-\def\do at bpgnumloop{%
+\def\docolumn at heads@v{%
+  % Called by \@multicolumntable.
+  % multicolumngradetable or multicolumnbonusgradetable, possibly
+  % partial.
+  \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+    \iftbl at pgs
+      \if at bonus
+        \@bvpgword
+      \else
+        \@vpgword
+      \fi
+    \else
+      \if at bonus
+        \@bvqword
+      \else
+        \@vqword
+      \fi
+    \fi
+    \hspace*{\fill}%
+  & \if at bonus
+      \@bvpword
+    \else
+      \@vpword
+    \fi
+  & \hspace*{\fill}%
+    \if at bonus
+      \@bvsword
+    \else
+      \@vsword
+    \fi
+    \hspace*{\tabcolsep}\hspace*{\fill}\vline
   \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \bpg at line
+  \ifnum \value{@iterator} < \value{num at cols}\relax
+    \hidden at ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden at ampersand
+    \let\nextdocolumn at heads@v=\docolumn at heads@v
+  \else
+    \let\nextdocolumn at heads@v=\relax
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at bpgnumloop=\do at bpgnumloop
+  \nextdocolumn at heads@v
+}% docolumn at heads@v
+
+\def\docolumn at heads@noscores at v{%
+  % Called by \@multicolumntable.
+  % multicolumnpointtable or multicolumnbonuspointtable, possibly
+  % partial.
+  \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+    \iftbl at pgs
+      \if at bonus
+        \@bvpgword
+      \else
+        \@vpgword
+      \fi
+    \else
+      \if at bonus
+        \@bvqword
+      \else
+        \@vqword
+      \fi
+    \fi
+    \hspace*{\fill}%
+  & \hspace*{\fill}%
+    \if at bonus
+      \@bvpword
+    \else
+      \@vpword
+    \fi
+    \hspace*{\tabcolsep}\hspace*{\fill}\vline
+  \addtocounter{@iterator}{1}%
+  \ifnum \value{@iterator} < \value{num at cols}\relax
+    \hidden at ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden at ampersand
+    \let\nextdocolumn at heads@noscores at v=\docolumn at heads@noscores at v
   \else
-    \let\next at bpgnumloop=\relax
+    \let\nextdocolumn at heads@noscores at v=\relax
   \fi
-  \next at bpgnumloop
-}% do at bpgnumloop
-\def\bpg at line{%
-  % We still don't know why we need to hide this inside of a macro:
-  \pageref{firstbonuspoints at onpage@\arabic{@iterator}}&
-}% bpg at line
+  \nextdocolumn at heads@noscores at v
+}% docolumn at heads@noscores at v
 
-\def\do at bpgptloop{%
+\def\docolumn at heads@comb at v{%
+  % Called by \@multicolumntable.
+  % multicolumncombinedgradetable, possibly partial.
+  \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+    \iftbl at pgs
+      \@cvpgword
+    \else
+      \@cvqword
+    \fi
+    \hspace*{\fill}%
+  & \@cvpword
+  & \@cvbpword
+  & \hspace*{\fill}%
+    \@cvsword
+    \hspace*{\tabcolsep}\hspace*{\fill}\vline
   \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-    \bpgpt at line
-  \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at bpgptloop=\do at bpgptloop
+  \ifnum \value{@iterator} < \value{num at cols}\relax
+    \hidden at ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden at ampersand
+    \let\nextdocolumn at heads@comb at v=\docolumn at heads@comb at v
   \else
-    \let\next at bpgptloop=\relax
+    \let\nextdocolumn at heads@comb at v=\relax
   \fi
-  \next at bpgptloop
-}% do at bpgptloop
-% \bpgpt at line is called by several macros:
-\def\bpgpt at line{%
-  % We still don't know why we need to hide this inside of a macro:
-  \bonuspointsonpage{\the at iterator}&
-}% bpgpt at line
+  \nextdocolumn at heads@comb at v
+}% docolumn at heads@comb at v
 
-\def\do at bpgsloop{%
+\def\docolumn at heads@comb at noscores@v{%
+  % Called by \@multicolumntable.
+  % multicolumncombinedpointtable, possibly partial.
+  \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+    \iftbl at pgs
+      \@cvpgword
+    \else
+      \@cvqword
+    \fi
+    \hspace*{\fill}%
+  & \@vpword
+  & \hspace*{\fill}%
+    \@bvpword
+    \hspace*{\tabcolsep}\hspace*{\fill}\vline
   \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at sentry
-  \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at bpgsloop=\do at bpgsloop
+  \ifnum \value{@iterator} < \value{num at cols}\relax
+    \hidden at ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden at ampersand
+    \let\nextdocolumn at heads@comb at noscores@v=\docolumn at heads@comb at noscores@v
   \else
-    \let\next at bpgsloop=\relax
+    \let\nextdocolumn at heads@comb at noscores@v=\relax
   \fi
-  \next at bpgsloop
-}% do at bpgsloop
+  \nextdocolumn at heads@comb at noscores@v
+}% docolumn at heads@comb at noscores@v
 
 %--------------------------------------------------------------------
-% These things are for combinedgradetable:
+% \do at lines@v is used by *all* multicolumn tables.
+% It calls \do at oneline@v for all non-combined tables and
+% \do at oneline@comb at v for all combined tables.
 
-% \do at cpgnumloop, \cpg at line, \cbpg at line, \do at cpgptloop, \bpgpt at line,
-% \do at cpgsloop, \bpg at sline, and \do at cbpgptloop are only for
-% combinedgradetable:
-\def\do at cpgnumloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at line
+\def\do at lines@v{%
+  % We get here from \@multicolumntable.
+  % ALL MULTICOLUMN TABLES!!!!
+  % pq at index@pts holds the question number or page number in the first
+  % column of the current row.
+  \addtocounter{current at row}{1}%
+  \setcounter{pq at index}{\value{pq at index@pts}}%
+  \setcounter{cols at done}{0}% Number of columns done
+  % We're doing both grade tables and point tables!!
+  \if at combined
+    \do at oneline@comb at v
   \else
-    \check at bpgpts
+    \do at oneline@v
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at cpgnumloop=\do at cpgnumloop
+  \increment at index{pq at index@pts}%
+  % We need the "\\ \cline at stuff@v" to *immediately* precede the
+  % \end{tabular} (i.e., with no \ifnum separating them), to avoid
+  % having crap after the \cline at stuff@v that
+  % causes there to be an extra row at the end of the table.  We also
+  % need there to be nothing between \\ and \cline at stuff@v.
+  \ifnum \value{current at row} = \value{num at rows}\relax
+    \\
+    \cline at stuff@v
+    \end{tabular}%
+    \let\nextdo at lines@v=\relax
   \else
-    \let\next at cpgnumloop=\relax
+    \\
+    \cline at stuff@v
+    \let\nextdo at lines@v=\do at lines@v
   \fi
-  \next at cpgnumloop
-}% do at bpgnumloop
-\def\check at bpgpts{%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \bpg at line
-  \fi
-}% check at bpgpts
+  \nextdo at lines@v
+}% do at lines@v
 
-\def\do at cpgptloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at points}{tmp at hlfcntr}%
-    \pgpt at entry
+\def\do at oneline@v{%
+  % Called by \do at lines@v.
+  % Used for all multicolumn non-combined tables.
+  % pq at index holds the question or page number we're about to do.
+  \ifnum \value{pq at index} > \last at pq@index\relax
+    % See if we're in the last column; use pq at index@bpts as a scratch
+    % counter: 
+    \setcounter{pq at index@bpts}{\value{cols at done}}%
+    \addtocounter{pq at index@bpts}{1}%
+    \ifnum \value{pq at index@bpts} = \value{num at cols}\relax
+      % We're in the last column; are we in the last row?
+      \ifnum \value{current at row} = \value{num at rows}\relax
+        % We're in the last column, last row!
+        % Print the total:
+        \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+          \if at bonus
+            \@bvtword
+          \else
+            \@vtword
+          \fi
+          \hspace*{\fill}%
+          \hidden at ampersand
+        \if at scores
+            \if at bonus
+              \prt at tablebonuspoints
+            \else
+              \prt at tablepoints
+            \fi
+          \hidden at ampersand
+            \hspace*{\fill}%
+            \hbox to \@cellwidth{\hfill}%
+            \hspace*{\tabcolsep}\hspace*{\fill}\vline
+        \else
+            \hspace*{\fill}%
+            \if at bonus
+              \prt at tablebonuspoints
+            \else
+              \prt at tablepoints
+            \fi
+            \hspace*{\tabcolsep}\hspace*{\fill}\vline
+        \fi
+      \else
+        % Not last column last row; insert empty space:
+        \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+          \hbox to \@cellwidth{\hfill}%
+          \hspace*{\fill}%
+        \if at scores
+          \hidden at ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \fi
+        \hidden at ampersand
+          \hspace*{\fill}%
+          \hbox to \@cellwidth{\hfill}%
+          \hspace*{\tabcolsep}\hspace*{\fill}\vline
+      \fi
+    \else
+      % Not last column; insert empty space:
+      \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\fill}%
+      \if at scores
+        \hidden at ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+      \hidden at ampersand
+        \hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \fi
   \else
-    \check at cbpts
+    % We need to do question (or page) number pq at index:
+    \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+      \refto at index{pq at index}%
+      \hspace*{\fill}%
+    \hidden at ampersand
+      \if at scores
+        \if at bonus
+          \bonuspointsof at index{pq at index}%
+          \addto at hlfcntr{tbl at bonuspoints}{\bonuspointsof at index{pq at index}}%
+        \else
+          \pointsof at index{pq at index}%
+          \addto at hlfcntr{tbl at points}{\pointsof at index{pq at index}}%
+        \fi
+      \hidden at ampersand
+        \hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \else
+        \hspace*{\fill}%
+        \if at bonus
+          \bonuspointsof at index{pq at index}%
+          \addto at hlfcntr{tbl at bonuspoints}{\bonuspointsof at index{pq at index}}%
+        \else
+          \pointsof at index{pq at index}%
+          \addto at hlfcntr{tbl at points}{\pointsof at index{pq at index}}%
+        \fi
+        \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \fi
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at cpgptloop=\do at cpgptloop
+  \addtocounter{cols at done}{1}% Number of columns done
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    \hidden at ampersand
+      \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+      \nextcolumn at index@v{pq at index}%
+    \hidden at ampersand
+      \let\nextdo at oneline@v=\do at oneline@v
   \else
-    \let\next at cpgptloop=\relax
+    \let\nextdo at oneline@v=\relax
   \fi
-  \next at cpgptloop
-}% do at cpgptloop
-\def\check at cbpts{%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pgpt at entry
-  \fi
-}% check at cbpts
+  \nextdo at oneline@v
+}% do at oneline@v
 
-\def\do at cbpgptloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \add at hlfcntrtohlfcntr{tbl at bonuspoints}{tmp at hlfcntr}%
-    \bpgpt at line
+\def\do at oneline@comb at v{%
+  % Called by \do at lines@v.
+  % All combined multicolumn tables.
+  % pq at index holds the question (or page) we're about to do.
+  \ifnum \value{pq at index} > \last at pq@index\relax
+    % See if we're in the last column; use pq at index@bpts as a scratch
+    % counter: 
+    \setcounter{pq at index@bpts}{\value{cols at done}}%
+    \addtocounter{pq at index@bpts}{1}%
+    \ifnum \value{pq at index@bpts} = \value{num at cols}\relax
+      % We're in the last column; are we in the last row?
+      \ifnum \value{current at row} = \value{num at rows}\relax
+        % We're in the last column, last row!
+        % Print the total:
+        \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+          \@cvtword
+          \hspace*{\fill}%
+        \hidden at ampersand
+          \prt at tablepoints
+        \hidden at ampersand
+        \if at scores
+            \prt at tablebonuspoints
+          \hidden at ampersand
+            \hspace*{\fill}%
+            \hbox to \@cellwidth{\hfill}%
+            \hspace*{\tabcolsep}\hspace*{\fill}\vline
+        \else
+          \hspace*{\fill}%
+          \prt at tablebonuspoints
+          \hspace*{\tabcolsep}\hspace*{\fill}\vline
+        \fi
+      \else
+        % Last column, but not last row; insert empty space:
+        \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+          \hbox to \@cellwidth{\hfill}%
+          \hspace*{\fill}%
+        \hidden at ampersand
+          \hbox to \@cellwidth{\hfill}%
+        \if at scores
+          \hidden at ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \fi
+        \hidden at ampersand
+          \hspace*{\fill}%
+          \hbox to \@cellwidth{\hfill}%
+          \hspace*{\tabcolsep}\hspace*{\fill}\vline
+      \fi
+    \else
+      % Not last column; insert empty space:
+      \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\fill}%
+      \hidden at ampersand
+        \hbox to \@cellwidth{\hfill}%
+      \if at scores
+        \hidden at ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+      \hidden at ampersand
+        \hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \fi
   \else
-    \check at cnonpts
+    % We need to do question number pq at index:
+    \vline \hspace*{\tabcolsep}\hspace*{\fill}%
+      \refto at index{pq at index}%
+      \hspace*{\fill}%
+    \hidden at ampersand
+      \pointsof at index{pq at index}%
+      \addto at hlfcntr{tbl at points}{\pointsof at index{pq at index}}%
+    \hidden at ampersand
+    \if at scores
+        \bonuspointsof at index{pq at index}%
+        \addto at hlfcntr{tbl at bonuspoints}{\bonuspointsof at index{pq at index}}%
+      \hidden at ampersand
+        \hspace*{\fill}%
+        \hbox to \@cellwidth{\hfill}%
+        \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \else
+      \hspace*{\fill}%
+      \bonuspointsof at index{pq at index}%
+      \addto at hlfcntr{tbl at bonuspoints}{\bonuspointsof at index{pq at index}}%
+      \hspace*{\tabcolsep}\hspace*{\fill}\vline
+    \fi
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at cbpgptloop=\do at cbpgptloop
+  \addtocounter{cols at done}{1}% Number of columns done
+  \ifnum \value{cols at done} < \value{num at cols}\relax
+    \hidden at ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \nextcolumn at index@v{pq at index}%
+    \hidden at ampersand
+    \let\nextdo at oneline@comb at v=\do at oneline@comb at v
   \else
-    \let\next at cbpgptloop=\relax
+    \let\nextdo at oneline@comb at v=\relax
   \fi
-  \next at cbpgptloop
-}% do at cbpgptloop
-\def\check at cnonpts{%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \bpgpt at line
+  \nextdo at oneline@comb at v
+}% do at oneline@comb at v
+
+%--------------------------------------------------------------------
+% \find at nextpagewithpoints and \find at nextcolumnpage@v:
+
+\def\find at nextpagewithpoints#1{%
+  % Called by \dofind at nextcolumnpage@v, \increment at index, and
+  % \@multicolumntable.
+  % The argument #1 should be the name of a counter with a nonnegative
+  % value.
+  % We increase #1 by at least 1 to either the number of the
+  % next page containing the appropriate kind of points, or to something
+  % greater than \tbl at lastp. 
+  \addtocounter{#1}{1}%
+  \if at combined
+    \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\value{#1}}}%
+    \addto at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\value{#1}}}%
+    % The sum is positive when at least one of them is positive.
+  \else
+    \if at bonus
+      \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\value{#1}}}%
+    \else
+      \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\value{#1}}}%
+    \fi
   \fi
-}% check at cnonpts
-
-\def\do at cpgsloop{%
-  \addtocounter{@iterator}{1}%
-  \set at hlfcntr{tmp at hlfcntr}{\pointsonpage{\the at iterator}}%
   \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at sentry
+    \let\nextfind at nextpagewithpoints=\relax
   \else
-    \check at bnsline
+    \ifnum \value{#1} > \tbl at lastp\relax
+      \let\nextfind at nextpagewithpoints=\relax
+    \else
+      \def\nextfind at nextpagewithpoints{\find at nextpagewithpoints{#1}}%
+    \fi
   \fi
-  \ifnum \the at iterator < \tbl at lastp\relax
-    \let\next at cpgsloop=\do at cpgsloop
+  \nextfind at nextpagewithpoints
+}% find at nextpagewithpoints
+
+\def\find at nextcolumnpage@v#1{%
+  % Called by \nextcolumn at index@v.
+  % This is used for all multicolumn tables that are indexed by
+  % pages.
+  % We use \find at nextpagewithpoints to increment #1 to either
+  % the (num at rows)'th page number after #1 that contains the
+  % appropriate kind of points or to a value greater than \tbl at lastp.
+  % We use pq at index@bpts as a scratch counter.
+  \setcounter{pq at index@bpts}{0}%
+  \dofind at nextcolumnpage@v{#1}%
+}% find at nextcolumnpage@v
+\def\dofind at nextcolumnpage@v#1{%
+  % Called only by \find at nextcolumnpage@v.
+  \addtocounter{pq at index@bpts}{1}%
+  \find at nextpagewithpoints{#1}%
+  \ifnum \value{pq at index@bpts} = \value{num at rows}\relax
+    \let\nextdofind at nextcolumnpage@v=\relax
   \else
-    \let\next at cpgsloop=\relax
+    % The following test shouldn't be needed, in theory, because the
+    % computation of num at cols should prevent trouble, but we're being
+    % paranoid.
+    \ifnum \value{#1} > \tbl at lastp\relax
+      \let\nextdofind at nextcolumnpage@v=\relax
+    \else
+      % Note: this is a \def, and not a \let, because we need to put
+      % in the argument #1:
+      \def\nextdofind at nextcolumnpage@v{\dofind at nextcolumnpage@v{#1}}%
+    \fi
   \fi
-  \next at cpgsloop
-}% do at cpgsloop
-\def\check at bnsline{%
-  \set at hlfcntr{tmp at hlfcntr}{\bonuspointsonpage{\the at iterator}}%
-  \ifhlfcntr at pos{tmp at hlfcntr}%
-    \pg at sentry
-  \fi
-}% check at bnsline
+  \nextdofind at nextcolumnpage@v
+}% dofind at nextcolumnpage@v
 
 %--------------------------------------------------------------------
-% \pointsinrange and \bonuspointsinrange:
+% \pointsinrange and \bonuspointsinrange, and then
+% \firstqinrange, \lastqinrange, and \numqinrange.
+
+
 % We say either \@bonusfalse or \@bonustrue, and then we check it only
 % in \do at countloop:
 \def\pointsinrange#1{%
@@ -6203,7 +7460,7 @@
   \@bonusfalse
   \def\tbl at range{#1}%
   \@ifundefined{exam at numpoints}%
-    {\mbox{\normalfont\bf ??}}%
+    {\mbox{\normalfont\bfseries ??}}%
     {\read at range}%
 }% pointsinrange
 
@@ -6211,19 +7468,22 @@
   \@bonustrue
   \def\tbl at range{#1}%
   \@ifundefined{exam at numpoints}%
-    {\mbox{\normalfont\bf ??}}%
+    {\mbox{\normalfont\bfseries ??}}%
     {\read at range}%
 }% bonuspointsinrange
 
 \def\bad at range{%
-  {\mbox{\normalfont\bf ??}}%
+  % Called by \read at range, \firstqinrange, \lastqinrange, and
+  % \numqinrange.
+  {\mbox{\normalfont\bfseries ??}}%
   \ClassWarning{exam}{%
-    Grading range `\tbl at range ' not defined.\MessageBreak
+    Grading range `\tbl at range' not defined.\MessageBreak
     \space\space Run LaTeX again.\MessageBreak
   }%
 }% bad at range
 
 \def\read at range{%
+  % Called by \pointsinrange and \bonuspointsinrange.
   \@ifundefined{range@\tbl at range @firstq}%
   {%
     \bad at range
@@ -6238,7 +7498,7 @@
       \edef\tbl at lastq{\csname range@\tbl at range @lastq\endcsname}%
       % Check that firstq precedes or equals lastq:
       \ifnum \tbl at firstq > \tbl at lastq\relax
-        \fbox{Error: Grading Range `\tbl at range ':
+        \fbox{\textbf{Error:} Grading Range `\tbl at range ':
           Last question precedes first question.}%
         \ClassError{exam}{%
           In grading range `\tbl at range ',
@@ -6289,26 +7549,29 @@
 % \firstqinrange, \lastqinrange, and \numqinrange.
 
 \newcommand{\firstqinrange}[1]{%
-  \@ifundefined{range@#1 at firstq}%
-  {\mbox{\normalfont\bf ??}}%
+  \def\tbl at range{#1}%
+  \@ifundefined{range@\tbl at range @firstq}%
+  {\bad at range}%
   {\csname range@#1 at firstq\endcsname}%
 }% firstqinrange
 
 \newcommand{\lastqinrange}[1]{%
-  \@ifundefined{range@#1 at lastq}%
-  {\mbox{\normalfont\bf ??}}%
+  \def\tbl at range{#1}%
+  \@ifundefined{range@\tbl at range @lastq}%
+  {\bad at range}%
   {\csname range@#1 at lastq\endcsname}%
 }% lastqinrange
 
 \newcommand{\numqinrange}[1]{%
+  \def\tbl at range{#1}%
   \@ifundefined{range@#1 at firstq}%
   {%
-    \mbox{\normalfont\bf ??}%
+    \bad at range
   }%
   {%
     \@ifundefined{range@#1 at lastq}%
     {%
-      \mbox{\normalfont\bf ??}%
+      \bad at range
     }%
     {%
       \setcounter{@iterator}{\csname range@#1 at lastq\endcsname}%
@@ -6319,6 +7582,7 @@
   }%
 }% numqinrange
 
+
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 %
@@ -6632,12 +7896,63 @@
 % much vertical space, then it will spill out of the bottom of the
 % box, overwriting whatever follows the box.
 
+% 2016/02/08: The solutionbox frame can now be printed in color, as
+% long as you load color.sty in the preamble.
+%
+%  Usage: Say
+%
+% \usepackage{color}
+%
+% in the preamble, and then give the command
+%
+%   \colorsolutionboxes
+%
+% to have the frame around a solutionbox in color.  The default color
+% was created by the command
+%
+%     \definecolor{SolutionBoxColor}{gray}{0.8}
+%
+% and you can change the color by giving a new \definecolor command
+% (which must be done *after* the \colorsolutionboxes command).
+%
+% To cancel color solutionbox frames and return to black, give the
+% command
+%
+%   \nocolorsolutionboxes
+
+\newif\if at colorsolutionboxes
+\@colorsolutionboxesfalse
+\def\colorsolutionboxes{%
+  \@ifundefined{definecolor}
+  {%
+    \ClassError{exam}{%
+      You must load the color package with the command\MessageBreak
+      \space\space\protect\usepackage{color}\MessageBreak
+      in order to use the command \protect\colorsolutionboxes
+      \MessageBreak
+      }{%
+      This command makes use of the package color.sty,\MessageBreak
+      and so you have to load color.sty before your\MessageBreak
+      \protect\begin{document} command.\MessageBreak
+      }%
+  }%
+  {%
+    \definecolor{SolutionBoxColor}{gray}{0.8}
+    \@colorsolutionboxestrue
+  }%
+}
+\def\nocolorsolutionboxes{\@colorsolutionboxesfalse}
+
 \newbox\exam at box
 \newenvironment{solutionbox}[1]{%
   \@insolutiontrue % cancelled by the end of the environment
   \@addpointsfalse % cancelled by the end of the environment
   \def\solutionbox at size{#1}% saved for end of environment
-  \@tempdima=\textwidth
+% Change, 2016/02/08: So that the solutionbox environment will work
+% correctly inside of a tabular environment, we use \hsize instead of
+% \textwidth:
+%  \@tempdima=\textwidth
+  \@tempdima=\hsize
   \advance\@tempdima -\@totalleftmargin
   \advance\@tempdima -6\fboxsep
   \advance\@tempdima -2\fboxrule
@@ -6654,6 +7969,11 @@
       \leftskip=0pt
       \rightskip=0pt
       \vskip 2\fboxsep
+% Change, 2016/05/09: We change \@totalleftmargin and \linewidth in
+% case there are enumerate, itemize, or description environments
+% inside the solution:
+      \@totalleftmargin=0pt
+      \linewidth=\hsize
       \solutiontitle
       \ignorespaces
   }%
@@ -6673,7 +7993,8 @@
       % \parshape) shift us to the right when we enter horizontal
       % mode.  If we don't use this \hbox, then we'd have to comment
       % out the \hskip \@totalleftmargin:
-      \hbox to \textwidth{%
+      % 2016/02/08: Changed \textwidth to \hsize:
+      \hbox to \hsize{%
         \noindent
         \hskip\@totalleftmargin
         \hskip3\fboxsep\hskip\fboxrule
@@ -6682,15 +8003,41 @@
       \par\nointerlineskip
     \fi
   \endgroup % Finish confining the \Solution at Emphasis
-  \makeemptybox{\solutionbox at size}%  
-  }
+% Starting in version 2.502, 2016/03/23,the decision of whether to
+% color the box is made in the \makeemptybox command:
+  \makeemptybox{\solutionbox at size}
+  }% End of the second argument of \newenvironment{solutionbox}
 
 
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Added in version 2.502: 2016/03/23, \colorfbox
 
+% The \colorfbox command is used in our modification of framed.sty
+% that allows us to print the frame around the solution in color when
+% the user has given the command \colorsolutionboxes.  It takes two
+% arguments, the first being the color for the frame, and the second
+% being the stuff to be framed.
 
+% If we had assumed that color.sty was used (instead of just color.sty),
+% then the line that saves the current color in saved at color could have
+% been just
 
+%   \colorlet{saved at color}{.}
 
+% but we wanted to make this work even if color.sty is being used.
 
+% When you define a color mycolor using either color.sty or
+% xcolor.sty, a macro \csname\string\color@ mycolor\endcsname is
+% defined (i.e., the macro name is \\color at mycolor).
+\newcommand{\colorfbox}[2]{%
+  % Save the current color in saved at color:
+  \expandafter\let\csname\string\color at saved@color\endcsname\current at color
+  % Create the box in color #1, with the text in saved at color
+  % (the braces are to confine the color change commands):
+  {\color{#1}\fbox{\color{saved at color}#2}}%
+}% colorfbox
+
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 
@@ -6945,7 +8292,19 @@
 \catcode`\|=\FrameRestore
 
 % Provide configuration commands:
-\providecommand\FrameCommand{\fboxrule=\FrameRule \fboxsep=\FrameSep \fbox}
+%psh: Version 2.502, 2016/03/23, changed \FrameCommand so that the
+%     frame is printed in color if the user has said
+%     \colorsolutionboxes:
+%\providecommand\FrameCommand{\fboxrule=\FrameRule \fboxsep=\FrameSep \fbox}
+\def\FrameCommand{\fboxrule=\FrameRule \fboxsep=\FrameSep
+  \if at colorsolutionboxes
+    \def\box at it{\colorfbox{SolutionBoxColor}}%
+  \else
+    \def\box at it{\fbox}%
+  \fi
+  \box at it
+}% \FrameCommand
+
 \@ifundefined{FrameRule}{\newdimen\FrameRule \FrameRule=\fboxrule}{}
 \@ifundefined{FrameSep} {\newdimen\FrameSep  \FrameSep =3\fboxsep}{}
 



More information about the tex-live-commits mailing list