Assignment 2
Assignment 2 is worth 3% of your final mark, and is due by Saturday February 18 at 11:59 pm. Late submissions will not be accepted, barring exceptional circumstances.
There are two Practical Lab sessions for this assignment (Tuesday February 7 and Tuesday February 14).
This assignment should be submitted via MarkUs.
Your assignments must follow the style guidelines discussed in the course style guide. Failure to do so may result in deductions. The style guide contains new requirements for this assignment.
This assignment covers topics up through Carter chapter 6.1, except for pointers. You should not make use of concepts from subsequent chapters.
Announcements and Updates
This section contains a summary the changes that have been made to this page since it was first posted. You should also keep an eye on the Assignment 2 forum on the Discussion Board for additional announcements and clarifications.
(Feb 12): The tester program is available. The tester's output is identical to the Assignment 1 tester; details about the format are available on the Assignment 1 tester page. The tester can be run on ECF by typing:
$ /share/copy/aps105s/A2/a2tester
Overview
Your job for this assignment is to finish writing the code for a simple drawing program. The program has a canvas and a pen, and the user can issue commands to move the pen, thereby creating simple images. Since we are limited to C's text-based input/output capabilities, our program will use a text-based interface rather than a graphical one, and the resolution of our images will be extremely low.
The goals of this assignment are to provide some practice working with arrays and loops, to give you some experience working with functions (and seeing how they can be used to structure a program), and to give you a chance to try reading (and understanding) a larger chunk of code. Reading code is a skill that is closely related to writing code, but is slightly different (think about the difference between reading an essay and writing one yourself). Both require practice.
Important note: Before jumping in and starting to write code, take a few minutes to read this handout carefully. (It seems long, but that is mostly because each of the screenshots of the program takes up a lot of space.) This assignment may seem like a lot of material, but there isn't actually that much code that you will have to write, and we provide you with step-by-step instructions. The key is understanding where your code fits into the existing code.
Drawing Basics
Important note: this program assumes that your terminal window is at least 80 x 24 (i.e., that it displays 80 columns and 24 rows of characters). This is the standard width for terminal windows (it dates back to the paper teletype machines we saw in that picture of the PDP-11), so if you open a new terminal window (and haven't changed the default settings), you should be fine. If you have a smaller terminal window, the canvas will not display correctly. On a related note, this webpage might not display accurate renditions of the canvas if you are using a narrow window (or something with a small screen like a smartphone). In that case, you might want to double check the text file transcript.
This section provides an overview of the program's operation. Details of input and output formats are provided later in this document. Characters in bold are entered by the user.
The first thing that the program does is to print out the canvas (an array that represents the environment we can draw on), along with a series of coordinates along the left and bottom edges.
0|o-----------------------------------------------------------
1|------------------------------------------------------------
2|------------------------------------------------------------
3|------------------------------------------------------------
4|------------------------------------------------------------
5|------------------------------------------------------------
6|------------------------------------------------------------
7|------------------------------------------------------------
8|------------------------------------------------------------
9|------------------------------------------------------------
10|------------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
Notice that the left-hand coordinates are printed using two characters (right-aligned, so that there is a blank character in the "tens" column for the values 0 to 9), followed by the pipe (|
) character. The bottom coordinates are printed using two lines. The "tens" digit is on the bottom line, and the "ones" digit is on the top line. Blank spaces on the canvas are represented by a '-'
(right now the entire canvas is blank), and the position of the pen (which defaults to position 0,0 in the top left corner) is indicated by an 'o'
.
Next, the program prompts the user to enter a command:
Enter command: S
If the command is a drawing command (the commands are discussed in detail below), the program then prompts the user for a distance:
Enter distance: 10
It then draws a line in the specified direction (in this case 10
squares 'S'
(South)), starting from the current position. Canvas positions that have been drawn on are indicated with an '*'
.
0|*-----------------------------------------------------------
1|*-----------------------------------------------------------
2|*-----------------------------------------------------------
3|*-----------------------------------------------------------
4|*-----------------------------------------------------------
5|*-----------------------------------------------------------
6|*-----------------------------------------------------------
7|*-----------------------------------------------------------
8|*-----------------------------------------------------------
9|*-----------------------------------------------------------
10|o-----------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
Notice that a line of length 10 means that we move the pen 10 squares on the canvas. In this case we started at y position 0, so we move 10 squares and end up at y position 10, filling in positions 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 as we go.
At this point, the program prompts for another command:
Enter command: E
Enter distance: 5
Note: the pen has also coloured the square that it is currently resting on at the end of a stroke. In other words, when we draw a line 'E'
(East) from our first line, the square at (0,10) which previously contained an 'o'
, now contains an '*'
.
0|*-----------------------------------------------------------
1|*-----------------------------------------------------------
2|*-----------------------------------------------------------
3|*-----------------------------------------------------------
4|*-----------------------------------------------------------
5|*-----------------------------------------------------------
6|*-----------------------------------------------------------
7|*-----------------------------------------------------------
8|*-----------------------------------------------------------
9|*-----------------------------------------------------------
10|*****o------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
The program keeps asking for a new command until the user enters a 'Q'
to quit the program, at which point it exits.
Enter command: Q
An extended transcript is also available as a text file.
The Commands
The drawing program supports the following commands:
Q
(Quit)
If the user enters 'Q'
, the program should exit.
U
(Pen Up) and D
(Pen Down)
The pen has two states: it can be raised so that it is not drawing on the canvas, or lowered so that it is. While the pen is lifted, the squares that it passes over remain unchanged. Lifting the pen allows the user to draw disconnected shapes (e.g., they could draw a rectangle on the left side of the canvas using four lines, lift the pen and move it to the right side of the canvas, lower the pen, and then draw another rectangle there).
If the user enters 'U'
(for "up"), the pen is lifted. If the user enters 'D'
(for "down"), the pen is lowered. Issuing a command to switch the state of the pen to its current state (e.g., issuing a 'U'
when the pen is already lifted, or a 'D'
when the pen is already lowered) has no effect. There is no visual indication of the state of the pen.
N
(North), E
(East), S
(South), and W
(West)
If the user enters any of the direction commands, the program should then prompt them for a distance:
Enter command: N
Enter distance: 50
The pen should then be moved the specified number of squares in the specified direction. If the pen hits the edge of the canvas, it should stop at the edge. In other words, if the pen was 5 squares away from an edge and the user issued a move command of 10 squares towards that edge, the pen should move 5 squares to the edge, and stop there. (You can think of the edges of the canvas as a solid boundary that the pen cannot cross.) If the pen is lowered, every square that it crosses should be coloured by changing its symbol to an '*'
; if the square is already coloured (i.e., if a previous pen stroke has already passed over that square), there is no change to the square (i.e., squares are either black or white -- there is no concept of "partially coloured" or "grey"). If the pen is raised, the state of the square should not be changed.
You can only move the pen in these four directions. It cannot be moved on a diagonal.
Input Validation
There are two things that the program reads from the user: the commands (as described above), and the distance associated with a movement command.
Command Validation
You can assume that the user will only enter a single character, followed by the return key. However, you need to validate the character to ensure that it is a valid command. If the user enters an invalid command, you should print an error, and prompt the user again:
Enter command: T
T is an invalid command.
Enter command: U
Commands are case sensitive, and all of the valid commands are capitalized. So a 'q'
would be rejected as invalid.
Distance Validation
The distance must be a non-zero positive int
. You can assume that the user will only enter characters that correspond to int
s (i.e., the characters 0123456789-
), but you must validate the range of the number. In other words, you will never receive the input hello
, but you might receive the input 794
or -1900
. The first of those numbers is valid (since it's greater than 0
), while the second is not. (Conceptually, a move -5 positions E would be the same as a move 5 positions W, but this program does not support this notation.)
If the user enters an invalid distance, you should print an error, and prompt the user again:
Enter command: E
Enter distance: -50
Distance must be greater than 0.
Enter distance: 2
Note that your distance validation does not check to see if the move would place the pen outside the bounds of the canvas; this check is performed later in the code. All that is checked in your distance validation code is whether or not the distance is a non-zero positive int
.
Output
All prompts should be formatted exactly as they occur in this document, including whitespace. Note that each prompt ends with a colon and a space.
The canvas should be formatted exactly as it appears in this document, including whitespace. (Pay careful attention to how the bottom coordinates are aligned.)
Drawing Limitations
We are creating an extremely simple display system for this assignment. All computer displays (and printers) are divided into pixels (short for "picture elements"), which are the smallest dots that can be drawn by the system. A modern screen might have millions of pixels (e.g., a 15" laptop might have a resolution of 1440 x 900, which works out to 1.3 million pixels), and each of those pixels can take on one of millions of possible colours. Our "display" has 60 x 20 pixels (1,200), and can only display one of two possible pixels ('-'
for white, and '*'
for black). This display is therefore a 1-bit (black or white) 1,200 pixel display. Compared to a modern 24-bit (millions of colours) 1.3 million pixel display, ours will be highly pixelated, since we lack the necessary resolution to properly display complicated images. (It's a similar problem to taking a low-resolution image off the web and then trying to print it; the image looks really blocky, since it had too few pixels to satisfy the resolution requirements of the printer.) There is an additional distortion due to the fact that our pixels aren't actually square (e.g., most fonts use characters that take up more vertical space than horizontal space, so each character is not square; this means that we have rectangular pixels, and therefore a vertical "stretch" of the image). Nonetheless, our simple display system is enough to draw some simple shapes.
What to do
First, you should begin by not reading the starter code. No, really, don't read it yet. Take a minute and think about how this program would need to work. You'll need an array to store the canvas. What type should it be? How large? What is the basic structure of the program? You'll need to repeatedly read information from the user, and then use that information to do some array manipulations. What kind of loop would be needed? Where might you want to use functions (e.g., where are there self-contained bits of functionality that are used over and over again in the program)? By spending a few minutes thinking about these issues before looking at the starter code, you are practicing your problem solving skills. (Note: we haven't spent a lot of time talking about these issues in class yet, so it's ok if you're not sure what the answers are. Thinking about them is the important thing for now, and we will be spending more time on it later in the course.)
Now, read over the starter code. You will notice that there are several #define
constants (some of which are defined, and some of which are set to the place holder value FILL_ME_IN
), several function declarations and definitions (which are mostly empty), and a main()
function that is mostly finished. Take a look at main()
, and compare the structure of the main loop and functions to the structure you had thought about. (The starter code is only one way to approach this problem; there are of course other alternatives. If you'd like to discuss any differences between the structure of the starter code and any ideas you had, please feel free to stop by during office hours and I'd be happy to chat with you about this.)
Now, let's try compiling and running the code (remember that you'll need to use the -std=c99
flag to get gcc
to accept variable declarations in the for
loops). It won't do much yet, since most of the functions are still "stubs". A stub function is a function that doesn't actually do anything. It might have a return statement with some constant (since gcc
would complain if our function was completely blank when we are supposed to return something), but it doesn't actually perform the operation that it's supposed to. Stub functions are useful when you are developing a program; they let you "rough in" the main body of the program, and then fill in the details bit by bit. Your job on this assignment will be to fill in the stub functions one by one. You should not make any changes to the starter code except where indicated.
Notice that the starter code never actually gives you an opportunity to enter any input, since the stub versions of getValidSomething()
do not actually call scanf()
; they simply return a fixed value every time. (I will use phrases like getValidSomething()
as a short form to refer to both of the getValid functions. There is no function called getValidSomething()
in the program.)
Step 0
Let's start off by filling in the #define
constants with the correct values for each of the commands. Remember that each of the commands (and therefore each of the constants) is a single character.
Step 1
Go back to the code, and take a look at the array canvas
. This is the main data structure of our program, since it holds the representation of the canvas.
The first function that you're going to write is clearCanvas()
, which sets every char
in the array to BLANK_SYMBOL
. This will require a nested loop.
Step 2
Next, we need some way to see if clearCanvas()
is working correctly, so we'll write printCanvas()
. This is also a nested loop; in this case, we print out each line of the array, followed by a \n
at the end of each line. Don't worry about the coordinates yet; we'll do that next.
Compile and run your program, and you should see the canvas being printed out, followed by part of the bottom coordinates. It should look like this:
o-----------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
Enter command:
Step 3
Next, we will print out all of the coordinates. First, go back to printCanvas()
, and add in the coordinates at the left side of the screen. Remember that each coordinate should take up three characters (a potentially blank "tens" digit, a "ones" digit, and a pipe symbol (|
) to divide the coordinates from the canvas). Hint: printf()
can do the formatting for you.
Next, we will finish printBottomCoordinates(void)
so that it functions correctly. Right now, it prints out the "ones" digits, although they are not aligned correctly (they are too far to the left). Finish the function so that the digits are correctly aligned, and so it also prints out the "tens" digits.
Run and test your code to make sure that both sets of coordinates (the left and the bottom) are being printed correctly. Your output should look like this:
0|o-----------------------------------------------------------
1|------------------------------------------------------------
2|------------------------------------------------------------
3|------------------------------------------------------------
4|------------------------------------------------------------
5|------------------------------------------------------------
6|------------------------------------------------------------
7|------------------------------------------------------------
8|------------------------------------------------------------
9|------------------------------------------------------------
10|------------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
Enter command:
Note that at this point, your program will still exit immediately after it asks for a command (without giving you an opportunity to enter anything), since we still haven't written the input functions.
Step 4
Finishing one of those input functions is our next task. Both of the getValidSomething()
functions should prompt the user for input in the format specified in this document, check if the input is valid, and ask the user for more input if it isn't valid. Once it has received valid input, it should return that value. Both of these functions are very similar.
Right now, the functions print a prompt to the user, but do not actually read in anything. Recall that we call this kind of function a stub function, meaning that they are placeholders that don't actually do anything useful. We need this stub function to return something, so that the code will compile successfully; if we left off the return
statement in the stub function (because we don't yet perform any work to generate a real return value), we would violate the function prototype, and the code wouldn't compile. We therefore need to have a "fake" return value (indicated with a // REPLACE THIS LINE
comment), which will be replaced when the function is actually written.
Complete getValidCommand()
so that it works correctly. As indicated, as part of this process, you should replace the fake return
statement with an appropriate return
statement.
Now, run your program, and see if it is correctly reading and validating the commands. Note that the only command that will actually work right now is 'Q'
.
Steps 5 and 6
Now, we'll go up to the main loop of the program, and start to fill in the logic of the program. Notice how the starter code prompts the user for a command, and then uses an if
statement to decide what action to take. The action for CMD_QUIT
is already filled in.
Next, fill in the logic for CMD_UP
and CMD_DOWN
. Both of these branches are quite simple.
Step 7
Next, look at the else if
branch for the direction commands. The code for this branch is already written, but the helper function used in the condition is currently a stub function. Fill in isDirectionCommand()
so that it works correctly (which includes deleting the stub return value). (Something to think about: is there anywhere else in this program that you could use isDirectionCommand()
?)
Step 8
Next, we'll fill in the remaining functions used in this branch of the if
statement. First, write getValidDistance()
; this function is very similar to getValidCommand()
.
Steps 9 and 10
Then, fill in calculateXPosition()
. Remember what this function does: given the current x position, the direction of travel, and a distance, it calculates the new x position after moving the pen. This means that we might need to add or subtract the distance from our current position, or do nothing to it if the movement is perpendicular to the x-axis. Also keep in mind that there should never be an x coordinate that is outside of the canvas; we stop when we hit the edge.
Next, fill in calculateYPosition()
. The logic is exactly the same as calculateXPosition()
.
At this point, your program will be able to move the pen, but will not be colouring any pixels when it moves:
0|o-----------------------------------------------------------
1|------------------------------------------------------------
2|------------------------------------------------------------
3|------------------------------------------------------------
4|------------------------------------------------------------
5|------------------------------------------------------------
6|------------------------------------------------------------
7|------------------------------------------------------------
8|------------------------------------------------------------
9|------------------------------------------------------------
10|------------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
Enter command: E
Enter distance: 5
0|o----o------------------------------------------------------
1|------------------------------------------------------------
2|------------------------------------------------------------
3|------------------------------------------------------------
4|------------------------------------------------------------
5|------------------------------------------------------------
6|------------------------------------------------------------
7|------------------------------------------------------------
8|------------------------------------------------------------
9|------------------------------------------------------------
10|------------------------------------------------------------
11|------------------------------------------------------------
12|------------------------------------------------------------
13|------------------------------------------------------------
14|------------------------------------------------------------
15|------------------------------------------------------------
16|------------------------------------------------------------
17|------------------------------------------------------------
18|------------------------------------------------------------
19|------------------------------------------------------------
012345678901234567890123456789012345678901234567890123456789
0 1 2 3 4 5
Steps 11 and 12
Finally, we need to write drawLine()
. Rather than having four branches of an if
statement (one for each direction), this function is able to handle all four directions using only two branches. We do this by always drawing horizontal lines from left-to-right and vertical lines from top-to-bottom. To do this, we "sort" the start and end positions so that we have a minimum value and a maximum value. We then draw the line from the minimum position to the maximum position. (Another way of thinking about this is that East strokes get drawn as-is, but West strokes get converted into an East stroke that runs from the original end point to the original start point.)
Take a look at the two branches of the if
statement: a horizontal or a vertical line. (We could have replaced the else if
with an else
, but the use of an explicit else if
makes a bit clearer what is going on in this case.) In each branch, you first need to figure out the minimum and maximum values for the coordinates of the line. Once you have the coordinates figured out, write a loop that draws the line between. Then do the same thing for the other branch.
Step 13
That's it! At this point, test your program thoroughly.
Tester
We will provide you with a simple tester program that will check the format of your output, but not your calculations. Details will be posted here shortly. The tester is now available. See the Announcements and Updates section for details.
Submission
Submit the file drawing.c
via MarkUs before the due date. Late submissions will not be accepted.