The coursework this week counts 25% towards your credit for the unit. The main purpose is to get to grips with structures, passed using pointers. You may need information from the lecture notes up to the objects chapter. Optionally, you may need to look ahead to the I/O chapter for input/output issues.
Your task is to write a program which plays the game Noughts and Crosses (also known as Tic-Tac-Toe). Here is the skeleton for you to start from:
As usual:
make ./oxo
Using make
is highly recommended because (except on Windows) it
will add the extra debugging option -fsanitize=undefined
which will
detect array indexes which are out of bounds. That option will be used for
marking.
There are logic and user interface functions. You need to develop both sets
to make a playable game. It is strongly recommended that you first develop the
logic functions, getting all the tests to pass as usual, then optionally
tackle the user interface. (You may, however, want to develop the
display
function during stage one, to help with visualization.)
Assessment is by automarking, which only tests the logic functions. Getting the user interface to work is an optional extra. It is recommended that you submit stage one for marks, before starting on stage two. It is up to you whether you submit stage two - it would just be to archive your program as part of your portfolio.
main
function supports the stagesThe program can be run with no arguments, ./oxo
, to run the
tests. Later, the user interface functions can be filled in, and the program
can be run with ./oxo X
or ./oxo O
to play the game,
with the given player having the first move.
Constants X
, O
, N
are set up to
represent which player owns a cell. These constants have the type enum
player
, and an abbreviation player
is set up for this.
The type enum player
is a synonym for
int
. It makes the program sightly more
readable by documenting the fact that a particular integer variable is intended
only to hold player constants. But the
-fsanitize=undefined
option may at least be able to detect if you
use anything outside the range 0..2.
Constants like these can be set up in at least three ways:
#define X 0 #define O 1 #define N 2 const char X = 0, O = 1, N = 2; enum { X, O, N };
Using #define
works, but (a) it is clumsy, with one line per
constant (b) it uses textual substitution, so the names are not known to the
compiler, which makes errors and warnings poorer and (c) using
#define
for anything other than constants is extremely dangerous, so it is a good idea
not to use it for anything (normally). Using const
is confusing,
because it
means read-only (you can't explicitly change the values) not constant from the
compiler's point of view, so, for example, the names can't be used in switch
statements. Using an enumerated type seems best, for integer-compatible
types.
The game structure contains a count of the number of moves made, mostly in order to tell when the game is over. Technically, this is redundant information, because the number of moves can be deduced from the grid, but it seems a little more convenient to count the moves rather than analyze the grid.
The function newGame
completely re-initializes an existing game
structure so that a single structure could, in principle, be reused for a
sequence of games.
Validity checking is carried out by the two functions valid
and
printInvalid
. The valid
function does the logic,
returning a constant, and is tested. The printInvalid
function
handles
the printing, including defining the strings to be displayed. A change of
language from English to something else would not affect the valid
function or its testing.
The function line
seems trivial and unnecessary. However,
it can be used to make the win
function slightly more readable,
if you want.
Checking for a winning line is probably the hardest part of the assignment. There isn't sufficient regularity in the eight winning lines for there to be much gain in trying to handle them with a loop and a formula, and the result probably wouldn't be very readable.
The best bet is probably to explicitly handle each of the eight lines one by
one, but try to deal with each in one line of code, to keep the
win
function short.
The functions main
, show
, printInvalid
have been provided. The display
, ask
and
play
functions need to be filled in to make the game playable.
There are no tests for these. The comments are all there is to specify what they do.
You know what to do by now.
Develop your own extra program as usual, for the second half of the marks. Ideally, you should use the same development technique. Design ultra-short functions, with logic separated from input output. You will have to develop your own tests for the logic functions. It is normal to write the tests for a function at the same time as the function itself. Don't leave the testing until afterwards. You don't need as many tests as in assignment skeletons, because it is only your confidence in correctness that is at stake.
Submit oxo.c
and your extra program, if any, along with a brief
readme file.