]> git.donarmstrong.com Git - lib.git/commitdiff
update exam.cls
authorDon Armstrong <don@donarmstrong.com>
Tue, 2 Mar 2021 00:35:51 +0000 (16:35 -0800)
committerDon Armstrong <don@donarmstrong.com>
Tue, 2 Mar 2021 00:35:51 +0000 (16:35 -0800)
texmf/tex/latex/exam.cls

index d6165bf41555c864f881beca6784665b38c0658e..ca441f5e73e476762536184e428d4166589518e3 100644 (file)
@@ -3,7 +3,8 @@
 % A LaTeX2e document class for preparing exams.
 
 %% exam.cls
-%% Copyright (c) 1994, 1997, 2000, 2004 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
@@ -18,7 +19,7 @@
 % This work consists of the files exam.cls and examdoc.tex.
 
 
-% The user documentation for exam.cls is in the file examdoc.tex.
+% The user's guide for exam.cls is in the file examdoc.tex.
 
 
 %%% Philip Hirschhorn
 % from my web page: http://www-math.mit.edu/~psh/
 
 
-\def\fileversion{2.2}
-\def\filedate{2004/08/14}
+\def\fileversion{2.603}
+\def\filedate{2017/12/17}
 %---------------------------------------------------------------------
 %---------------------------------------------------------------------
-% PLEASE DO NOT MAKE ANY CHANGES TO THIS FILE!
-% 
-% If you wish to make changes to this file, rename this file
-% to something other than exam.cls BEFORE YOU MAKE THE CHANGES!
 % 
 % If there's some feature that you'd like that this file doesn't
 % provide, tell me about it.
 %
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
-%                             Changelog:
+%                    Changelog since version 2.4:
 
+%--------------------------------------------------------------------
+% Version 2.603, 2017/12/15
+
+% No longer betatest.
 
-% Version 2.2:
-%
-% Enough already with betatest.
-%
 %--------------------------------------------------------------------
-%
-% Version 2.116$\beta$:
-%
-% We futzed with the oneparchoices environment so that a bit more
-% space will be left before the first choice when the list of choices
-% continues the paragraph of the question.
-%
+% Version 2.602$\beta$, 2017/12/15
+
+% We changed the code for multicolumn grade and point tables to remove
+% the incompatibility with colortbl.sty and other packages that load
+% colortbl.sty (e.g., xcolor.sty with the "table" option).
+
 %--------------------------------------------------------------------
-%
-% Version 2.115$\beta$:
-%
-% New environment: oneparchoices
-%
-% Intended for multiple choice questions (just like the choices
-% environment), except that the oneparchoices prints all the choices in
-% a single paragraph.  This environment does *not* create a paragraph
-% break at its beginning, so if you begin the environment without
-% skipping a line before it, the choices will be printed as a
-% continuation of the paragraph preceding the environment.
-%
+% Version 2.601$\beta$, 2017/09/22
+
+% We changed command and environment names in the code from framed.sty
+% that's included (slightly modified) in exam.cls so that the user can
+% say \usepackage{framed} without creating conflicts.  This also allows
+% the user to use packages, such as minted.sty, that load framed.sty
+
 %--------------------------------------------------------------------
-%
-% Version 2.114$\beta$:
-%
-% We fixed up the code for \uplevel and \fullwidth so that they work
-% correctly if a solution environment is inside the argument.
-%
+% Version 2.6, 2017/09/19
+
+% No longer betatest.
+
 %--------------------------------------------------------------------
-%
-% Version 2.113$\beta$:
-%
-% Added code to warn if point totals have changed since the last run of
-% LaTeX (which requires running LaTeX again to make sure that
-% gradetables, pointsofquestion, and pointsonpage values are correct).
-%
-%
-% We also added code to create a label for every question, part,
-% subpart, and subsubpart.  We make no use of these labels, but we put
-% them there so that if a question (or part, etc.) is, e.g., moved from
-% one page to another, LaTeX will notice this and warn the user that
-% LaTeX must be run one more time to be sure everything is correct.
-%
-% We need to do this even though we've already included code to check
-% when point totals change because questions (and parts, etc.) know what
-% page they're on from reading the info written to the .aux file on the
-% previous run.  Thus, if a question (or part, etc.) is moved to a
-% different page, then the pointsonpage totals won't notice until the
-% *second* subsequent run of LaTeX, and so there'll be no warning to the
-% user on the *first* run.  Including these labels gives the user a
-% warning on that first run.
-%
+% 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.112$\beta$:
-%
-% New environment: coverpages
-%
-% This is for use only *before* any questions (or parts, or subparts, or
-% subsubparts).  It allows you to print one or more cover pages,
-% numbered in a different sequence of page numbers from the main pages.
-% That is, the page number is reset to 1 at the end of a coverpages
-% environment.
-%
-% The default is that headers and footers are empty on the coverpages,
-% but if you print the page number (using \thepage in the header or
-% footer) then the page number will be printed in roman numerals.  When
-% the coverpages environment ends, the page number is reset to 1 and the
-% page numbers revert to the (standard) arabic numbers.
-%
-% Headers and footers on coverpages are determined by commands
-% completely analogous to the relevant commands for the main pages,
-% except that the names of the commands begin with the word ``cover''.
-% That is, the following commands are available to define headers and
-% footers, and to leave additional space for the headers and footers as
-% needed: 
-%
-%
-% New commands:
-%
-% \coverheader
-% \coverrunningheader
-% \coverfirstpageheader
-%
-% \coverlhead
-% \coverchead
-% \coverrhead
-%
-% \coverfooter
-% \coverrunningfooter
-% \coverfirstpagefooter
-%
-% \coverlfoot
-% \covercfoot
-% \coverrfoot
-%
-% \coverextraheadheight
-% \coverextrafootheight
-%
+% 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.111$\beta$:
-%
-% Bugfix: There was an incompatibility with spanish.ldf (loaded when the
-% user loads the babel package with the spanish option) (and possibly
-% with other language packages as well).  That file redefines \@roman,
-% which is used by \roman, which I had used to define some internally
-% used command names.  I replaced
-%
-%   \roman{countername}
-%
-% with
-%
-%   \romannumeral \csname c@countername\endcsname
-%
-% for all of those command names, and now all at least seems to be
-% well.
-%
-%
-% The following stuff isn't really complete yet:
-% New environment: coverpages
-%
+% 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:
-%
-% \coverheader
-% \coverrunningheader
-% \coverfirstpageheader
-%
-% \coverlhead
-% \coverchead
-% \coverrhead
-%
-% \coverfooter
-% \coverrunningfooter
-% \coverfirstpagefooter
-%
-% \coverlfoot
-% \covercfoot
-% \coverrfoot
-%
-% \coverextraheadheight
-% \coverextrafootheight
-%
+
+%   \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.110$\beta$:
-%
-% We change the definition of \half to print a fraction one half that
-% uses a slanted fraction bar.  We also created two new commands:
-%
-%   \newcommand*\usehorizontalhalf
-%   \newcommand*\useslantedhalf
-%
-% The first one changes the definition of \half so that it produces a
-% fraction with a horizontal fraction bar (via $\frac{1}{2}$), and the
-% second one returns the definition of \half to its default.
-%
+% 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.109$\beta$:
-%
-% Point values for questions (and parts, etc.) can now include half
-% points.  To specify a half point, you include ``\half'' immediately
-% following the integer part (or just with ``\half'' if the integer part
-% is zero).  For example, the following are all valid point values:
-%
-% 0
-% \half
-% 1
-% 1\half
-% 2
-% 2\half
-% Etc.
-%
+% 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@begingroup and
+% \color@endgroup as needed.  (We're pretty sure the actual fix was
+% the \endgraf in the expansion of \color@endgroup.)
+
 %--------------------------------------------------------------------
-%
-% Version 2.108$\beta$:
-%
-% New environment: solutionorlines
-%
-% This is almost identical to the solution environment, except that when
-% solutions are not being printed and an optional argument appears
-% specifying an amount of space to be left for answers, that space is
-% filled with ruled lines (created by \fillwithlines), rather than being
-% left blank (as it is by the solution environment).
-%
-% Also: Changed the shade of gray used as the default color for
-%       \shadedsolutions.
-%
-%       Changed the default thickness of lines used by \fillwithlines.
-%
+% 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.107$\beta$:
-%
-% We added an option to have solution environments use a \colorbox (from
-% the color package) instead of an \fbox.  Instead of surrounding the
-% solution with a printed box, it prints the solution on a colored
-% background.  The default color is a light gray (so that it can be
-% printed on any printer that can do grayscale), but the color can be
-% changed.
-%
-% To use this option, the user must load the color package with the
-% command
-%
-%   \usepackage{color}
-%
-% in the preamble.  (If appropriate, optional arguments can be used in
-% that command.)  The user can then give the command
-%
-%   \shadedsolutions
-%
-% to have all solutions printed using a \colorbox.  After giving that
-% command, the user can change the color of the backgrounds of solutions
-% by defining the color ``SolutionColor''.  The default SolutionColor is
-% set via the command
-%
-%   \definecolor{SolutionColor}{gray}{0.9}
-%
-% and the user can change that by giving a \definecolor command *after*
-% giving the command \shadedsolutions.  For example, the command
-%
-%   \definecolor{SolutionColor}{rgb}{0.8,0.9,1}
-%
-% sets the background color to a light blue.
-%
-% You can return to the default situation of having solutions printed
-% inside an \fbox by giving the command
-%
-%   \framedsolutions
-%
-% Note added later: The default shade of gray was changed in version
-% 2.108$\beta$.
-%
+% 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.106$\beta$:
-%
-%
-% We redid the solution environment to use Donald Arseneau's framed.sty
-% macros, so that the solution can be broken across pages with each
-% piece enclosed in a frame.
-%
-% We also changed the default definition of \solutiontitle; it's now
-% defined by:
-%
-%  \newcommand{\solutiontitle}{\noindent\textbf{Solution:}\enspace}
-%
-%
+% 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.
 %
-% Version 2.105$\beta$:
-%
-% We're making the thickness of the lines used by \fillwithlines
-% changeable, and changing the default thickness.
+%  Usage: Say
 %
-% We're doing this by defining a new command:
+%   \usepackage{color}
 %
-%   \linefill,
+% in the preamble, and then give the command
 %
-% which is similar to \hrulefill, except that \linefill uses a line of
-% height \linefillthickness, whose default value is 0.2pt.
-% (\fillwithlines used to use \hrulefill, which uses a line of height
-% 0.4pt.)
+%   \colorsolutionboxes
 %
-% The default value of \linefillthickness is set by the command
+% to have the frame around a solutionbox in color.  The default color
+% was created by the command
 %
-%   \setlength\linefillthickness{0.2pt}
+%   \definecolor{SolutionBoxColor}{gray}{0.8}
 %
-% and this can be changed by giving a new \setlength command.
+% and you can change the color by giving a new \definecolor command
+% (which must be done *after* the \colorsolutionboxes command).
 %
-% Note added later: The default thickness was changed in version
-% 2.108$\beta$.
+% To cancel color solutionbox frames and return to black, give the
+% command
 %
+%   \nocolorsolutionboxes
+
 %--------------------------------------------------------------------
-%
-% Version 2.104$\beta$:
-%
-% We added a new command: \fillwithlines
-%
-% \fillwithlines takes one argument, which is either a length or \fill,
-% and it fills that much vertical space with horizontal lines that run
-% the length of the current line.  That is, they extend from the current
-% left margin (which depends on whether we're in a question, part,
-% subpart, or subsubpart) to the right margin.
-%
-% The distance between the lines is \linefillheight, whose default value
-% is set with the command
-%
-%   \setlength\linefillheight{.25in}
-%
-% This value can be changed by giving a new \setlength command.
-%
+% Version 2.5 2015/05/07
+
+% No longer betatest.
+
 %--------------------------------------------------------------------
+% Version 2.408$\beta$ 2013/11/17
+
+% New commands:
 %
-% Version 2.103$\beta$:
-%
-% We added a new command: \answerline
-%
-% This is intended for short answer questions.  It inserts a \vskip of
-% length \answerskip and then inserts a horizontal line of length
-% \answerlinelength at the right margin, preceded by the number of the
-% current question, part, subpart, or subsubpart.
-%
-% The default values are set by the commands
-%
-%   \setlength\answerlinelength{1in}
-%   \setlength\answerskip{2ex}
-%
-% and these can be changed by giving new \setlength commands.
+%   \firstqinrange{whatever}
+%   \lastqinrange{whatever}
+%   \numqinrange{whatever}
 %
-%--------------------------------------------------------------------
+% where ``whatever'' is the name of a grading range.
 %
-% Version 2.101$\beta$:
+% \firstqinrange{whatever} prints the number of the first question in
+% the range.
 %
-% We eliminated the command \marks, since it conflicts with a definition
-% of that name in some package or other.  We changed the definition of
-% \marksnotpoints so that it still accomplishes the same thing, but now
-% it works through the \pointpoints command.
+% \lastqinrange{whatever} prints the number of the last question in the
+% range.
 %
-% We changed the default format for the \droptotalpoints command, so
-% that it now prints
+% \numqinrange{whatever} prints the number of questions in the range.
 %
-%   Total for Question 1: 25
+% We changed a couple of internal command name related to grading
+% ranges.  If the user defines the range `myrange', then we now use
+
+%   \range@myrange@firstp
+%   \range@myrange@lastp
+%   \range@myrange@firstq
+%   \range@myrange@lastq
 %
-% where the ``25'' is followed by \marginpointname, whose default value
-% is empty.
+% where we used to use
 %
+%   \tbl@myrange@firstp
+%   \tbl@myrange@lastp
+%   \tbl@myrange@firstq
+%   \tbl@myrange@lastq
+
 %--------------------------------------------------------------------
+% Version 2.407$\beta$ 2012/12/19
+
+% New environment:
 %
-% Version 2.098$\beta$:
-%
-% We moved the definitions of \thepartno, \thesubpart, and
-% \thesubsubpart outside of the associated list environments, so that
-% the user can redefine them once at the beginning of the LaTeX file and
-% not have their redefinition overridden in every such list.
-%
-% We also replaced most occurrences of \thequestion with
-% \arabic{question}, so that the user can safely redefine \thequestion
-% to something like \Alph{question}, etc.
-%
-% We rewrote the components of the \gradetable[v][questions] and
-% \gradetable[h][questions] to use the question counter in place of
-% @iterator, and to insert \thequestion instead of \the@iterator as the
-% question number in the table (so that if the user redefines
-% \thequestion, then the table will print the question ``numbers'' (or
-% letters, etc.) in whatever format \thequestion prints the question
-% numbers on the exam).
-%
-% We also created a new command:
+%   solutionbox
 %
-%   \totalpoints
+% The solutionbox environment is different from the other solution
+% environments (solution, solutionorbox, solutionorlines,
+% solutionordottedlines, and solutionorgrid), in that
 %
-% for use in the argument of a \totalformat command.  \totalpoints is
-% just an abbreviation for \pointsofquestion{\arabic{question}}.  That
-% is, it's a macro that prints the total number of points for the
-% current question.
+%   (1) The box is always printed, whether answers are being printed
+%   or not.
 %
-% We changed the longsolution environment to make it easy to change the
-% amount by which the left and right margins are increased.  The default
-% amount is set by the command
+%   (2) The argument giving the size of the box is a required
+%   argument, not an optional argument, and so it should be enclosed
+%   in braces, not in brackets.  It can be either a length or
+%   \stretch{number}.
 %
-%   \setlength{\longsolutionindent}{2em}
+%   (3) We make no use of the TheSolution environment; the solutionbox
+%   environment is completely freestanding.
 %
-% and the user can change this by giving a new \setlength command.
+% 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.
 %
-% Note added later: longsolution was eliminated in version
-% 2.106$\beta$.
+% Note: It's the user's responsibility to be sure that the box is
+% large enough to hold the solution!  If the solution takes up too
+% much vertical space, then it will spill out of the bottom of the
+% box, overwriting whatever follows the box.
 %
 %--------------------------------------------------------------------
+% Version 2.406$\beta$, 2012/12/16
 %
-% Version 2.097$\beta$:
-%
-% We've changed the default format for the total points for a question
-% printed by the \droptotalpoints command.  This command still prints
-% the total points for the question right justified a distance of
-% \rightpointsmargin from the right edge of the paper, but now the
-% number of points will, by default, be surrounded by either
-%
-%   a double box, if \boxedpoints is in effect,
-%   double brackets, if \bracketedpoints is in effect, or
-%   double parentheses, otherwise.
-%
-% It's still true that if you use a \totalformat command, then the
-% format of the printed total points is completely controlled by the
-% argument of the \totalformat command and the default doesn't matter at
-% all.
+% New command:
 %
+%   \noquestionsonthispage
 %
-% Note added later: We changed the default format again in version
-% 2.101$\beta$; see the notes above.
+%   This command tells the \ifcontinuation and \ifincomplete commands
+%   to assume that no part of any question is on this page.  This is
+%   similar to the job done by the \nomorequestions command for the
+%   pages that follow the end of all of the questions.
 %
-%--------------------------------------------------------------------
-%
-% Version 2.096$\beta$:
 %
-% The labels for questions, parts, subparts, and subsubparts can now be
-% customized.  The format now depends on the commands
+% If you give the command \noquestionsonthispage on a page, then
 %
-%   \questionlabel
-%   \partlabel
-%   \subpartlabel
-%   \subsubpartlabel
+%   (1) \ifcontinuation on that page will expand to its second
+%   argument,
+%   (2) \ifincomplete on that page will expand to its second
+%   argument, and
+%   (3) an \ifincomplete on an earlier page will not assume that a
+%   question from that earlier page continues onto this page.
 %
-% the default definitions of which are:
+% The way that this command affects the \ifincomplete command on
+% earlier pages is as follows: If there is a page with no questions or
+% parts or subparts or subsubparts, then the last page before that
+% with a question (or part, etc.) would normally be deemed incomplete;
+% if, however, the page with no questions (or parts, etc.) (along with
+% all adjacent pages with no questions or parts etc.) has a
+% \noquestionsonthispage command, then that last page with a question
+% (or part, etc.) will not be deemed incomplete.
 %
-%   \newcommand\questionlabel{\thequestion.}
-%   \newcommand\partlabel{(\thepartno)}
-%   \newcommand\subpartlabel{\thesubpart.}
-%   \newcommand\subsubpartlabel{\thesubsubpart)}
-%
-% These definions can be changed by using a \renewcommand command.
-% Note that the definition of \partlabel used `\thepartno', and *not*
-% `\thepart'.  This is because `\thepart' refers to the counter for the
-% standard sectioning command \part, and not the counter used in the
-% parts environment.  The counter used by the parts environment is
-% inserted with the command `\thepartno'.
+% Note that if you're tempted to use this command on a page that follows
+% the end of all of the questions, then you should probably use the
+% command \nomorequestions instead.
 %
 %--------------------------------------------------------------------
-%
-% Version 2.095$\beta$:
-%
-% Added solution and longsolution environments.
-%
-% The command \printanswers causes both of these environments print the
-% solution, and the command \noprintanswers causes both of these
-% environments not to print the solution.  The default is
-% \noprintanswers.
-%
-% The commands \printanswers and \noprintanswers can be given as many
-% times as desired to switch back and forth between the two.
-%
-% The documentclass option ``answers'' is equivalent to giving a
-% \printanswers command at the beginning of the file.
-%
-%
-% Both of these environments take an optional argument which is an
-% amount of blank vertical space to be left when \noprintanswers is in
-% effect.  (The default value of this blank vertical space is 0pt.)
-%
-%
-% The solution environment prints the solution inside of a box, which
-% cannot be broken across pages.  Thus, it is only appropriate for
-% solutions that are short enough to fit on a page.
-%
-% The longsolution environment prints the solution with left and right
-% margins slightly increased, and it can be broken across pages.
-%
-% When printing the solution, both environments begin with
-% \solutiontitle, the default value of which is created by the command
-%
-% \newcommand{\solutiontitle}{\textbf{Solution:}\enspace}
-%
-% This can be changed with the \renewcommand command.
-%
-% Note added later: solution was changed and longsolution was
-% eliminated in version 2.106$\beta$.
+% Version 2.405$\beta$, 2012/10/21
+%
+% It is now possible to use a parts, subparts, or subsubparts
+% environment inside one of the solution environments (solution,
+% solutionorbox, solutionorlines, solutionordottedlines, or
+% solutionorgrid) without getting problems from multiply defined
+% labels or having its points (if any) counted as being actual points
+% on the exam.
+%
+% Any \part, \subpart, or \subsubpart command inside one of the
+% solution environments now writes a \PgInfo command in the .aux file
+% of the form question2@object3, but no labels and no other \PgInfo
+% commands.  In addition, if there are points assigned to any of these
+% commands inside any of the solution environments, those points are
+% not added to the points of the question or the points on the page,
+% and do not affect any gradetables or pointtables.
 %
 %--------------------------------------------------------------------
+% Version 2.404$\beta$, 2012/09/03
 %
-% Version 2.094$\beta$:
-%
-% We've expanded the options of the \gradetable command, so that you
-% can now get a grading table indexed by either question numbers or by
-% page numbers, and in either case you can have either a horizontally
-% oriented table or a vertically oriented table.
-%
-% \gradetable now takes two optional arguments: The first can be either
-% `[v]' or `[h]', and the second can be either `[questions]' or
-% `[pages]'.  The defaults are `[v]' and `[questions]', and you must
-% specify a first option if you want to specify a second option.
-%
-% Thus: \gradetable is equivalent to \gradetable[v][questions]
+% New command:
 %
-% `[v]' and `[h]' are as before: Vertically oriented or horizontally
-% oriented.
+%   \fillwithgrid{length}
 %
-% `[questions]' is the same as the earlier version: A table that lists
-% the question numbers, the possible total points for each question the
-% total for the entire exam, and spaces are left for the score on each
-% question.
-%
-% `[pages]' prints a table indexed by page numbers instead of by
-% question numbers.  It lists only the numbers of pages that have points
-% on them, and for each such page it lists the number of points possible
-% on that page.  It also lists the total possible points on the exam,
-% and spaces are left for the score on each page.
+% New environment:
 %
-% For both tables indexed by questions and tables indexed by points,
-% there are commands that allow you to change the column and row
-% headings (the following are shown setting the default values:)
+%   solutionorgrid
 %
-%    \hpword{Points:}
-%    \hsword{Score:}
-%    \htword{Total}
-%    \vpword{Points}
-%    \vsword{Score}
-%    \vtword{Total:}
+% These are similar to the \fillwithlines command and the
+% solutionorlines environment.
 %
-% For tables indexed by questions, the appropriate row and column titles
-% are set by the following commands:
+% By default, the created grids are in black.  However, if you give the
+% commands
 %
-%    \hqword{Question:}
-%    \vqword{Question}
+% \usepackage{color}
+% \colorgrids
 %
-% For tables indexed by pages, the appropriate row and column titles are
-% set by the following commands:
+% then the grids will be in color, by default a light gray.  That
+% default color was defined by the command
 %
-%    \hpgword{Page:}
-%    \vpgword{Page}
+% \definecolor{GridColor}{gray}{0.8}
 %
+% You can change the color by redefining the color GridColor, and you
+% can return to using black grids by giving the command
 %
-% For both \gradetable[h] and \gradetable [v], the width of the blank
-% cells created for filling in the grades can be changed with the
-% command \cellwidth, and the arraystretch applied to the table can be
-% changed with the command \gradetablestretch.  The default values are
-% created by the commands:
+% \nocolorgrids
 %
-%   \cellwidth{2em}
-%   \gradetablestretch{1.5}
-%
-%--------------------------------------------------------------------
+% The default grid size and grid line thickness were set by the
+% commands
 %
-% Version 2.093$\beta$:
+% \setlength{\gridsize}{5mm}
+% \setlength{\gridlinewidth}{0.1pt}
 %
-% If the user gives the comman \addpoints, then
-% \pointsonpage{n} expands to the number of points on page n.
+% You can change either or both of those by giving new \setlength
+% commands.  The period of the grid is \gridsize (both horizontally
+% and vertically).  That is, the horizontal distance from the left
+% edge of one vertical line to the left edge of the next vertical line
+% is \gridsize, as is the vertical distance from the top edge of one
+% horizontal line to the top edge of the next horizontal line.  Thus,
+% each square has outer side length equal to \gridsize+\gridlinewidth.
 %
 %--------------------------------------------------------------------
+% Version 2.403$\beta$, 2012/08/29:
 %
-% Version 2.092$\beta$:
-%
-% Increased the \leftmargin in the choices environment.
+% We changed the code for the command \fillin (which had been modified
+% in version 2.402beta) so that if only one optional argument is used,
+% a space following that optional argument will not be ignored.  We
+% did this in such a way that the second optional argument will be
+% recognized even when spaces appear in between the optional
+% arguments.
 %
 %--------------------------------------------------------------------
+% Version 2.402$\beta$, 2012/08/21:
 %
-% Version 2.091$\beta$:
+% We modified the command \fillin that we had created in version
+% 2.401beta.  \fillin now takes two optional arguments (and no required
+% arguments).
 %
-% New commands:
-% \pointsdroppedatright
-% \droppoints
-% \droptotalpoints
-% \totalformat
-%
-% \pointsdroppedatright: The command \pointsdroppedatright causes points
-% not to be printed until you give the command \droppoints, except that
-% a \qformat command still works as expected (i.e., if you include
-% ``\thepoints'' in the argument of the \qformat, it will print the
-% points as usual).
-%
-%
-% \droppoints: The command \droppoints should be given only at the end
-% of a paragraph or between paragraphs; if you give it within a
-% paragraph, it causes the paragraph to end.  \droppoints prints the
-% most recent point value in the right margin, formatted as it is when
-% you give the command \pointsinrightmargin, except that the points
-% appear opposite the last line of the paragraph (or, if the command
-% \droppoints is given between paragraphs, then additional vertical
-% space is left between the paragraphs and the points are printed
-% opposite the blank space).  Thus, the formatting can be changed by
-% using giving the commands \bracketedpoints, \boxedpoints, or
-% \marginpointname.
-%
-% The command \droppoints actually works this way even if one of the
-% commands \pointsdroppedatright, \pointsinmargin, or
-% \pointsinrightmargin, is in effect, but if you use it that way the
-% points will appear twice on the page.
-%
-%
-% \droptotalpoints: To use the command \droptotalpoints, you must first
-% give the command \addpoints.  The command \droptotalpoints should be
-% given only at the end of a paragraph or between paragraphs; if you
-% give it within a paragraph, it causes the paragraph to end.
-% \droptotalpoints prints the total points for the current question
-% (i.e., the sum of the points assigned to the question and to all of
-% its parts, subparts, and subsubparts) in the right margin, formatted
-% formatted by default inside either double brackets, double box, or
-% double parentheses, depending on whether \bracketedpoints,
-% \boxedpoints, or neither is in effect.
-%
-% \totalformat: The \totalformat command allows you to change the format
-% used by the \droptotalpoints command.  It takes one argument, and that
-% argument becomes the command to print the total points, right
-% justified a distance of \rightpointsmargin from the right edge of the
-% paper.  The argument should contain the expression
-% ``\pointsofquestion{\arabic{question}}'' at the point at which the number
-% of points should appear.  For example, the command
-%
-%   \totalformat{[\pointsofquestion{\arabic{question}}]}
-%
-% will produce the same appearance as the default does when the command
-% \bracketedpoints is in effect and \marginpointname is empty, and the
-% command
+% \fillin can take two optional arguments, as in
 %
-%   \totalformat{\fbox{Total: \pointsofquestion{\arabic{question}}}}
+% \fillin[Answer][Length]
 %
-% produces a box surrounding ``Total: 25''.
+% The first optional argument is the answer to be printed above the line
+% when \printanswers is in effect; the default value is empty.  That
+% line is printed a distance of \answerclearance below the baseline.
 %
-%--------------------------------------------------------------------
+% The second optional argument is the length of the line that we print;
+% the default value is \fillinlinelength.  The value of
+% \fillinlinelength is set with the command
 %
-% Version 2.087$\beta$:
+%   \setlength\fillinlinelength{1in}
 %
-% We changed ``\point@toks={}'' into ``\global \point@toks={}'' when
-% \point@toks is set, so that if the user somehow arranges for us to be
-% inside of a group when we enter horizontal mode, the \point@toks will
-% be properly set equal to null, and so the points won't accidentally be
-% placed a second time at the second paragraph of the question.
+% and can be changed by giving a new \setlength command.
 %
-% Similarly, we changed ``\pageinfo@commands={}'' to 
-% ``\global \pageinfo@commands={}''.
+% When answers are being printed, the first optional argument is
+% printed subject to the declarations in the argument of the last
+% \CorrectChoiceEmphasis command.  It is centered on the line unless
+% it is too long, in which case it extends to the right of the line.
 %
 %--------------------------------------------------------------------
+% Version 2.401$\beta$, 2012/08/20:
 %
-% Version 2.080$\beta$:
-%
-% We added a new command \gradetable that produces a grading table,
-% oriented either vertically or horizontally, that lists the questions,
-% their point values, and the total points, and leaves spaces for
-% entering the grade for each question and the total grade.  We also
-% added a new command \pointsofquestion that takes one argument that is
-% assumed to be the number of a question and returns the total number of
-% points for that question.  \pointsofquestion is used by the
-% \gradetable command, but the user can choose to use it separately.
-
-% To use either \gradetable or \pointsofquestion, the user must give the
-% command \addpoints.  \pointsofquestion{n} will then return the total
-% number of points for question n and all of its parts, subparts, and
-% subsubparts.  Since all this information is stored in the .aux file
-% and then read back on the next run of LaTeX, the user must run LaTeX
-% twice after any changes to the questions or points.
-
-% \gradetable[h] produces a horizonatlly oriented grade table, and
-% \gradetable[v] produces a vertically oriented grade table.  The
-% command \gradetable is equivalent to \gradetable[v].
-
-% \gradetable[h] produces a table with three rows, titled (by default)
-% ``Question:'', ``Points:'', and ``Score:'' (the titles are in the
-% first column of the table).  There is then one column for each
-% question (with the question number and point value in the first two
-% rows), plus a final column labelled ``Total'' with the total points in
-% the second row.
-
-% \gradetable[v] produces a table with three columns, titled (by
-% default) ``Question'', ``Points'', and ``Score'' (the titles are in
-% the first row of the table).  There is then one row for each question
-% (with the question number and point value in the first two columns),
-% plus a final row labelled ``Total:'' with the total points in the
-% second column.
-
-% Note that \gradetable[h] produces row titles whose default values
-% contains colons, while \gradetable[v] produces column titles whose
-% default values do not contain colons.
-
-% The row and column titles and the word ``Total'' can be changed using
-% the commands \hqword, \hpword, \hsword, \htword, \vqword,
-% \vpword, \vsword, and \vtword, where the first four affect
-% \gradetable[h] and the second four affect \gradetable[v].  The default
-% values are created by the commands:
-
-%   \hqword{Question:}
-%   \hpword{Points:}
-%   \hsword{Score:}
-%   \htword{Total}
-%   \vqword{Question}
-%   \vpword{Points}
-%   \vsword{Score}
-%   \vtword{Total:}
-
-% For both \gradetable[h] and \gradetable [v], the width of the blank
-% cells created for filling in the grades can be changed with the
-% command \cellwidth, and the arraystretch applied to the table can be
-% changed with the command \gradetablestretch.  The default values are
-% created by the commands:
-
-%   \cellwidth{2em}
-%   \gradetablestretch{1.5}
-
-
-
-% Changing the definition of \points:
-
-% The default definition of \points is that it expands to ``point'' if
-% the number of points is 1 and to ``points'' otherwise.  There is now a
-% command ``\pointpoints'' that takes two arguments, after which
-% \points will expand to the first argument when the number of points is
-% 1 and to the second argument otherwise.  For example, the default is
-% the result of the command \pointpoints{point}{points}.
 %
-%--------------------------------------------------------------------
-%
-% Version 2.070$\beta$:
+% New command:
 %
-% We made this compatible with hyperref.sty.
-% We did this by eliminating all use of LaTeX's 
-% \label, \newlabel, \ref, and \pageref commands.
-% We now put information into the .aux file using the
-% \PgInfo@write command, and get that info out of the aux file
-% using \PgInfo and \PgInfo@get, along with a few \gdef commands
-% written straight to the .aux file.
+%   \fillin[CorrectAnswer]{width}
 %
-%--------------------------------------------------------------------
+% This is for use in fill in the blank questions.  This command inserts
+% a blank line of width ``width''.  If answers are being printed and if
+% the optional argument ``CorrectAnswer'' appears, then the optional
+% argument is printed subject to the declarations in the argument of the
+% last \CorrectChoiceEmphasis command, and it is printed a distance of
+% \answerclearance above the line.  It is centered on the line unless it
+% is too long, in which case it extends to the right of the line.
 %
-% Version 2.067$\beta$:
+% Note: We changed this command in version 2.401beta.
 %
-% We're adding code to enable headers and footers
-% to know whether the current page continues a question started on an
-% earlier page, and whether the page ends with a question still
-% incomplete.
-
 %--------------------------------------------------------------------
-% For use in headers and footers:
-%
-% \ifcontinuation{Text 1}{Text 2}
-% Expands to ``Text 1'' if this page begins with a part or subpart or
-% subsubpart that continues a question begun on an earlier page, and
-% expands to ``Text 2'' if this page begins with a new question.
-%
-% \ContinuedQuestion expands to the number of the question that is
-% being continued from an earlier page.
-%
-% \ifincomplete{Text 1}{Text 2}
-% Expands to ``Text 1'' if the last question begun on or before this
-% page has a part, subpart, or subsubpart that begins on a later page,
-% and if we have not yet encountered a \nomorequestions command.
-%
-% \IncompleteQuestion expands to the number of the question that is
-% continued on the next page.
-%
-% \nomorequestions is a command that you can give after the last
-% question if you intend to include extra material (e.g., tables for
-% use on the exam) but you don't want the pages containing the extra
-% material to be labelled as continuing the last question on the exam.
-%
-%--------------------------------------------------------------------
-%
-%
-% Also: 
-% Changed the code for headers and footers to
-% use the command \normalfont instead of \rm so that the main
-% document font will appear in headers and footers even when the
-% main document font is *not* a roman font.
-%
-% \def\marksnotpoints
-%
-% Changes \pointname so that it is identical to the default \pointname
-% except that the work ``mark'' is used instead of the word
-% ``point''.  That is, if the number of points is 1, then you'll get
-% ``1 mark'', and if the number of points is other than 1 then you'll
-% get ``n marks'' (where n is the number of points).
-%
-%
-% \bracketedpoints
-% \nobracketedpoints
-%
-% The default remains to put the points inside of parentheses.
-% \bracketedpoints switches to points inside of square brackets and
-% \boxedpoints switches to points in side of a box.  Both
-% \nobracketedpoints and \noboxedpoints return to the default of
-% points inside of parentheses no matter how many \boxedpoints and
-% \bracketedpoints commands have been given in any order.
-%
-%
-% \pointsinrightmargin
-% \nopointsinrightmargin
-%
-% The default remains to put the points right after the question
-% number, before the text of the question. \pointsinrightmargin
-% switches to points in the right margin and \pointsinleftmargin
-% switches to points in the left margin.  Both \nopointsinrightmargin
-% and \nopointsinleftmargin return to the default of points right
-% after the question number, before the text of the question, no
-% matter how many \pointsinrightmargin and \pointsinleftmargin
-% commands have been given in any order. 
-%
-% if \pointsinrightmargin is used, then the points are printed
-% right justified in the right margin, with the right edge a distance
-% of \rightpointsmargin from the right edge of the paper.
-% The default value of \rightpointsmargin is 1 cm, but it can be set
-% to any other length with the usual \setlength command
-% (as in \setlength{\rightpointsmargin}{0.5cm}).
-%
-%
-% subsubparts environment
-% Numbered using lower case greek letters.
-%
-%
-% choices environment, for multiple choice answers
-%
-%
-% \qformat{Format line}
-% 
-% Format line must have some stretch (e.g., at least one \hfil or
-% \dotfill or something similar).
-% \thequestion inserts the question number,
-% \thepoints inserts the number of points followed by pointname if
-%   a number of points has been specified for this question, and it
-%   inserts nothing at all if no points have been specified.
-%
-% \noqformat returns to the standard question number formatting.
-%
-%
-%
-% The standard options
-%   a4paper
-%   a5paper
-%   b5paper
-%   letterpaper
-%   legalpaper
-%   executivepaper
-%   landscape
-% now all work.
-%
-%
-%
-% Two new commands for use in \pointname, \marginpointname, and
-% \qformat: 
-%
-% \points expands to either ``point'' or ``points'', depending on
-% whether the number of points is 1 or other than 1.
-%
-% \marks expands to either ``mark'' or ``marks'', depending on
-% whether the number of points is 1 or other than 1.
-%
-%
-%--------------------------------------------------------------------
-%--------------------------------------------------------------------
-%
-% The only change from version 2.0 to version 2.01 is that this
-% documentclass (and its accompanying documentation) is now
-% explicitly distributed under the LaTeX Project Public License.
-%
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 
 
 \RequirePackage{ifthen}
 
-\newif\if@printanswers
-\@printanswersfalse
-\DeclareOption{answers}{\@printanswerstrue}
-\DeclareOption{noanswers}{\@printanswersfalse}
+\newif\ifprintanswers
+\printanswersfalse
+\DeclareOption{answers}{\printanswerstrue}
+\DeclareOption{noanswers}{\printanswersfalse}
+
+\newif\ifcancelspace
+\cancelspacefalse
+\DeclareOption{cancelspace}{\cancelspacetrue}
+\DeclareOption{nocancelspace}{\cancelspacefalse}
+
+% The following keeps track of whether the user has requested that we
+% add up the points on the exam.  We make the default false so that
+% users who put other than numbers into the points argument of a
+% question (or part, or subpart) won't get error messages.
+% We use \if@printtotalpoints as a flag to signal that we are counting
+% points, so that we will know to print the total on the screen (and
+% in the log file).  We use this separate flag so that the user can
+% use both \addpoints and \noaddpoints to count some points and not
+% others, but still have the total printed when we finish the file no
+% matter what the state of \if@addpoints.
+\newif\if@addpoints
+\newif\if@printtotalpoints
+\def\addpoints{\global\@addpointstrue\global\@printtotalpointstrue}
+\def\noaddpoints{\global\@addpointsfalse}
+\@addpointsfalse
+\@printtotalpointsfalse
+\DeclareOption{addpoints}{\addpoints}
+
 
 \DeclareOption*{%
   \PassOptionsToClass{\CurrentOption}{article}%
 \setlength{\marginparwidth}{.5in}
 \setlength{\marginparsep}{5pt}
 
-
-
-
-
 %--------------------------------------------------------------------
 
 %                          ****************
   \advance\@rightmargin by -\@extrawidth
 }
 
-
-
-
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 %             Making room for large headers and footers
 \setlength{\covrun@exft}{0in}
 \setlength{\covfp@exft}{0in}
 
-
 \newcommand*\adj@hdht@ftht{%
   \if@coverpages
-    \ifnum\value{page}=1
+    \ifnum\value{page}=1\relax
       \@setheadheight{\covfp@exhd}%
       \@setfootheight{\covfp@exft}%
     \else
       \@setfootheight{\covrun@exft}%
     \fi
   \else
-    \ifnum\value{page}=1
+    \ifnum\value{page}=1\relax
       \@setheadheight{\fp@exhd}%
       \@setfootheight{\fp@exft}%
     \else
   \fi
 }
 
-
 \newcommand*\extraheadheight{%
   \@ifnextchar[{\@xtrahd}{\@ytrahd}%
 }
   \adj@hdht@ftht
 }
 
-
 \newcommand*\coverextraheadheight{%
   \@ifnextchar[{\cov@xtrahd}{\cov@ytrahd}%
 }
 \@appendoutput{\adj@hdht@ftht}
 
 %--------------------------------------------------------------------
-%                 \setheadheight and \setfootheight:
+%                 \@setheadheight and \@setfootheight:
 
 \def\@setheadheight#1{%
   \begingroup % Avoid trouble from using \@temp and \@spaces
     % 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{ }
     % 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
     % routine, but we're leaving it in so that this will still work if
     % we use this at some random point in the middle of composing a
-    % page). 
-    \global\@colht=\textheight
-    \global\@colroom=\textheight
-    \global\vsize=\textheight
-    \global\pagegoal=\textheight
+    % page).
+    % Bugfix, Version 2.306beta, 2009/03/28:
+    % We don't do this!!
+    % If the user had a figure environment that floated to the
+    % top of a page, then this would cause that page to run
+    % over the footer and off the bottom of the page, because
+    % this somehow caused a full page's worth of stuff to be
+    % placed after the figure, as if the figure wasn't taking
+    % up space on the page.
+    % 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\@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{ }
     \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
     % routine, but we're leaving it in so that this will still work if
     % we use this at some random point in the middle of composing a
     % page). 
-    \global\@colht=\textheight
-    \global\@colroom=\textheight
-    \global\vsize=\textheight
-    \global\pagegoal=\textheight
+    % Bugfix, Version 2.306beta, 2009/03/28:
+    % We don't do this!!
+    % If the user had a figure environment that floated to the
+    % top of a page, then this would cause that page to run
+    % over the footer and off the bottom of the page, because
+    % this somehow caused a full page's worth of stuff to be
+    % placed after the figure, as if the figure wasn't taking
+    % up space on the page.
+    % 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\@colroom=\textheight
+%     \global\vsize=\textheight
+%     \global\pagegoal=\textheight
   \endgroup
-}
-
-
+}% @setfootheight
 
 
 %---------------------------------------------------------------------
 \newif\if@coverpages
 \@coverpagesfalse
 
+\newcounter{num@coverpages}
+% We'll set this to zero in case there is no coverpages environment:
+\setcounter{num@coverpages}{0}
+
 \newenvironment{coverpages}{%
-    \ifnum \value{numquestions}>0
+    \ifnum \value{numquestions}>0\relax
       \ClassError{exam}{%
         Coverpages cannot be used after questions have begun.\MessageBreak
       }{%
     \adj@hdht@ftht
   }{%
     \clearpage
+    \setcounter{num@coverpages}{\value{page}}%
+    \addtocounter{num@coverpages}{-1}%
     \pagenumbering{arabic}%
+    % Bugfix, Version 2.307\beta, 2009/06/11:
+    % We have to say \@coverpagesfalse before \adj@hdht@ftht
+    % because we're still inside the group created by the
+    % coverpages environment and we want to set the 
+    % extraheadheight and extrafootheight to the values correct
+    % for the first non-cover page:
+    \@coverpagesfalse
     \adj@hdht@ftht
 }
 
   }%
 }
 
-
-
 \newcommand*\@dohead{%
   \def\@oddhead{%
     \if@coverpages
-      \ifnum\value{page}=1
+      \ifnum\value{page}=1\relax
         \cov@fullhead
       \else
         \covrun@fullhead
       \fi
     \else
-      \ifnum\value{page}=1
+      \ifnum\value{page}=1\relax
         \@fullhead
       \else
         \run@fullhead
 \newcommand*\@dofoot{%
   \def\@oddfoot{%
     \if@coverpages
-      \ifnum\value{page}=1
+      \ifnum\value{page}=1\relax
         \cov@fullfoot
       \else
         \covrun@fullfoot
       \fi
     \else
-      \ifnum\value{page}=1
+      \ifnum\value{page}=1\relax
         \@fullfoot
       \else
         \run@fullfoot
   \let\@evenfoot=\@oddfoot
 }
 
-
-
-
 %--------------------------------------------------------------------
 %       \@fullhead, \run@fullhead, \@fullfoot, and \run@fullfoot:
 
   }% vbox
 }
 
-
 \newcommand*\run@fullhead{%
   \vbox to \headheight{%
     \vss
   }% vbox
 }
 
-
-
 % We arrange it so that the very top of first line of text in the
 % foot is at a fixed position on the page, whether or not there's
 % a footrule:
   }% vbox
 }
 
-
 \newcommand*\run@fullfoot{%
   \vbox to 0pt{%
     \ifrun@footrule
   }% vbox
 }
 
-
 \newcommand*\covrun@fullhead{%
   \vbox to \headheight{%
     \vss
   }% vbox
 }
 
-
-
 % We arrange it so that the very top of first line of text in the
 % foot is at a fixed position on the page, whether or not there's
 % a footrule:
   }% vbox
 }
 
-
 \newcommand*\covrun@fullfoot{%
   \vbox to 0pt{%
     \ifcovrun@footrule
   }% vbox
 }
 
-
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 %
   \runningheader{#1}{#2}{#3}%
 }
 
-
 \def\firstpagefooter#1#2#3{%
   \def\@lfoot{#1}%
   \def\@cfoot{#2}%
   \runningfooter{#1}{#2}{#3}%
 }
 
-
-
 \def\lhead{\@ifnextchar[{\@xlhead}{\@ylhead}}
 \def\@xlhead[#1]#2{\def\@lhead{#1}\def\run@lhead{#2}}
 \def\@ylhead#1{\def\run@lhead{#1}\def\@lhead{#1}}
 
 %                    Initialize head and foot:
 
-
-
 \pagestyle{headandfoot}
 
 \lhead{}
   \coverrunningheader{#1}{#2}{#3}%
 }
 
-
 \def\coverfirstpagefooter#1#2#3{%
   \def\cov@lfoot{#1}%
   \def\cov@cfoot{#2}%
   \coverrunningfooter{#1}{#2}{#3}%
 }
 
-
-
 \def\coverlhead{\@ifnextchar[{\cov@xlhead}{\cov@ylhead}}
 \def\cov@xlhead[#1]#2{\def\cov@lhead{#1}\def\covrun@lhead{#2}}
 \def\cov@ylhead#1{\def\covrun@lhead{#1}\def\cov@lhead{#1}}
 
 %                 Initialize coverpage head and foot:
 
-
 \coverlhead{}
 \coverchead{}
 \coverrhead{}
 \def\headrule{\@headruletrue\run@headruletrue}
 \def\noheadrule{\@headrulefalse\run@headrulefalse}
 
-
-
 \newif\if@footrule
 \newif\ifrun@footrule
 
 \def\footrule{\@footruletrue\run@footruletrue}
 \def\nofootrule{\@footrulefalse\run@footrulefalse}
 
-
-
 %                             Initialize:
 
 \noheadrule
 \nofootrule
 
-
 %                 Cover page headrules and footrules:
 
 \newif\ifcov@headrule
 \def\coverheadrule{\cov@headruletrue\covrun@headruletrue}
 \def\nocoverheadrule{\cov@headrulefalse\covrun@headrulefalse}
 
-
-
 \newif\ifcov@footrule
 \newif\ifcovrun@footrule
 
 \def\coverfootrule{\cov@footruletrue\covrun@footruletrue}
 \def\nocoverfootrule{\cov@footrulefalse\covrun@footrulefalse}
 
-
-
 %                             Initialize:
 
 \nocoverheadrule
 \nocoverfootrule
 
-
-
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 
 %                \numpages, \iflastpage, and \oddeven
 %     Also: \numpoints, \numquestions, \numparts, and \numsubparts
 %                    Also also: \pointsofquestion
+%     Also: \numcoverpages and \totalnumpages
 
 % Make the number of pages available as the macro \numpages,
 % the number of points as \numpoints,
 % the number of questions as \numquestions,
 % the number of parts as \numparts, and
 % the number of subparts as \numsubparts
-%\def\numpages{\pageref{@lastpage}}
-%\def\numpoints{\pageref{@numpoints}}
-%\def\numquestions{\pageref{@numquestions}}
-%\def\numparts{\pageref{@numparts}}
-%\def\numsubparts{\pageref{@numsubparts}}
-%\def\numsubsubparts{\pageref{@numsubsubparts}}
-
-% This was previously done with \pageref commands.  When I eliminated
-% all \label, \ref, and \pageref commands in order to make this
-% compatible with hyperref.sty, this stuff was created:
-
-% \gdef commands for exam@lastpage, exam@numpoints, exam@numquestions,
-% exam@numparts, exam@numsubparts and exam@numsubsubparts are written to
-% the .aux file via \AtEndDocument.
-
-% \gdef commands for pointsofq@i, pointsofq@ii, etc. are written to the
-% .aux file as each question is completed (see the definition of the
+
+% This was previously done with \pageref commands.  When I stopped
+% using \pageref for this (in order to make this compatible with
+% hyperref.sty), this stuff was created:
+
+% \gdef commands for exam@lastpage, exam@numpoints,
+% exam@numbonuspoints, exam@numquestions, exam@numparts,
+% exam@numsubparts and exam@numsubsubparts are written to the .aux
+% file via \AtEndDocument.
+
+% \gdef commands for pointsofq@i, pointsofq@ii, etc. and
+% bonuspointsofq@i, bonuspointsofq@ii, etc.  are written to the .aux
+% file as each question is completed (see the definition of the
 % questions environment).
 
-% \gdef commands for pointsonpage@i, pointsonpage@ii, etc. are written
-% to the .aux file as we encounter points defined for a later page, and
-% for the last such page with AtEndDocument.
+% \gdef commands for pointsonpage@i, pointsonpage@ii, etc. and
+% bonuspointsonpage@i, bonuspointsonpage@ii, etc. are written to the
+% .aux file as we encounter points defined for a later page, and for
+% the last such page with AtEndDocument.
 
 \def\numpages{\@ifundefined{exam@lastpage}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@lastpage
-}
+}% numpages
+
+% Change 2011/04/01: We added a ``0'' in front of the
+% mbox when \exam@lastcoverpage isn't defined.  This is
+% so that the construction \romannumeral\numcoverpages
+% won't generate an error on the first run of latex.
+\def\numcoverpages{\@ifundefined{exam@lastcoverpage}%
+  {0\mbox{\normalfont\bfseries ??}}%
+  \exam@lastcoverpage
+}% numpages
+
+\def\totalnumpages{\@ifundefined{exam@totalpages}%
+  {\mbox{\normalfont\bfseries ??}}%
+  \exam@totalpages
+}% numpages
 
 \def\numpoints{\@ifundefined{exam@numpoints}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@numpoints
-}
+}% numpoints
+\def\numbonuspoints{\@ifundefined{exam@numbonuspoints}%
+  {\mbox{\normalfont\bfseries ??}}%
+  \exam@numbonuspoints
+}% numbonuspoints
 
 \def\numquestions{\@ifundefined{exam@numquestions}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@numquestions
-}
+}% numquestions
 
 \def\numparts{\@ifundefined{exam@numparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@numparts
-}
+}% numparts
 
 \def\numsubparts{\@ifundefined{exam@numsubparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@numsubparts
-}
+}% numsubparts
 
 \def\numsubsubparts{\@ifundefined{exam@numsubsubparts}%
-  {\mbox{\normalfont\bf ??}}%
+  {\mbox{\normalfont\bfseries ??}}%
   \exam@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\bfseries ??}}%
+  {\csname bonuspointsofq@\romannumeral #1\endcsname}%
+}% bonuspointsofquestion
+
+% For use in \combinedgradetable and \combinedpointtable, we're
+% changing the defintions of \pointsonpage and \bonuspointsonpage
+% so that when, e.g.,  pointsonpage@ii is undefined, we just produce
+% 0.  This comes up because the final page of the exam may have either
+% points with no bonus point or bonus points with no points, in which
+% case the combined table will try to list both points and bonus points
+% for that last page, and one of those will be undefined.
+%
+% \def\pointsonpage#1{\@ifundefined{pointsonpage@\romannumeral #1}%
+%   {\mbox{\normalfont\bfseries ??}}%
+%   {\csname pointsonpage@\romannumeral #1\endcsname}%
+% }% pointsonpage
+% \def\bonuspointsonpage#1{\@ifundefined{bonuspointsonpage@\romannumeral #1}%
+%   {\mbox{\normalfont\bfseries ??}}%
+%   {\csname bonuspointsonpage@\romannumeral #1\endcsname}%
+% }% bonuspointsonpage
+%
+% spanish.ldf redefines \@roman, so we'll avoid using \roman:
 \def\pointsonpage#1{\@ifundefined{pointsonpage@\romannumeral #1}%
-  {\mbox{\normalfont\bf ??}}%
+  {0}%
   {\csname pointsonpage@\romannumeral #1\endcsname}%
-}
+}% pointsonpage
+\def\bonuspointsonpage#1{\@ifundefined{bonuspointsonpage@\romannumeral #1}%
+  {0}%
+  {\csname bonuspointsonpage@\romannumeral #1\endcsname}%
+}% bonuspointsonpage
 
 
 \newif\if@pointschanged
 \@pointschangedfalse
 
+\newcommand*{\CheckIfChanged@hlf}[2]{%
+  % The first argument is the name of a half counter.
+  % The second argument expands to the name (without the escape
+  % character, and not assumed to be defined) of the control sequence
+  % holding the previous value.
+  \@ifundefined{#2}%
+    {\global\@pointschangedtrue}%
+    {%
+    % OK; it's defined.  See if it's changed:
+    \begingroup
+      \set@hlfcntr{tmp@hlfcntr}{\csname #2\endcsname}%
+      \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
+      \edef\pt@check{\prtaux@hlfcntr{#1}}%
+      \ifx \pt@check \othpt@check
+        % Do nothing
+      \else
+        \global\@pointschangedtrue
+      \fi
+    \endgroup
+    }%
+}% CheckIfChanged@hlf
 
 
 %%%\let\@realenddocument=\enddocument
 %%%   \@realenddocument
 %%%}
 
-
 \AtEndDocument{%
   \clearpage
   \if@filesw
     \advance\c@page-1
     \immediate\write\@mainaux
       {\string\gdef\string\exam@lastpage{\arabic{page}}}%
+    \immediate\write\@mainaux
+      {\string\gdef\string\exam@lastcoverpage{\arabic{num@coverpages}}}%
+    % We can now trash the value of num@coverpages:
+    \addtocounter{num@coverpages}{\value{page}}%
+    \immediate\write\@mainaux
+      {\string\gdef\string\exam@totalpages{\arabic{num@coverpages}}}%
     \advance\c@page+1 % In case some other package looks at \c@page
+    %
     \immediate\write\@mainaux
-        {\string\gdef\string\exam@numpoints{\prtaux@hlfcntr{numpoints}}}%
+        {\string\gdef\string\exam@numpoints{%
+                            \prtaux@hlfcntr{numpoints}}}%
     % See if this has changed from the last run of LaTeX:
-    \@ifundefined{exam@numpoints}%
-      {\global\@pointschangedtrue}%
-      {%
-      % OK; it's defined.  See if it's changed:
-      \begingroup
-        \set@hlfcntr{tmp@hlfcntr}{\exam@numpoints}%
-        \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
-        \edef\pt@check{\prtaux@hlfcntr{numpoints}}%
-        \ifx \pt@check \othpt@check
-          % Do nothing
-        \else
-          \global\@pointschangedtrue
-        \fi
-      \endgroup
-      }%
+    \CheckIfChanged@hlf{numpoints}{exam@numpoints}%
+    \immediate\write\@mainaux
+        {\string\gdef\string\exam@numbonuspoints{%
+                            \prtaux@hlfcntr{numbonuspoints}}}%
+    % See if this has changed from the last run of LaTeX:
+    \CheckIfChanged@hlf{numbonuspoints}{exam@numbonuspoints}%
     \immediate\write\@mainaux
       {\string\gdef\string\exam@numquestions{\thenumquestions}}%
     \immediate\write\@mainaux
                           \csname c@pageof@pagepoints\endcsname
              {\prtaux@hlfcntr{@pagepoints}}}%
       % See if this has changed from the last run of LaTeX:
-      \@ifundefined{pointsonpage@\romannumeral
+      \CheckIfChanged@hlf{@pagepoints}{pointsonpage@\romannumeral
                           \csname c@pageof@pagepoints\endcsname}%
-        {\global\@pointschangedtrue}%
-        {%
-        % OK; it's defined.  See if it's changed:
-        \begingroup
-          \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral 
-                    \csname c@pageof@pagepoints\endcsname\endcsname}%
-          \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
-          \edef\pt@check{\prtaux@hlfcntr{@pagepoints}}%
-          \ifx \pt@check \othpt@check
-            % Do nothing
-          \else
-            \global\@pointschangedtrue
-          \fi
-        \endgroup
-        }%
+    \fi
+    \ifnum \thepageof@pagebonuspoints > 0\relax
+      \immediate\write\@mainaux
+          {\string\gdef\string\bonuspointsonpage@\romannumeral
+                          \csname c@pageof@pagebonuspoints\endcsname
+             {\prtaux@hlfcntr{@pagebonuspoints}}}%
+      \CheckIfChanged@hlf{@pagebonuspoints}{bonuspointsonpage@\romannumeral
+                          \csname c@pageof@pagebonuspoints\endcsname}%
     \fi
     \immediate\write\@mainaux
       {\string\gdef\string\lastpage@withpoints{\page@withpoints}}%
         \fi
       \endgroup
       }%
+    \immediate\write\@mainaux
+      {\string\gdef\string\lastpage@withbonuspoints{\page@withbonuspoints}}%
+    % See if this has changed from the last run of LaTeX:
+    \@ifundefined{lastpage@withbonuspoints}%
+      {\global\@pointschangedtrue}%
+      {%
+      % OK; it's defined.  See if it's changed:
+      \begingroup
+        \edef\othpt@check{\page@withbonuspoints}%
+        \edef\pt@check{\lastpage@withbonuspoints}%
+        \ifx \pt@check \othpt@check
+          % Do nothing
+        \else
+          \global\@pointschangedtrue
+        \fi
+      \endgroup
+      }%
   \fi
   % Echo numbers of questions, parts, and subparts:
   \typeout{This exam contains \thenumquestions\space questions
         \fi
       }
       \typeout{This exam has a total of \typ@expnd\space points.}
+      \def\typ@expnd{%
+        \thenumbonuspoints
+        \ifnumbonuspoints@half
+          \space and a half%
+        \fi
+      }
+      \typeout{This exam has a total of \typ@expnd\space bonus points.}
     \endgroup
   \fi
   \if@pointschanged
     \ClassWarningNoLine{exam}{Point totals have changed.
                Rerun to get point totals right}%
   \fi
-}
+}% AtEndDocument
 
 
 % We define \iflastpage so that it can safely be used
 \def\iflastpage#1#2{%
   \@ifundefined{exam@lastpage}{\def\@@lastpage{-1}}%
           {\edef\@@lastpage{\exam@lastpage}}%
-  \ifnum\value{page}=\@@lastpage
+  \ifnum\value{page}=\@@lastpage\relax
     #1%
   \else
     #2%
   \fi
-}
+}% iflastpage
 
 
 % The macro \oddeven takes two arguments.  If the page number is odd,
 % argument.
 \def\oddeven#1#2{%
   \ifodd\value{page}%
-    #1
+    #1%
   \else
-    #2
+    #2%
   \fi
-}
+}% oddeven
 
 
 
 % with hyperref.sty, which redefines those commands.)
 
 % We use \PgInfo, \PgInfo@write, and \PgInfo@get to know on which page
-% each question, part, subpart, and subsubpart appears.
+% each question, part, subpart, subsubpart, and choice appears.
 
 % We use \PgInfo@write to write \PgInfo commands to the .aux file.  The
 % \PgInfo command takes two arguments: A question (or part, or subpart,
 % The label for a subsubpart is of the form `subsubpart@2@1@3@4' (if
 % it's subsubpart $\delta$ of subpart iii of part a of question 2).
 
-% The \PgInfo command defines a control sequence of the form `Pg@label'
-% that expands to the page number for the corresponding question.  For
-% example, \PgInfo{subsubpart@2@1@3@4}{7} defines 
-%   \csname Pg@subsubpart@2@1@3@4\endcsname 
-% to expand to 7.
+% Each question, part, subpart, subsubpart, and choice also gets a
+% \PgInfo@write command using a label of the form question2@object3 (if
+% it's the third object of the second question).
+
+% Inside one of the solution environments (solution, solutionorbox,
+% etc.) each part, subpart, subsubpart, and choice get only the
+% \PgInfo@write command using a label of the form question2@object3
+% (if it's the third object of the second question).
+
+% When read in from the .aux file, the \PgInfo command defines a
+% control sequence of the form `Pg@label' that expands to the page
+% number for the corresponding question.  For example,
+% \PgInfo{subsubpart@2@1@3@4}{7} defines \csname
+% Pg@subsubpart@2@1@3@4\endcsname to expand to 7.
 
 % The \PgInfo@get{label} command returns the value of the macro Pg@label,
 % but it *doesn't* check whether that macro is defined.  Thus, it's
 % *before* calling \Pginfo@get
 \def\PgInfo@get#1{\csname Pg@#1\endcsname}
 
-% \set@counter@to@pageof takes two arguments: The first is the name
-% of a counter, and the second (expands to) the label of a question,
-% part, subpart, or subsubpart.  If that label exists, then we set
-% the counter equal to the page on which the question (or part, etc.)
-% appears.  If that label doesn't exist, we set the counter equal
-% to -1.  (No labels exist on the first run of LaTeX on the file,
-% and if a new question (or part, etc.) was created by editing the
-% file, then the label will not exist until the second run of LaTeX
-% after that.
+% \set@counter@to@pageof takes two arguments: The first is the name of a
+% counter, and the second (expands to) the label of a question, part,
+% subpart, subsubpart, or choice.  If that label exists, then we set the
+% counter equal to the page on which the question (or part, etc.)
+% appears.  If that label doesn't exist, we set the counter equal to -1.
+% (No labels exist on the first run of LaTeX on the file, and if a new
+% question (or part, etc.) was created by editing the file, then the
+% label will not exist until the run of LaTeX after that.)
 \def\set@counter@to@pageof#1#2{%
   \@ifundefined{Pg@#2}%
   {\setcounter{#1}{-1}}%
 
 %--------------------------------------------------------------------
 
-% \ifcontinuation#1#2 expands to #2 if either: (1) The current page is
-% before the page containing question number 1, or (2) A question begins
-% on this page before any part, subpart, or subsubpart begins, or (3)
-% The current page is later than a page with the \nomorequestions
-% command.  Otherwise, it expands to #1.
+% \ifcontinuation#1#2 expands to #2 if either:
+% (1) The command \noquestionsonthispage has been given on this page,
+% or
+% (2) The current page is before the page containing question number
+% 1, or
+% (3) A question begins on this page before any part, subpart,
+% subsubpart, or choice begins, or
+% (4) The current page is later than a page with the \nomorequestions
+% command.
+% Otherwise, it expands to #1.
+%
+% Thus, for example, if there are no questions, parts, subparts,
+% subsubparts, or choices on this page, and no \noquestionsonthispage
+% command, and we're not before question 1, and not after a
+% \nomorequestions command, then \ifcontinuation will expand to #1.
 
 
 \def\ifcontinuation#1#2{%
-  % We need to first check whether we're on a page *before* the page on
-  % which the first question appears.  If we don't yet know which page
-  % has question number 1, then we must be doing an early run of LaTeX,
+  % If there's a \noquestionsonthispage command on this page, then
+  % we assume that we're not continuing anything:
+  \@ifundefined{No@Questions@Pg@\thepage}%
+    {\chk@contin{#1}{#2}}%
+    {#2}%
+}% \ifcontinuation
+
+\def\chk@contin#1#2{%
+  % We check whether we're on a page *before* the page on which the
+  % first question appears.  If we don't yet know which page has
+  % question number 1, then we must be doing an early run of LaTeX,
   % and we'll assume we're not a continuation.
   \expandafter\ifx\csname Pg@question@1\endcsname\relax
     % No page info yet; assume not a continuation
       #2%
     \else
       % The current page begins a new question if Contin@\thepage
-      % has been defined as a macro that expands to \relax
-      % (Note that this is different from if Contin@\thepage
-      % has never been defined at all, in which case it will
-      % be let equal to \relax (temporarily) by the \csname command.)
+      % has been defined as a macro that expands to \relax (Note
+      % that this is different from if Contin@\thepage has never
+      % been defined at all, in which case it will be let equal to
+      % \relax (temporarily) by the \csname command.)
       \expandafter\ifx\csname Contin@\thepage\endcsname\ref@relax
         #2%
       \else
         {#1}%
         {\ifnum \thepage > \PgInfo@get{@endquestions}\relax
           % We're after a \nomorequestions:
-          #2
+          #2%
          \else
            % We actually are incomplete:
-           #1
+           #1%
          \fi
         }%
       \fi
     \fi 
   \fi
-}
+}% chk@contin
 
-\def\nomorequestions{
+\def\nomorequestions{%
   \PgInfo@write{@endquestions}%
-}
+}% nomorequestions
 
+\def\noquestionsonthispage{%
+  \write\@mainaux{\string\expandafter\string\gdef
+    \string\csname\space No@Questions@Pg@\thepage\string\endcsname
+    {No questions here}}%
+}% noquestionsonthispage
 
 
 %--------------------------------------------------------------------
 
 % ACTUALLY: \ContinuedQuestion expands to a positive number if either
 % (1) this page doesn't contain the beginning of any question, part,
-% subpart, or subsubpart, or (2) this page has a part, subpart, or
-% subsubpart that appears before any question.  That means that if the
-% current page actually begins with space for a continuation of the
-% previous question (but doesn't begin any part, subpart, or subsubpart
-% of that question) and then has a question, then we'll be asserting
-% that this page begins with a new question, but the actual top of the
-% page will begin with some blank space that's intended for the previous
-% question.
+% subpart, subsubpart, or choice, or (2) this page has a part, subpart,
+% subsubpart, or choice that appears before any question.  That means
+% that if the current page actually begins with space for a continuation
+% of the previous question (but doesn't begin any part, subpart,
+% subsubpart, or choice of that question) and then has a question, then
+% we'll be asserting that this page begins with a new question, but the
+% actual top of the page will begin with some blank space that's
+% intended for the previous question.
 
 % \ContinuedQuestion works by examining the value of the macro
 % Contin@\thepage.  If this page starts with a question (i.e., if no
 % If Contin@\thepage is undefined, then when it is used in an \ifx
 % command it will be temporarily set equal to \relax (which is
 % *different* from being a macro that expands to \relax); in this case,
-% there is no question, part, subpart, or subsubpart that begins on this
-% page, and so \ContinuedQuestion will be set equal to the last question
-% that was begun on a page before this one.
+% there is no question, part, subpart, subsubpart, or choice that begins
+% on this page, and so \ContinuedQuestion will be set equal to the last
+% question that was begun on a page before this one.
 
 % The last possibility is that this page begins with either a part,
-% subpart, or subsubpart.  In this case, Contin@\thepage is defined, and
-% it expands to the number of the question that is continues onto this
-% page.
+% subpart, subsubpart, or choice.  In this case, Contin@\thepage is
+% defined, and it expands to the number of the question that is
+% continued onto this page.
 
 
 \def\ref@relax{\relax}
 
 \def\ContinuedQuestion{%
   \expandafter\ifx\csname Contin@\thepage\endcsname\relax
-    % We get here if there's no question, part, subpart, or
-    % subsubpart on this page, and so Contin@\thepage has
+    % We get here if there's no question, part, subpart,
+    % subsubpart, or choice on this page, and so Contin@\thepage has
     % never been defined at all.  In that case, this page
     % continues whichever question was last begun on or 
     % before this page.
       % which is why Contin@\thepage has been defined to be
       % a macro that expands to \relax.
       % ACTUALLY: We get here if this page has a question that
-      % appears before any part, subpart, or subsubpart.  That
+      % appears before any part, subpart, subsubpart, or choice.  That
       % means that if the current page actually begins with space
       % for a continuation of the previous question but doesn't begin
-      % any part, subpart, or subsubpart of that question, then
+      % any part, subpart, subsubpart, or choice of that question, then
       % we'll be asserting that this page begins with a new question,
       % but the actual top of the page will begin with some space
       % that's intended for the previous question.
   % \find@quesend, which is why it has to find the last question
   % begun on or before the current page, rather than just before
   % the current page.
-  \ifnum 1 > \arabic{question}\relax
+  \ifnum 1 > \value{question}\relax
     % Oops; probably because we're before the first question
     % Just set latest@ques to -1:
     \setcounter{latest@ques}{-1}%
       % then call \decr@latest@ques to recursively decrement
       % latest@ques as needed to find a question that begins on
       % or before the current page:
-      \setcounter{latest@ques}{\arabic{question}}%
+      \setcounter{latest@ques}{\value{question}}%
       \decr@latest@ques
     \fi
   \fi
   \fi
   \next@dlq
 }
-         
-
 
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 \newcounter{ques@end}
+\newcounter{last@object}
+
 \def\find@quesend{%
   % We find the last question started on or before the current page
   % and then find the page containing the last part (or subpart, or
-  % subsubpart) of that question, and set the counter ques@end to that
-  % page number.
+  % subsubpart, or choice) of that question, and set the counter
+  % ques@end to that page number.
   % Set latest@ques equal to the correct question number:
   \find@latestques
   \ifnum \value{latest@ques} < 0\relax
     % \PgInfo commands in the .aux file:
     \setcounter{ques@end}{-1}%
   \else
-    % See if this question has any parts.  (It has one or more parts
-    % if and only if it has a part number 1.)
-    \@ifundefined{Pg@part@\thelatest@ques @1}%
-      {%
-        % Nope; no parts.
-        \setcounter{ques@end}{\PgInfo@get{question@\thelatest@ques}}%
-      }%
-      {\find@part}%
+    % We now know that this question has at least one object (since
+    % we know that latest@ques isn't negative).
+    % We'll find its highest numbered object by setting last@object
+    % equal to 2 and then calling \find@lastobject to recursively
+    % test whether that object number exists and, if so, incrementing
+    % last@object to test for a higher numbered one:
+    \setcounter{last@object}{2}%
+    \find@lastobject
+    \setcounter{ques@end}{\PgInfo@get{question\thelatest@ques
+               @object\thelast@object}}%
   \fi
-}
-
-\newcounter{last@part}
-\def\find@part{%
-  % We now know that this question has at least one part.
-  % We'll find its highest numbered part by setting last@part
-  % equal to 2 and then calling \find@lastpart to recursively
-  % test whether that part number exists and, if so, incrementing
-  % last@part to test for a higher numbered one:
-  \setcounter{last@part}{2}%
-  \find@lastpart
-  % Now: See if this part has any subparts.  (It has one or more
-  % subparts if and only if it has a subpart numbered 1.)
-  \@ifundefined{Pg@subpart@\thelatest@ques @\thelast@part @1}%
-    {%
-      % Nope; no subparts.
-      \setcounter{ques@end}{\PgInfo@get{part@\thelatest@ques
-                                          @\thelast@part}}%
-    }%
-    {\find@subpart}%
-}
-
-\def\find@lastpart{%
-  % We check whether this question has a part numbered last@part
-  % and recursively increment last@part to find the highest
-  % numbered value for which the part exists:
-  \@ifundefined{Pg@part@\thelatest@ques @\thelast@part}%
-    {\addtocounter{last@part}{-1}%
-      \let\nextfind@lastpart=\relax
-    }%
-    {\addtocounter{last@part}{1}%
-      \let\nextfind@lastpart=\find@lastpart
+}% find@quesend
+
+\def\find@lastobject{%
+  % We check whether this question has an object numbered last@object
+  % and recursively increment last@object to find the highest
+  % numbered value for which the object exists:
+  \@ifundefined{Pg@question\thelatest@ques @object\thelast@object}%
+    {\addtocounter{last@object}{-1}%
+      \let\nextfind@lastobject=\relax
     }%
-  \nextfind@lastpart
-}
-
-\newcounter{last@subpart}
-\def\find@subpart{%
-  % We now know that this part has at least one subpart.
-  % We'll find its highest numbered subpart by setting last@subpart
-  % equal to 2 and then calling \find@lastsubpart to recursively
-  % test whether that subpart number exists and, if so, incrementing
-  % last@subpart to test for a higher numbered one:
-  \setcounter{last@subpart}{2}%
-  \find@lastsubpart
-  % Now: See if this subpart has any subsubparts.  (It has one or more
-  % subsubparts if and only if it has a subsubpart numbered 1.)
-  \@ifundefined{Pg@subsubpart@\thelatest@ques 
-                         @\thelast@part @\thelast@subpart @1}%
-    {%
-      % Nope; no subsubparts
-      \setcounter{ques@end}{\PgInfo@get{subpart@\thelatest@ques
-                                  @\thelast@part @\thelast@subpart}}%
-    }%
-    {\find@subsubpart}%
-}
-
-\def\find@lastsubpart{%
-  % We check whether this part has a subpart numbered last@subpart
-  % and recursively increment last@subpart to find the highest
-  % numbered value for which the subpart exists:
-  \@ifundefined{Pg@subpart@\thelatest@ques 
-                         @\thelast@part @\thelast@subpart}%
-    {\addtocounter{last@subpart}{-1}%
-      \let\nextfind@lastsubpart=\relax
+    {\addtocounter{last@object}{1}%
+      \let\nextfind@lastobject=\find@lastobject
     }%
-    {\addtocounter{last@subpart}{1}%
-      \let\nextfind@lastsubpart=\find@lastsubpart
-    }%
-  \nextfind@lastsubpart
-}
+  \nextfind@lastobject
+}% find@lastobject
 
-\newcounter{last@subsubpart}
-\def\find@subsubpart{%
-  % We now know that this subpart has at least one subsubpart.
-  % We'll find its highest numbered subsubpart by setting last@subsubpart
-  % equal to 2 and then calling \find@lastsubsubpart to recursively
-  % test whether that subsubpart number exists and, if so, incrementing
-  % last@subsubpart to test for a higher numbered one:
-  \setcounter{last@subsubpart}{2}%
-  \find@lastsubsubpart
-  \setcounter{ques@end}{\PgInfo@get{subsubpart@\thelatest@ques
-               @\thelast@part @\thelast@subpart @\thelast@subsubpart}}%
-}
-
-\def\find@lastsubsubpart{%
-  % We check whether this subpart has a subsubpart numbered last@subsubpart
-  % and recursively increment last@subsubpart to find the highest
-  % numbered value for which the subsubpart exists:
-  \@ifundefined{Pg@subsubpart@\thelatest@ques 
-              @\thelast@part @\thelast@subpart @\thelast@subsubpart}%
-    {\addtocounter{last@subsubpart}{-1}%
-      \let\nextfind@lastsubsubpart=\relax
-    }%
-    {\addtocounter{last@subsubpart}{1}%
-      \let\nextfind@lastsubsubpart=\find@lastsubsubpart
-    }%
-  \nextfind@lastsubsubpart
-}
 
 
 %--------------------------------------------------------------------
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+
 \newcounter{incmp@ques}
 
 \def\IncompleteQuestion{%
   \Find@Incmp@ques
+  % If there's no incomplete question, the counter incmp@ques will be
+  % set to -1:
   \theincmp@ques
 }
 
 \def\Find@Incmp@ques{%
+  % If we're on the last page, then there's no incomplete question:
   \iflastpage{\setcounter{incmp@ques}{-1}}{\chk@incomp}%
-}
+}% Find@Incmp@ques
 \newcounter{next@ques}
-\newcounter{nextq@page}
+\newcounter{next@page}
+
 \def\chk@incomp{%
+  % If we get here, we're not on the last page.
+  % \find@quesend calls \find@latestques to set the counter
+  % latest@ques equal to the number of the last question begun on or
+  % before the current page, and then it sets the counter ques@end to
+  % the page containing the last ques@object of that question:
   \find@quesend
   \ifnum \theques@end > \thepage\relax
     % This question has a part (or sub...) starting on a later page
     \setcounter{incmp@ques}{\value{latest@ques}}%
   \else
-    \setcounter{next@ques}{\thelatest@ques}%
-    \addtocounter{next@ques}{1}%
-    \expandafter\ifx\csname Pg@question@\thenext@ques \endcsname\relax
-      % This isn't the last page but there is no next question:
-      \setcounter{incmp@ques}{\thelatest@ques}%
-    \else
-      % This isn't the last page and there is a next question:
-      \setcounter{nextq@page}{\PgInfo@get{question@\thenext@ques}}%
-      \addtocounter{nextq@page}{-1}%
-      \ifnum \thenextq@page > \thepage\relax
-        \setcounter{incmp@ques}{\thelatest@ques}%
-      \else
-        \setcounter{incmp@ques}{-1}%
-      \fi
-    \fi
+    \chk@incompi
   \fi
-}
-
+}% chk@incomp
+
+\def\chk@incompi{%
+  % If there are any pages after the current one and before the next
+  % question (if there is a next question) that lack a
+  % \noquestionsonthispage and that aren't following the page of a
+  % \nomorequestions command, then question latest@ques is incomplete.
+  % Otherwise, there is no incomplete question:
+  \setcounter{next@ques}{\thelatest@ques}%
+  \addtocounter{next@ques}{1}%
+  % We use next@page as a scratch counter.  We start by setting it
+  % to the last page we want to check for a \noquestionsonthispage
+  % command:
+  \expandafter\ifx\csname Pg@question@\thenext@ques \endcsname\relax
+    % This isn't the last page but there is no next question:
+%
+    \@ifundefined{exam@lastpage}%
+      {\setcounter{next@page}{-1}}%
+      {\setcounter{next@page}{\exam@lastpage}}%
+%
+%    \setcounter{next@page}{\exam@lastpage}%
+  \else
+    \setcounter{next@page}{\PgInfo@get{question@\thenext@ques}}%
+    \addtocounter{next@page}{-1}%
+  \fi
+  % See if that's after a \nomorequestions command:
+  \@ifundefined{Pg@@endquestions}%
+    {}%
+    {\ifnum \PgInfo@get{@endquestions} < \value{next@page}\relax
+       \setcounter{next@page}{\PgInfo@get{@endquestions}}%
+     \fi
+    }%
+  % OK, the counter next@page now contains the last page to check.
+  \chk@incompii
+}% chk@incompi
+
+\def\chk@incompii{%
+  \ifnum \value{next@page} > \value{page}\relax
+    % We need to check the page next@page:
+    \@ifundefined{No@Questions@Pg@\arabic{next@page}}%
+    {\setcounter{incmp@ques}{\value{latest@ques}}%
+      \let\next@incompii=\relax
+    }%
+    {\addtocounter{next@page}{-1}%
+      \let\next@incompii = \chk@incompii
+    }%
+  \else
+    % There's no incomplete question:
+    \setcounter{incmp@ques}{-1}%
+    \let\next@incompii=\relax
+  \fi
+  \next@incompii
+}% chk@incompii
 
 \def\ifincomplete#1#2{%
+  % We need to pass the arguments to \chk@ifincomp; we save them in
+  % macros so they won't be messed up by the call to \Find@Incmp@ques:
   \def\incomp@first{#1}%
   \def\incomp@second{#2}%
+  % If there's a \noquestionsonthispage command on this page, then
+  % we assume nothing from this page is incomplete:
+  \@ifundefined{No@Questions@Pg@\thepage}%
+    {\chk@ifincomp}%
+    {\incomp@second}%
+}% ifincomplete
+
+\def\chk@ifincomp{%
   \Find@Incmp@ques
+  % If there's no incomplete question, \Find@Incmp@ques sets the
+  % counter incmp@ques to -1:
   \ifnum \theincmp@ques < 0\relax
     \incomp@second
   \else
-        \@ifundefined{Pg@@endquestions}%
-        {\incomp@first}%
-        {\ifnum \thepage < \PgInfo@get{@endquestions}%
-           \incomp@first
-         \else
-           \incomp@second
-         \fi
-        }%
+    % Are we after a page with \nomorequestions?
+    \@ifundefined{Pg@@endquestions}%
+      {\incomp@first}%
+      {\ifnum \thepage < \PgInfo@get{@endquestions}\relax
+         \incomp@first
+       \else
+         \incomp@second
+       \fi
+      }%
   \fi
-}
-
-
-
-
-%---------------------------------------------------------------------
-%
-%                    ***************************
-%                    ** QUESTION ENVIRONMENTS **
-%                    ***************************
-%
-%
-%
-
-% We define the command \part only inside of a parts environment, so
-% that we don't interfere with the meaning of the standard article
-% documentclass command \part if that is used inside of a questions
-% environment.  The commands \question, \subpart, and \subsubpart are
-% defined everywhere inside of a questions environment.  If the user
-% accidentally gives a \subpart command outside of a subparts
-% environment, then an error will be created.
-
+}% chk@ifincomp
 
 %--------------------------------------------------------------------
 % These are the commands for dealing with hlfcntr's, i.e., the things
 %
 % The commands:
 %
+% \new@hlfcntr{countername}
 % \set@hlfcntr{countername}{value}
 % \copy@hlfcntr{tocounter}{fromcounter}
 % \addto@hlfcntr{countername}{value}
-% \add@hlfcntrtohlfcntr
+% \add@hlfcntrtohlfcntr{getsaddedto}{whatsadded}
 % \ifhlfcntr@pos{countername}
 % \prtaux@hlfcntr{countername}
+% \prt@hlfcntr{countername}
 %
 % ``value'' can be either a (nonnegative) integer, an integer followed by
 % ``\half'', or just plain ``\half''.  (Actually, ``value'' can be empty
 % \ifsomething) aren't.  Thus, we need to say ``\global'' when setting
 % these things.
 
+% To create a hlfcntr:
+\newcommand*\new@hlfcntr[1]{%
+  \newcounter{#1}%
+  \expandafter\newif\csname if#1@half\endcsname
+}% new@hlfcntr
+
+
 % A scratch hlfcntr:
-\newcounter{tmp@hlfcntr}
-\newif\iftmp@hlfcntr@half
+\new@hlfcntr{tmp@hlfcntr}
 
 \newcommand*\horiz@half{$\frac{1}{2}$}
 \newcommand*\slanted@half{%
   $\raise0.6ex\hbox{$\scriptstyle 1$}\kern -.2em/\kern -.2em
      \raise-0.5ex\hbox{$\scriptstyle 2$}$%
-}
-\newcommand*\useslantedhalf{\let\half\slanted@half}
-\newcommand*\usehorizontalhalf{\let\half\horiz@half}
+}% slanted@half
+\newcommand*\useslantedhalf{\global\let\half\slanted@half}
+\newcommand*\usehorizontalhalf{\global\let\half\horiz@half}
 \newcommand*\half{\slanted@half}
 
 
 \newcommand*\set@hlfcntr[2]{%
   \begingroup
-    \expandafter\global\csname #1@halffalse\endcsname
+    \global\csname #1@halffalse\endcsname
     % If there as a `\half' present, it will be executed
     % right after the assignment of the digit part of #2
     % to the counter #1.
     \def\half{%
-      \expandafter\global\csname #1@halftrue\endcsname
+      \global\csname #1@halftrue\endcsname
     }%
     % We insert a `0' in case there are no digits present:
-    \setcounter{#1}{0#2}\relax
+    % We avoid using \setcounter, because calc.sty redefines
+    % \setcounter in a way that conflicts with the \half trick
+    % we're using:
+    %    \setcounter{#1}{0#2}\relax
+    \global\csname c@#1\endcsname 0#2\relax
   \endgroup
-}
+}% set@hlfcntr
 
 \newcommand*\copy@hlfcntr[2]{%
   % We set #1 to the value of #2
   \setcounter{#1}{\value{#2}}%
   \csname if#2@half\endcsname
-    \expandafter\global\csname #1@halftrue\endcsname
+    \global\csname #1@halftrue\endcsname
   \else
-    \expandafter\global\csname #1@halffalse\endcsname
+    \global\csname #1@halffalse\endcsname
   \fi
-}
+}% copy@hlfcntr
 
 \newcommand*\addto@hlfcntr[2]{%
   % We add the valueandhalf #2 to hlfcntr #1
   \begingroup
     \def\half{\add@half{#1}}%
     % We insert a `0' in case there are no digits present:
-    \addtocounter{#1}{0#2}\relax
+    % We avoid using \addtocounter, because calc.sty redefines
+    % \addtocounter in a way that conflicts with the \half trick
+    % we're using:
+    %    \addtocounter{#1}{0#2}\relax
+    \global\advance\csname c@#1\endcsname 0#2\relax
   \endgroup
-}
+}% addto@hlfcntr
 
 \newcommand*\add@hlfcntrtohlfcntr[2]{%
   % We add the hlfcntr #2 to the hlfcntr #1
   \csname if#2@half\endcsname
     \add@half{#1}%
   \fi
-}
+}% add@hlfcntrtohlfcntr
 
 \newcommand*\add@half[1]{%
   % We add one half to hlfcntr #1:
   \csname if#1@half\endcsname
     \addtocounter{#1}{1}%
-    \expandafter\global\csname #1@halffalse\endcsname
+    \global\csname #1@halffalse\endcsname
   \else
-    \expandafter\global\csname #1@halftrue\endcsname
+    \global\csname #1@halftrue\endcsname
   \fi
-}
-
+}% add@half
+
+% Important reminder about \ifhlfcntr@pos: Do not use it inside
+% another conditional!  The construction
+%  \ifhlfcntr@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@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@cntr}
 \def\ifhlfcntr@pos#1{%
   % The argument must be a hlfcntr (which, of course,
   \csname if#1@half\endcsname
     \addtocounter{ifpos@cntr}{1}%
   \fi
-  \ifnum \value{ifpos@cntr} > 0
-}
+  \ifnum \value{ifpos@cntr} > 0\relax
+}% ifhlfnctr@pos
 
 % \prtaux@hlfcntr is used inside the argument of a \write command for
 % writing to the .aux file:
     % there's no \if visible until the \csname is expanded:
     \prtaux@halforblank{#1}%
   \fi
-}
+}% prtaux@hlfcntr
 \newcommand*\prtaux@halforzero[1]{%
   \csname if#1@half\endcsname
     \string\half
   \else
     0%
   \fi
-}
+}% prtaux@hlforzero
 \newcommand*\prtaux@halforblank[1]{%
   \csname if#1@half\endcsname
     \string\half
   \fi
-}
-%--------------------------------------------------------------------
+}% prtaux@halforblank
 
-
-
-
-% We use the counter name `partno' for the parts environment so that
-% we will not interfere with the counter `part' used by the article
-% document class.
-
-\newcounter{question}
-\newcounter{partno}
-\newcounter{subpart}
-\newcounter{subsubpart}
-\newcounter{choice}
-\newcounter{numpoints}
-\newif\ifnumpoints@half
-\set@hlfcntr{numpoints}{0}
-\newcounter{pointsof@thisquestion}
-\newif\ifpointsof@thisquestion@half
-\set@hlfcntr{pointsof@thisquestion}{0}
-\newcounter{numquestions}
-\newcounter{numparts}
+\newcommand*\prt@hlfcntr[1]{%
+  % We don't want a \relax after the 0 in the following
+  % line, because it would sometimes appear in the aux file:
+  \ifnum \value{#1} = 0 
+    % We have to make the following a macro, because if we
+    % don't do this part, the \fi will cause confusion, since
+    % there's no \if visible until the \csname is expanded:
+    \prt@halforzero{#1}%
+  \else
+    \arabic{#1}%
+    % We have to make the following a macro, because if we
+    % don't do this part, the \fi will cause confusion, since
+    % there's no \if visible until the \csname is expanded:
+    \prt@halforblank{#1}%
+  \fi
+}% prt@hlfcntr
+\newcommand*\prt@halforzero[1]{%
+  \csname if#1@half\endcsname
+    \half
+  \else
+    0%
+  \fi
+}% prt@hlforzero
+\newcommand*\prt@halforblank[1]{%
+  \csname if#1@half\endcsname
+    \half
+  \fi
+}% prt@halforblank
+
+% End of the commands for dealing with hlfcntr's
+
+%--------------------------------------------------------------------
+%---------------------------------------------------------------------
+%
+%                    ***************************
+%                    ** QUESTION ENVIRONMENTS **
+%                    ***************************
+%
+%
+%
+
+% We define the command \part only inside of a parts environment, so
+% that we don't interfere with the meaning of the standard article
+% documentclass command \part if that is used inside of a questions
+% environment.  The commands \question, \subpart, and \subsubpart are
+% defined everywhere inside of a questions environment.  If the user
+% accidentally gives a \subpart command outside of a subparts
+% environment, then an error will be created.
+
+
+
+
+% We use the counter name `partno' for the parts environment so that
+% we will not interfere with the counter `part' used by the article
+% document class.
+
+\newcounter{question}
+\newcounter{partno}
+\newcounter{subpart}
+\newcounter{subsubpart}
+\newcounter{choice}
+\new@hlfcntr{numpoints}
+\set@hlfcntr{numpoints}{0}
+\new@hlfcntr{numbonuspoints}
+\set@hlfcntr{numbonuspoints}{0}
+\new@hlfcntr{pointsof@thisquestion}
+\set@hlfcntr{pointsof@thisquestion}{0}
+\new@hlfcntr{bonuspointsof@thisquestion}
+\set@hlfcntr{bonuspointsof@thisquestion}{0}
+\newcounter{numquestions}
+\newcounter{numparts}
 \newcounter{numsubparts}
 \newcounter{numsubsubparts}
 \newcounter{Curr@Page}
 
 % @pagepoints accumulates the points on a single page:
-\newcounter{@pagepoints}
-\newif\if@pagepoints@half
-%\setcounter{@pagepoints}{0}
+\new@hlfcntr{@pagepoints}
 \set@hlfcntr{@pagepoints}{0}
+\new@hlfcntr{@pagebonuspoints}
+\set@hlfcntr{@pagebonuspoints}{0}
 \newcounter{pageof@pagepoints}
 \setcounter{pageof@pagepoints}{0}
+\newcounter{pageof@pagebonuspoints}
+\setcounter{pageof@pagebonuspoints}{0}
 
 % latest@points is a holding area for points until we know
 % whether they'll land on the same page as the points
 % currently counted in @pagepoints:
-\newcounter{latest@points}
-\newif\iflatest@points@half
+\new@hlfcntr{latest@points}
 \set@hlfcntr{latest@points}{0}
+\new@hlfcntr{latest@bonuspoints}
+\set@hlfcntr{latest@bonuspoints}{0}
 
 % Whenever we meet a new page on which points are defined, we'll
 % redefine \page@withpoints to expand to that page.  At the end of the
 % a \gdef\lastpage@withpoints command to the .aux file.
 % We initialize \page@withpoints here:
 \def\page@withpoints{0}%
+\def\page@withbonuspoints{0}%
 
 % \pageinfo@commands is used by each question, part, subpart, and
 % subsubpart to insert into everypar the \PgInfo@write command to put
-% its page number inot the .aux file, the \PgInfo@get command to read
+% its page number into the .aux file, the \PgInfo@get command to read
 % the page number into the counter Curr@Page, and to test and set
 % \Contin@\theCurr@Page.  \temp@toks is used by part, subpart, and
 % subsubpart to append all that to \pageinfo@commands, rather than
   $\rho$\or $\sigma$\or $\tau$\or $\upsilon$\or $\phi$\or $\chi$\or
   $\psi$\or $\omega$\else \@ctrerr
   \fi
-}%
+}% lc@greek
 
 
 % The following macros are a variation on a trick from Victor
 \def\prepend@toklist#1#2{%
   \edef\do@it{\noexpand#1={\the#2\the#1}}%
   \do@it
-}%
+}% prepend@toklist
 
 \def\append@toklist#1#2{%
   \edef\do@it{\noexpand#1={\the#1\the#2}}%
   \do@it
-}%
+}% append@toklist
 
 
 % The command \qformat is provided for the user who wants to
 % design a nonstandard question line.  If this command is used,
-% then the usual line containing the question numbers will be
-% replaced by the line specified by the \qformat command.
+% then the usual line containing the question number and the beginning
+% of the question will be replaced by the line specified by the
+% \qformat command, and the question will begin on the following
+% line.
 % Within the argument of the \qformat command:
 % \thequestion will be replaced by the question number, and
 % \thepoints will be replaced by ``\@points \@pointname'' if the
 % number of points has been specified for this question, and otherwise
 % it inserts nothing at all.  (The conditional @placepoints is used to
-% determine if the number of points is nonzero.)
+% determine if there were points specified for this question.)
 % The argument to the \qformat command *must* contain some
 % stretch, i.e., at least one \hfil or \dotfill or ...
+%
+% The command \noqformat cancels the effect of \qformat and returns us
+% to the default situation.
+%
+% The commands \bonusqformat and \nobonusformat are analogous.
 \newif\if@qformat
 \@qformatfalse
+\newif\if@bonusqformat
+\@bonusqformatfalse
 
 \def\qformat#1{%
   \global\@qformattrue
   \gdef\@questionformat{#1}%
-}
+}% qformat
+\def\bonusqformat#1{%
+  \global\@bonusqformattrue
+  \gdef\@bonusquestionformat{#1}%
+}% bonusqformat
 
 \newcommand\noqformat{%
   \global\@qformatfalse
-}
+}% noqformat
+\newcommand\nobonusqformat{%
+  \global\@bonusqformatfalse
+}% nobonusqformat
 
+
+% \thepoints is for use in either a \qformat command
+% or a \pointformat command (or a \bonusqformat command).
+% It needs to have the
+% \if@placepoints so that if it's used in a \qformat command
+% it won't print anything if there are no points:
 \newcommand\thepoints{%
   \if@placepoints
-    \@points \@pointname
+    \if@bonus
+      \@points \@bonuspointname
+    \else
+      \@points \@pointname
+    \fi
   \fi
-}
+}% thepoints
+
+% \themarginpoints is for use only in a \pointformat command,
+% and so it doesn't need the \if@placepoints bit in \thepoints:
+\newcommand\themarginpoints{%
+  \if@bonus
+    \@points \@marginbonuspointname
+  \else
+    \@points \@marginpointname
+  \fi
+}% themarginpoints
 
 % We define the \subpart and \subsubpart commands when we enter a
 % questions environment (rather than waiting until we enter a subparts
 % can use the standard sectioning \part command outside of a parts
 % environment.)
 
+% The counter ques@object will count the items in each question, where
+% an item is defined as either the question itself, or a part, or a
+% subpart, or a subsubpart, or a choice.  This will be used by
+% \find@quesend to find the last page occupied by the last question
+% begun on or before the current page:
+\newcounter{ques@object}
+
+% \first@questionobject will be used by the \question command.
+% That is, it will be used only once, but we want to keep its
+% definition here, near the definitions of \addquestionobject and
+% \questionobject@pluspagecheck.
+\newcommand{\first@questionobject}{%
+  \setcounter{ques@object}{1}%
+  % \PgInfo@write expands it's argument, so we don't need edef:
+%   \edef\q@object@label{%
+%     question\arabic{question}@object\arabic{ques@object}}%
+%   \PgInfo@write{\q@object@label}%
+  \PgInfo@write{question\arabic{question}@object\arabic{ques@object}}%
+}% first@questionobject
+
+% \addquestionobject will be used by each part, subpart, and
+% subsubpart, and can also be used by the user to mark the end of a
+% question that spills over onto the next page without any part,
+% subpart, etc. starting on that page:
+\newcommand{\addquestionobject}{%
+  \addtocounter{ques@object}{1}%
+  % \PgInfo@write expands it's argument, so we don't need edef:
+%   \edef\q@object@label{%
+%     question\arabic{question}@object\arabic{ques@object}}%
+%   \PgInfo@write{\q@object@label}%
+  \PgInfo@write{question\arabic{question}@object\arabic{ques@object}}%
+}% addquestionobject
+
+% \questionobject@pluspagecheck will be used by each choice, as well
+% as by any \part, \subpart, or \subsubpart that's inside of a
+% solution.  It uses the questionobject to check if we're the first
+% one on the current page, since choices (and questions etc. inside of
+% solution environments) don't have labels the way that questions,
+% parts, subparts, and subsubparts do (those things use the label to
+% check if they're the first thing on the page).
+\newcommand{\questionobject@pluspagecheck}{%
+  % We don't want to do any of this if we're both inside a solution
+  % environment and not printing answers (because we want to avoid
+  % incrementing ques@object):
+  \if@insolution
+    \ifprintanswers
+      \doqobj@ppchk
+    \fi
+  \else
+    \doqobj@ppchk
+  \fi
+}% questionobject@pluspagecheck
+\newcommand{\doqobj@ppchk}{%
+  \addtocounter{ques@object}{1}%
+  % We need the edef because we check the page of \q@object@label:
+  \edef\q@object@label{%
+    question\arabic{question}@object\arabic{ques@object}}%
+  \PgInfo@write{\q@object@label}%
+  \set@counter@to@pageof{Curr@Page}{\q@object@label}%
+  \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
+    % We're the first \question, \part, \subpart, \subsubpart,
+    % or choice on this page:
+    \global\expandafter\edef\csname
+    Contin@\theCurr@Page\endcsname{\arabic{question}}%
+  \fi
+}% doqobj@ppchk                               
+
+
+
+% if@bonus will be true when we're doing a bonusquestion or bonuspart
+% or etc., and it will also be used also to distinguish between
+% \gradetable and \bonusgradetable (and between \pointtable and
+% \bonuspointtable, etc.), and also to distinguish between
+% \pointsinrange and \bonuspointsinrange:
+\newif\if@bonus
+\@bonusfalse
+
+% The following are for advanced users who want to customize the list
+% parameters (\topsep, \partopsep, \itemsep, \parsep, etc.) for the
+% lists that these environments create.  They are all defined to be
+% empty, but the user can change them using \renewcommand.
+\newcommand\questionshook{}
+\newcommand\partshook{}
+\newcommand\subpartshook{}
+\newcommand\subsubpartshook{}
+\newcommand\choiceshook{}
+\newcommand\checkboxeshook{}
+
+
 \newenvironment{questions}{%
   % \@queslevel is used for two purposes:
   % (1) We check that every \question, \part, \subpart, and 
   % \global \point@toks={} to avoid setting the points for a 
   % question other than via the qformat command.
   \def\@queslevel{question}%
+  \def\titledquestion##1{%
+    \@bonusfalse
+    \def\thequestiontitle{##1}%
+    \process@question
+  }%
+  \def\bonustitledquestion##1{%
+    \@bonustrue
+    \def\thequestiontitle{##1}%
+    \process@question
+  }%
   \def\question{%
+    \@bonusfalse
+    \def\thequestiontitle{\csname p@question\endcsname
+                          \csname thequestion\endcsname}%
+    \process@question
+  }%
+  \def\bonusquestion{%
+    \@bonustrue
+    \def\thequestiontitle{\csname p@question\endcsname
+                          \csname thequestion\endcsname}%
+    \process@question
+  }%
+  \def\process@question{%
     \if@coverpages
       \cover@question@error
     \fi
     \addtocounter{numquestions}{1}%
     % Write the sum of points of the previous question (if any)
     % to the .aux file.  (At this point, the question counter
-    % has not yet been incremented, so \arabic{question} has the
+    % has not yet been incremented, so \value{question} is the
     % number of the question that was just completed.)
     \if@filesw
-      \ifnum \arabic{question} > 0\relax
+      \ifnum \value{question} > 0\relax
+        % First do regular points:
         \immediate\write\@mainaux
           {\string\gdef\string\pointsofq@
             \romannumeral \csname c@question\endcsname
               {\prtaux@hlfcntr{pointsof@thisquestion}}}%
         % See if this has changed from the last run of LaTeX:
-        \@ifundefined{pointsofq@\romannumeral
+        \CheckIfChanged@hlf{pointsof@thisquestion}{pointsofq@\romannumeral
                             \csname c@question\endcsname}%
-          {\global\@pointschangedtrue}%
-          {%
-          % OK; it's defined.  See if it's changed:
-          \begingroup
-            \set@hlfcntr{tmp@hlfcntr}{\csname pointsofq@\romannumeral 
-                         \csname c@question\endcsname\endcsname}%
-            \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
-            \edef\pt@check{\prtaux@hlfcntr{pointsof@thisquestion}}%
-            \ifx \pt@check \othpt@check
-              % Do nothing
-            \else
-              \global\@pointschangedtrue
-            \fi
-          \endgroup
-          }%
+        % Now do bonus points:
+        \immediate\write\@mainaux
+          {\string\gdef\string\bonuspointsofq@
+            \romannumeral \csname c@question\endcsname
+              {\prtaux@hlfcntr{bonuspointsof@thisquestion}}}%
+        % See if this has changed from the last run of LaTeX:
+        \CheckIfChanged@hlf{bonuspointsof@thisquestion}%
+                           {bonuspointsofq@\romannumeral
+                             \csname c@question\endcsname}%
       \fi
     \fi
     \set@hlfcntr{pointsof@thisquestion}{0}%
+    \set@hlfcntr{bonuspointsof@thisquestion}{0}%
     % If there was a question with points immediately preceding
     % this question (i.e., there were no parts in the previous
     % question), then @placepoints will still be true, and we need to
     % cancel it.  (We used to do this inside of the \thepoints macro,
     % but that allowed for an error if the user specified points for a
     % question but had a \qformat that didn't mention \thepoints.)  We
-    % also set @placepoints to be false in the \part command.
+    % also set @placepoints to be false when entering a parts
+    % environment. 
     \global \@placepointsfalse
     % point@toks will normally be empty at this point, but it might be
     % nonempty if there were points somewhere in the previous question
     % horizontal mode by \everypar, and so any blank lines will
     % cause paragraph breaks. 
     \pageinfo@commands={%
-      \edef\@queslabel{question@\arabic{question}}%
+      \edef\@queslabel{question@\arabic{section}@\arabic{subsection}@\arabic{question}}%
       \PgInfo@write{\@queslabel}%
+      \first@questionobject
+      % In addition to the \PgInfo@write we use an actual \label
+      % command.  We do this in order to make the question numbers in
+      % the grade tables into \ref's, so that if the user says
+      % \usepackage{hyperref}, those question numbers will be clickable.
+      % 
+      % A further purpose of these labels (which we actually do for all
+      % questions, parts, subparts, and subsubparts) is that if a
+      % question (or part, etc.) is, e.g., moved from one page to
+      % another, LaTeX will notice this and warn the user that LaTeX
+      % must be run one more time to be sure everything is correct.
+      % 
+      % We need to do this even though we've already included code to
+      % check when point totals change because questions (and parts,
+      % etc.) know what page they're on from reading the info written to
+      % the .aux file on the previous run.  Thus, if a question (or
+      % part, etc.) is moved to a different page, then the pointsonpage
+      % totals won't notice until the *second* subsequent run of LaTeX,
+      % and so there'll be no warning to the user on the *first* run.
+      % Including these labels gives the user a warning on that first
+      % run.
+      %
+      % Further futzing required: Since this is being put into the
+      % token list \pageinfo@commands, the contents of which won't
+      % actually be  executed until we enter horizontal mode (which
+      % may well be in the first part of a parts environment), we need
+      % to make sure that \@currentlabel is the number of the
+      % question:
+      \begingroup % to confine the change to \@currentlabel
+%        \def\@currentlabel{\csname p@question\endcsname
+%                           \csname thequestion\endcsname}%
+        \def\@currentlabel{\thequestiontitle}%
+        \label{\@queslabel}%
+      \endgroup
       \set@counter@to@pageof{Curr@Page}{\@queslabel}%
       \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
-        % We're the first \question, \part, \subpart, or \subsubpart
-        % on this page:
+        % We're the first \question, \part, \subpart, \subsubpart,
+        % or choice on this page:
         \global\expandafter\edef
                \csname Contin@\theCurr@Page\endcsname{\relax}%
       \fi
       \unskip\unskip \par
     \fi
     \@doitem
-  }% question
+  }% process@question
   \def\subpart{%
+    \@bonusfalse
+    \process@subpart
+  }%
+  \def\bonussubpart{%
+    \@bonustrue
+    \process@subpart
+  }%
+  \def\process@subpart{%
     \if@coverpages
       \cover@question@error
     \fi
     \@checkqueslevel{subpart}%
-    \addtocounter{numsubparts}{1}%
-    % Important: Don't leave any blank lines inside of
-    % \pageinfo@commands!!  This token list will be dumped into
-    % horizontal mode by \everypar, and so any blank lines will
-    % cause paragraph breaks. 
-    \temp@toks={%
-      \edef\@subpartlabel{subpart@\arabic{question}%
-        @\arabic{partno}@\arabic{subpart}}%
-      \PgInfo@write{\@subpartlabel}%
-      \set@counter@to@pageof{Curr@Page}{\@subpartlabel}%
-      \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
-        % We're the first \question, \part, \subpart, or \subsubpart
-        % on this page:
-        \global\expandafter\edef\csname
-                       Contin@\theCurr@Page\endcsname{\arabic{question}}%
-      \fi
-      \the\pagepoint@commands
-      \global \pageinfo@commands={}%
-    }% temp@toks
+    \if@insolution
+      % We don't count this subpart, so no addtocounter{numsubparts}.
+      \temp@toks={%
+        \questionobject@pluspagecheck
+        \global \pageinfo@commands={}%
+        % We omit the pagepoint@commands
+      }% temp@toks
+    \else
+      \addtocounter{numsubparts}{1}%
+      % Important: Don't leave any blank lines inside of
+      % \pageinfo@commands!!  This token list will be dumped into
+      % horizontal mode by \everypar, and so any blank lines will
+      % cause paragraph breaks. 
+      \temp@toks={%
+        \edef\@subpartlabel{subpart@\arabic{section}@\arabic{subsection}@\arabic{question}%
+          @\arabic{partno}@\arabic{subpart}}%
+        \PgInfo@write{\@subpartlabel}%
+        \addquestionobject
+        % In addition to the \PgInfo@write we use an actual \label
+        % command.  We do this in order to make the question numbers in
+        % the grade tables into \ref's, so that if the user says
+        % \usepackage{hyperref}, those question numbers will be clickable.
+        % 
+        % A further purpose of these labels (which we actually do for all
+        % questions, parts, subparts, and subsubparts) is that if a
+        % question (or part, etc.) is, e.g., moved from one page to
+        % another, LaTeX will notice this and warn the user that LaTeX
+        % must be run one more time to be sure everything is correct.
+        % 
+        % We need to do this even though we've already included code to
+        % check when point totals change because questions (and parts,
+        % etc.) know what page they're on from reading the info written to
+        % the .aux file on the previous run.  Thus, if a question (or
+        % part, etc.) is moved to a different page, then the pointsonpage
+        % totals won't notice until the *second* subsequent run of LaTeX,
+        % and so there'll be no warning to the user on the *first* run.
+        % Including these labels gives the user a warning on that first
+        % run.
+        \label{\@subpartlabel}%
+        \set@counter@to@pageof{Curr@Page}{\@subpartlabel}%
+        \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
+          % We're the first \question, \part, \subpart, \subsubpart,
+          % or choice on this page:
+          \global\expandafter\edef\csname
+                         Contin@\theCurr@Page\endcsname{\arabic{question}}%
+        \fi
+        \the\pagepoint@commands
+        \global \pageinfo@commands={}%
+      }% temp@toks
+    \fi
     \append@toklist \pageinfo@commands \temp@toks
     \ifhmode
       % Remove any skips at the end of the previous paragraph
       \unskip\unskip \par
     \fi
     \@doitem
-  }% subpart
+  }% process@subpart
   \def\subsubpart{%
+    \@bonusfalse
+    \process@subsubpart
+  }%
+  \def\bonussubsubpart{%
+    \@bonustrue
+    \process@subsubpart
+  }%
+  \def\process@subsubpart{%
     \if@coverpages
       \cover@question@error
     \fi
     \@checkqueslevel{subsubpart}%
-    \addtocounter{numsubsubparts}{1}%
-    % Important: Don't leave any blank lines inside of
-    % \pageinfo@commands!!  This token list will be dumped into
-    % horizontal mode by \everypar, and so any blank lines will
-    % cause paragraph breaks. 
-    \temp@toks={%
-      \edef\@subsubpartlabel{subsubpart@\arabic{question}%
-        @\arabic{partno}@\arabic{subpart}@\arabic{subsubpart}}%
-      \PgInfo@write{\@subsubpartlabel}%
-      \set@counter@to@pageof{Curr@Page}{\@subsubpartlabel}%
-      \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
-        % We're the first \question, \part, \subpart, or \subsubpart
-        % on this page:
-        \global\expandafter\edef\csname
-                       Contin@\theCurr@Page\endcsname{\arabic{question}}%
-      \fi
-      \the\pagepoint@commands
-      \global \pageinfo@commands={}%
-    }% temp@toks
+    \if@insolution
+      % We don't count this subsubpart, so no addtocounter{numsubsubparts}.
+      \temp@toks={%
+        \questionobject@pluspagecheck
+        \global \pageinfo@commands={}%
+        % We omit the pagepoint@commands
+      }% temp@toks
+    \else
+      \addtocounter{numsubsubparts}{1}%
+      % Important: Don't leave any blank lines inside of
+      % \pageinfo@commands!!  This token list will be dumped into
+      % horizontal mode by \everypar, and so any blank lines will
+      % cause paragraph breaks. 
+      \temp@toks={%
+        \edef\@subsubpartlabel{subsubpart@\arabic{section}@\arabic{subsection}@\arabic{question}%
+          @\arabic{partno}@\arabic{subpart}@\arabic{subsubpart}}%
+        \PgInfo@write{\@subsubpartlabel}%
+        \addquestionobject
+        % In addition to the \PgInfo@write we use an actual \label
+        % command.  We do this in order to make the question numbers in
+        % the grade tables into \ref's, so that if the user says
+        % \usepackage{hyperref}, those question numbers will be clickable.
+        % 
+        % A further purpose of these labels (which we actually do for all
+        % questions, parts, subparts, and subsubparts) is that if a
+        % question (or part, etc.) is, e.g., moved from one page to
+        % another, LaTeX will notice this and warn the user that LaTeX
+        % must be run one more time to be sure everything is correct.
+        % 
+        % We need to do this even though we've already included code to
+        % check when point totals change because questions (and parts,
+        % etc.) know what page they're on from reading the info written to
+        % the .aux file on the previous run.  Thus, if a question (or
+        % part, etc.) is moved to a different page, then the pointsonpage
+        % totals won't notice until the *second* subsequent run of LaTeX,
+        % and so there'll be no warning to the user on the *first* run.
+        % Including these labels gives the user a warning on that first
+        % run.
+        \label{\@subsubpartlabel}%
+        \set@counter@to@pageof{Curr@Page}{\@subsubpartlabel}%
+        \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
+          % We're the first \question, \part, \subpart, \subsubpart,
+          % or choice on this page:
+          \global\expandafter\edef\csname
+                         Contin@\theCurr@Page\endcsname{\arabic{question}}%
+        \fi
+        \the\pagepoint@commands
+        \global \pageinfo@commands={}%
+      }% temp@toks
+    \fi
     \append@toklist \pageinfo@commands \temp@toks
     \ifhmode
       % Remove any skips at the end of the previous paragraph
       \unskip\unskip \par
     \fi
     \@doitem
-  }% subsubpart
+  }% process@subsubpart
   \list{\question@number}%
     {\usecounter{question}%
     % We use the default definition of \makelabel
     \settowidth{\leftmargin}{10.\hskip\labelsep}%
     \labelwidth\leftmargin\advance\labelwidth-\labelsep
     \partopsep=0pt
+    \questionshook
     }%
-  }%
+  }% End of the first argument of \newenvironment{questions}
   {%
     \endlist
     % Write the number of points of the final question
     % to the .aux file:
     \if@filesw
-      \ifnum \arabic{question} > 0\relax
+      \ifnum \value{question} > 0\relax
+        % First do the regular points:
         \immediate\write\@mainaux
           {\string\gdef\string\pointsofq@\romannumeral
                                 \csname c@question\endcsname
             {\prtaux@hlfcntr{pointsof@thisquestion}}}%
         % See if this has changed from the last run of LaTeX:
-        \@ifundefined{pointsofq@\romannumeral
+        \CheckIfChanged@hlf{pointsof@thisquestion}%
+                           {pointsofq@\romannumeral
+                            \csname c@question\endcsname}%
+        % Now do the bonus points:
+        \immediate\write\@mainaux
+          {\string\gdef\string\bonuspointsofq@\romannumeral
+                                \csname c@question\endcsname
+            {\prtaux@hlfcntr{bonuspointsof@thisquestion}}}%
+        % See if this has changed from the last run of LaTeX:
+        \CheckIfChanged@hlf{bonuspointsof@thisquestion}%
+                           {bonuspointsofq@\romannumeral
                             \csname c@question\endcsname}%
-          {\global\@pointschangedtrue}%
-          {%
-          % OK; it's defined.  See if it's changed:
-          \begingroup
-            \set@hlfcntr{tmp@hlfcntr}{\csname pointsofq@\romannumeral 
-                         \csname c@question\endcsname\endcsname}%
-            \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
-            \edef\pt@check{\prtaux@hlfcntr{pointsof@thisquestion}}%
-            \ifx \pt@check \othpt@check
-              % Do nothing
-            \else
-              \global\@pointschangedtrue
-            \fi
-          \endgroup
-          }%
       \fi
     \fi
-  }
+  }% End of the second argument of \newenvironment{questions}
   
 % \question@number is used as the label in the question list (instead
 % of \questionlabel) so that if the user uses a \qformat command,
 % we'll use the \@questionformat specified by the \qformat command:
+
 \def\question@number{%
-  \if@qformat
-    \makebox[\hsize][s]{\@questionformat}\hskip-\labelsep
+  \if@bonus
+    \if@bonusqformat
+      \makebox[\hsize][s]{\@bonusquestionformat}\hskip-\labelsep
+    \else
+      \questionlabel
+    \fi
   \else
-    \questionlabel
+    \if@qformat
+      \makebox[\hsize][s]{\@questionformat}\hskip-\labelsep
+    \else
+      \questionlabel
+    \fi
   \fi
 }
 \newcommand\questionlabel{\thequestion.}
 \newenvironment{parts}{%
   \def\@queslevel{part}%
   % If the question numbers are being inserted via a \qformat,
-  % and if a question is beginning with a parts environmnt, then
-  % we need to enter horizonal mode to the the qformat printed
+  % and if a question is beginning with a parts environment, then
+  % we need to enter horizonal mode to get the qformat printed
   % on the page, rather than saving up the question label (and
   % possible points) to be combined with the label of the first
   % part.  (\if@inlabel tells us if we are still waiting to enter
   % horizontal mode after seeing a \question command.)
-  \if@qformat
-    \if@inlabel
-      \leavevmode
-      \@inlabelfalse
+  \if@bonus
+    \if@bonusqformat
+      \if@inlabel
+        \leavevmode
+        \@inlabelfalse
+      \fi
+      % The following is just in case the question had points,
+      % in which case @placepoints will still be true.
+      % (We used to do this inside of the \thepoints macro,
+      % but that allowed for an error if the user specified points for
+      % a question but had a \qformat that didn't mention \thepoints.)
+      % We also set @placepoints to be false in the \question command,
+      % in case one queation follows a previous one that had no parts.
+      \global \@placepointsfalse
+    \fi
+  \else
+    \if@qformat
+      \if@inlabel
+        \leavevmode
+        \@inlabelfalse
+      \fi
+      % The following is just in case the question had points,
+      % in which case @placepoints will still be true.
+      % (We used to do this inside of the \thepoints macro,
+      % but that allowed for an error if the user specified points for
+      % a question but had a \qformat that didn't mention \thepoints.)
+      % We also set @placepoints to be false in the \question command,
+      % in case one queation follows a previous one that had no parts.
+      \global \@placepointsfalse
     \fi
-    % The following is just in case the question had points,
-    % in which case @placepoints will still be true.
-    % (We used to do this inside of the \thepoints macro,
-    % but that allowed for an error if the user specified points for
-    % a question but had a \qformat that didn't mention \thepoints.)
-    % We also set @placepoints to be false in the \question command,
-    % in case one queation follows a previous one that had no parts.
-    \global \@placepointsfalse
   \fi
   \def\part{%
+    \@bonusfalse
+    \process@part
+  }%
+  \def\bonuspart{%
+    \@bonustrue
+    \process@part
+  }%
+  \def\process@part{%
     \if@coverpages
       \cover@question@error
     \fi
     \@checkqueslevel{part}%
-    \addtocounter{numparts}{1}%
-    % Important: Don't leave any blank lines inside of
-    % \pageinfo@commands!!  This token list will be dumped into
-    % horizontal mode by \everypar, and so any blank lines will
-    % cause paragraph breaks. 
-    \temp@toks={%
-      \edef\@partlabel{part@\arabic{question}@\arabic{partno}}%
-      \PgInfo@write{\@partlabel}%
-      \set@counter@to@pageof{Curr@Page}{\@partlabel}%
-      \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
-        \global\expandafter\edef\csname
-            Contin@\theCurr@Page\endcsname{\arabic{question}}%
-      \fi
-      \the\pagepoint@commands
-      \global \pageinfo@commands={}%
-    }%
+    \if@insolution
+      % We don't count this part, so no addtocounter{numparts}.
+      \temp@toks={%
+        \questionobject@pluspagecheck
+        \global \pageinfo@commands={}%
+        % We omit the pagepoint@commands
+      }% temp@toks
+    \else
+      \addtocounter{numparts}{1}%
+      % Important: Don't leave any blank lines inside of
+      % \pageinfo@commands!!  This token list will be dumped into
+      % horizontal mode by \everypar, and so any blank lines will
+      % cause paragraph breaks. 
+      \temp@toks={%
+        \edef\@partlabel{part@\arabic{question}@\arabic{partno}}%
+        \PgInfo@write{\@partlabel}%
+        \addquestionobject
+        % In addition to the \PgInfo@write we use an actual \label
+        % command.  We do this in order to make the question numbers in
+        % the grade tables into \ref's, so that if the user says
+        % \usepackage{hyperref}, those question numbers will be clickable.
+        % 
+        % A further purpose of these labels (which we actually do for all
+        % questions, parts, subparts, and subsubparts) is that if a
+        % question (or part, etc.) is, e.g., moved from one page to
+        % another, LaTeX will notice this and warn the user that LaTeX
+        % must be run one more time to be sure everything is correct.
+        % 
+        % We need to do this even though we've already included code to
+        % check when point totals change because questions (and parts,
+        % etc.) know what page they're on from reading the info written to
+        % the .aux file on the previous run.  Thus, if a question (or
+        % part, etc.) is moved to a different page, then the pointsonpage
+        % totals won't notice until the *second* subsequent run of LaTeX,
+        % and so there'll be no warning to the user on the *first* run.
+        % Including these labels gives the user a warning on that first
+        % run.
+        \label{\@partlabel}%
+        \set@counter@to@pageof{Curr@Page}{\@partlabel}%
+        \expandafter\ifx\csname Contin@\theCurr@Page\endcsname\relax
+          \global\expandafter\edef\csname
+              Contin@\theCurr@Page\endcsname{\arabic{question}}%
+        \fi
+        \the\pagepoint@commands
+        \global \pageinfo@commands={}%
+      }% temp@toks
+    \fi
     \append@toklist \pageinfo@commands \temp@toks
     \ifhmode
       % Remove any skips at the end of the previous paragraph
       \unskip\unskip \par
     \fi
     \@doitem
-  }% part
+  }% process@part
   \list{\partlabel}%
     {%
     \usecounter{partno}\def\makelabel##1{\hss\llap{##1}}%
     \labelwidth\leftmargin\advance\labelwidth-\labelsep
     \topsep=0pt
     \partopsep=0pt
+    \partshook
     }%
   }% newenvironment{parts}
   {\endlist}
     \labelwidth\leftmargin\advance\labelwidth-\labelsep
     \topsep=0pt
     \partopsep=0pt
+    \subpartshook
     }%
   }%
   {\endlist}
     \labelwidth\leftmargin\advance\labelwidth-\labelsep
     \topsep=0pt
     \partopsep=0pt
+    \subsubpartshook
     }%
   }%
   {\endlist}
 \def\thesubsubpart{\greeknum{subsubpart}}
 
 
+
+
 \pagepoint@commands={%
   \ifhlfcntr@pos{latest@points}%
     % We're putting a question (or part, etc.)
            \romannumeral \csname c@pageof@pagepoints\endcsname
              {\prtaux@hlfcntr{@pagepoints}}}%
         % See if this has changed from the last run of LaTeX:
-        \@ifundefined{pointsonpage@\romannumeral
+        \CheckIfChanged@hlf{@pagepoints}{pointsonpage@\romannumeral
                             \csname c@pageof@pagepoints\endcsname}%
-          {\global\@pointschangedtrue}%
-          {%
-          % OK; it's defined.  See if it's changed:
-          \begingroup
-            \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral 
-                      \csname c@pageof@pagepoints\endcsname\endcsname}%
-            \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
-            \edef\pt@check{\prtaux@hlfcntr{@pagepoints}}%
-            \ifx \pt@check \othpt@check
-              % Do nothing
-            \else
-              \global\@pointschangedtrue
-            \fi
-          \endgroup
-          }%
       \fi
       % The following is a macro because \theCurr@Page and
       % \thepageof@pagepoints might differ by more than 1:
       \increment@pageof@pagepoints
+      % The following label is so that we can make the page
+      % numbers in a grade table indexed by page into \pageref's
+      % so that \usepackage{hyperref} and pdflatex will
+      % make them clickable:
+      \label{firstpoints@onpage@\arabic{Curr@Page}}%
     \else
       % These points go on the same page as the points
       % currently counted in @pagepoints:
       \set@hlfcntr{latest@points}{0}%
     \fi
   \fi
-}
+  \ifhlfcntr@pos{latest@bonuspoints}%
+    % We're putting a question (or part, etc.)
+    % with bonus points onto this page: 
+    \ifnum \theCurr@Page > \thepageof@pagebonuspoints\relax
+      % These bonus points go on a later page than
+      % the points currently counted in @pagebonuspoints:
+      \ifnum \thepageof@pagebonuspoints = 0\relax
+        % Do nothing...
+      \else
+        \immediate\write\@mainaux
+          {\string\gdef\string\bonuspointsonpage@
+           \romannumeral \csname c@pageof@pagebonuspoints\endcsname
+             {\prtaux@hlfcntr{@pagebonuspoints}}}%
+        % See if this has changed from the last run of LaTeX:
+        \CheckIfChanged@hlf{@pagebonuspoints}%
+                           {bonuspointsonpage@\romannumeral
+                            \csname c@pageof@pagebonuspoints\endcsname}%
+      \fi
+      % The following is a macro because \theCurr@Page and
+      % \thepageof@pagebonuspoints might differ by more than 1:
+      \increment@pageof@pagebonuspoints
+      % The following label is so that we can make the page
+      % numbers in a bonus grade table indexed by page into \pageref's
+      % so that \usepackage{hyperref} and pdflatex will
+      % make them clickable:
+      \label{firstbonuspoints@onpage@\arabic{Curr@Page}}%
+    \else
+      % These points go on the same page as the points
+      % currently counted in @pagebonuspoints:
+      \add@hlfcntrtohlfcntr{@pagebonuspoints}{latest@bonuspoints}%
+      \set@hlfcntr{latest@bonuspoints}{0}%
+    \fi
+  \fi
+}% pagepoint@commands
 \def\increment@pageof@pagepoints{%
   \addtocounter{pageof@pagepoints}{1}%
   \ifnum \theCurr@Page > \thepageof@pagepoints\relax
     \set@hlfcntr{latest@points}{0}%
     % \page@withpoints will be used to find the last
     % page that has points, which will be written to
-    % the .aux file vis \AtEndDocument:
+    % the .aux file via \AtEndDocument:
     \global\edef\page@withpoints{\thepageof@pagepoints}%
     \let\next@incr@pageof = \relax
   \fi
   \next@incr@pageof
-}
-
+}% increment@pageof@pagepoints
+\def\increment@pageof@pagebonuspoints{%
+  \addtocounter{pageof@pagebonuspoints}{1}%
+  \ifnum \theCurr@Page > \thepageof@pagebonuspoints\relax
+    \immediate\write\@mainaux
+        {\string\gdef\string\bonuspointsonpage@
+         \romannumeral \csname c@pageof@pagebonuspoints\endcsname{0}}%
+    % See if this has changed from the last run of LaTeX:
+    \@ifundefined{bonuspointsonpage@\romannumeral
+                        \csname c@pageof@pagebonuspoints\endcsname}
+      {\global\@pointschangedtrue}%
+      {%
+      % OK; it's defined.  See if it's changed:
+      \begingroup
+        \set@hlfcntr{tmp@hlfcntr}{\csname bonuspointsonpage@\romannumeral 
+                  \csname c@pageof@pagebonuspoints\endcsname\endcsname}%
+        \edef\othpt@check{\prtaux@hlfcntr{tmp@hlfcntr}}%
+        \def\pt@check{0}%
+        \ifx \pt@check \othpt@check
+          % Do nothing
+        \else
+          \global\@pointschangedtrue
+        \fi
+      \endgroup
+      }%
+    \let\next@incr@pageof = \increment@pageof@pagebonuspoints
+  \else
+    \copy@hlfcntr{@pagebonuspoints}{latest@bonuspoints}%
+    \set@hlfcntr{latest@bonuspoints}{0}%
+    % \page@withbonuspoints will be used to find the last
+    % page that has bonus points, which will be written to
+    % the .aux file via \AtEndDocument:
+    \global\edef\page@withbonuspoints{\thepageof@pagebonuspoints}%
+    \let\next@incr@pageof = \relax
+  \fi
+  \next@incr@pageof
+}% increment@pageof@pagebonuspoints
 
 
 
 
 \def\@checkqueslevel#1{%
   \begingroup
-    \def\@temp{#1}%
-    \ifx\@temp\@queslevel
+    \def\exam@temp{#1}%
+    \ifx\exam@temp\@queslevel
       % Everything's fine; do nothing.
     \else
       \ClassError{exam}{%
   \def\@points{#1}%
   \global \@placepointstrue
   \if@addpoints
-    \addto@hlfcntr{numpoints}{\@points}%
-    \addto@hlfcntr{pointsof@thisquestion}{\@points}%
-    % latest@points is a holding area for points to be
-    % added to @pagepoints after we check whether they're
-    % on the same page as the points currently counted
-    % by @pagepoints:
-    \addto@hlfcntr{latest@points}{\@points}%
+    \if@bonus
+      \addto@hlfcntr{numbonuspoints}{\@points}%
+      \addto@hlfcntr{bonuspointsof@thisquestion}{\@points}%
+      % latest@bonuspoints is a holding area for bonus points to be
+      % added to @pagepoints after we check whether they're
+      % on the same page as the points currently counted
+      % by @pagepoints:
+      \addto@hlfcntr{latest@bonuspoints}{\@points}%
+    \else
+      \addto@hlfcntr{numpoints}{\@points}%
+      \addto@hlfcntr{pointsof@thisquestion}{\@points}%
+      % latest@points is a holding area for points to be
+      % added to @pagepoints after we check whether they're
+      % on the same page as the points currently counted
+      % by @pagepoints:
+      \addto@hlfcntr{latest@points}{\@points}%
+    \fi
   \fi
   \item@points@pageinfo
 }
 
 
 
-% do@int@lbl is the command used to create a label for every question,
-% part, subpart, and subsubpart.  We make no use of these labels, but we
-% put them there so that if a question (or part, etc.) is, e.g., moved
-% from one page to another, LaTeX will notice this and warn the user
-% that LaTeX must be run one more time to be sure everything is correct.
-%
-% We need to do this even though we've already included code to check
-% when point totals change because questions (and parts, etc.) know what
-% page they're on from reading the info written to the .aux file on the
-% previous run.  Thus, if a question (or part, etc.) is moved to a
-% different page, then the pointsonpage totals won't notice until the
-% *second* subsequent run of LaTeX, and so there'll be no warning to the
-% user on the *first* run.  Including these labels gives the user a
-% warning on that first run.
-
-\newcounter{lbl@cntr}
-\def\do@int@lbl{%
-  \addtocounter{lbl@cntr}{1}%
-  % Looking at latex.ltx reveals that the argument to \label is
-  % expanded, so we don't really need the \expandafter's in the
-  % following, but I haven't seen any statement anywhere in
-  % documentation about it being expanded, so we'll be paranoid and
-  % protect against future changes in latex.ltx by using
-  % \expandafter's: 
-  \expandafter\label\expandafter{\thelbl@cntr @@exam@lbl}%
-}
-
 
 % Bug fix, 5 April 2004: \item@points@pageinfo
 % Appending \point@toks and \pageinfo@commands to \everypar:
 % Since we've put the command \global \point@toks={} inside of 
 % \point@toks and the command \pageinfo@commands={} inside of
 % \pageinfo@commands, when the contents of \point@toks and of
-% \pageinfo@commands are executed when we enter horizontal mode and
-% \everypar is dumped in, the contents of \point@toks and
+% \pageinfo@commands are executed (when we enter horizontal mode and
+% \everypar is dumped in), the contents of \point@toks and
 % \pageinfo@commands will be made empty, and so if
-% the second paragraph also get \the\point@toks and
+% the second paragraph also gets \the\point@toks and
 % \the\pageinfo@commands, it won't matter.
 
 \def\item@points@pageinfo{%
   \item
-  % If @qformat is true, and if we're currently doing a question
-  % (rather than a part, subpart, or subsubpart), then we don't want
-  % to set the points (if any), since the points of a question will
-  % appear only if the user chooses to cause that by putting a
-  % \thepoints in the argument of the \qformat command.
-  % 
   % Also: We need to do this here, *after* the \item command, rather
   % than inside the macro \@readpoints, because the \item command
   % puts the result of the \qformat command into an \hbox (with the
   % user put a \thepoints command inside that argument it will
   % correctly expand to the number of points.  (When @placepoints is
   % false, \thepoints expands to nothing at all).
-  \if@qformat
-    \ifx\ques@ref\@queslevel
-      \global \@placepointsfalse
-    \fi
-  \fi
+  %
+  % We also want to define \padded@point@block when @placepoints is
+  % true even if qformat and bonusqformat are true just in case the
+  % user, for some deranged reason, says \droppoints immediately
+  % following a \question.
+  %
   \if@placepoints
-    % \setup@point@toks defines \point@block to be a macro that prints
-    % the points with the correct choice of parentheses, brackets, or
-    % box, and \@pointname or \@marginpointname and puts commands into
-    % \point@toks to place \point@block at the correct spot.  It
-    % doesn't append anything to \everypar (we do that in this macro,
-    % below). 
+    % Since we want the user to be able to say \thepoints in the
+    % argument to a \pointformat command, we need \@placepointstrue
+    % when \point@block is expanded so that \thepoints will actually
+    % print something.  (After setting up \point@toks, we do
+    % \@placepointsfalse, but \point@block isn't actually expanded
+    % until we enter horizontal mode.)  Thus, we define
+    % \padded@point@block, and use that instead of \point@block.  We
+    % put  \begingroup and \endgroup around this to confine the
+    % effect of \@placepointstrue and also to confine the effect of
+    % any declarations like, e.g., \bfseries that the user might put
+    % in the argument of a \pointformat command.
+    %
+    % Note: We first tried using an \edef to expand \point@block right
+    % here, while @placepoints is true, but that causes problems if
+    % the user puts a \boldmath declaration in the argument of a
+    % \pointformat command.  Apparently, expanding \boldmath (without 
+    % executing anything) gives you bunches of undefined control
+    % sequence errors.
+    \if@bonus
+      \def\padded@point@block{%
+        \begingroup
+          \@placepointstrue
+          \bonuspoint@block
+        \endgroup
+      }%
+    \else
+      \def\padded@point@block{%
+        \begingroup
+          \@placepointstrue
+          \point@block
+        \endgroup
+      }%
+    \fi
+    % \setup@point@toks puts commands into \point@toks to place
+    % \padded@point@block at the correct spot.  It doesn't append
+    % anything to \everypar (we do that in this macro, below). 
+    %
+    % If @qformat is true, and if we're currently doing a question (or
+    % if @bonusqformat is true and we're doing a bonusquestion)
+    % (rather than a part, subpart, or subsubpart), then we don't want
+    % to set the points (if any), since the points of a question will
+    % appear only if the user chooses to cause that by putting a
+    % \thepoints in the argument of the \qformat command.
     \if@pointsdropped
       % Do nothing!
     \else
-      \setup@point@toks
+      \if@bonus
+        \if@bonusqformat
+          \ifx\ques@ref\@queslevel
+            % Do nothing
+          \else
+            \setup@point@toks
+          \fi
+        \else
+          \setup@point@toks
+        \fi
+      \else
+        \if@qformat
+          \ifx\ques@ref\@queslevel
+            % Do nothing
+          \else
+            \setup@point@toks
+          \fi
+        \else
+          \setup@point@toks
+        \fi
+      \fi
     \fi
     \global \@placepointsfalse
   \fi
-  % We *don't* use \append@toklist; see Bug fix note above
+  % We *don't* use \append@toklist; see the Bug fixnote above
+  % (Bug fix, 5 April 2004).
   % We can append the tokens ``\the\point@toks'' whether or not we're
   % setting any points because if we're not setting them, \point@toks
   % will be empty.
   % Also: It's important to do this *after* the \item command above,
   % since the \item command discards the previous contents of
   % \everypar.
-  \edef\append@everypar{\noexpand\everypar={\the\everypar
-                        \noexpand\the \noexpand\pageinfo@commands
-                        \noexpand\the \noexpand\point@toks}}%
-  \append@everypar
-  \do@int@lbl
+  % Version 2.218$\beta$, 2007/10/31 changes:
+  % Instead of appending
+  %   \the \pageinfo@commands \the \point@toks
+  % to \everypar, we insert them into the box \@labels.  This corrects
+  % the problem that arose when a question (or part, etc.) begins with
+  % a list environment (including verbatim, flushleft, center,
+  % flushright, and possibly others that are implemented as trivlist
+  % environments).  The \item command in those environments throws
+  % away the previous contents of \everypar, and so the tokens \the
+  % \pageinfo@commands \the \point@toks didn't get inserted where we
+  % expected.  List environments *do* preserve the contents of the box
+  % \@labels, though.
+  \global\setbox\@labels\hbox{\unhbox\@labels
+    \the \pageinfo@commands
+    \the \point@toks}%
+  %   \edef\append@everypar{\noexpand\everypar={\the\everypar
+  %                         \noexpand\the \noexpand\pageinfo@commands
+  %                         \noexpand\the \noexpand\point@toks}}%
+  %   \append@everypar
 }
 
 
 
 
 % Initialize \@points:
-% (Now that I think of it, this seems totally unnecessary,
-% but we're not deleting it because we're chicken.)
+% (The only reason I think this is necessary is in case the user uses
+% a \qformat command, puts \themarginpoints into the format (which is
+% *not* the intended use of \themarginpoints), and then doesn't have
+% any points for the first question.)
 \def\@points{0}
 
 
 \def\setup@point@toks{%
-% We begin by defining \point@block so that it expands to the properly
-% formatted points (including either \pointname or \marginpointname,
-% enclosed in either parentheses, brackets, or a box).  We then set
-% the token list \point@toks equal to the sequence of commands needed
-% to put \point@block at the correct location, followed by the tokens
-% ``\global \point@toks={}''.  The \question, \part, \subpart, or 
-% \subsubpart command then adds the two tokens ``\the\point@toks'' to
-% \everypar.
+% We set the token list \point@toks equal to the sequence of commands
+% needed to put \padded@point@block at the correct location, followed
+% by the tokens ``\global \point@toks={}''.  The \question, \part,
+% \subpart, or \subsubpart command then adds the two tokens
+% ``\the\point@toks'' to \everypar.
 %
 % Note: It is not the *contents* of \point@toks that is added to
 % \everypar; just the two tokens ``\the\point@toks''.  This difference
 % tokens ``\the\point@toks'' will insert an *empty* token list, which
 % will do no harm.
 %
-%
-% \point@block consists of \point@string surrounded by either
-% parentheses, brackets, or an fbox.
-%
-% \point@string is either \@points\@pointname or
-% \@points\@marginpointname.
-  \setup@point@block
+  \if@pointstwosided
+    % Set \csname \q@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@ref
+      \def\q@label{Pg@question@\arabic{question}}
+    \else
+      \ifx\@queslevel\part@ref
+        \def\q@label{Pg@part@\arabic{question}@\arabic{partno}}
+      \else
+        \ifx\@queslevel\subpart@ref
+          \def\q@label{Pg@subpart@\arabic{question}%
+            @\arabic{partno}@\arabic{subpart}}
+        \else
+          \ifx\@queslevel\subsubpart@ref
+            \def\q@label{Pg@subsubpart@\arabic{question}%
+              @\arabic{partno}@\arabic{subpart}@\arabic{subsubpart}}
+          \else
+            \ClassError{exam}{%
+              This can't happen in function \protect\setup@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@label \endcsname\relax
+      % No page info yet; put it into the right margin
+      \@pointsinrightmargintrue
+      \@pointsinleftmarginfalse
+    \else
+      \ifodd \csname \q@label \endcsname\relax
+        \if@pointsinoutsidemargin
+          \@pointsinrightmargintrue
+          \@pointsinleftmarginfalse
+        \else
+          \@pointsinrightmarginfalse
+          \@pointsinleftmargintrue
+        \fi
+      \else
+        \if@pointsinoutsidemargin
+          \@pointsinrightmarginfalse
+          \@pointsinleftmargintrue
+        \else
+          \@pointsinrightmargintrue
+          \@pointsinleftmarginfalse
+        \fi
+      \fi
+    \fi
+  \fi
+  % That ends the \if@pointstwosided.
+  % Now we actually setup \point@toks:
   \if@pointsinleftmargin
-    \def\point@string{\@points\@marginpointname}%
     \point@toks={%
-          \llap{\point@block 
+          \llap{\padded@point@block 
                 \hskip\@totalleftmargin
                 \hskip\marginpointssep
           }%
     }%
   \else
     \if@pointsinrightmargin
-      \def\point@string{\@points\@marginpointname}%
       \point@toks={%
             \rlap{\hskip-\@totalleftmargin
                   \hskip\textwidth
                   \hskip\@rightmargin
                   \hskip-\rightpointsmargin
-                  \llap{\point@block}%
+                  \llap{\padded@point@block}%
             }%
             \global \point@toks={}%
       }%
     \else
       % The points just go after the question number:
-      \def\point@string{\@points\@pointname}%
       \point@toks={%
-            \point@block
+            \padded@point@block
             \enspace
             \global \point@toks={}%
       }%
   \fi
 }% setup@point@toks
 
-
-\def\setup@point@block{%
-  \if@boxedpoints
-    \def\point@block{\fbox{\point@string}}%
-  \else
-    \if@bracketedpoints
-      \def\point@block{[\point@string]}%
-    \else
-      % plain old parentheses:
-      \def\point@block{(\point@string)}%
-    \fi
-  \fi
-}
-
 \def\droppoints{%
-  \def\point@string{\@points\@marginpointname}%
-  \setup@point@block
   \leavevmode\unskip\nobreak\hfill
   \rlap{\hskip\rightmargin  % Defined by the list environment
         \hskip\@rightmargin % Defined by exam.cls
         \hskip-\rightpointsmargin
-        \llap{\point@block}%
+        \llap{\padded@point@block}%
   }% rlap
   \par
 }
 
-% The following is the default definition.
-% It will be redefined if the user givea a \totalformat command.
-\def\setup@total@block{%
-  \def\total@block{%
-    Total for Question \thequestion: \totalpoints\@marginpointname
-  }%
-}
-
-\def\totalformat#1{%
-  \def\setup@total@block{\def\total@block{#1}}%
-}
-% The following is for use in the argument to a \totalformat command:
-\def\totalpoints{\pointsofquestion{\arabic{question}}}
-
 \def\droptotalpoints{%
-  \setup@total@block
   \leavevmode\unskip\nobreak\hfill
   \rlap{\hskip\rightmargin  % Defined by the list environment
         \hskip\@rightmargin % Defined by exam.cls
         \llap{\total@block}%
   }% rlap
   \par
-}
-
-
-% @placepoints is set true when we encounter a question (or part, etc.)
-% that has points.  It is set to false (1) when we set \point@toks equal
-% to the sequence of commands required to put the properly formatted
+}% droptotalpoints
+\def\droptotalbonuspoints{%
+  \leavevmode\unskip\nobreak\hfill
+  \rlap{\hskip\rightmargin  % Defined by the list environment
+        \hskip\@rightmargin % Defined by exam.cls
+        \hskip-\rightpointsmargin
+        \llap{\bonustotal@block}%
+  }% rlap
+  \par
+}% droptotalbonuspoints
+
+% The following is the default definition;
+% it can be changed by a \totalformat command.
+\def\total@block{%
+  Total for Question \thequestion: \totalpoints\@marginpointname
+}% total@block
+\def\bonustotal@block{%
+  Total for Question \thequestion: \totalbonuspoints\@marginbonuspointname
+}% bonustotal@block
+
+\def\totalformat#1{%
+  \gdef\total@block{\begingroup #1\endgroup}%
+}% totalformat
+\def\bonustotalformat#1{%
+  \gdef\bonustotal@block{\begingroup #1\endgroup}%
+}% bonustotalformat
+% The following is for use in the argument to a \totalformat command:
+\def\totalpoints{\pointsofquestion{\arabic{question}}}
+\def\totalbonuspoints{\bonuspointsofquestion{\arabic{question}}}
+
+
+
+% @placepoints is set true when we encounter a question (or part, etc.)
+% that has points.  It is set to false (1) when we set \point@toks equal
+% to the sequence of commands required to put the properly formatted
 % points onto the page (this happens only if @qformat is false or if
 % @qformat is true but we're not doing a question), or (2) by a
-% \question or \part command (since if we're doing a question and
-% @qformat is true, we need to leave @placepoints true so that the
-% \thepoints command can tell if it should expand to points or to
-% nothing, and encountering a \question or \part command tells us that
-% we no longer have to deal with a possible \thepoints, since we won't
-% be expanding a qformat).
+% \question command or entering a parts environment (since if we're
+% doing a question and @qformat is true, we need to leave @placepoints
+% true so that the \thepoints command can tell if it should expand to
+% points or to nothing, and encountering a \question command or parts
+% environment tells us that we no longer have to deal with a possible
+% \thepoints, since we won't be expanding a qformat).
 \newif\if@placepoints
 \@placepointsfalse
 
 \newif\if@pointsdropped
 \newif\if@pointsinleftmargin
 \newif\if@pointsinrightmargin
+\newif\if@pointstwosided
+\newif\if@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@pointsinoutsidemargin is ignored.
+
+% If we have \@pointstwosidedtrue, then both \@pointsinleftmargin and
+% \@pointsinrightmargin will be flipped back and forth, as needed, in
+% \setup@point@toks.
+
 \def\pointsinleftmargin{\global\@pointsinleftmargintrue 
                     \global\@pointsinrightmarginfalse
-                    \global\@pointsdroppedfalse}
+                    \global\@pointsdroppedfalse
+                    \global\@pointstwosidedfalse
+                    \gdef\pt@name{\@marginpointname}%
+                    \gdef\bnspt@name{\@marginbonuspointname}}
 \def\pointsinrightmargin{\global\@pointsinrightmargintrue
                          \global\@pointsinleftmarginfalse
-                         \global\@pointsdroppedfalse}
+                         \global\@pointsdroppedfalse
+                         \global\@pointstwosidedfalse
+                         \gdef\pt@name{\@marginpointname}%
+                         \gdef\bnspt@name{\@marginbonuspointname}}
 \def\nopointsinmargin{\global\@pointsinleftmarginfalse
                       \global\@pointsinrightmarginfalse
-                      \global\@pointsdroppedfalse}
+                      \global\@pointsdroppedfalse
+                      \global\@pointstwosidedfalse
+                      \gdef\pt@name{\@pointname}%
+                      \gdef\bnspt@name{\@bonuspointname}}
 \def\pointsdroppedatright{\global\@pointsdroppedtrue
                           \global\@pointsinleftmarginfalse
-                          \global\@pointsinrightmarginfalse}
-\let\pointsinmargin\pointsinleftmargin
-\let\nopointsinrightmargin\nopointsinmargin
-\let\nopointsinleftmargin\nopointsinmargin
-
+                          \global\@pointsinrightmarginfalse
+                          \global\@pointstwosidedfalse
+                          \gdef\pt@name{\@marginpointname}%
+                          \gdef\bnspt@name{\@marginbonuspointname}}
+\def\pointstwosided{\global\@pointstwosidedtrue
+                    \global\@pointsinoutsidemargintrue
+                    \global\@pointsdroppedfalse
+                    \gdef\pt@name{\@marginpointname}%
+                    \gdef\bnspt@name{\@marginbonuspointname}}
+\def\pointstwosidedreversed{\global\@pointstwosidedtrue
+                            \global\@pointsinoutsidemarginfalse
+                            \global\@pointsdroppedfalse
+                            \gdef\pt@name{\@marginpointname}%
+                            \gdef\bnspt@name{\@marginbonuspointname}}
+\let\pointsinmargin=\pointsinleftmargin
+\let\nopointsinrightmargin=\nopointsinmargin
+\let\nopointsinleftmargin=\nopointsinmargin
 
 \nopointsinmargin
 
 
 % Will the points be displayed inside parentheses (the default), or
-% will they be boxed or bracketed:
-\newif\if@boxedpoints
-\def\boxedpoints{\global\@boxedpointstrue \global\@bracketedpointsfalse}
-\def\noboxedpoints{\global\@boxedpointsfalse \global\@bracketedpointsfalse}
-\@boxedpointsfalse
+% will they be boxed or bracketed, or customized using pointformat:
+\def\boxedpoints{%
+  \gdef\point@block{\fbox{\@points\pt@name}}%
+  \gdef\bonuspoint@block{\fbox{\@points\bnspt@name}}%
+}
+\def\noboxedpoints{%
+  \gdef\point@block{(\@points\pt@name)}%
+  \gdef\bonuspoint@block{(\@points\bnspt@name)}%
+}
+\def\bracketedpoints{%
+  \gdef\point@block{[\@points\pt@name]}%
+  \gdef\bonuspoint@block{[\@points\bnspt@name]}%
+}
+\let\nobracketedpoints=\noboxedpoints
+
+\def\pointformat#1{%
+  % We don't have to worry about the user putting things
+  % like \bfseries, etc. into \point@block, because
+  % \padded@point@block encloses \point@block in a group,
+  % which confines the effects of anything here:
+  \gdef\point@block{#1}%
+}
+
+\def\bonuspointformat#1{%
+  % We don't have to worry about the user putting things
+  % like \bfseries, etc. into \point@block, because
+  % \padded@point@block encloses \point@block in a group,
+  % which confines the effects of anything here:
+  \gdef\bonuspoint@block{#1}%
+}
+
+%Initialize:
+\noboxedpoints
 
-\newif\if@bracketedpoints
-\def\bracketedpoints{\global\@bracketedpointstrue \global\@boxedpointsfalse}
-\def\nobracketedpoints{\global\@bracketedpointsfalse \global\@boxedpointsfalse}
-\@bracketedpointsfalse
 
 
 
 \def\pointname#1{\gdef\@pointname{#1}}
+\def\bonuspointname#1{\gdef\@bonuspointname{#1}}
 % Initialize to leave a space, and then the word `points':
 %%\pointname{ points}
 % The following improvement was contributed by 
 % Note the space before the \points in the following; it's
 % intentional!)
 \pointname{ \points}
-
+\bonuspointname{ \bonuspoints}
 \newcommand\point@sing{point}
 \newcommand\point@plur{points}
 \newcommand\pointpoints[2]{%
   \renewcommand\point@sing{#1}%
   \renewcommand\point@plur{#2}%
 }
+%\newcommand\bonuspoint@sing{bonus point}
+%\newcommand\bonuspoint@plur{bonus points}
+\newcommand\bonuspoint@sing{point (bonus)}
+\newcommand\bonuspoint@plur{points (bonus)}
+\newcommand\bonuspointpoints[2]{%
+  \renewcommand\bonuspoint@sing{#1}%
+  \renewcommand\bonuspoint@plur{#2}%
+}
 
 
 
     \ifthenelse{\equal{\pt@string}{1} \or \equal{\pt@string}{\half}}
           {\point@sing}{\point@plur}%
   \endgroup
-}
+}% \points
+\newcommand\bonuspoints{%
+  \begingroup
+    \let\half=\relax
+    \edef\pt@string{\@points}%
+    \ifthenelse{\equal{\pt@string}{1} \or \equal{\pt@string}{\half}}
+          {\bonuspoint@sing}{\bonuspoint@plur}%
+  \endgroup
+}% \bonuspoints
 %\newcommand\points{%
 %  \begingroup
 %    \let\half=\relax
 % except that it expands to either ``mark'' or ``marks'', but that
 % conflicts with some package or other.  Thus, we'll implement
 % \marksnotpoints using the \pointpoints command instead:
-\newcommand\marksnotpoints{\pointpoints{mark}{marks}}
+\newcommand\marksnotpoints{%
+  \pointpoints{mark}{marks}%
+  \bonuspointpoints{mark (bonus)}{marks (bonus)}%
+}% \marksnotpoints
 
 
 % \@marginpointname is used in place of \@pointname if any of
 % true:
 \def\marginpointname#1{\gdef\@marginpointname{#1}}
 \marginpointname{}
+\def\marginbonuspointname#1{\gdef\@marginbonuspointname{#1}}
+\marginbonuspointname{ (bonus)}
 
 
-% The following keeps track of whether the user has requested that we
-% add up the points on the exam.  We make the default false so that
-% users who put other than numbers into the points argument of a
-% question (or part, or subpart) won't get error messages.
-% We use \if@printtotalpoints as a flag to signal that we are counting
-% points, so that we will know to print the total on the screen (and
-% in the log file).  We use this separate flag so that the user can
-% use both \addpoints and \noaddpoints to count some points and not
-% others, but still have the total printed when we finish the file no
-% matter what the state of \if@addpoints.
-\newif\if@addpoints
-\newif\if@printtotalpoints
-\def\addpoints{\global\@addpointstrue\global\@printtotalpointstrue}
-\def\noaddpoints{\global\@addpointsfalse}
-\@addpointsfalse
-\@printtotalpointsfalse
-
 %--------------------------------------------------------------------
-%                   choices (for multiple choice)
+%         choices (for multiple choice) and checkboxes
 
 
 \renewcommand\thechoice{\Alph{choice}}
 \newcommand\choicelabel{\thechoice.}
 
+% We will have \@correctchoicetrue when we're printing solutions
+% and we're printing the correct choice of a choices or
+% oneparchoices environment.
+% We'll say \begingroup before saying \@correctchoicetrue
+% and we'll say \endgroup at either the next \choice or \correctchoice
+% or the end of the choices or oneparchoices environment.
+% Thus, we'll never again need to say \@correctchoicefalse
+\newif\if@correctchoice
+\@correctchoicefalse
+
+\newcommand\CorrectChoiceEmphasis[1]{%
+  \def\CorrectChoice@Emphasis{#1}%
+}
+\CorrectChoiceEmphasis{\bfseries}
+\let\correctchoiceemphasis\CorrectChoiceEmphasis
+
+% Note: \do@choice@pageinfo is used in both the choices and
+% the checkboxes environments.
+\newtoks\choice@toks
+\def\do@choice@pageinfo{%
+  \choice@toks={%
+    \questionobject@pluspagecheck
+    \choice@toks={}%
+  }%
+  % Version 2.217-beta changes:
+  % Instead of appending stuff to \everypar, we insert
+  % \the \pageinfo@commands and \the \point@toks 
+  % into the box \@labels:
+  \global\setbox\@labels\hbox{\unhbox\@labels
+    \the \choice@toks}%
+  %   \edef\append@everypar{\noexpand\everypar={\the\everypar
+  %                         \noexpand\the \noexpand\choice@toks}}%
+  %   \append@everypar
+}% do@choice@pageinfo
+
+
+
 % Added 22 April 2004: Increased the \leftmargin by 2.5em,
 % so the choices will be visibly indented.
 \newenvironment{choices}%
   {\list{\choicelabel}%
      {\usecounter{choice}\def\makelabel##1{\hss\llap{##1}}%
        \settowidth{\leftmargin}{W.\hskip\labelsep\hskip 2.5em}%
-       \let\choice=\item
+       \def\choice{%
+         \if@correctchoice
+           \color@endgroup
+           \endgroup
+         \fi
+         \item
+         \do@choice@pageinfo
+       } % choice
+       \def\CorrectChoice{%
+         \if@correctchoice
+           \color@endgroup
+           \endgroup
+         \fi
+         \ifprintanswers
+           % We can't say \choice here, because that would
+           % insert an \endgroup:
+           % 2016/05/10: We say \color@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@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@x}
+           % we will now leave hmode (if we're in hmode):
+           \ifhmode \unskip\unskip\unvbox\voidb@x \fi
+           \begingroup \color@begingroup \@correctchoicetrue
+           \CorrectChoice@Emphasis
+         \fi
+         \item
+         \do@choice@pageinfo
+       } % CorrectChoice
+       \let\correctchoice\CorrectChoice
        \labelwidth\leftmargin\advance\labelwidth-\labelsep
        \topsep=0pt
        \partopsep=0pt
+       \choiceshook
      }%
   }%
-  {\endlist}
+  {\if@correctchoice \color@endgroup \endgroup \fi \endlist}
 
 \newenvironment{oneparchoices}%
   {%
     \setcounter{choice}{0}%
     \def\choice{%
-                 \refstepcounter{choice}%
-                 \ifnum\value{choice}>1
-                   \penalty -50\hskip 1em plus 1em\relax
-                 \fi
-                 \choicelabel\nobreak\enskip
-               }%
+      \if@correctchoice \endgroup \fi
+      \refstepcounter{choice}%
+      \ifnum\value{choice}>1\relax
+        \penalty -50\hskip 1em plus 1em\relax
+      \fi
+      \choicelabel
+      % No need to put the following into a token string; we just put
+      % the choicelabel onto the page, so we're at the spot whose page
+      % number we want to record:
+      \questionobject@pluspagecheck
+      \nobreak\enskip
+    }% choice
+    \def\CorrectChoice{%
+      \if@correctchoice \endgroup \fi
+      \refstepcounter{choice}%
+      \ifprintanswers
+        \begingroup \@correctchoicetrue 
+        \CorrectChoice@Emphasis
+      \fi
+      \ifnum\value{choice}>1\relax
+        \penalty -50\hskip 1em plus 1em\relax
+      \fi
+      \choicelabel
+      % No need to put the following into a token string; we just put
+      % the choicelabel onto the page, so we're at the spot whose page
+      % number we want to record:
+      \questionobject@pluspagecheck
+      \nobreak\enskip
+    }% CorrectChoice
+    \let\correctchoice\CorrectChoice
+    \let\par\@empty
+    % If we're continuing the paragraph containing the question,
+    % then leave a bit of space before the first choice:
+    \ifvmode\else\enskip\fi
+    \ignorespaces
+  }%
+  {\if@correctchoice \endgroup \fi}
+
+\newcommand{\checkboxchar}[1]{\def\checkbox@char{#1}}
+\newcommand{\checkedchar}[1]{\def\checked@char{#1}}
+\checkboxchar{$\bigcirc$}
+\checkedchar{$\surd$}
+
+\newenvironment{checkboxes}%
+  {\list{\checkbox@char}%
+     {%
+       \settowidth{\leftmargin}{W.\hskip\labelsep\hskip 2.5em}%
+       \def\choice{%
+         \if@correctchoice
+           \color@endgroup \endgroup
+         \fi
+         \item
+         \do@choice@pageinfo
+       } % choice
+       \def\CorrectChoice{%
+         \if@correctchoice
+           \color@endgroup \endgroup
+         \fi
+         \ifprintanswers
+           % We can't say \choice here, because that would
+           % insert an \endgroup.
+           % 2016/05/10: We say \color@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@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@x}
+           % we will now leave hmode (if we're in hmode):
+           \ifhmode \unskip\unskip\unvbox\voidb@x \fi
+           \begingroup \color@begingroup \@correctchoicetrue
+           \CorrectChoice@Emphasis
+           \item[\checked@char]
+         \else
+           \item
+         \fi
+         \do@choice@pageinfo
+       } % CorrectChoice
+       \let\correctchoice\CorrectChoice
+       \labelwidth\leftmargin\advance\labelwidth-\labelsep
+       \topsep=0pt
+       \partopsep=0pt
+       \checkboxeshook
+     }%
+  }%
+  {\if@correctchoice \color@endgroup \endgroup \fi \endlist}
+
+\newenvironment{oneparcheckboxes}%
+  {%
+    % Although we're not printing numbers for the choices, we use the
+    % choice counter to keep track of whether a choice is the first
+    % one (in which case we don't leave any additional space) or a
+    % later one (in which case we do leave additional space):    
+    \setcounter{choice}{0}%
+    \def\choice{%
+      \if@correctchoice \endgroup \fi
+      \stepcounter{choice}%
+      \ifnum\value{choice}>1\relax
+        \penalty -50\hskip 1em plus 1em\relax
+      \fi
+      \checkbox@char
+      % No need to put the following into a token string; we just put
+      % \checkbox@char onto the page, so we're at the spot whose page
+      % number we want to record:
+      \questionobject@pluspagecheck
+      \nobreak\enskip
+    }% choice
+    \def\CorrectChoice{%
+      \if@correctchoice \endgroup \fi
+      \stepcounter{choice}%
+      \ifprintanswers
+        \begingroup \@correctchoicetrue 
+        \CorrectChoice@Emphasis
+      \fi
+      \ifnum\value{choice}>1\relax
+        \penalty -50\hskip 1em plus 1em\relax
+      \fi
+      \ifprintanswers
+        \checked@char
+      \else
+        \checkbox@char
+      \fi
+      % No need to put the following into a token string; we just put
+      % the choicelabel onto the page, so we're at the spot whose page
+      % number we want to record:
+      \questionobject@pluspagecheck
+      \nobreak\enskip
+    }% CorrectChoice
+    \let\correctchoice\CorrectChoice
     \let\par\@empty
     % If we're continuing the paragraph containing the question,
     % then leave a bit of space before the first choice:
     \ifvmode\else\enskip\fi
     \ignorespaces
   }%
-  {}
+  {\if@correctchoice \endgroup \fi}
 
 
 %--------------------------------------------------------------------
 %             Answer Lines (for short answer questions)
 
-% Note: \ques@ref is also used in \item@points@pageinfo
+% Note: \ques@ref is also used in \item@points@pageinfo, and all four
+% of the following are used in \setup@point@toks
 
 \def\ques@ref{question}
 \def\part@ref{part}
 \def\subpart@ref{subpart}
 \def\subsubpart@ref{subsubpart}
 
+% Note: \answerclearance is also used by \fillin
+
 \newlength\answerlinelength
 \newlength\answerskip
+\newlength\answerclearance
 \setlength\answerlinelength{1in}
 \setlength\answerskip{2ex}
+\setlength\answerclearance{0.2ex}
 
-\def\answerline{%
+\newcommand\answerline[1][{}]{%
+  % One optional argument, the default value of which is empty.
   \ifx\@queslevel\ques@ref
     \let\ans@l=\questionlabel
   \else
     \fi
   \fi
   \par \nobreak \vskip \answerskip
-  \hfill \ans@l~\hbox to \answerlinelength{\hrulefill}%
+  \hfill 
+  \ifprintanswers
+    \ans@l~\hbox to 0pt{\hbox to \answerlinelength{\hrulefill}\hss}%
+    \raise \answerclearance\hbox to \answerlinelength{%
+      % 2016/05/10: Added \color@begingroup and \color@endgroup:
+      \color@begingroup
+      \CorrectChoice@Emphasis \hfil #1\hss
+      \color@endgroup}%
+  \else
+    \ans@l~\hbox to \answerlinelength{\hrulefill}%
+  \fi
   \par
+}% answerline
+
+%--------------------------------------------------------------------
+%               \fillin, for fill-in-the-blank questions
+
+\newlength\fillinlinelength
+\setlength\fillinlinelength{1in}
+
+% \fillin can take two optional arguments.
+%
+% The first optional argument is the answer to be printed above the line
+% when \printanswers is in effect; the default value is empty.  That
+% line is printed a distance of \answerclearance below the baseline.
+%
+% The second optional argument is the length of the line that we print;
+% the default value is \fillinlinelength.  The value of
+% \fillinlinelength is set with the command
+%
+%   \setlength\fillinlinelength{1in}
+%
+% and can be changed by giving a new \setlength command.
+%
+% When answers are being printed, the optional argument is printed
+% subject to the declarations in the argument of the last
+% \CorrectChoiceEmphasis command.  It is centered on the line unless it
+% is too long, in which case it extends to the right of the line.
+%
+% \fillin eats (and ignores) space characters appearing before the
+% first optional argument.  It also eats (and ignores) space
+% characters appearing after the first optional argument and before
+% the second optional argument.  However, if exactly one optional
+% argument appears, and if there are one or more space characters
+% following that one optional argument, then those spaces are replaced
+% by a single space character, but not eaten.
+
+\newcommand\fillin[1][{}]{%
+   \def\fillin@ans{#1}%
+   \fillin@relay
+}% fillin
+
+\newcommand\fillin@relay{%
+  % We use \exam@ifnextchar, a variation on \@ifnextchar.
+  % If \exam@ifnextchar encounters one or more space characters
+  % followed by a [, then those spaces are ignored (just as they would
+  % be by \@ifnextchar).  However, if one or more space characters are
+  % followed by a non-space character other than [, then
+  % \exam@ifnextchar inserts a space following the
+  % {\@fillin@relay[\fillinlinelength]} that is the third argument to
+  % \exam@ifnextchar.
+  \exam@ifnextchar[{\@fillin@relay}
+                   {\@fillin@relay[\fillinlinelength]}%
+}% fillin@relay
+
+\def\@fillin@relay[#1]{%
+  % The first argument is in \fillin@ans, the second is #1.
+  \leavevmode
+  \ifprintanswers
+    \rlap{\raise -\answerclearance \hbox to #1{\hrulefill}}%
+    \begingroup
+      \setbox0 \hbox{\color@begingroup
+             \CorrectChoice@Emphasis \fillin@ans \color@endgroup}%
+      \ifdim\wd0 > #1\relax
+        \hbox{\color@begingroup\CorrectChoice@Emphasis \fillin@ans
+              \color@endgroup}%
+      \else
+        \hbox to #1{\color@begingroup\CorrectChoice@Emphasis 
+                    \hfil \fillin@ans \hfil\color@endgroup}%
+      \fi
+    \endgroup
+  \else
+    \raise -\answerclearance \hbox to #1{\hrulefill}%
+  \fi
+}% @fillin@relay
+
+% \exam@ifnextchar is used by \fillin.
+% \exam@ifnextchar is a variation of \@ifnextchar that does not always
+% ignore space tokens.  If \exam@ifnextchar encounters one or more
+% space tokens, it makes note of that (with the command
+% \@tempswatrue).  If the first non-space character encountered
+% matches argument #1, then any spaces that had been encountered are
+% ignored.  However, if one or more spaces are encountered and the
+% first non-space character found does not match argument #1, then
+% \exam@ifnextchar produces argument #3 followed by a space character.
+% (This differs from the behavior of \new@ifnextchar in amsgen.sty,
+% which does lookahead for any character, including a space.)  This
+% code (as well as the idea for it) is due to Dan Luecking and Ulrich
+% Diez.
+\long\def\exam@ifnextchar#1#2#3{%
+  \let\reserved@d=#1%
+  \def\reserved@a{#2}%
+  \def\reserved@b{#3}%
+  % The following says we haven't yet seen any spaces:
+  \@tempswafalse
+  \futurelet\@let@token\exam@ifnch
+}% exam@ifnextchar
+
+\def\exam@ifnch{%
+  \ifx\@let@token\@sptoken
+    % Signal that we've found a space:
+    \@tempswatrue
+    \let\reserved@c\exam@xifnch % this gobbles the space
+  \else
+    \ifx\@let@token\reserved@d
+      \let\reserved@c\reserved@a
+    \else
+      \if@tempswa
+        \def\reserved@c{\expandafter\reserved@b\space}%
+      \else 
+        \let\reserved@c\reserved@b
+      \fi
+    \fi
+  \fi
+  \reserved@c
+}% exam@ifnch
+
+% The following defines \exam@xifnch so that it will eat a space
+% following it and then call \exam@ifnch:
+{% keep redefinition of \: local
+  \def\:{\exam@xifnch}
+  \expandafter\gdef\: {\futurelet\@let@token\exam@ifnch}
 }
 
 %--------------------------------------------------------------------
 %                            \fillwithlines
 
 
-% \fillwithlines takes one argument, which is either a length or \fill,
-% and it fills that much vertical space with horizontal lines that run
-% the length of the current line.  That is, the extend from the current
-% left margin (which depends on whether we're in a quesiton, parts,
-% subpart, or subsubpart) to the right margin.
+% \fillwithlines takes one argument, which is either a length or \fill
+% or \stretch{number}, and it fills that much vertical space with
+% horizontal lines that run the length of the current line.  That is,
+% they extend from the current left margin (which depends on whether
+% we're in a question, part, subpart, or subsubpart) to the right
+% margin.
 %
 % The distance between the lines is \linefillheight, whose default value
 % is set with the command
 % The thickness of the lines is \linefillthickness, whose default value
 % is set with the command
 %
-% \setlength\linefillthickness{.2pt}
+% \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@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@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@colorfillwithlines
+    \color@begingroup
+      \color{FillWithLinesColor}%
+      \do@fillwithlines{#1}%
+    \color@endgroup
+  \else
+    \do@fillwithlines{#1}%
+  \fi
+}% \fillwithlines
+
 \newcommand\linefill{\leavevmode
     \leaders\hrule height \linefillthickness \hfill\kern\z@}
 
-
-\def\fillwithlines#1{%
+% \do@fillwithlines is called only by \fillwithlines
+\def\do@fillwithlines#1{%
   \begingroup
   \ifhmode
     \par
   % no matter where on the page it happens to start:
   \cleaders \copy0 \vskip #1 \hbox{}%
   \endgroup
+}% \do@fillwithlines
+
+%--------------------------------------------------------------------
+%                         \fillwithdottedlines
+
+
+% \fillwithdottedlines is similar to \fillwithlines, except that it
+% fills the space with dotted lines (created by \dotfill) rather than
+% with solid lines.
+
+% \fillwithdottedlines takes one argument, which is either a length or
+% \fill or \stretch{number}, and it fills that much vertical space
+% with dotted lines that run the length of the current line.  That is,
+% they extend from the current left margin (which depends on whether
+% we're in a question, part, subpart, or subsubpart) to the right
+% margin.
+%
+% The distance between the lines is \dottedlinefillheight, whose
+% default value is set with the command
+%
+% \setlength\dottedlinefillheight{.25in}
+%
+% 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@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}
+
+\newif\if@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@colorfillwithdottedlines
+    \color@begingroup
+      \color{FillWithDottedLinesColor}%
+      \do@fillwithdottedlines{#1}%
+    \color@endgroup
+  \else
+    \do@fillwithdottedlines{#1}%
+  \fi
+}% \fillwithdottedlines
+
+% \do@fillwithdottedlines is called only by \fillwithdottedlines
+\def\do@fillwithdottedlines#1{%
+  \begingroup
+  \ifhmode
+    \par
+  \fi
+  \hrule height \z@
+  \nobreak
+  \setbox0=\hbox to \hsize{\hskip \@totalleftmargin
+          \vrule height \dottedlinefillheight depth \z@ width \z@
+          \dotfill}%
+  % We use \cleaders (rather than \leaders) so that a given
+  % vertical space will always produce the same number of lines
+  % no matter where on the page it happens to start:
+  \cleaders \copy0 \vskip #1 \hbox{}%
+  \endgroup
+}% \do@fillwithdottedlines
+
+%--------------------------------------------------------------------
+%                            \fillwithgrid
+
+
+% \fillwithgrid is similar to \fillwithlines, except that it
+% fills the space with a grid.
+
+% \fillwithgrid takes one argument, which is either a length or \fill
+% or \stretch{number}, and it fills that much vertical space with a
+% grid that runs the length of the current line.  That is, it extends
+% from the current left margin (which depends on whether we're in a
+% question, part, subpart, or subsubpart) to the right margin.
+%
+% The default grid size and grid line thickness were set by the
+% commands
+%
+% \setlength{\gridsize}{5mm}
+% \setlength{\gridlinewidth}{0.1pt}
+%
+% You can change either or both of those by giving new \setlength
+% commands.  The period of the grid is \gridsize (both horizontally
+% and vertically).  That is, the horizontal distance from the left
+% edge of one vertical line to the left edge of the next vertical line
+% is \gridsize, as is the vertical distance from the top edge of one
+% horizontal line to the top edge of the next horizontal line.  Thus,
+% each square has outer side length equal to \gridsize+\gridlinewidth.
+
+% By default, the created grids are in black.  However, if you give the
+% commands
+%
+% \usepackage{color}
+% \colorgrids
+%
+% then the grids will be in color, by default a light gray.  That
+% default color was defined by the command
+%
+% \definecolor{GridColor}{gray}{0.8}
+%
+% You can change the color by redefining the color GridColor by giving
+% a new \definecolor command.
+
+\newif\if@colorgrids
+\newcommand\colorgrids{%
+  \@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\colorgrids
+      }{%
+      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{GridColor}{gray}{0.8}
+    \@colorgridstrue
+  }%
+}% \colorgrids
+\newcommand\nocolorgrids{\@colorgridsfalse}
+\nocolorgrids
+
+\newlength\gridsize
+\newlength\gridlinewidth
+\setlength{\gridsize}{5mm}
+\setlength{\gridlinewidth}{0.1pt}
+
+\def\fillwithgrid#1{%
+  \begingroup
+  \ifhmode
+    \par
+  \fi
+  \hrule height \z@
+  \nobreak
+
+  % We first set box0 equal to an \hbox which, when printed, is a
+  % square with width and height equal to \gridsize+\gridlinewidth,
+  % but which has
+  % width equal to \gridsize,
+  % height equal to \gridsize, and
+  % depth equal to 0pt.
+  % When we put multiple copies of it together using \leaders or
+  % \cleaders, the right edge will coincide with the left edge of the
+  % next box and the bottom edge will coincide with the top edge of
+  % the box below it.
+  \setlength{\@tempdima}{\gridsize}
+  \addtolength{\@tempdima}{\gridlinewidth}
+  \setlength{\@tempdimb}{\gridsize}
+  \addtolength{\@tempdimb}{-\gridlinewidth}
+  \setbox0=\hbox{%
+    \rlap{\vrule height \gridsize depth \gridlinewidth width \gridlinewidth}%
+    \rlap{\vrule height \gridsize depth -\@tempdimb width \@tempdima}%
+    \vrule height 0pt depth \gridlinewidth width \@tempdima
+    \llap{\vrule height \gridsize depth \gridlinewidth width \gridlinewidth}%
+  }%
+  \wd0=\gridsize
+  \dp0=0pt
+  % Now we set box1 equal to an \hbox containing a single line of
+  % copies of box0.  We use \leaders (instead of \cleaders) so that
+  % if we use it twice on a page, once with a question and once
+  % with a part, the boxes will line up vertically.  We add a kern of
+  % \gridlinewidth at the right because the rightmost vertical line
+  % appears to the right of where the \leaders command thinks that it
+  % appears.
+  \setbox1=\hbox to \textwidth{%
+    \color@begingroup
+    \if@colorgrids
+      \color{GridColor}%
+    \fi
+    \hskip \@totalleftmargin \leaders\copy0\hfil \kern\gridlinewidth
+    \color@endgroup
+  }%
+  % Finally: We create the grid, using \cleaders: We use \cleaders
+  % (rather than \leaders) so that a given vertical space will always
+  % produce the same number of lines no matter where on the page it
+  % happens to start.  We add a kern of \gridlinewidth because the
+  % bottommost horizontal line appears below where the \cleaders
+  % command thinks that it appears.
+  \cleaders \copy1 \vskip #1 \kern \gridlinewidth \hbox{}%
+  \endgroup
+}% fillwithgrid
+
+%--------------------------------------------------------------------
+%                            \makeemptybox
+
+% \makeemptybox takes one argument, which is a length, and it creates
+% an empty box of width the length of the current line and of height
+% equal to the argument. That is, the box extends from the current
+% left margin (which depends on whether we're in a question, part,
+% subpart, or subsubpart) to the right margin.
+
+% As of version 2.304, the argument of \makeemptybox can be either
+% a length, or \fill, or \stretch{number}.
+
+% \newcommand\makeemptybox[1]{
+%   \par
+%   \begingroup
+%     \setlength{\fboxsep}{0pt}%
+%     \framebox[\linewidth]{%
+%       \vrule height 0pt depth #1 width 0pt
+%     }%
+%   \endgroup
+% }
+
+\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@emptybox, after the \makeemptybox
+% command decides whether it will be in color.
+
+\newcommand\makeemptybox[1]{%
+  \if@colorsolutionboxes
+    \color@begingroup
+      \color{SolutionBoxColor}%
+      \do@emptybox{#1}%
+    \color@endgroup
+  \else
+    \do@emptybox{#1}%
+  \fi
 }
 
+% The command \do@emptybox is called only by \makeemptybox.
+\newcommand\do@emptybox[1]{%
+  \par
+  \hbox to \hsize{\hskip\@totalleftmargin \leaders\hrule\hfill}%
+  \nointerlineskip
+  \begingroup
+    \setbox0=\hbox to \hsize{\hskip\@totalleftmargin
+                           \vrule height\minboxheight \hfill \vrule}%
+    % The vertical size desired may not be an exact multiple of
+    % \minboxheight, and so \cleaders might leave a gap between the
+    % vertical lines and the horizontal lines above and below it.
+    % Thus, we put a single copy of \box0 immediately below the
+    % horizontal line above and we'll also put a single copy of \box0
+    % immediately above the horizontal line below.
+    \copy0
+    \nobreak
+    \vskip -\minboxheight
+    \cleaders \copy0 \vskip #1
+    \vskip -\minboxheight
+    \nointerlineskip
+    \copy0
+  \endgroup
+  \nointerlineskip
+  \hbox to \hsize{\hskip\@totalleftmargin \leaders\hrule\hfill}%
+}
 
 %--------------------------------------------------------------------
-%                      \uplevel and \fullwidth:
+%                      \uplevel and \fullwidth
+%           and the EnvUplevel and EnvFullwidth environments:
 
 % \uplevel is used to print text at the indentation level of the 
 % enclosing environment.  For example, to precede a question with
 % 
 % \fullwidth is similar, but uses the full page of text on the page.
 
+% The EnvUplevel environment is similar to the \uplevel command, but it
+% has the advantage that you can include verbatim material (using, e.g.,
+% the \verb command) in the environment.  (You can't include verbatim
+% material in the argument of an \uplevel command.)
+
+% The EnvFullwidth environment is similar to the \fullwidth command, but
+% it has the advantage that you can include verbatim material (using,
+% e.g., the \verb command) in the environment.  (You can't include
+% verbatim material in the argument of an \fullwidth command.)
+
+
 \long\def\uplevel#1{%
   \par\bigskip
   \vbox{%
+    % We entered internal vertical mode, and so we get \parshape=0.
     % We set \leftskip to provide the correct left margin for whatever
-    % text is in the argument of the \uplevel command:
+    % is in the argument of the \uplevel command:
     \leftskip=\@totalleftmargin
     \advance\leftskip-\leftmargin
-    % We adjust \@totalleftmargin (and linewidth?) in case there's a
+    % We adjust \@totalleftmargin and linewidth in case there's a
     % solution environment inside of the argument to the \uplevel:
     \advance\@totalleftmargin-\leftmargin
     \advance\linewidth\leftmargin
   \nobreak
 }
 
+\newenvironment{EnvUplevel}
+  {\par\bigskip\vbox\bgroup
+    % We set \leftskip to provide the correct left margin for whatever
+    % is inside of the environment:
+    \leftskip=\@totalleftmargin
+    \advance\leftskip-\leftmargin
+    % We adjust \@totalleftmargin (and linewidth?) in case there's a
+    % solution environment inside of the environment:
+    \advance\@totalleftmargin-\leftmargin
+    \advance\linewidth\leftmargin
+  }
+  {\egroup\nobreak}
+
+
 \long\def\fullwidth#1{%
   \par\bigskip
   \vbox{%
+    % We entered internal vertical mode, and so we get \parshape=0.
     \leftskip=0pt \rightskip=0pt
     \advance\linewidth\@totalleftmargin
     \@totalleftmargin=0pt
   \nobreak
 }
 
+\newenvironment{EnvFullwidth}
+  {\par\bigskip\vbox\bgroup
+    % We entered internal vertical mode, and so we get \parshape=0.
+    \leftskip=0pt \rightskip=0pt
+    % We adjust \@totalleftmargin (and linewidth?) in case there's a
+    % solution environment inside of the environment:
+    \advance\linewidth\@totalleftmargin
+    \@totalleftmargin=0pt
+  }
+  {\egroup\nobreak}
+
 
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 \def\cellwidth#1{\@cellwidth=#1}
 \def\gradetablestretch#1{\def\@gtblstretch{#1}}
 
+% \settabletotalpoints allows the user to specify a total
+% number of points to appear in a table that may be different
+% from the sum of the points in the table:
+\newcommand\prt@tablepoints{\prt@hlfcntr{tbl@points}}
+\newcommand\settabletotalpoints[1]{%
+  \def\prt@tablepoints{#1}%
+}% \settabletotalpoints
+
+% \settabletotalbonuspoints is similar to \settabletotalpoints:
+\newcommand\prt@tablebonuspoints{\prt@hlfcntr{tbl@bonuspoints}}
+\newcommand\settabletotalbonuspoints[1]{%
+  \def\prt@tablebonuspoints{#1}%
+}% \settabletotalbonuspoints
+
 % All of the following that begin with `h' are for horizontal tables,
 % and all of them that begin with `v' are for vertical tables:
 \def\hqword#1{\def\@hqword{#1}}
 \def\hpword#1{\def\@hpword{#1}}
 \def\hsword#1{\def\@hsword{#1}}
 \def\htword#1{\def\@htword{#1}}
+\def\hpgword#1{\def\@hpgword{#1}}
+
 \def\vqword#1{\def\@vqword{#1}}
 \def\vpword#1{\def\@vpword{#1}}
 \def\vsword#1{\def\@vsword{#1}}
 \def\vtword#1{\def\@vtword{#1}}
-
 \def\vpgword#1{\def\@vpgword{#1}}
-\def\hpgword#1{\def\@hpgword{#1}}
+
+
+% The following are the versions for bonusgradetable:
+\def\bhqword#1{\def\@bhqword{#1}}
+\def\bhpword#1{\def\@bhpword{#1}}
+\def\bhsword#1{\def\@bhsword{#1}}
+\def\bhtword#1{\def\@bhtword{#1}}
+\def\bhpgword#1{\def\@bhpgword{#1}}
+
+\def\bvqword#1{\def\@bvqword{#1}}
+\def\bvpword#1{\def\@bvpword{#1}}
+\def\bvsword#1{\def\@bvsword{#1}}
+\def\bvtword#1{\def\@bvtword{#1}}
+\def\bvpgword#1{\def\@bvpgword{#1}}
+
+% The following are the versions for combinedgradetable:
+\def\chqword#1{\def\@chqword{#1}}
+\def\chpword#1{\def\@chpword{#1}}
+\def\chbpword#1{\def\@chbpword{#1}}
+\def\chsword#1{\def\@chsword{#1}}
+\def\chtword#1{\def\@chtword{#1}}
+\def\chpgword#1{\def\@chpgword{#1}}
+
+\def\cvqword#1{\def\@cvqword{#1}}
+\def\cvpword#1{\def\@cvpword{#1}}
+\def\cvbpword#1{\def\@cvbpword{#1}}
+\def\cvsword#1{\def\@cvsword{#1}}
+\def\cvtword#1{\def\@cvtword{#1}}
+\def\cvpgword#1{\def\@cvpgword{#1}}
+
+
 
 % Initialize:
 \cellwidth{2em}
 \gradetablestretch{1.5}
+
+\hqword{Question:}
+\hpgword{Page:}
 \hpword{Points:}
 \hsword{Score:}
 \htword{Total}
 \vpword{Points}
 \vsword{Score}
 \vtword{Total:}
-
-% For tables indexed by question number:
 \vqword{Question}
-\hqword{Question:}
-
-% For tables indexed by page number:
 \vpgword{Page}
-\hpgword{Page:}
 
+\bhqword{Question:}
+\bhpgword{Page:}
+\bhpword{Bonus Points:}
+\bhsword{Score:}
+\bhtword{Total}
+\bvqword{Question}
+\bvpgword{Page}
+\bvpword{Bonus Points}
+\bvsword{Score}
+\bvtword{Total:}
+
+\chqword{Question:}
+\chpgword{Page:}
+\chpword{Points:}
+\chbpword{Bonus Points:}
+\chsword{Score:}
+\chtword{Total}
+\cvqword{Question}
+\cvpgword{Page}
+\cvpword{Points}
+\cvbpword{Bonus Points}
+\cvsword{Score}
+\cvtword{Total:}
+
+% 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.  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 only command here accessible to the user is \gradetable.
 % The possibilities are
 
 %   \gradetable[v][questions]
 %   \gradetable[h][questions]
 %   \gradetable[h][pages]
 
-% If one or both optional arguments are omitted, the defaults are `[v]'
-% and `[questions]'.
+%   \bonusgradetable[v][questions]
+%   \bonusgradetable[v][pages]
+%   \bonusgradetable[h][questions]
+%   \bonusgradetable[h][pages]
+
+%   \combinedgradetable[v][questions]
+%   \combinedgradetable[v][pages]
+%   \combinedgradetable[h][questions]
+%   \combinedgradetable[h][pages]
+
+%   \pointtable[v][questions]
+%   \pointtable[v][pages]
+%   \pointtable[h][questions]
+%   \pointtable[h][pages]
+
+%   \bonuspointtable[v][questions]
+%   \bonuspointtable[v][pages]
+%   \bonuspointtable[h][questions]
+%   \bonuspointtable[h][pages]
+
+%   \combinedpointtable[v][questions]
+%   \combinedpointtable[v][pages]
+%   \combinedpointtable[h][questions]
+%   \combinedpointtable[h][pages]
+
+%   \partialgradetable{whatever}[v][questions]
+%   \partialgradetable{whatever}[v][pages]
+%   \partialgradetable{whatever}[h][questions]
+%   \partialgradetable{whatever}[h][pages]
+
+%   \partialbonusgradetable{whatever}[v][questions]
+%   \partialbonusgradetable{whatever}[v][pages]
+%   \partialbonusgradetable{whatever}[h][questions]
+%   \partialbonusgradetable{whatever}[h][pages]
+
+%   \partialcombinedgradetable{whatever}[v][questions]
+%   \partialcombinedgradetable{whatever}[v][pages]
+%   \partialcombinedgradetable{whatever}[h][questions]
+%   \partialcombinedgradetable{whatever}[h][pages]
+
+%   \partialpointtable{whatever}[v][questions]
+%   \partialpointtable{whatever}[v][pages]
+%   \partialpointtable{whatever}[h][questions]
+%   \partialpointtable{whatever}[h][pages]
+
+%   \partialbonuspointtable{whatever}[v][questions]
+%   \partialbonuspointtable{whatever}[v][pages]
+%   \partialbonuspointtable{whatever}[h][questions]
+%   \partialbonuspointtable{whatever}[h][pages]
+
+%   \partialcombinedpointtable{whatever}[v][questions]
+%   \partialcombinedpointtable{whatever}[v][pages]
+%   \partialcombinedpointtable{whatever}[h][questions]
+%   \partialcombinedpointtable{whatever}[h][pages]
+
+%   \begingradingrange{whatever}
+%   \endgradingrange{whatever}
+%
+%   \pointsinrange{whatever}
+%   \bonuspointsinrange{whatever}
+%
+%    \firstqinrange{whatever}
+%    \lastqinrange{whatever}
+%    \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]'.
+
+% \@scorestrue means we're doing \gradetable
+% \@scoresfalse mans we're doing \pointtable
+\newif\if@scores
+
+% \@partialtrue means we're doing \partialgradetable,
+% \partialbonusgradetable, \partialcombinedgradetable,
+% \partialpointtable, \partialbonuspointtable, or
+% \partialcombinedpointtable:
+\newif\if@partial
+
+% \@combinedtrue means we're doing \combinedgradetable,
+% \combinedpointtable, \partialcombinedgradetable, or
+% \partialcombinedpointtable:
+\newif\if@combined
+
+% It's OK to use the counter num@cols as a scratch counter
+% in \begingradingrange and \endgradingrange because
+% it's only used in typesetting tables:
+\def\begingradingrange#1{%
+  \setcounter{num@cols}{\value{question}}%
+  \addtocounter{num@cols}{1}%
+  \immediate\write\@mainaux
+  {\string\expandafter\string\gdef
+    \string\csname\space range@#1@firstq\string\endcsname
+      {\arabic{num@cols}}}%
+  \write\@mainaux
+  {\string\expandafter\string\gdef
+    \string\csname\space range@#1@firstp\string\endcsname
+      {\thepage}}%
+}% begingradingrange
+
+\def\endgradingrange#1{%
+  \setcounter{num@cols}{\value{question}}%
+  \immediate\write\@mainaux
+  {\string\expandafter\string\gdef
+    \string\csname\space range@#1@lastq\string\endcsname
+      {\arabic{num@cols}}}%
+  \write\@mainaux
+  {\string\expandafter\string\gdef
+    \string\csname\space range@#1@lastp\string\endcsname
+      {\thepage}}%
+}% endgradingrange
+
+
+% Now that grading tables may be for only part of the exam,
+% we need the counter tbl@points to add up the total points
+% for the questions (or pages) that appear on the table:
+\new@hlfcntr{tbl@points}
+
+% We'll use the counter tbl@bonuspoints to add up the total bonus
+% points for the questions (or pages) that appear on the table:
+\new@hlfcntr{tbl@bonuspoints}
+
+%--------------------------------------------------------------------
+% multirow tables, non-partial:
+
+\def\multirowgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowbonusgradetable#1{%
+  \@scorestrue
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowbonuspointtable#1{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowcombinedgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowcombinedpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+%--------------------------------------------------------------------
+% multirow tables, partial:
+
+\def\multirowpartialgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpartialpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpartialbonusgradetable#1#2{%
+  \@scorestrue
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpartialbonuspointtable#1#2{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpartialcombinedgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+\def\multirowpartialcombinedpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@range{#2}%
+  \setcounter{num@rows}{#1}%
+  \i@gtable[h]%
+}
+
+%--------------------------------------------------------------------
+%  multicolumn tables, non-partial:
+
+\def\multicolumngradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnbonusgradetable#1{%
+  \@scorestrue
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnbonuspointtable#1{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialfalse
+  \@combinedfalse
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumncombinedgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumncombinedpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialfalse
+  \@combinedtrue
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+%--------------------------------------------------------------------
+% multicolumn tables, partial:
+
+\def\multicolumnpartialgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpartialpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpartialbonusgradetable#1#2{%
+  \@scorestrue
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpartialbonuspointtable#1#2{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpartialcombinedgradetable#1#2{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+\def\multicolumnpartialcombinedpointtable#1#2{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@range{#2}%
+  \setcounter{num@cols}{#1}%
+  \i@gtable[v]%
+}
+
+%--------------------------------------------------------------------
+% partial single row (and column) tables:
+
+\def\partialgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialgradetable
+
+\def\partialbonusgradetable#1{%
+  \@scorestrue
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialbonusgradetable
+
+\def\partialcombinedgradetable#1{%
+  \@scorestrue
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialcombinedgradetable
+
+\def\partialpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialpointtable
+
+\def\partialbonuspointtable#1{%
+  \@scoresfalse
+  \@bonustrue
+  \@partialtrue
+  \@combinedfalse
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialbonuspointtable
+
+\def\partialcombinedpointtable#1{%
+  \@scoresfalse
+  \@bonusfalse
+  \@partialtrue
+  \@combinedtrue
+  \def\tbl@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% partialcombinedpointtable
+
+%--------------------------------------------------------------------
+% single row (and column) tables, non-partial:
 
 \def\gradetable{%
+  \@scorestrue
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
   % If the user doesn't include the optional argument
   % choosing between vertical and horizontal,
   % we give them vertical:
   \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
-}
-\def\i@gtable[#1]{%
+}% gradetable
+
+\def\bonusgradetable{%
+  \@scorestrue
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% bonusgradetable
+
+\def\combinedgradetable{%
+  \@scorestrue
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% bonusgradetable
+
+\def\pointtable{%
+  \@scoresfalse
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% pointtable
+
+\def\bonuspointtable{%
+  \@scoresfalse
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
   % If the user doesn't include the optional argument
-  % choosing between questions and pages,
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% bonuspointtable
+
+\def\combinedpointtable{%
+  \@scoresfalse
+  \@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@cols or num@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@cols}{1}%
+  \setcounter{num@rows}{1}%
+  % If the user doesn't include the optional argument
+  % choosing between vertical and horizontal,
+  % we give them vertical:
+  \@ifnextchar[{\i@gtable}{\i@gtable[v]}%
+}% bonuspointtable
+
+%--------------------------------------------------------------------
+
+% \i@gtable and \ii@gtable insert any missing optional arguments
+% (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.
+% \find@p@or@q@range then branches, depending on whether the user
+% selected [questions] or [pages].
+
+\def\i@gtable[#1]{%
+  % If the user doesn't include the second optional argument,
+  % which chooses between questions and pages,
   % we give them questions:
   \@ifnextchar[{\ii@gtable{#1}}{\ii@gtable{#1}[questions]}%
 }
 \def\ii@gtable#1[#2]{%
+  % We get here from \i@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@numpoints is defined).  If both of those are OK,
+  % we go to \find@p@or@q@range to see whether we're doing a table
+  % indexed by questions or by pages.
   \if@addpoints
     \@ifundefined{exam@numpoints}%
       {\ClassWarning{exam}%
         {%
-          You must run LaTeX again to produce the grade table.
-            \MessageBreak
+          You must run LaTeX again to produce the
+            table.\MessageBreak
         }%
         \fbox{Run \LaTeX{} again to produce the table}%
       }%
-      {\do@gtable{#1}{#2}}%
+      {\find@p@or@q@range{#1}{#2}}%
   \else
     \ClassError{exam}{%
       You must give the command \protect\addpoints\MessageBreak
-      \space\space in order to use the command \protect\gradetable
-      \MessageBreak
+      \space\space in order to create a grade table.\MessageBreak
       }{%
       If you don't give the command \protect\addpoints\MessageBreak
       \space\space then we're not keeping track of point values.
       \MessageBreak
       }%
   \fi
-}
+}% ii@gtable
+
 \def\@questionsref{questions}
 \def\@pagesref{pages}
-\def\do@gtable#1#2{%
-  \begingroup % avoid trouble from using \@temp
-    \def\@temp{#2}%
-    \ifx\@temp\@questionsref
-      \@grdtblquestions{#1}%
+\def\find@p@or@q@range#1#2{%
+  % We get here from \ii@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 (in which case we go to \find@qrange) or by pages (in
+  % which case we go to \find@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@temp{#2}%
+    \ifx\exam@temp\@questionsref
+      \tbl@pgsfalse
+      \find@qrange{#1}%
     \else
-      \ifx\@temp\@pagesref
-        \@grdtblpages{#1}%
+      \ifx\exam@temp\@pagesref
+        \tbl@pgstrue
+        \find@prange{#1}%
       \else
         \ClassError{exam}{%
-          The second optional argument to \protect\gradetable \MessageBreak
-          \space \space must be either `questions' or `pages'\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 can be indexed by questions or pages;\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 table: Invalid second optional argument `#1'.}%
+        \fbox{\textbf{Error:} grade or point table: Invalid argument
+              `#2' must be `questions' or `pages'.}%
       \fi
     \fi
   \endgroup
-}
+}% find@p@or@q@range
+
+% \range@undefined can be called from either \find@qrange or
+% \find@prange
+\def\range@undefined{%
+  \fbox{Warning: grading range `\tbl@range ' not defined;
+                               run \LaTeX{} again.}%
+  \ClassWarning{exam}{%
+    Grading range `\tbl@range' not defined.\MessageBreak
+    \space\space Run LaTeX again to produce the table.\MessageBreak
+  }%
+}% range@undefined
+
 
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
-% Grading tables indexed by question numbers:
-
-\def\@grdtblquestions#1{%
-  \if v#1%
-    \@vgrdtblquestions
+% Grade and point tables indexed by question numbers:
+
+% When we get to \find@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@qrange sets \tbl@firstq and \first@pq@index to 1
+% and \tbl@lastq and \last@pq@index to \numquestions.  Otherwise,
+% \find@qrange makes sure the grading range is defined and that its
+% last question isn't before its first question.  \find@qrange then
+% calls \tbl@v@or@h, passing along the argument that is either ``v''
+% or ``h''.
+
+\def\find@qrange#1{%
+  % We get here from \find@p@or@q@range.
+  % We're doing a table indexed by question numbers.
+  \if@partial
+    \@ifundefined{range@\tbl@range @firstq}%
+      {%
+        \range@undefined
+      }%
+      {%
+        \@ifundefined{range@\tbl@range @lastq}%
+          {%
+            \range@undefined
+          }%
+          {%
+            \edef\tbl@firstq{\csname range@\tbl@range @firstq\endcsname}%
+            \edef\tbl@lastq{\csname range@\tbl@range @lastq\endcsname}%
+            \let\first@pq@index=\tbl@firstq
+            \let\last@pq@index=\tbl@lastq
+            % Check that firstq precedes or equals lastq:
+            \ifnum \tbl@firstq > \tbl@lastq\relax
+              \fbox{\textbf{Error:} Grading Range `\tbl@range':
+                      Last question precedes first question.}%
+              \ClassError{exam}{%
+                In grading range `\tbl@range',
+                                the last question\MessageBreak
+                \space\space comes before the first question.\MessageBreak
+                }{%
+                  \string\begingradingrange \space must precede
+                  \string\endgradingrange \space by at
+                             least one question.\MessageBreak
+                }%
+            \else
+              \tbl@v@or@h{#1}%
+            \fi
+          }%
+      }%
   \else
-    \if h#1%
-      \@hgrdtblquestions
+    \def\tbl@firstq{1}%
+    \let\first@pq@index=\tbl@firstq
+    % \numquestions is always defined, even if this is the first
+    % run of LaTeX and \exam@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@lastq{\numquestions}%
+    \let\last@pq@index=\tbl@lastq
+    \tbl@v@or@h{#1}%
+  \fi
+}% find@qrange
+
+\def\@vref{v}
+\def\@href{h}
+\def\tbl@v@or@h#1{%
+  % \first@pq@index=\tbl@firstq or \tbl@firstp and
+  % \last@pq@index=\tbl@lastq or \tbl@lastp have already been set.
+  % The argument should be either `v' or `h', and we branch
+  % accordingly.
+  \def\exam@temp{#1}%
+  \ifx\exam@temp\@vref
+    \check@num@cols@v
+  \else
+    \ifx\exam@temp\@href
+      \check@num@rows@h
     \else
       \ClassError{exam}{%
-        The first optional argument to \protect\gradetable \MessageBreak
-        \space \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 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
       }%
-      \fbox{Error: Grade table: Invalid first optional argument `#1'.}%
+      \fbox{\textbf{Error:} grade or point table: Invalid argument
+            `#1' must be `v' or `h'.}%
     \fi
   \fi
-}
+}% tbl@v@or@h
 
 %--------------------------------------------------------------------
-% Vertical, indexed by question numbers:
+%--------------------------------------------------------------------
+% Grade and point tables indexed by page numbers:
 
-\def\@vgrdtblquestions{%
-  \begingroup
-    % Save the current value of question in @iterator, so that
-    % we cna restore it after doing the table:
-    \setcounter{@iterator}{\arabic{question}}%
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \begin{tabular}{|c|c|c|}
-      \hline
-      {\@vqword}& {\@vpword}& {\@vsword}\\
-      \hline
-      \setcounter{question}{0}\do@vloop
-      {\@vtword}& \numpoints&\hbox to \@cellwidth{\hfill}\\
-      \hline
+
+% The only pages listed are those on which there are a nonzero number
+% of points.  We check pages \tbl@firstp through \tbl@lastp
+% Once we've checked that, e.g., \lastpage@withpoints and
+% \pointsonpage@\romannumeral{\lastpage@withpoints} are defined, we
+% can safely (we think) check \pointsonpage@\romannumeral{n} for all n
+% between \tbl@firstp and \tbl@lastp without generating errors.
+%
+% Actually: Since we added the notion of half points and half counters
+% (a long time ago), there won't be any errors even if
+% \pointsonpage@\romannumeral{n} isn't defined, since it's tested by the
+% lines:
+%   \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
+%                          \csname c@@iterator\endcsname\endcsname}%
+%   \ifhlfcntr@pos{tmp@hlfcntr}%
+% and if 
+%   \csname pointsonpage@\romannumeral
+%             \csname c@@iterator\endcsname\endcsname
+% isn't defined, tmp@hlfcntr gets the value zero (because of the way
+% that \set@hlfcntr is written).
+
+% \find@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@firstp and \tbl@lastp, and calls
+% \check@secondrun.
+
+\def\find@prange#1{%
+  % We get here from \find@p@or@q@range.
+  % We're doing a table indexed by pages.
+  % The argument is either ``v'' or ``h''.
+  % We first determine the first and last page of the range, storing
+  % those in \first@pq@index=\tbl@firstp and
+  % \last@pq@index=\tbl@lastp. If not a partial table, we set
+  % \first@pq@index=\tbl@firstp to 1 and \last@pq@index=\tbl@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@secondrun, passing it the argument that we
+  % received (i.e., we say \check@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@partial
+    \@ifundefined{range@\tbl@range @firstp}%
+      {%
+        \range@undefined
+      }%
+      {%
+        \@ifundefined{range@\tbl@range @lastp}%
+          {%
+            \range@undefined
+          }%
+          {%
+            \edef\tbl@firstp{\csname range@\tbl@range @firstp\endcsname}%
+            \edef\tbl@lastp{\csname range@\tbl@range @lastp\endcsname}%
+            \let\first@pq@index=\tbl@firstp
+            \let\last@pq@index=\tbl@lastp
+            % Check that firstp precedes or equals lastp:
+            \ifnum \tbl@firstp > \tbl@lastp\relax
+              \fbox{\textbf{Error:} Grading Range `\tbl@range ':
+                      Last page precedes first page.}%
+              \ClassError{exam}{%
+                In grading range `\tbl@range', the last page\MessageBreak
+                \space\space comes before the first page.\MessageBreak
+                }{%
+                  \string\begingradingrange \space must precede
+                  \string\endgradingrange.\MessageBreak
+                }%
+            \else
+              \check@secondrun{#1}%
+            \fi
+          }%
+      }%
+  \else
+    % It's not a partial table:
+    \def\tbl@firstp{1}%
+    \let\first@pq@index=\tbl@firstp
+    % We never get here on the first run of LaTeX, and
+    % \lastpage@withbonuspoints is defined on the second and later runs.
+    \def\tbl@lastp{\lastpage@withpoints}%
+    \let\last@pq@index=\tbl@lastp
+    \if@bonus
+      \def\tbl@lastp{\lastpage@withbonuspoints}%
+      \let\last@pq@index=\tbl@lastp
+    \fi
+    \if@combined
+      \ifnum \lastpage@withbonuspoints > \lastpage@withpoints\relax
+        \def\tbl@lastp{\lastpage@withbonuspoints}%
+        \let\last@pq@index=\tbl@lastp
+      \fi        
+    \fi
+    \check@secondrun{#1}%
+  \fi
+}% find@prange
+
+\def\check@secondrun#1{%
+  % The function \ii@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@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 \tbl@v@or@h{#1}:
+  \@ifundefined{pointsonpage@\romannumeral
+               \csname lastpage@withpoints\endcsname}%
+    {\@ifundefined{bonuspointsonpage@\romannumeral
+               \csname lastpage@withbonuspoints\endcsname}%
+        {\ClassWarning{exam}{%
+           You must run LaTeX again to produce the table.\MessageBreak}%
+           \fbox{Run \LaTeX{} again to produce the table}%
+        }%
+        {\tbl@v@or@h{#1}%
+        }%
+    }%
+    {\tbl@v@or@h{#1}%
+    }%
+}% check@secondrun
+
+%--------------------------------------------------------------------
+% 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@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@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@h and \@computenumrows@v.
+  \setcounter{#1}{0}%
+  \setcounter{@iterator}{\tbl@firstp}%
+  \addtocounter{@iterator}{-1}%
+  \if@bonus
+    \docount@pgswbpts{#1}%
+  \else
+    \if@combined
+      \docount@pgswcpts{#1}%
+    \else
+      \docount@pgswpts{#1}%
+    \fi
+  \fi
+}% count@pgswpts
+
+\def\docount@pgswcpts#1{%
+  % Called by \count@pgswpts
+  % Count the number of pages in range with any kind of point (bonus
+  % or non-bonus):
+  \addtocounter{@iterator}{1}%
+  \set@hlfcntr{tmp@hlfcntr}{\pointsonpage{\the@iterator}}%
+  \ifhlfcntr@pos{tmp@hlfcntr}%
+    \addtocounter{#1}{1}%
+  \else
+    \check@bnsptpage{#1}%
+  \fi
+  \ifnum \the@iterator < \tbl@lastp\relax
+    \def\nextdocount@pgswcpts{\docount@pgswcpts{#1}}%
+  \else
+    \let\nextdocount@pgswcpts=\relax
+  \fi
+  \nextdocount@pgswcpts
+}% docount@pgswcpts
+\def\check@bnsptpage#1{%
+  % We need to hide this inside of a macro because if \ifhlfcntr@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@pos, but it does see the \fi, and so it get confused.
+  \set@hlfcntr{tmp@hlfcntr}{\bonuspointsonpage{\the@iterator}}%
+  \ifhlfcntr@pos{tmp@hlfcntr}%
+    \addtocounter{#1}{1}%
+  \fi
+}% check@bnsptpage
+
+\def\docount@pgswpts#1{%
+  % Called by \count@pgswpts.
+  % Count the number of pages in range with regular points.
+  \addtocounter{@iterator}{1}%
+  \set@hlfcntr{tmp@hlfcntr}{\pointsonpage{\the@iterator}}%
+  \ifhlfcntr@pos{tmp@hlfcntr}%
+    \addtocounter{#1}{1}%
+  \fi
+  \ifnum \the@iterator < \tbl@lastp\relax
+    \def\nextdocount@pgswpts{\docount@pgswpts{#1}}%
+  \else
+    \let\nextdocount@pgswpts=\relax
+  \fi
+  \nextdocount@pgswpts
+}% docount@pgswpts
+
+\def\docount@pgswbpts#1{%
+  % Called by \count@pgswpts
+  % Count the number of pages in range with bonus points.
+  \addtocounter{@iterator}{1}%
+  \set@hlfcntr{tmp@hlfcntr}{\bonuspointsonpage{\the@iterator}}%
+  \ifhlfcntr@pos{tmp@hlfcntr}%
+    \addtocounter{#1}{1}%
+  \fi
+  \ifnum \the@iterator < \tbl@lastp\relax
+    \def\nextdocount@pgswbpts{\docount@pgswbpts{#1}}%
+  \else
+    \let\nextdocount@pgswbpts=\relax
+  \fi
+  \nextdocount@pgswbpts
+}% docount@pgswbpts
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multirow horizontal tables, indexed by question numbers:
+
+\newcounter{pq@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@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@index@bpts}% used to set bonus point values in
+% horizontal tables.  Often used as scratch elsewhere.
+
+\def\hidden@ampersand{&}% Needed because an ampersand can't appear in
+% the replacement text of a conditional.
+
+\newif\iftbl@pgs
+% \tbl@pgstrue means a table indexed by page numbers
+% \tbl@pgsfalse means a table indexed by question numbers
+
+\newcounter{num@cols}
+\newcounter{num@rows}
+\newcounter{current@row}
+\newcounter{cols@done}% Holds the number of columns done in the
+% current row.
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Stuff to unify tables indexed by questions and tables indexed by
+% pages:
+
+% \first@pq@index and \last@pq@index will hold either \tbl@firstq and
+% \tbl@lastq or \tbl@firstp and \tbl@lastp.
+
+\def\increment@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@lastp.
+  \iftbl@pgs
+    \find@nextpagewithpoints{#1}%
+  \else
+    \addtocounter{#1}{1}%
+  \fi
+}% increment@index
+
+\def\nextcolumn@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@cols.
+  % If we're doing a table indexed by page numbers,
+  % we use \find@nextcolumnpage@v to increment the counter #1 to either
+  % the (num@rows)'th page number after #1 that contains the
+  % appropriate kind of points or to a value greater than \tbl@lastp.
+  \iftbl@pgs
+    \find@nextcolumnpage@v{#1}%
+  \else
+    \addtocounter{#1}{\value{num@rows}}%
+  \fi
+}% nextcolumn@index@v
+
+\def\pointsof@index#1{%
+  \iftbl@pgs
+    \pointsonpage{\arabic{#1}}%
+  \else
+    \pointsofquestion{\arabic{#1}}%
+  \fi
+}% pointsof@index
+
+\def\bonuspointsof@index#1{%
+  \iftbl@pgs
+    \bonuspointsonpage{\arabic{#1}}%
+  \else
+    \bonuspointsofquestion{\arabic{#1}}%
+  \fi
+}% bonuspointsof@index
+
+\def\refto@index#1{%
+  \iftbl@pgs
+    \if@combined
+      % Need to hide this inside of a macro:
+      \refto@comb@index{#1}%
+    \else
+      \if@bonus
+        \pageref{firstbonuspoints@onpage@\arabic{#1}}%
+      \else
+        \pageref{firstpoints@onpage@\arabic{#1}}%
+      \fi
+    \fi
+  \else
+    \ref{question@\arabic{#1}}%
+  \fi
+}% refto@index
+
+\def\refto@comb@index#1{%
+  % We're called only by \refto@index.
+  % We can't have the \ifhlfcntr@pos...\fi inside of another
+  % conditional, so we're hiding it in this macro.
+  \set@hlfcntr{tmp@hlfcntr}{\pointsonpage{\arabic{#1}}}%
+  \ifhlfcntr@pos{tmp@hlfcntr}%
+    \pageref{firstpoints@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@pos):
+    \pageref{firstbonuspoints@onpage@\arabic{#1}}%
+  \fi
+}% refto@comb@index
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multirow tables:
+
+
+%--------------------------------------------------------------------
+% Check that the number of rows is OK, and compute the number of
+% columns:
+
+\def\check@num@rows@h{%
+  % We get here from \tbl@v@or@h.
+  % We make sure the number of rows is a positive integer.  If it
+  % is, we go on to \@computenumcols@h
+  \ifnum \value{num@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
+    \@computenumcols@h
+  \fi
+}% check@num@rows@h
+
+\def\@computenumcols@h{%
+  % We get here from \check@num@rows@h.
+  % Compute the number of columns.
+  % First: set num@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@pgs
+    \count@pgswpts{num@cols}%
+    \addtocounter{num@cols}{1}%
+  \else
+    \setcounter{num@cols}{\tbl@lastq}%
+    \addtocounter{num@cols}{-\tbl@firstq}%
+    \addtocounter{num@cols}{2}%
+  \fi
+  % Save the number of slots needed in pq@index (used for scratch), to
+  % check for truncation:
+  \setcounter{pq@index}{\value{num@cols}}%
+  % Divide the number of slots needed by num@rows:
+  \divide \csname c@num@cols\endcsname by
+    \csname c@num@rows\endcsname
+  % Division truncates: See if there was truncation.
+  % Use @iterator as a scratch counter.
+  \setcounter{@iterator}{\value{num@cols}}%
+  \multiply \csname c@@iterator\endcsname by
+    \csname c@num@rows\endcsname
+  \ifnum \value{@iterator} < \value{pq@index}\relax
+    % There was truncation; add a column to num@cols:
+    \addtocounter{num@cols}{1}%
+  \fi
+  \@multirowtable
+}% @computenumcols@h
+
+%--------------------------------------------------------------------
+% Construct the actual table:
+
+\def\@multirowtable{%
+  % We get here from \@computenumcols@h.
+  % All multirow tables!
+  \renewcommand\arraystretch{\@gtblstretch}%
+  \set@hlfcntr{tbl@points}{0}%
+  \set@hlfcntr{tbl@bonuspoints}{0}%
+  \setcounter{pq@index}{\first@pq@index}%
+  \addtocounter{pq@index}{-1}%
+  \setcounter{pq@index@pts}{\value{pq@index}}%
+  \setcounter{pq@index@bpts}{\value{pq@index}}%
+  \setcounter{current@row}{0}%
+  \begin{tabular}{|l|*{\value{num@cols}}{c|}}
+    \hline
+    \if@combined
+      \do@comblines@h
+    \else
+      \do@lines@h
+    \fi
+}% @multirowtable
+
+
+\def\do@lines@h{%
+  % Called only by \@multirowtable.
+  % It's either bonus or regular, but not combined:
+  \addtocounter{current@row}{1}% Set to the number of the current row
+  \iftbl@pgs
+    \if@bonus
+      \@bhpgword
+    \else
+      \@hpgword
+    \fi
+  \else
+    \if@bonus
+      \@bhqword
+    \else
+      \@hqword
+    \fi
+  \fi
+  \setcounter{cols@done}{0}%
+  \do@pq@indexloop@h
+  % When we finish \do@pq@indexloop@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@pq@index, or both:
+  \ifnum \value{cols@done} < \value{num@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@row} = \value{num@rows}\relax
+      % This is the last row; put in the total:
+      \do@htword@h
+    \else
+      % This isn't the last row.  We insert (\value{num@cols} -
+      % \value{cols@done}) ampersands.
+      \setcounter{@iterator}{\value{num@cols}}%
+      \addtocounter{@iterator}{-\value{cols@done}}%
+      \do@emptycols@h
+    \fi
+  \fi
+  \\
+  \hline
+  % Point values go here!
+  \setcounter{cols@done}{0}%
+  \if@bonus
+    \@bhpword
+    \do@bptloop@h
+  \else
+    \@hpword
+    \do@ptloop@h
+  \fi
+  % When we finish \do@ptloop@h or \do@bptloop@h, either
+  % we've finished a complete row of point values, or we've done all
+  % the question (or page) numbers through \last@pq@index, or both:  
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current@row} = \value{num@rows}\relax
+      % This is the last row; put in the total:
+      \if@bonus
+        \do@totalbpts@h
+      \else
+        \do@totalpts@h
+      \fi
+    \else
+      % This isn't the last row.  We insert (\value{num@cols} -
+      % \value{cols@done}) ampersands.
+      \setcounter{@iterator}{\value{num@cols}}%
+      \addtocounter{@iterator}{-\value{cols@done}}%
+      \do@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@scores
+    \\
+    \hline
+    \if@bonus
+      \@bhsword \hidden@ampersand
+    \else
+      \@hsword \hidden@ampersand
+    \fi
+    \setcounter{cols@done}{0}%
+    \do@sloop@h
+  \fi
+  \ifnum \value{current@row} = \value{num@rows}\relax
+    % This is the last line!  End the tabular:
+    \\
+    \hline
     \end{tabular}%
-    % Restore the saved value of question:
-    \setcounter{question}{\arabic{@iterator}}%
-  \endgroup
-}
-\def\do@vloop{%
-  \addtocounter{question}{1}%
-  \thequestion & \pointsofquestion{\arabic{question}}&\\
+  \else
+    % Don't end the tabular:
+    \\
+    \hline\hline
+  \fi
+  % Check if we should repeat:
+  \ifnum \value{current@row} < \value{num@rows}\relax
+    \let\nextdo@lines@h=\do@lines@h
+  \else
+    \let\nextdo@lines@h=\relax
+  \fi
+  \nextdo@lines@h
+}% do@lines@h
+
+\def\do@comblines@h{%
+  % Called only by \@multirowtable.
+  % Combined tables.
+  \addtocounter{current@row}{1}% Set to the number of the current row
+  \iftbl@pgs
+    \@chpgword
+  \else
+    \@chqword
+  \fi
+  \setcounter{cols@done}{0}%
+  \do@pq@indexloop@h
+  % When we finish \do@pq@indexloop@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@pq@index, or both: 
+  \ifnum \value{cols@done} < \value{num@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@row} = \value{num@rows}\relax
+      % This is the last row; put in the total:
+      \do@htword@h
+    \else
+      % This isn't the last row.  We insert (\value{num@cols} -
+      % \value{cols@done}) ampersands.
+      \setcounter{@iterator}{\value{num@cols}}%
+      \addtocounter{@iterator}{-\value{cols@done}}%
+      \do@emptycols@h
+    \fi
+  \fi
+  \\
   \hline
-  \ifnum \arabic{question} < \numquestions\relax
-    \let\next@vloop=\do@vloop
+  % Point values go here!
+  \@chpword
+  \setcounter{cols@done}{0}%
+  \do@ptloop@h
+  % When we finish \do@ptloop@h, either we've finished a complete
+  % row of point values, or we've done all the question (or page)
+  % numbers through \last@pq@index, or both:   
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current@row} = \value{num@rows}\relax
+      % This is the last row; put in the total:
+      \do@totalpts@h
+    \else
+      % This isn't the last row.  We insert (\value{num@cols} -
+      % \value{cols@done}) ampersands.
+      \setcounter{@iterator}{\value{num@cols}}%
+      \addtocounter{@iterator}{-\value{cols@done}}%
+      \do@emptycols@h
+    \fi
+  \fi
+  \\
+  \hline
+  % Bonus point values go here!
+  \@chbpword
+  \setcounter{cols@done}{0}%
+  \do@bptloop@h
+  % When we finish \do@bptloop@h, either
+  % we've finished a complete row of point values, or we've done all
+  % the question (or page) numbers through \last@pq@index, or both:  
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    % We've inserted all the point values, and there's room
+    % remaining on the current line for Total Points:
+    \ifnum \value{current@row} = \value{num@rows}\relax
+      % This is the last row; put in the total:
+      \do@totalbpts@h
+    \else
+      % This isn't the last row.  We insert (\value{num@cols} -
+      % \value{cols@done}) ampersands.
+      \setcounter{@iterator}{\value{num@cols}}%
+      \addtocounter{@iterator}{-\value{cols@done}}%
+      \do@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@scores
+    \\
+    \hline
+    \@chsword \hidden@ampersand
+    \setcounter{cols@done}{0}%
+    \do@sloop@h
+  \fi
+  \ifnum \value{current@row} = \value{num@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@row} < \value{num@rows}\relax
+    \let\nextdo@comblines@h=\do@comblines@h
+  \else
+    \let\nextdo@comblines@h=\relax
+  \fi
+  \nextdo@comblines@h
+}% do@comblines@h
+
+\def\do@pq@indexloop@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert at most one row of pq@index:
+  \increment@index{pq@index}%
+  \ifnum \value{pq@index} > \last@pq@index\relax
+    % Do nothing!
+  \else
+    \hidden@ampersand
+    \refto@index{pq@index}%
+    \addtocounter{cols@done}{1}%
+  \fi
+  \ifnum \value{pq@index} < \last@pq@index\relax
+    \ifnum \value{cols@done} < \value{num@cols}\relax
+      \let\nextdo@pq@indexloop@h=\do@pq@indexloop@h
+    \else
+      \let\nextdo@pq@indexloop@h=\relax
+    \fi
+  \else
+    \let\nextdo@pq@indexloop@h=\relax
+  \fi
+  \nextdo@pq@indexloop@h
+}% do@pq@indexloop@h
+
+\def\do@ptloop@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert at most one row of non-bonus point values:
+  \increment@index{pq@index@pts}%
+  \ifnum \value{pq@index@pts} > \last@pq@index\relax
+    % Do nothing!
+  \else
+    \hidden@ampersand
+    \addtocounter{cols@done}{1}%
+    \pointsof@index{pq@index@pts}%
+    \addto@hlfcntr{tbl@points}{\pointsof@index{pq@index@pts}}%
+  \fi
+  \ifnum \value{pq@index@pts} < \last@pq@index\relax
+    \ifnum \value{cols@done} < \value{num@cols}\relax
+      \let\nextdo@ptloop@h=\do@ptloop@h
+    \else
+      \let\nextdo@ptloop@h=\relax
+    \fi
+  \else
+    \let\nextdo@ptloop@h=\relax
+  \fi
+  \nextdo@ptloop@h
+}% do@ptloop@h
+
+\def\do@bptloop@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert at most one row of bonus point values:
+  \increment@index{pq@index@bpts}%
+  \ifnum \value{pq@index@bpts} > \last@pq@index\relax
+    % Do nothing!
+  \else
+    \hidden@ampersand
+    \addtocounter{cols@done}{1}%
+    \bonuspointsof@index{pq@index@bpts}%
+    \addto@hlfcntr{tbl@bonuspoints}{\bonuspointsof@index{pq@index@bpts}}%
+  \fi
+  \ifnum \value{pq@index@bpts} < \last@pq@index\relax
+    \ifnum \value{cols@done} < \value{num@cols}\relax
+      \let\nextdo@bptloop@h=\do@bptloop@h
+    \else
+      \let\nextdo@bptloop@h=\relax
+    \fi
+  \else
+    \let\nextdo@bptloop@h=\relax
+  \fi
+  \nextdo@bptloop@h
+}% do@bptloop@h
+
+\def\do@htword@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert (\value{num@cols} - \value{cols@done}) ampersands,
+  % and then either \@htword or \@bhtword or \@chtword:
+  \setcounter{@iterator}{\value{num@cols}}%
+  \addtocounter{@iterator}{-\value{cols@done}}%
+  \do@emptycols@h
+  \if@combined
+    \@chtword
+  \else
+    \if@bonus
+      \@bhtword
+    \else
+      \@htword
+    \fi
+  \fi
+}% do@htword@h
+
+\def\do@totalpts@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert (\value{num@cols} - \value{cols@done}) ampersands
+  % and then the total points:
+  \setcounter{@iterator}{\value{num@cols}}%
+  \addtocounter{@iterator}{-\value{cols@done}}%
+  \do@emptycols@h
+  \prt@tablepoints
+}% do@totalpts@h
+
+\def\do@totalbpts@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We insert (\value{num@cols} - \value{cols@done}) ampersands,
+  % and then the total bonus points:
+  \setcounter{@iterator}{\value{num@cols}}%
+  \addtocounter{@iterator}{-\value{cols@done}}%
+  \do@emptycols@h
+  \prt@tablebonuspoints
+}% do@totalbpts@h
+
+\def\do@emptycols@h{%
+  % Called by \do@lines@h, \do@comblines@h, \do@htword@h,
+  % \do@totalpts@h, and \do@totalbpts@h.
+  % We insert \value{@iterator} ampersands:
+  \ifnum \value{@iterator} > 0\relax
+    \hidden@ampersand
+    \addtocounter{@iterator}{-1}%
+    \let\nextdo@emptycols@h=\do@emptycols@h
+  \else
+    \let\nextdo@emptycols@h=\relax
+  \fi
+  \nextdo@emptycols@h
+}% do@emptycols@h
+
+\def\do@sloop@h{%
+  % Called by both \do@lines@h and \do@comblines@h.
+  % We assume that cols@done has been set to zero.
+  % We insert num@cols \hbox to \@cellwidth,
+  % separated by ampersands.
+  \addtocounter{cols@done}{1}%
+  \hbox to \@cellwidth{\hfill}%
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    \hidden@ampersand
+    \let\nextdo@sloop@h=\do@sloop@h
+  \else
+    \let\nextdo@sloop@h=\relax
+  \fi
+  \nextdo@sloop@h
+}% do@sloop@h
+
+
+%--------------------------------------------------------------------
+%--------------------------------------------------------------------
+% Multicolumn tables
+
+
+%--------------------------------------------------------------------
+% Here's an example of a multicolumn grade table indexed by questions.
+
+% 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}
+%   Question%
+%   & Points%
+%   & Score%
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & Question%
+%   & Points%
+%   & Score%
+%   \\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   1%
+%   & 5%
+%   & \hbox to \@cellwidth{\hfill}%
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & 4%
+%   & 20%
+%   & \hbox to \@cellwidth{\hfill}%
+%   \\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   2%
+%   & 10%
+%   & \hbox to \@cellwidth{\hfill}%
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & 5%
+%   & 25%
+%   & \hbox to \@cellwidth{\hfill}%
+%   \\
+%   \cline{1-3} \cline{5-7}
+%   \noalign{\vskip\arrayrulewidth}
+%   3%
+%   & 15%
+%   & \hbox to \@cellwidth{\hfill}%
+%   & \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+%   & Total:%
+%   & 75%
+%   & \hbox to \@cellwidth{\hfill}%
+%   \\
+%   \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@num@cols@v{%
+  % We get here from \tbl@v@or@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@v
+  \ifnum \value{num@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@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@v
+    \fi
+  \fi
+}% check@num@cols@v
+
+\def\@computenumrows@v{%
+  % We get here from \check@num@cols@v.
+  % Compute the number of rows.
+  % First: set num@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@pgs
+    \count@pgswpts{num@rows}%
+    \addtocounter{num@rows}{1}%
+  \else
+    \setcounter{num@rows}{\last@pq@index}%
+    \addtocounter{num@rows}{-\first@pq@index}%
+    \addtocounter{num@rows}{2}%
+  \fi
+  % Save the number of slots needed, using pq@index@bpts as a scratch
+  % counter, to check for truncation on division:
+  \setcounter{pq@index@bpts}{\value{num@rows}}%
+  % Divide the number of slots needed by num@cols:
+  \divide \csname c@num@rows\endcsname by
+    \csname c@num@cols\endcsname
+  % Division truncates: See if there was truncation.
+  % Use the counter @iterator as a scratch counter:
+  \setcounter{@iterator}{\value{num@rows}}%
+  \multiply \csname c@@iterator\endcsname by
+    \csname c@num@cols\endcsname
+  \ifnum \value{@iterator} < \value{pq@index@bpts}\relax
+    % There was truncation; add one to num@rows:
+    \addtocounter{num@rows}{1}%
+  \fi
+  \@multicolumntable
+}% @computenumrows@v
+
+%--------------------------------------------------------------------
+% Construct the actual table:
+
+\def\@multicolumntable{%
+  % We get here from \@computenumrows@v.
+  % Set \cline@stuff@v equal to the line of \cline's:
+  \create@cline@stuff@v
+  \renewcommand\arraystretch{\@gtblstretch}%
+  \set@hlfcntr{tbl@points}{0}%
+  \set@hlfcntr{tbl@bonuspoints}{0}%
+  \if@combined
+    \if@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@cols}}{|c|c|c|c|c}}
+      % We need to make sure that the \cline@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@stuff@v above and
+      % below:
+      \cline@stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn@heads@comb@v
+      \\
+      \cline@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@cols}}{|c|c|c|c}}
+      % We need to make sure that the \cline@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@stuff@v above and
+      % below:
+      \cline@stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn@heads@comb@noscores@v
+      \\
+      \cline@stuff@v
+    \fi
+    % pq@index@pts will hold the question number (or page number) in
+    % the first column of the row. 
+    \setcounter{pq@index@pts}{\first@pq@index}%
+    \iftbl@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@index@pts}{-1}%
+      \find@nextpagewithpoints{pq@index@pts}%
+    \fi
+    \setcounter{current@row}{0}%
+    \do@lines@v
+  \else
+    % It's not combined:
+    \if@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@cols}}{|c|c|c|c}}
+      % We need to make sure that the \cline@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@stuff@v above and
+      % below:
+      \cline@stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn@heads@v
+      \\
+      \cline@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@cols}}{|c|c|c}}
+      % We need to make sure that the \cline@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@stuff@v above and
+      % below:
+      \cline@stuff@v  
+      \setcounter{@iterator}{0}%
+      \docolumn@heads@noscores@v
+      \\
+      \cline@stuff@v
+    \fi
+    % pq@index@pts will hold the question number (or page number) in
+    % the first column of the row. 
+    \setcounter{pq@index@pts}{\first@pq@index}%
+    \iftbl@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@index@pts}{-1}%
+      \find@nextpagewithpoints{pq@index@pts}%
+    \fi
+    \setcounter{current@row}{0}%
+    \do@lines@v
+  \fi
+}% @multicolumntable
+
+%--------------------------------------------------------------------
+% \create@cline@stuff@v
+
+% The function \create@cline@stuff@v defines \cline@stuff@v to be whatever's
+% appropriate given the values of num@cols, \if@bonus, \if@combined, and
+% \if@scores.
+
+% We wimped out of generating \cline@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@ii@whatever is for tables in which a  logical column consists
+% of two columns; it's used for pointtable and bonuspointtable.
+
+\def\clines@ii@i{\cline{1-2}}
+\def\clines@ii@ii{\cline{1-2} \cline{4-5}}
+\def\clines@ii@iii{\cline{1-2} \cline{4-5} \cline{7-8}}
+\def\clines@ii@iv{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}}
+\def\clines@ii@v{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14}}
+\def\clines@ii@vi{\cline{1-2} \cline{4-5} \cline{7-8} \cline{10-11}
+  \cline{13-14} \cline{16-17}}
+\def\clines@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@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@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@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@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@iii@i{\cline{1-3}}
+\def\clines@iii@ii{\cline{1-3} \cline{5-7}}
+\def\clines@iii@iii{\cline{1-3} \cline{5-7} \cline{9-11}}
+\def\clines@iii@iv{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}}
+\def\clines@iii@v{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19}}
+\def\clines@iii@vi{\cline{1-3} \cline{5-7} \cline{9-11} \cline{13-15}
+  \cline{17-19} \cline{21-23}}
+\def\clines@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@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@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@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@iv@whatever is for tables in which a  logical column
+% consists of four columns; it's used for combinedgradetable.
+
+\def\clines@iv@i{\cline{1-4}}
+\def\clines@iv@ii{\cline{1-4} \cline{6-9}}
+\def\clines@iv@iii{\cline{1-4} \cline{6-9} \cline{11-14}}
+\def\clines@iv@iv{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}}
+\def\clines@iv@v{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24}}
+\def\clines@iv@vi{\cline{1-4} \cline{6-9} \cline{11-14} \cline{16-19}
+  \cline{21-24} \cline{26-29}}
+\def\clines@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@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@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@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@correction{\noalign{\vskip\arrayrulewidth}}
+
+\def\create@cline@stuff@v{%
+  % Called by \@multicolumntable.
+  \if@combined
+    \if@scores
+      \edef\cline@stuff@v{\expandafter\noexpand\csname
+        clines@iv@\romannumeral \c@num@cols\endcsname
+        \noexpand\cline@correction}%
+    \else
+      \edef\cline@stuff@v{\expandafter\noexpand\csname
+        clines@iii@\romannumeral \c@num@cols\endcsname
+        \noexpand\cline@correction}%
+    \fi
+  \else
+    \if@scores
+      \edef\cline@stuff@v{\expandafter\noexpand\csname
+        clines@iii@\romannumeral \c@num@cols\endcsname
+        \noexpand\cline@correction}%
+    \else
+      \edef\cline@stuff@v{\expandafter\noexpand\csname
+        clines@ii@\romannumeral \c@num@cols\endcsname
+        \noexpand\cline@correction}%
+    \fi
+  \fi
+}% create@cline@stuff@v
+
+%--------------------------------------------------------------------
+% The various \docolumn@heads@something@v
+
+\def\docolumn@heads@v{%
+  % Called by \@multicolumntable.
+  % multicolumngradetable or multicolumnbonusgradetable, possibly
+  % partial.
+    \iftbl@pgs
+      \if@bonus
+        \@bvpgword
+      \else
+        \@vpgword
+      \fi
+    \else
+      \if@bonus
+        \@bvqword
+      \else
+        \@vqword
+      \fi
+    \fi
+  & \if@bonus
+      \@bvpword
+    \else
+      \@vpword
+    \fi
+  & \if@bonus
+      \@bvsword
+    \else
+      \@vsword
+    \fi
+  \addtocounter{@iterator}{1}%
+  \ifnum \value{@iterator} < \value{num@cols}\relax
+    \hidden@ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden@ampersand
+    \let\nextdocolumn@heads@v=\docolumn@heads@v
+  \else
+    \let\nextdocolumn@heads@v=\relax
+  \fi
+  \nextdocolumn@heads@v
+}% docolumn@heads@v
+
+\def\docolumn@heads@noscores@v{%
+  % Called by \@multicolumntable.
+  % multicolumnpointtable or multicolumnbonuspointtable, possibly
+  % partial.
+    \iftbl@pgs
+      \if@bonus
+        \@bvpgword
+      \else
+        \@vpgword
+      \fi
+    \else
+      \if@bonus
+        \@bvqword
+      \else
+        \@vqword
+      \fi
+    \fi
+    &
+    \if@bonus
+      \@bvpword
+    \else
+      \@vpword
+    \fi
+  \addtocounter{@iterator}{1}%
+  \ifnum \value{@iterator} < \value{num@cols}\relax
+    \hidden@ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden@ampersand
+    \let\nextdocolumn@heads@noscores@v=\docolumn@heads@noscores@v
+  \else
+    \let\nextdocolumn@heads@noscores@v=\relax
+  \fi
+  \nextdocolumn@heads@noscores@v
+}% docolumn@heads@noscores@v
+
+\def\docolumn@heads@comb@v{%
+  % Called by \@multicolumntable.
+  % multicolumncombinedgradetable, possibly partial.
+    \iftbl@pgs
+      \@cvpgword
+    \else
+      \@cvqword
+    \fi
+  & \@cvpword
+  & \@cvbpword
+  &  \@cvsword
+  \addtocounter{@iterator}{1}%
+  \ifnum \value{@iterator} < \value{num@cols}\relax
+    \hidden@ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden@ampersand
+    \let\nextdocolumn@heads@comb@v=\docolumn@heads@comb@v
+  \else
+    \let\nextdocolumn@heads@comb@v=\relax
+  \fi
+  \nextdocolumn@heads@comb@v
+}% docolumn@heads@comb@v
+
+\def\docolumn@heads@comb@noscores@v{%
+  % Called by \@multicolumntable.
+  % multicolumncombinedpointtable, possibly partial.
+    \iftbl@pgs
+      \@cvpgword
+    \else
+      \@cvqword
+    \fi
+  & \@vpword
+  & \@bvpword
+  \addtocounter{@iterator}{1}%
+  \ifnum \value{@iterator} < \value{num@cols}\relax
+    \hidden@ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \hidden@ampersand
+    \let\nextdocolumn@heads@comb@noscores@v=\docolumn@heads@comb@noscores@v
   \else
-    \let\next@vloop=\relax
+    \let\nextdocolumn@heads@comb@noscores@v=\relax
   \fi
-  \next@vloop
-}
+  \nextdocolumn@heads@comb@noscores@v
+}% docolumn@heads@comb@noscores@v
 
 %--------------------------------------------------------------------
-% Horizontal, indexed by question numbers:
-
-\def\@hgrdtblquestions{%
-  \begingroup
-    % Save the current value of question in @iterator, so that
-    % we can restore it after doing the table:
-    \setcounter{@iterator}{\arabic{question}}%
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \begin{tabular}{|l|*{\numquestions}{c|}c|}
-      \hline
-      {\@hqword}& \setcounter{question}{0}\do@qnumloop
-      {\@htword}\\
-      \hline
-      {\@hpword}& \setcounter{question}{0}\do@ptloop
-      \numpoints\\
-      \hline
-      {\@hsword}& \setcounter{question}{0}\do@sloop
-      \\
-      \hline
+% \do@lines@v is used by *all* multicolumn tables.
+% It calls \do@oneline@v for all non-combined tables and
+% \do@oneline@comb@v for all combined tables.
+
+\def\do@lines@v{%
+  % We get here from \@multicolumntable.
+  % ALL MULTICOLUMN TABLES!!!!
+  % pq@index@pts holds the question number or page number in the first
+  % column of the current row.
+  \addtocounter{current@row}{1}%
+  \setcounter{pq@index}{\value{pq@index@pts}}%
+  \setcounter{cols@done}{0}% Number of columns done
+  % We're doing both grade tables and point tables!!
+  \if@combined
+    \do@oneline@comb@v
+  \else
+    \do@oneline@v
+  \fi
+  \increment@index{pq@index@pts}%
+  % We need the "\\ \cline@stuff@v" to *immediately* precede the
+  % \end{tabular} (i.e., with no \ifnum separating them), to avoid
+  % having crap after the \cline@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@stuff@v.
+  \ifnum \value{current@row} = \value{num@rows}\relax
+    \\
+    \cline@stuff@v
     \end{tabular}%
-    % Restore the saved value of question:
-    \setcounter{question}{\arabic{@iterator}}%
-  \endgroup
-}
-\def\do@qnumloop{%
-  \addtocounter{question}{1}%
-  \thequestion &
-  \ifnum \arabic{question} < \numquestions\relax
-    \let\next@qnloop=\do@qnumloop
+    \let\nextdo@lines@v=\relax
   \else
-    \let\next@qnloop=\relax
+    \\
+    \cline@stuff@v
+    \let\nextdo@lines@v=\do@lines@v
   \fi
-  \next@qnloop
-}
-\def\do@ptloop{%
-  \addtocounter{question}{1}%
-  \pointsofquestion{\arabic{question}}&
-  \ifnum \arabic{question} < \numquestions\relax
-    \let\next@ptloop=\do@ptloop
+  \nextdo@lines@v
+}% do@lines@v
+
+\def\do@oneline@v{%
+  % Called by \do@lines@v.
+  % Used for all multicolumn non-combined tables.
+  % pq@index holds the question or page number we're about to do.
+  \ifnum \value{pq@index} > \last@pq@index\relax
+    % See if we're in the last column; use pq@index@bpts as a scratch
+    % counter: 
+    \setcounter{pq@index@bpts}{\value{cols@done}}%
+    \addtocounter{pq@index@bpts}{1}%
+    \ifnum \value{pq@index@bpts} = \value{num@cols}\relax
+      % We're in the last column; are we in the last row?
+      \ifnum \value{current@row} = \value{num@rows}\relax
+        % We're in the last column, last row!
+        % Print the total:
+          \if@bonus
+            \@bvtword
+          \else
+            \@vtword
+          \fi
+          \hidden@ampersand
+        \if@scores
+            \if@bonus
+              \prt@tablebonuspoints
+            \else
+              \prt@tablepoints
+            \fi
+          \hidden@ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \else
+            \hspace*{\fill}%
+            \if@bonus
+              \prt@tablebonuspoints
+            \else
+              \prt@tablepoints
+            \fi
+        \fi
+      \else
+        % Not last column last row; insert empty space:
+          \hbox to \@cellwidth{\hfill}%
+        \if@scores
+          \hidden@ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \fi
+        \hidden@ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+    \else
+      % Not last column; insert empty space:
+      \hbox to \@cellwidth{\hfill}%
+      \if@scores
+        \hidden@ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+      \hidden@ampersand
+        \hbox to \@cellwidth{\hfill}%
+    \fi
   \else
-    \let\next@ptloop=\relax
+    % We need to do question (or page) number pq@index:
+      \refto@index{pq@index}%
+    \hidden@ampersand
+      \if@scores
+        \if@bonus
+          \bonuspointsof@index{pq@index}%
+          \addto@hlfcntr{tbl@bonuspoints}{\bonuspointsof@index{pq@index}}%
+        \else
+          \pointsof@index{pq@index}%
+          \addto@hlfcntr{tbl@points}{\pointsof@index{pq@index}}%
+        \fi
+      \hidden@ampersand
+        \hbox to \@cellwidth{\hfill}%
+    \else
+      \if@bonus
+        \bonuspointsof@index{pq@index}%
+        \addto@hlfcntr{tbl@bonuspoints}{\bonuspointsof@index{pq@index}}%
+      \else
+        \pointsof@index{pq@index}%
+        \addto@hlfcntr{tbl@points}{\pointsof@index{pq@index}}%
+      \fi
+    \fi
   \fi
-  \next@ptloop
-}
-\def\do@sloop{%
-  \addtocounter{question}{1}%
-  \hbox to \@cellwidth{\hfill}&
-  \ifnum \arabic{question} < \numquestions\relax
-    \let\next@sloop=\do@sloop
+  \addtocounter{cols@done}{1}% Number of columns done
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    \hidden@ampersand
+      \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+      \nextcolumn@index@v{pq@index}%
+    \hidden@ampersand
+      \let\nextdo@oneline@v=\do@oneline@v
   \else
-    \let\next@sloop=\relax
+    \let\nextdo@oneline@v=\relax
   \fi
-  \next@sloop
-}
-
-
-%--------------------------------------------------------------------
-%--------------------------------------------------------------------
-% Grading tables indexed by page numbers:
-%
-% The only pages listed are those on which there is a nonzero number
-% of points.  We check pages 1 through \lastpage@withpoints; this way,
-% once we've checked that \lastpage@withpoints and
-% \pointsonpage@\romannumeral{\lastpage@withpoints} are defined, we
-% can safely (we think) check \pointsonpage@\romannumeral{n} for all n
-% between 1 and \lastpage@withpoints without generating errors.
-
-% Check that there's enough info from the .aux file to do a page
-% indexed grade table:
-\def\@grdtblpages#1{%
-  \@ifundefined{lastpage@withpoints}%
-    {\ClassWarning{exam}{%
-      You must run LaTeX twice more\MessageBreak
-      \space\space to produce the grade table.\MessageBreak}%
-     \fbox{Run \LaTeX{} twice more to produce the grade table}%
-    }%
-    {%
-      \@ifundefined{pointsonpage@\romannumeral
-               \csname lastpage@withpoints\endcsname}%
-        {\ClassWarning{exam}{%
-          You must run LaTeX again\MessageBreak
-          \space\space to produce the grade table.\MessageBreak}%
-         \fbox{Run \LaTeX{} again to produce the grade table}%
-        }%
-        {%
-          \@whchtblpgs#1
-        }%
-    }%
-}
-
-\def\@whchtblpgs#1{%
-  \if v#1%
-    \@vgrdtblpages
+  \nextdo@oneline@v
+}% do@oneline@v
+
+\def\do@oneline@comb@v{%
+  % Called by \do@lines@v.
+  % All combined multicolumn tables.
+  % pq@index holds the question (or page) we're about to do.
+  \ifnum \value{pq@index} > \last@pq@index\relax
+    % See if we're in the last column; use pq@index@bpts as a scratch
+    % counter: 
+    \setcounter{pq@index@bpts}{\value{cols@done}}%
+    \addtocounter{pq@index@bpts}{1}%
+    \ifnum \value{pq@index@bpts} = \value{num@cols}\relax
+      % We're in the last column; are we in the last row?
+      \ifnum \value{current@row} = \value{num@rows}\relax
+        % We're in the last column, last row!
+        % Print the total:
+        \@cvtword
+        \hidden@ampersand
+          \prt@tablepoints
+        \hidden@ampersand
+        \if@scores
+            \prt@tablebonuspoints
+          \hidden@ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \else
+          \prt@tablebonuspoints
+        \fi
+      \else
+        % Last column, but not last row; insert empty space:
+          \hbox to \@cellwidth{\hfill}%
+        \hidden@ampersand
+          \hbox to \@cellwidth{\hfill}%
+        \if@scores
+          \hidden@ampersand
+            \hbox to \@cellwidth{\hfill}%
+        \fi
+        \hidden@ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+    \else
+      % Not last column; insert empty space:
+        \hbox to \@cellwidth{\hfill}%
+      \hidden@ampersand
+        \hbox to \@cellwidth{\hfill}%
+      \if@scores
+        \hidden@ampersand
+          \hbox to \@cellwidth{\hfill}%
+      \fi
+      \hidden@ampersand
+        \hbox to \@cellwidth{\hfill}%
+    \fi
   \else
-    \if h#1%
-      \@hgrdtblpages
+    % We need to do question number pq@index:
+      \refto@index{pq@index}%
+    \hidden@ampersand
+      \pointsof@index{pq@index}%
+      \addto@hlfcntr{tbl@points}{\pointsof@index{pq@index}}%
+    \hidden@ampersand
+    \if@scores
+        \bonuspointsof@index{pq@index}%
+        \addto@hlfcntr{tbl@bonuspoints}{\bonuspointsof@index{pq@index}}%
+      \hidden@ampersand
+        \hbox to \@cellwidth{\hfill}%
     \else
-      \ClassError{exam}{%
-        The first optional argument to \protect\gradetable \MessageBreak
-        \space \space must be either `h' or `v'\MessageBreak
-      }{%
-        Grade tables can be either horizontal or vertical;\MessageBreak
-        \space\space no diagonals allowed.\MessageBreak
-      }%
-      \fbox{Error: Grade table: Invalid first optional argument `#1'.}%
+      \bonuspointsof@index{pq@index}%
+      \addto@hlfcntr{tbl@bonuspoints}{\bonuspointsof@index{pq@index}}%
     \fi
   \fi
-}
-
-%--------------------------------------------------------------------
-% Vertical, indexed by pages:
-
-\def\@vgrdtblpages{%
-  \begingroup
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \begin{tabular}{|c|c|c|}
-      \hline
-      {\@vpgword}& {\@vpword}& {\@vsword}\\
-      \hline
-      \setcounter{@iterator}{0}\pg@vloop
-      {\@vtword}& \numpoints&\hbox to \@cellwidth{\hfill}\\
-      \hline
-    \end{tabular}%
-  \endgroup
-}
-\def\pg@vloop{%
-  \addtocounter{@iterator}{1}%
-  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
-                              \csname c@@iterator\endcsname\endcsname}%
-%  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\roman{@iterator}\endcsname}%
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
-  \ifhlfcntr@pos{tmp@hlfcntr}%
-    \pg@vloopline
-  \fi
-  \ifnum \the@iterator < \lastpage@withpoints\relax
-    \let\next@pg@vloop=\pg@vloop
+  \addtocounter{cols@done}{1}% Number of columns done
+  \ifnum \value{cols@done} < \value{num@cols}\relax
+    \hidden@ampersand
+    \hspace*{-\arrayrulewidth}\hspace*{\doublerulesep}%
+    \nextcolumn@index@v{pq@index}%
+    \hidden@ampersand
+    \let\nextdo@oneline@comb@v=\do@oneline@comb@v
   \else
-    \let\next@pg@vloop=\relax
+    \let\nextdo@oneline@comb@v=\relax
   \fi
-  \next@pg@vloop
-}
-\def\pg@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.
-  \the@iterator & \pointsonpage{\the@iterator}&\\
-  \hline
-}
+  \nextdo@oneline@comb@v
+}% do@oneline@comb@v
 
 %--------------------------------------------------------------------
-% Horizontal, indexed by pages:
-
-% For a horizontal table, we need to know how many pages there are
-% with points on them:
-\newcounter{numpgs@withpts}
-\def\count@pgswpts{%
-  \setcounter{numpgs@withpts}{0}%
-  \setcounter{@iterator}{0}%
-  \docount@pgswpts
-}
-\def\docount@pgswpts{%
-  \addtocounter{@iterator}{1}%
-  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
-                              \csname c@@iterator\endcsname\endcsname}%
-%  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\roman{@iterator}\endcsname}%
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
-  \ifhlfcntr@pos{tmp@hlfcntr}%
-    \addtocounter{numpgs@withpts}{1}%
-  \fi
-  \ifnum \the@iterator < \lastpage@withpoints\relax
-    \let\next@docount=\docount@pgswpts
+% \find@nextpagewithpoints and \find@nextcolumnpage@v:
+
+\def\find@nextpagewithpoints#1{%
+  % Called by \dofind@nextcolumnpage@v, \increment@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@lastp. 
+  \addtocounter{#1}{1}%
+  \if@combined
+    \set@hlfcntr{tmp@hlfcntr}{\pointsonpage{\value{#1}}}%
+    \addto@hlfcntr{tmp@hlfcntr}{\bonuspointsonpage{\value{#1}}}%
+    % The sum is positive when at least one of them is positive.
   \else
-    \let\next@docount=\relax
+    \if@bonus
+      \set@hlfcntr{tmp@hlfcntr}{\bonuspointsonpage{\value{#1}}}%
+    \else
+      \set@hlfcntr{tmp@hlfcntr}{\pointsonpage{\value{#1}}}%
+    \fi
   \fi
-  \next@docount
-}
-
-
-\def\@hgrdtblpages{%
-  \begingroup
-    \renewcommand\arraystretch{\@gtblstretch}%
-    \count@pgswpts
-    \begin{tabular}{|l|*{\thenumpgs@withpts}{c|}c|}
-      \hline
-      {\@hpgword}& \setcounter{@iterator}{0}\do@pgnumloop
-      {\@htword}\\
-      \hline
-      {\@hpword}& \setcounter{@iterator}{0}\do@pgptloop
-      \numpoints\\
-      \hline
-      {\@hsword}& \setcounter{@iterator}{0}\do@pgsloop
-      \\
-      \hline
-    \end{tabular}%
-  \endgroup
-}
-
-\def\do@pgnumloop{%
-  \addtocounter{@iterator}{1}%
-  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
-                             \csname c@@iterator\endcsname\endcsname}%
-%  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\roman{@iterator}\endcsname}%
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
   \ifhlfcntr@pos{tmp@hlfcntr}%
-    \pg@line
-  \fi
-  \ifnum \the@iterator < \lastpage@withpoints\relax
-    \let\next@pgnumloop=\do@pgnumloop
+    \let\nextfind@nextpagewithpoints=\relax
   \else
-    \let\next@pgnumloop=\relax
-  \fi
-  \next@pgnumloop
-}
-\def\pg@line{%
-  \the@iterator &
-}
-
-\def\do@pgptloop{%
-  \addtocounter{@iterator}{1}%
-  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
-                              \csname c@@iterator\endcsname\endcsname}%
-%  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\roman{@iterator}\endcsname}%
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
-  \ifhlfcntr@pos{tmp@hlfcntr}%
-    \pgpt@line
+    \ifnum \value{#1} > \tbl@lastp\relax
+      \let\nextfind@nextpagewithpoints=\relax
+    \else
+      \def\nextfind@nextpagewithpoints{\find@nextpagewithpoints{#1}}%
+    \fi
   \fi
-  \ifnum \the@iterator < \lastpage@withpoints\relax
-    \let\next@pgptloop=\do@pgptloop
+  \nextfind@nextpagewithpoints
+}% find@nextpagewithpoints
+
+\def\find@nextcolumnpage@v#1{%
+  % Called by \nextcolumn@index@v.
+  % This is used for all multicolumn tables that are indexed by
+  % pages.
+  % We use \find@nextpagewithpoints to increment #1 to either
+  % the (num@rows)'th page number after #1 that contains the
+  % appropriate kind of points or to a value greater than \tbl@lastp.
+  % We use pq@index@bpts as a scratch counter.
+  \setcounter{pq@index@bpts}{0}%
+  \dofind@nextcolumnpage@v{#1}%
+}% find@nextcolumnpage@v
+\def\dofind@nextcolumnpage@v#1{%
+  % Called only by \find@nextcolumnpage@v.
+  \addtocounter{pq@index@bpts}{1}%
+  \find@nextpagewithpoints{#1}%
+  \ifnum \value{pq@index@bpts} = \value{num@rows}\relax
+    \let\nextdofind@nextcolumnpage@v=\relax
   \else
-    \let\next@pgptloop=\relax
+    % The following test shouldn't be needed, in theory, because the
+    % computation of num@cols should prevent trouble, but we're being
+    % paranoid.
+    \ifnum \value{#1} > \tbl@lastp\relax
+      \let\nextdofind@nextcolumnpage@v=\relax
+    \else
+      % Note: this is a \def, and not a \let, because we need to put
+      % in the argument #1:
+      \def\nextdofind@nextcolumnpage@v{\dofind@nextcolumnpage@v{#1}}%
+    \fi
   \fi
-  \next@pgptloop
-}
-\def\pgpt@line{%
-  \csname pointsonpage@\romannumeral \csname c@@iterator\endcsname\endcsname &
-%  \csname pointsonpage@\roman{@iterator}\endcsname &
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
-}
+  \nextdofind@nextcolumnpage@v
+}% dofind@nextcolumnpage@v
 
-\def\do@pgsloop{%
+%--------------------------------------------------------------------
+% \pointsinrange and \bonuspointsinrange, and then
+% \firstqinrange, \lastqinrange, and \numqinrange.
+
+
+% We say either \@bonusfalse or \@bonustrue, and then we check it only
+% in \do@countloop:
+\def\pointsinrange#1{%
+  \@bonusfalse
+  \def\tbl@range{#1}%
+  \@ifundefined{exam@numpoints}%
+    {\mbox{\normalfont\bfseries ??}}%
+    {\read@range}%
+}% pointsinrange
+
+\def\bonuspointsinrange#1{%
+  \@bonustrue
+  \def\tbl@range{#1}%
+  \@ifundefined{exam@numpoints}%
+    {\mbox{\normalfont\bfseries ??}}%
+    {\read@range}%
+}% bonuspointsinrange
+
+\def\bad@range{%
+  % Called by \read@range, \firstqinrange, \lastqinrange, and
+  % \numqinrange.
+  {\mbox{\normalfont\bfseries ??}}%
+  \ClassWarning{exam}{%
+    Grading range `\tbl@range' not defined.\MessageBreak
+    \space\space Run LaTeX again.\MessageBreak
+  }%
+}% bad@range
+
+\def\read@range{%
+  % Called by \pointsinrange and \bonuspointsinrange.
+  \@ifundefined{range@\tbl@range @firstq}%
+  {%
+    \bad@range
+  }%
+  {%
+    \@ifundefined{range@\tbl@range @lastq}%
+    {%
+      \bad@range
+    }%
+    {%
+      \edef\tbl@firstq{\csname range@\tbl@range @firstq\endcsname}%
+      \edef\tbl@lastq{\csname range@\tbl@range @lastq\endcsname}%
+      % Check that firstq precedes or equals lastq:
+      \ifnum \tbl@firstq > \tbl@lastq\relax
+        \fbox{\textbf{Error:} Grading Range `\tbl@range ':
+          Last question precedes first question.}%
+        \ClassError{exam}{%
+          In grading range `\tbl@range ',
+          the last question\MessageBreak
+          \space\space comes before the first question.\MessageBreak
+        }{%
+          \string\begingradingrange \space must precede
+          \string\endgradingrange \space by at
+          least one question.\MessageBreak
+        }%
+      \else
+        \count@pointsinrange
+      \fi
+    }%
+  }%
+}% read@range
+
+\def\count@pointsinrange{%
+  % Used for both \pointsinrange and \bonuspointsinrange:
+  \set@hlfcntr{tbl@points}{0}%
+  \setcounter{@iterator}{\tbl@firstq}%
+  \addtocounter{@iterator}{-1}\do@countloop
+  \prt@hlfcntr{tbl@points}%
+}% count@pointsinrange
+\def\do@countloop{%
+  % We check \if@bonus here when needed:
   \addtocounter{@iterator}{1}%
-%  \ifnum \csname pointsonpage@\roman{@iterator}\endcsname > 0\relax
-  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\romannumeral
-                              \csname c@@iterator\endcsname\endcsname}%
-%  \set@hlfcntr{tmp@hlfcntr}{\csname pointsonpage@\roman{@iterator}\endcsname}%
-%  spanish.ldf redefines \@roman, so we'll avoid \roman
-  \ifhlfcntr@pos{tmp@hlfcntr}%
-    \pg@sline
+  \if@bonus
+    \@ifundefined{bonuspointsofq@\romannumeral \c@@iterator}%
+      {}%
+      {\addto@hlfcntr{tbl@points}
+         {\csname bonuspointsofq@\romannumeral \c@@iterator\endcsname}}%
+  \else
+    \@ifundefined{pointsofq@\romannumeral \c@@iterator}%
+      {}%
+      {\addto@hlfcntr{tbl@points}
+         {\csname pointsofq@\romannumeral \c@@iterator\endcsname}}%
   \fi
-  \ifnum \the@iterator < \lastpage@withpoints\relax
-    \let\next@pgsloop=\do@pgsloop
+  \ifnum \value{@iterator} < \tbl@lastq\relax
+    \let\next@countloop=\do@countloop
   \else
-    \let\next@pgsloop=\relax
+    \let\next@countloop=\relax
   \fi
-  \next@pgsloop
-}
-\def\pg@sline{%
-  \hbox to \@cellwidth{\hfill}&
-}
+  \next@countloop
+}% do@countloop
+
+%--------------------------------------------------------------------
+% \firstqinrange, \lastqinrange, and \numqinrange.
+
+\newcommand{\firstqinrange}[1]{%
+  \def\tbl@range{#1}%
+  \@ifundefined{range@\tbl@range @firstq}%
+  {\bad@range}%
+  {\csname range@#1@firstq\endcsname}%
+}% firstqinrange
+
+\newcommand{\lastqinrange}[1]{%
+  \def\tbl@range{#1}%
+  \@ifundefined{range@\tbl@range @lastq}%
+  {\bad@range}%
+  {\csname range@#1@lastq\endcsname}%
+}% lastqinrange
+
+\newcommand{\numqinrange}[1]{%
+  \def\tbl@range{#1}%
+  \@ifundefined{range@#1@firstq}%
+  {%
+    \bad@range
+  }%
+  {%
+    \@ifundefined{range@#1@lastq}%
+    {%
+      \bad@range
+    }%
+    {%
+      \setcounter{@iterator}{\csname range@#1@lastq\endcsname}%
+      \addtocounter{@iterator}{-\csname range@#1@firstq\endcsname}%
+      \stepcounter{@iterator}%
+      \arabic{@iterator}%
+    }%
+  }%
+}% numqinrange
 
 
 %--------------------------------------------------------------------
 
 
 % If the documentclass options include ``answers'', then the command
-% \@printanswerstrue is given at the beginning of the run.
+% \printanswerstrue is given at the beginning of the run.
 
 % If the documentclass options include ``noanswers'', then the command
-% \@printanswersfalse is given at the beginning of the run.
+% \printanswersfalse is given at the beginning of the run.
+
+\def\printanswers{\printanswerstrue}
+\def\noprintanswers{\printanswersfalse}
+
+% If the documentclass options include ``cancelspace'', then the
+% command \cancelspacetrue is given at the beginning of the run.
 
-\def\printanswers{\@printanswerstrue}
-\def\noprintanswers{\@printanswersfalse}
+% If the documentclass options include ``nocancelspace'', then the
+% command \cancelspacefalse is given at the beginning of the run.
 
+\def\cancelspace{\cancelspacetrue}
+\def\nocancelspace{\cancelspacefalse}
 
-% If @printanswers is true, we print the solution using a TheSolution
-% environment.  If @printanswers is false, we insert blank vertical space
-% equal to the optional argument (the default value of which is 0pt).
+% \if@insolution will be true while we're inside of any of the
+% solution environments.  This is used to supress \PgInfo@write and
+% \label commands generated if there's a parts (or subparts, or
+% subsubparts) environment inside of a solution.  (It won't suppress
+% the labels for the question objects, since a question object is
+% never a label that's been used before.)
+\newif\if@insolution
+\@insolutionfalse
+
+\newcommand\SolutionEmphasis[1]{%
+  \def\Solution@Emphasis{#1}%
+}
+\SolutionEmphasis{}
+
+% If printanswers is true, we print the solution using a TheSolution
+% environment.  If printanswers is false and cancelspace is false, we
+% insert blank vertical space equal to the optional argument (the
+% default value of which is 0pt).
 \newenvironment{solution}[1][0pt]%
   {%
-    \if@printanswers
+    \@insolutiontrue % cancelled by the end of the environment
+    \@addpointsfalse % cancelled by the end of the environment
+    \ifprintanswers
+      \begingroup
+      \Solution@Emphasis
       \begin{TheSolution}%
     \else
-      \par
-      \vspace*{#1}%
+      \ifcancelspace
+        % Do nothing
+      \else
+        \par
+        \penalty 0
+        \vspace*{#1}%
+      \fi
       \setbox\z@\vbox\bgroup
     \fi
   }{%
-    \if@printanswers
+    \ifprintanswers
       \end{TheSolution}%
+      \endgroup
     \else
       \egroup
     \fi
   }%
 
-% If @printanswers is true, we print the solution using a TheSolution
-% environment.  If @printanswers is false, we insert lined vertical space
-% equal to the optional argument (the default value of which is 0pt).
+% If printanswers is true, we print the solution using a TheSolution
+% environment.  If printanswers is false and cancelspace is false,
+% we insert an empty box of width the current line width and of
+% height equal to the optional argument, which can be a length, or
+% \fill, or \stretch{number}.  If the optional argument is omitted,
+% then the box is entirely omitted when printanswers is false.
+\newenvironment{solutionorbox}[1][-1pt]%
+  {%
+    \@insolutiontrue % cancelled by the end of the environment
+    \@addpointsfalse % cancelled by the end of the environment
+    \ifprintanswers
+      \begingroup
+      \Solution@Emphasis
+      \begin{TheSolution}%
+    \else
+      \ifcancelspace
+        % Do nothing
+      \else
+        \par
+        % Note: It's important that the following test be
+        % ``\ifdim 0pt > #1'' rather than ``\ifdim #1 < 0pt''
+        % That's because if the user says
+        % ``\begin{solutionorbox}{\stretch{1}}''
+        % (or \stretch{anythingelse}), then this will expand to
+        % ``\ifdim 0pt > \z@ plus 1fill\relax''.
+        % The \ifdim will be ``\ifdim 0pt > \z@'', and we'll have
+        % ``plus 1fill\relax'' left over.  This is OK because if the
+        % \ifdim is false, that leftover stuff will be ignored,
+        % and it will only be true if the user omitted the optional
+        % argument, in which case there's no \stretch and thus no
+        % left over part.
+        % If we said ``\ifdim #1 < 0pt'', then we'd get an error
+        % when the user used \stretch, since the leftover stuff
+        % would appear when TeX was looking for <, =, or >.
+        \ifdim 0pt > #1
+          % do nothing
+        \else
+          \makeemptybox{#1}%
+        \fi
+      \fi
+      \setbox\z@\vbox\bgroup
+    \fi
+  }{%
+    \ifprintanswers
+      \end{TheSolution}%
+      \endgroup
+    \else
+      \egroup
+    \fi
+  }%
+  
+% If printanswers is true, we print the solution using a TheSolution
+% environment.  If printanswers is false and cancelspace is false,
+% we insert lined vertical space equal to the optional argument (the
+% default value of which is 0pt).
 \newenvironment{solutionorlines}[1][0pt]%
   {%
-    \if@printanswers
+    \@insolutiontrue % cancelled by the end of the environment
+    \@addpointsfalse % cancelled by the end of the environment
+    \ifprintanswers
+      \begingroup
+      \Solution@Emphasis
       \begin{TheSolution}%
     \else
-      \par
-      \fillwithlines{#1}%
+      \ifcancelspace
+        % Do nothing
+      \else
+        \par
+        \penalty 0
+        \fillwithlines{#1}%
+      \fi
       \setbox\z@\vbox\bgroup
     \fi
   }{%
-    \if@printanswers
+    \ifprintanswers
       \end{TheSolution}%
+      \endgroup
     \else
       \egroup
     \fi
   }%
-
   
-% The environment TheSolution is called from the solution environment
-% when @printanswers is true.  It uses Donald Arseneau's
-% framed.sty macros (included at the end if this file) to allow the
-% solution to be broken across pages and have each piece enclosed in
-% an fbox (or a colorbox, if the user has given the command
-% \shadedsolution).
-%
+% If printanswers is true, we print the solution using a TheSolution
+% environment.  If printanswers is false and cancelspace is false,
+% we insert dotted lined vertical space equal to the optional
+% argument (the default value of which is 0pt).
+\newenvironment{solutionordottedlines}[1][0pt]%
+  {%
+    \@insolutiontrue % cancelled by the end of the environment
+    \@addpointsfalse % cancelled by the end of the environment
+    \ifprintanswers
+      \begingroup
+      \Solution@Emphasis
+      \begin{TheSolution}%
+    \else
+      \ifcancelspace
+        % Do nothing
+      \else
+        \par
+        \penalty 0
+        \fillwithdottedlines{#1}%
+      \fi
+      \setbox\z@\vbox\bgroup
+    \fi
+  }{%
+    \ifprintanswers
+      \end{TheSolution}%
+      \endgroup
+    \else
+      \egroup
+    \fi
+  }%
+
+% If printanswers is true, we print the solution using a TheSolution
+% environment.  If printanswers is false and cancelspace is false,
+% we insert a grid occupying vertically the optional argument (the
+% default value of which is 0pt).
+\newenvironment{solutionorgrid}[1][0pt]%
+  {%
+    \@insolutiontrue % cancelled by the end of the environment
+    \@addpointsfalse % cancelled by the end of the environment
+    \ifprintanswers
+      \begingroup
+      \Solution@Emphasis
+      \begin{TheSolution}%
+    \else
+      \ifcancelspace
+        % Do nothing
+      \else
+        \par
+        \penalty 0
+        \fillwithgrid{#1}%
+      \fi
+      \setbox\z@\vbox\bgroup
+    \fi
+  }{%
+    \ifprintanswers
+      \end{TheSolution}%
+      \endgroup
+    \else
+      \egroup
+    \fi
+  }%
+
+
+% The environment TheSolution is called from the solution,
+% solutionorbox, solutionorlines, solutionordottedlines, and
+% solutionorgrid environments when printanswers is true.  It uses
+% Donald Arseneau's framed.sty macros (included at the end of this
+% file) to allow the solution to be broken across pages and have each
+% piece enclosed in an fbox (or a colorbox, if the user has given the
+% command \shadedsolutions), (or no box at all, if the user has given
+% the command \unframedsolutions).
+
 % Of course, the user can change TheSolution with a \renewenvironment
 % command.
 \newcommand{\solutiontitle}{\noindent\textbf{Solution:}\enspace}
     % inside of the solution box:
     \leftskip=0pt
     \rightskip=0pt
+    % If the user said \unframedsolutions, then both
+    % \if@framedsolutions and \if@shadedsolutions are false:
     \if@framedsolutions
-      % Do nothing; we'll use the default \FrameCommand
+      % We'll use the default \exam@FrameCommand
     \else
-      \def\FrameCommand{\colorbox{SolutionColor}}%
+      \if@shadedsolutions
+        \def\exam@FrameCommand{\colorbox{SolutionColor}}%
+      \else
+        % It's \unframedsolutions:
+        \def\exam@FrameCommand{}%
+      \fi
     \fi
-    \MakeFramed{\advance\hsize-\width}%
+    \exam@MakeFramed{\advance\hsize-\exam@width}%
     \solutiontitle
     \ignorespaces
   }%
   {%
     \unskip
-    \endMakeFramed
+    \endexam@MakeFramed
   }%
 
 \newif\if@framedsolutions
 \@framedsolutionstrue
+\newif\if@shadedsolutions
+\@shadedsolutionsfalse
+% If the user said \unframedsolutions, then both
+% \if@framedsolutions and \if@shadedsolutions are false.
 
-\def\framedsolutions{\@framedsolutionstrue}
+\def\framedsolutions{\@framedsolutionstrue\@shadedsolutionsfalse}
 \def\shadedsolutions{%
   \@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\colorsolutions
+      in order to use the command \protect\shadedsolutions
       \MessageBreak
       }{%
       This command makes use of the package color.sty,\MessageBreak
   }%
   {%
     \definecolor{SolutionColor}{gray}{0.8}
+    \@shadedsolutionstrue
     \@framedsolutionsfalse
   }%
 }
+\def\unframedsolutions{\@framedsolutionsfalse\@shadedsolutionsfalse}
+
+
+% The solutionbox environment is different from the other solution
+% environments (solution, solutionorbox, solutionorlines,
+% solutionordottedlines, and solutionorgrid), in that
+%
+%   (1) The box is always printed, whether answers are being printed
+%   or not.
+%
+%   (2) The argument giving the size of the box is a required
+%   argument, not an optional argument, and so it should be enclosed
+%   in braces, not in brackets.  It can be either a length or
+%   \stretch{number}.
+%
+%   (3) We make no use of the TheSolution environment; the solutionbox
+%   environment is completely freestanding.
+%
+% 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.
+%
+% Note: It's the user's responsibility to be sure that the box is
+% large enough to hold the solution!  If the solution takes up too
+% 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@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@box
+\newenvironment{solutionbox}[1]{%
+  \@insolutiontrue % cancelled by the end of the environment
+  \@addpointsfalse % cancelled by the end of the environment
+  \def\solutionbox@size{#1}% saved for end of environment
+% 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
+  % Confine the \Solution@Emphasis, as well as anything the user puts
+  % into the solution (e.g., \color{red}, or whatever); don't say
+  % \endgroup until after using \box\exam@box:
+  \begingroup 
+    \Solution@Emphasis
+    % We save the solution in a box of the proper width.  We'll either
+    % print it (if we're printing solutions) or throw it away by just
+    % not using it before the environment ends:
+    \setbox\exam@box=\vtop\bgroup
+      \hsize=\@tempdima
+      \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
+  }%
+  {%
+    \unskip
+    \egroup
+    % OK, the solution is now inside \box\exam@box.
+    % Set the height and depth to 0pt, so that if we use it we won't
+    % be advancing our position on the page:
+    \ht\exam@box=0pt
+    \dp\exam@box=0pt
+    \par
+    \vspace{\parskip}
+    \ifprintanswers
+      % We enclose the \vtop in an \hbox to avoid having the
+      % indentation of the enclosing list environment (implemented via
+      % \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:
+      % 2016/02/08: Changed \textwidth to \hsize:
+      \hbox to \hsize{%
+        \noindent
+        \hskip\@totalleftmargin
+        \hskip3\fboxsep\hskip\fboxrule
+        \box\exam@box\hfill
+      }%
+      \par\nointerlineskip
+    \fi
+  \endgroup % Finish confining the \Solution@Emphasis
+% Starting in version 2.502, 2016/03/23,the decision of whether to
+% color the box is made in the \makeemptybox command:
+  \makeemptybox{\solutionbox@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 xcolor.sty was used (instead of just
+% color.sty), then the line that saves the current color in
+% saved@color could have been just
+
+%   \colorlet{saved@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@mycolor).
+\newcommand{\colorfbox}[2]{%
+  % Save the current color in saved@color:
+  \expandafter\let\csname\string\color@saved@color\endcsname\current@color
+  % Create the box in color #1, with the text in saved@color
+  % (the braces are to confine the color change commands):
+  {\color{#1}\fbox{\color{saved@color}#2}}%
+}% colorfbox
 
 %--------------------------------------------------------------------
 %--------------------------------------------------------------------
 % provided that this notice is left intact.
 %
 % The modifications I made are marked with ``psh'' in a comment:
+%
+% Further modifications, 2017-09-21
+% I changed the names of many commands by prepending ``exam@'', so
+% that the user can use the framed.sty package with exam.cls and not
+% have conflicts.  (I also renamed the framed, shaded, and leftbar
+% environments to examframed, examshaded, and examleftbar.)  I didn't
+% mark these name changes.
 % 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Create framed or shaded regions that can break across pages using 
-% \begin{framed} ... \end{framed}    -- ordinary frame box (box at margin)
-% \begin{shaded} ... \end{shaded}    -- shaded background (into margin)
-%    ... leftbar ...                 -- line on left side
+% \begin{examframed} ... \end{examframed}    -- ordinary frame box
+% (box at margin)
+% \begin{examshaded} ... \end{examshaded}    -- shaded background
+% (into margin)
+%    ... examleftbar ...                 -- line on left side
 % \begin{MakeFramed}{settings} ... \end{MakeFramed}
 %                        -- generic frame (for new environments)
 %
-% The "framed" environment puts the text into "\fbox" with the
-% settings "\fboxrule=\FrameRule" and "\fboxsep=\FrameSep".
+% The "examframed" environment puts the text into "\fbox" with the
+% settings "\fboxrule=\exam@FrameRule" and "\fboxsep=\exam@FrameSep".
 % You can change these lengths (using "\setlength") and you
-% can even change the definition of "\FrameCommand" to use
+% can even change the definition of "\exam@FrameCommand" to use
 % much fancier boxes.
 %
-% In fact, the "shaded" environment just redefines "\FrameCommand"
+% In fact, the "shaded" environment just redefines "\exam@FrameCommand"
 % to use "\colorbox{shadecolor}" (and you have to define the
 % color "shadecolor": \newcolor{shadecolor}...).
 %
 % A page break is allowed, and even encouraged, before the framed
 % environment.  If you want to attach some text (a box title) to the
-% frame, then the text should be inserted by \FrameCommand
+% frame, then the text should be inserted by \exam@FrameCommand
 %
 % The contents of the framed regions are restricted: 
 % Floats, footnotes, marginpars and head-line entries will be lost.
 %
 % The MakeFramed environment does the work.  Its "settings" argument
 % should contain any adjustments to the text width (applied to \hsize,
-% and using the "\width" of the frame itself) as well as a `restore' 
-% command -- \@parboxrestore or \FrameRestore or something similar.
+% and using the "\exam@width" of the frame itself) as well as a `restore' 
+% command -- \@parboxrestore or \exam@FrameRestore or something similar.
 % 
 % Expert commands:
-% \MakeFramed, \endMakeFramed: the "MakeFramed" environment
-% \FrameCommand: command to draw the frame around its argument
-% \FrameRestore: restore some text settings, but fewer than \@parboxrestore
-% \FrameRule: length register; \fboxrule for default "framed".
-% \FrameSep: length register; \fboxsep for default "framed".
-% \FrameHeightAdjust: macro; height of frame above baseline at top of page
+% \exam@MakeFramed, \endexam@MakeFramed: the "MakeFramed" environment
+% \exam@FrameCommand: command to draw the frame around its argument
+% \exam@FrameRestore: restore some text settings, but fewer than
+% \@parboxrestore 
+% \exam@FrameRule: length register; \fboxrule for default "framed".
+% \exam@FrameSep: length register; \fboxsep for default "framed".
+% \exam@frameHeightAdjust: macro; height of frame above baseline at
+% top of page
 % 
 % This is still a `pre-production' version because I can think of many
 % features/improvements that should be made.  Nevertheless, starting 
 \newdimen\saved@totalleftmargin
 \newcount\@sollistdepth
 
-\newenvironment{framed}% using default \FrameCommand
-  {\MakeFramed {\advance\hsize-\width \FrameRestore}}%
-  {\endMakeFramed}
+\newenvironment{examframed}% using default \exam@FrameCommand
+  {\exam@MakeFramed {\advance\hsize-\exam@width \exam@FrameRestore}}%
+  {\endexam@MakeFramed}
 
-\newenvironment{shaded}{%
-  \def\FrameCommand{\colorbox{shadecolor}}%
-  \MakeFramed {\FrameRestore}}%
- {\endMakeFramed}
+\newenvironment{examshaded}{%
+  \def\exam@FrameCommand{\colorbox{shadecolor}}%
+  \exam@MakeFramed {\exam@FrameRestore}}%
+ {\endexam@MakeFramed}
 
-\newenvironment{leftbar}{%
-  \def\FrameCommand{\vrule width 3pt \hspace{10pt}}%
-  \MakeFramed {\advance\hsize-\width \FrameRestore}}%
- {\endMakeFramed}
+\newenvironment{examleftbar}{%
+  \def\exam@FrameCommand{\vrule width 3pt \hspace{10pt}}%
+  \exam@MakeFramed {\advance\hsize-\exam@width \exam@FrameRestore}}%
+ {\endexam@MakeFramed}
 
-\chardef\FrameRestore=\catcode`\| % for debug
+\chardef\exam@FrameRestore=\catcode`\| % for debug
 \catcode`\|=\catcode`\% % (debug: insert space after backslash)
 
-\def\MakeFramed#1{\par
- % measure added width and height; call result \width and \height
+\def\exam@MakeFramed#1{\par
+ % measure added width and height; call result \exam@width and \exam@height
  \setbox\z@\vbox{\vskip-1in \hbox{\hskip-1in 
-   \FrameCommand{\hbox{\vrule \@height .7in \@depth.3in \@width 1in}}}%
+   \exam@FrameCommand{\hbox{\vrule \@height .7in \@depth.3in \@width 1in}}}%
    \vskip\z@skip}%
- \def\width{\wd\z@}\def\height{\ht\z@}%
- \edef\fb@frw{\the\width}\edef\fb@frh{\the\height}%
+ \def\exam@width{\wd\z@}\def\exam@height{\ht\z@}%
+ \edef\exam@fb@frw{\the\exam@width}\edef\exam@fb@frh{\the\exam@height}%
  % insert pre-penalties and skips
  \begingroup
  \skip@\lastskip
     \penalty9999 % updates \page parameters
     \ifdim\pagefilstretch=\z@ \ifdim\pagefillstretch=\z@
        \edef\@tempa{\the\skip@}%
-       \ifx\@tempa\zero@glue \penalty-30
+       \ifx\@tempa\exam@zero@glue \penalty-30
        \else \vskip-\skip@ \penalty-30 \vskip\skip@
     \fi\fi\fi
     \penalty\z@
     % calculated badness is really 8192, not 10000, so the multiplier
     % is 0.2301. 
     \advance\skip@ \z@ plus-.5\baselineskip
-    \advance\skip@ \z@ plus-.231\height
+    \advance\skip@ \z@ plus-.231\exam@height
     \advance\skip@ \z@ plus-.231\skip@
     \advance\skip@ \z@ plus-.231\topsep
     \vskip-\skip@ \penalty 1800 \vskip\skip@
  \addvspace{\topsep}%
  \endgroup
  % clear out pending page break
- \penalty\@M \vskip 2\baselineskip \vskip\height
- \penalty9999 \vskip -2\baselineskip \vskip-\height
+ \penalty\@M \vskip 2\baselineskip \vskip\exam@height
+ \penalty9999 \vskip -2\baselineskip \vskip-\exam@height
  \penalty9999 % updates \pagetotal
 |\message{After clearout, \pagetotal=\the\pagetotal, \pagegoal=\the\pagegoal. }%
- \fb@adjheight 
+ \exam@fb@adjheight 
 %psh: Added commands:
   \advance\hsize-\@totalleftmargin
   \saved@totalleftmargin=\@totalleftmargin
   \leftmargin=0pt
 %psh: end of added commands
  \setbox\@tempboxa\vbox\bgroup
-   #1% Modifications to \hsize (can use \width and \height)
+   #1% Modifications to \hsize (can use \exam@width and \exam@height)
    \textwidth\hsize \columnwidth\hsize
 %psh: added one line:
    \linewidth=\hsize
 }
 
-\def\endMakeFramed{\par
+\def\endexam@MakeFramed{\par
      \kern\z@ \penalty-100 % put depth into height
  \egroup
- \begingroup \put@frame \endgroup
+ \begingroup \exam@put@frame \endgroup
 %psh: Added one line:
  \@totalleftmargin=\saved@totalleftmargin
 }
 
-% \put@frame takes the contents of \@tempboxa and puts all, or a piece,
-% of it on the page with a frame (\FrameCommand).  It recurses until
-% all of \@tempboxa has been used up. (\@tempboxa must have zero depth.)
+% \exam@put@frame takes the contents of \@tempboxa and puts all, or a
+% piece, of it on the page with a frame (\exam@FrameCommand).  It
+% recurses until all of \@tempboxa has been used up. (\@tempboxa must
+% have zero depth.)
 
-\def\put@frame{\relax
+\def\exam@put@frame{\relax
  \ifdim\pagegoal=\maxdimen \pagegoal\vsize \fi
 |   \message{=============== Entering putframe ====================^^J
 |     \pagegoal=\the\pagegoal,  \pagetotal=\the\pagetotal. }%
     \dimen@\pagegoal \advance\dimen@-\pagetotal % natural space left on page
   \ifdim\dimen@<2\baselineskip 
 |   \message{Page has only \the\dimen@\space room left; eject. }%
-    \eject \fb@adjheight \put@frame
+    \eject \exam@fb@adjheight \exam@put@frame
   \else % there's appreciable room left on the page
 |    \message{\string\pagetotal=\the\pagetotal,
 |        \string\pagegoal=\the\pagegoal, 
 |        \string\pagestretch=\the\pagestretch,
 |        \string\pageshrink=\the\pageshrink,
-|        \string\fb@frh=\fb@frh. \space}
-|    \message{Box of size \the\ht\@tempboxa\space + \fb@frh}%
+|        \string\exam@fb@frh=\exam@fb@frh. \space}
+|    \message{Box of size \the\ht\@tempboxa\space + \exam@fb@frh}%
      \begingroup % temporarily set \dimen@ to be...
      \advance\dimen@.8\pageshrink  % maximum space available on page
-     \advance\dimen@-\fb@frh\relax % space available for frame's contents
+     \advance\dimen@-\exam@fb@frh\relax % space available for frame's contents
      \expandafter\endgroup
      % restore \dimen@ to real room left on page
      \ifdim\dimen@>\ht\@tempboxa % whole box does fit
      \else % box must be split
 |       \message{must be split to fit in \the\dimen@. }%
         \setbox\@tempboxa\vbox{% simulate frame and flexiblity of the page:
-           \vskip \fb@frh \@plus\pagestretch \@minus.8\pageshrink
+           \vskip \exam@fb@frh \@plus\pagestretch \@minus.8\pageshrink
            \kern137sp\kern-137sp\penalty-30
            \unvbox\@tempboxa}%
-        \edef\fb@resto@set{\boxmaxdepth\the\boxmaxdepth \splittopskip\the\splittopskip}%
+        \edef\exam@fb@resto@set{\boxmaxdepth\the\boxmaxdepth
+          \splittopskip\the\splittopskip}%
         \boxmaxdepth\z@ \splittopskip\z@
         \setbox\tw@\vsplit\@tempboxa to\dimen@
         \setbox\tw@\vbox{\unvbox\tw@}% natural-sized
 |         \message{Frame is big -- Use up the full column. }%
           \dimen@ii\pagegoal
           \advance\dimen@ii -\topskip
-          \advance\dimen@ii \FrameHeightAdjust\relax
+          \advance\dimen@ii \exam@frameHeightAdjust\relax
         \else  % suspect this is wrong:
           % If the split-to size > feasible room_on_page, rebox it smaller.
           \advance\dimen@.8\pageshrink
           \fi
         \fi
         % Re-box contents to desired size \dimen@ii
-        \advance\dimen@ii -\fb@frh
+        \advance\dimen@ii -\exam@fb@frh
         \setbox\tw@\vbox to\dimen@ii \bgroup
         % remove simulated frame and page flexibility:
-        \vskip -\fb@frh \@plus-\pagestretch \@minus-.8\pageshrink
+        \vskip -\exam@fb@frh \@plus-\pagestretch \@minus-.8\pageshrink
         \unvbox\tw@ \unpenalty\unpenalty
         \ifdim\lastkern=-137sp % whole box went to next page
 |          \message{box split at beginning! }%
-           \egroup \fb@resto@set \eject % (\vskip for frame size was discarded) 
-           \fb@adjheight
+           \egroup \exam@fb@resto@set \eject % (\vskip for frame size
+                                             % was discarded)  
+           \exam@fb@adjheight
         \else %
-           \egroup \fb@resto@set
+           \egroup \exam@fb@resto@set
            \ifvoid\@tempboxa % it all fit after all
 |             \message{box split at end! }%
               \setbox\@tempboxa\box\tw@
               \ifdim\wd\tw@>\z@
 %psh: Changed the command that inserts the box:
 %     Instead of \centerline, we shift right by \saved@totalleftmargin:
-%              \centerline{\FrameCommand{\box\tw@}}%  ??? \centerline bad idea
-       \hbox{\hskip \saved@totalleftmargin\FrameCommand{\box\tw@}}%
+%              \centerline{\exam@FrameCommand{\box\tw@}}%  ??? \centerline bad idea
+       \hbox{\hskip \saved@totalleftmargin\exam@FrameCommand{\box\tw@}}%
               \else
 |               \message{Zero width means likely blank. Don't frame it (guess)}%
                 \box\tw@
               \fi
               \hrule \@height\z@
               \eject
-              \fb@adjheight
-              \put@frame
+              \exam@fb@adjheight
+              \exam@put@frame
   \fi\fi\fi\fi\fi
   \ifvoid\@tempboxa\else
 %psh: Changed the command that inserts the box:
 %     Instead of \centerline, we shift right by \saved@totalleftmargin:
-%    \centerline{\FrameCommand{\box\@tempboxa}}%
-    \hbox{\hskip\saved@totalleftmargin\FrameCommand{\box\@tempboxa}}%
+%    \centerline{\exam@FrameCommand{\box\@tempboxa}}%
+    \hbox{\hskip\saved@totalleftmargin\exam@FrameCommand{\box\@tempboxa}}%
     \nointerlineskip \null %{\showoutput \showlists}
     \penalty-30 \vskip\topsep
   \fi}
 
-\def\fb@adjheight{%
-  \vbox to\FrameHeightAdjust{}% get proper baseline skip from above.
+\def\exam@fb@adjheight{%
+  \vbox to\exam@frameHeightAdjust{}% get proper baseline skip from above.
   \penalty\@M \nointerlineskip
-  \vskip-\FrameHeightAdjust
+  \vskip-\exam@frameHeightAdjust
   \penalty\@M} % useful for tops of pages
 
-\edef\zero@glue{\the\z@skip}
+\edef\exam@zero@glue{\the\z@skip}
 
-\catcode`\|=\FrameRestore
+\catcode`\|=\exam@FrameRestore
 
 % Provide configuration commands:
-\providecommand\FrameCommand{\fboxrule=\FrameRule \fboxsep=\FrameSep \fbox}
-\@ifundefined{FrameRule}{\newdimen\FrameRule \FrameRule=\fboxrule}{}
-\@ifundefined{FrameSep} {\newdimen\FrameSep  \FrameSep =3\fboxsep}{}
+%psh: Version 2.502, 2016/03/23, changed \exam@FrameCommand so that the
+%     frame is printed in color if the user has said
+%     \colorsolutionboxes:
+%\providecommand\exam@FrameCommand{\fboxrule=\exam@FrameRule
+%\fboxsep=\exam@FrameSep \fbox}
+\def\exam@FrameCommand{\fboxrule=\exam@FrameRule \fboxsep=\exam@FrameSep
+  \if@colorsolutionboxes
+    \def\box@it{\colorfbox{SolutionBoxColor}}%
+  \else
+    \def\box@it{\fbox}%
+  \fi
+  \box@it
+}% \exam@FrameCommand
+
+\@ifundefined{FrameRule}{\newdimen\exam@FrameRule \exam@FrameRule=\fboxrule}{}
+\@ifundefined{FrameSep} {\newdimen\exam@FrameSep  \exam@FrameSep =3\fboxsep}{}
 
 % Height of frame above first baseline when frame starts a page:
-\providecommand\FrameHeightAdjust{6pt}
+\providecommand\exam@frameHeightAdjust{6pt}
 
-% \FrameRestore has parts of \@parboxrestore.  See how it is used in the 
+% \exam@FrameRestore has parts of \@parboxrestore.  See how it is used in the 
 % "settings" argument of \MakeFrame.  Previous behavior can be restored by 
 % using \@parboxrestore there, or redefining:
-% \makeatletter \renewcommand\FrameRestore{\@parboxrestore} \makeatother
-\def\FrameRestore{%
+% \makeatletter \renewcommand\exam@FrameRestore{\@parboxrestore} \makeatother
+\def\exam@FrameRestore{%
   \let\if@nobreak\iffalse
   \let\if@noskipsec\iffalse
 %  \let\par\@@par  ??
 }
 
 %  Compatibility with previous versions (temporary!):
-\let\fram@d=\MakeFramed  \let\endfram@d=\endMakeFramed
+% psh: we'll remove this 2017-09-21
+%\let\fram@d=\MakeFramed  \let\endfram@d=\endMakeFramed
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%