COMS10016 I Week 10-11 I Summative Coursework 02:
SKETCH CHALLENGE
The coursework over approximately the next two weeks counts 30% towards your credit for the COMS10016 unit. The coursework brings together various aspects of the course: file handling, bit manipulation, data structures, dynamic allocation, function pointers, callbacks, and graphics alongside most of the basic C features. You will also get to work on a small multi-module program (though you only have to develop one module called sketch. c).
For this coursework you must work individually. All code you submit must be your own, do not allow others to see your code and do not copy any code parts from peers or other sources. As before, do not paste code snippets or solution details anywhere - this would amount to plagiarism or invite it. The programs we use for checking against the web, your peers and other sources are advanced. Plagiarism may result in 0 marks for the coursework, the entire unit or worse. So just do the right thing an do not attempt to plagiarise, not only due to the serious consequences this has. Remember our lecture in week one re study 101 and have another look at the notes on plagiarism at the top of the unit website again. The assignment is designed to cover approx. 15-20h of work for an average student to achieve an average mark.
Use only standard libraries, if any, so your code compiles and runs without linking any extra libraries except SDL2. You must use the provided display module only for graphics and make no direct calls to the SDL2 library at all. In contrast to former courseworks, this week's task has three closed-task stages which are each autotested - allowing you to check for yourselves that you secured overall marks of 50%, 58%, and 63%, respectively. Beyond that there will be an open-ended task as usual. Make sure you have finished last week's formative task and all other formative tasks before starting this coursework.
Step 1: Sketch File Formats and Skeleton Code
A Sketch File with the extension (. sk) is a simple binary file that contains a drawing or graphic. There is a basic, intermediate, and advanced version of the file format, each of which is backwards-compatible to include all simpler formats as subsets of the functionality. The formats have been designed to store simple freehand sketches and more complex graphics, even animations in one compact file, in such a way that these can be viewed or replayed as an image or even animation.
Your task will be to develop a program sketch. c which reads in and visualises . sk files using the display module displayfull. h for graphics, a module nearly identical to the one covered in last week's formative exercise. You are not allowed to change the given display module, testing module, nor the function signatures and data structures already provided in the sketch skeleton and header. You are of course allowed to implement functions and add any new function you would like outside the given function signatures and data structures to the sketch. c file. You are given the following files and modules to start your development: sketch.zip (All Files in one Zip apart from Mac+M1 Makefile) sketch.h (skeleton header) sketch.c (skeleton) Makefile (For Linux and Windows WSL, some old Macs) IMPORTANT: For most Macs and M1 Macs compile the sketch viewer and testing suite, respectively, via: clang -std=cll -Wall -pedantic -g sketch.c displayfull.c -o sketch - fsanitize=undefined -fsanitize=address 'pkg-config --libs --cflags sdI2' clang -DTESTING -std=c11 -Wall -pedantic -g sketch.c test.c -o test -fsanitize=undefined -fsanitize=address 'pkg-config --libs --cflags sdI2' displayfull.h (display header) displayfull.c (display module) test.c (testing framework) sketch00.sk, sketchnsk, sketch02.sk, sketch03.sk, sketch04.sk, sketch05.sk, sketch06.sk, sketch07.sk, sketch08.sk, sketch09.sk As you see, this assignment is going to involve lots of files. It is suggested that you create a new empty folder to work in and extract all files contained in the sketch.zip file into it before you start work. Make sure that the folder permissions are set to owner read only if you work on university computers to make sure nobody else can access your work. Back up your work regularly in any case, since data loss is not a valid extenuating circumstance.
Step 2: Understanding the Basic Sketch File Format
A Basic Sketch File contains a picture made up of white lines drawn on a black background. For simplicity we will stick with a fixed image size of exactly 200x200 pixels for this assignment. Sketch files are encoded as a sequence of single-byte commands, which your program will have to translate into calls to the display module. During drawing a basic sketch file, your program will have to keep track of a current drawing state, which is a data structure defined in sketch. h. This contains the current pixel location (x,y) in the window (which must be initialised as location (0,0) at the beginning of reading a sketch file), a pixel target location (tx,ty) (which must be initialised as (0,0) at the beginning of reading a sketch file), and the currently set tool type (which is initialised as LINE at the beginning of reading a sketch file). the remaining six bits determine the operand (i.e. what data to do it with):
- TOOL lf the two most significant bits (or opcode) of a command byte are 1 (most significant bit) and 0, respectively, then the command is interpreted as the TOOL command. This command selects a tool and uses the remaining 6 bits as an unsigned integer operand, which indicates the type of tool. For a Basic Sketch File the tool type can either be NONE = 0 (switching all drawing off) or LINE = 1 (switching line drawing on).
- DX.... lf the two most significant bits of a command byte are both 0 then the command is interpreted as the DX command, that shifts the position of the target location (tx,ty) along the x direction by dx pixels. The operand dx is specified between -32 and 31 encoded as a two-complement integer via the remaining 6 bits of the command byte.
- DY.... lf the two most significant bits of a command byte are 0 (most significant bit) and 1, respectively, then the command is interpreted as the DY opcode, that is shifting the position of the target location (tx,ty) along the y direction by dy pixels. Then, and only if line drawing is switched on, a line is drawn from the current location (x,y) to the target location (tx,ty). Finally, the current location is set to the target location in any case. (If line drawing is switched off then nothing is drawn, but the current location is still changed to the target location). Note that dy is a value between -32 and 31 encoded as a two-complement integer via the remaining 6 bits of the byte.
A number on the first column like 0000000 says what byte position in the binary file the line of output refers to, which is useful for larger files. The rest of the line shows you the bytes in HEX, that is two paired hexadecimal digits for each byte. We can see that the sketch00. sk file contains just two bytes, '1 e' and '5e'. Each of these represents a single-byte command.
The remaining rightmost six bits represent the operands for each of these commands encoded as two-complement representation, in both cases these are '011110' which represents +30. If needed, review Bits Lecture at this point and remember that the leftmost of the six bits is indicative of the sign. Note that only 6 bits are used to represent the operand, not 8, so only operands between -32 and 31 can be encoded. Thus, the sketch00. sk file has the commands 'DX+30' and 'DY-F30'.
Step 3: Understanding the Skeleton Code
COMPILING, RUNNING AND TESTING YOUR CODE: In order to compile and run the provided skeleton files with graphics use the appropriate Makefile for your system, for instance: make sketch After this you can run the program on a sketch file like: . /sketch sketch00. sk It is essential that you test your program every time you change your code. If you do not test your program, you cannot know whether it is correct or not. Just as the proof of the pudding is in the eating, the proof of the program is in the testing. Be warned, if you do not test your program, you will almost certainly fail this coursework, simply because your code will be too broken to mark. You can run the tests without even having graphics fully installed by compiling and running the provided skeleton files with 'make', along with the correct Makefile for your system, with this command: make test After this you can test your program (which requires all the 10 sketch files for testing to be in your local folder): . /test We will use exactly the commands in the provided Makefile to test your code (with our fresh test. c copy), so do not change the compilation commands. It cannot be emphasised enough: you must test your code! STRUCTURE OF THE SKETCH PROGRAM: The sketch. h file summarises the API of the sketch module. If not compiled for TESTING, the main (... ) function of sketch. c simply checks that a single argument is provided for the filename and then calls the view (... ) function with the provided filename (of the sketch file to view) as argument:
ifndef TESTING
int main(int n, char *args[n]) { if (n != 2) { // return usage hint if not exactly one argument printf ("Use . /sketch file\n"); exit(1); } else view(args[1]); // otherwise view sketch file in argument return 0; 1
endif
Exactly like in the excercises last week, in the view (... ) function a new display window is created, some state is created and then the run (... ) function of the graphics module is called and handed our function processSketch(... ) via a function pointer in order to be called back by run (... ) repeatedly. Once this run (... ) function returns, both state and display are freed: void view(char filename) I display d = newDisplay(filename, 200, 200); state s = newState(); run(d, s, processSketch); freeState(s); freeDisplay(d); 1 During the execution of the program our processSketch(... ) function is called repeatedly. It will be your task to implement it so that everytime it is called it opens the sketch file, reads the commands and draws the full picture, shows it in the window, and then closes the file. The getName (. .. ) function of the display module can be used to get the filename of the sketch file to open: bool processSketch(display d, const char pressedKey, void *data) {
//NOTE: TO GET THE FILENAME... char filename = getName(d); //NOTE: DO NOT FORGET TO CALL show(d); AND TO RESET THE DRAWING STATE APART FROM // THE 'START' FIELD AFTER CLOSING THE FILE (pressedKey == 27); Finally, to interpret the single-byte commands of a sketch file there are three helper functions which we will focus on first: // Extract an opcode from a byte (two most significant bits). int getOpcode (byte b) {...} // Extract an operand (-32. . 31) from the rightmost 6 bits of a byte. int getOperand (byte b) {. } // Execute the next byte of the command sequence. void obey (display d, state *s, byte op) {. }
Step 4: Implementing a Basic Sketch Viewer (overall 50%)
DRAWING STATE: Your first task is to implement the newState (. ) and freeState (. ) functions. The former should allocate memory for a drawing state structure defined in sketch. h and initialise it as discussed above. The latter function should release this memory.
OPCODES: Your second task is to implement the int getOpcode (byte b) function, which given a single-byte command has to return which of the opcodes DX, DY, or TOOL it represents. The possible opcodes to return for basic commands are defined in the sketch. h header file. Once you have implemented this function correctly the first 10 tests will pass and you should see the output 'Opcode Tests OK.' when testing your program.
OPERANDS: Your third task is to implement the int getOperand (byte b) function, which given a single-byte command has to return the value of the operand represented by the rightmost 6 bits as a two's complement integer. The possible return values range from -32 to 31. When extracting the operand from the byte and storing it in an int remember the sign expansion must be performed correctly (see Bits Lecture for details). Once you have implemented this function correctly the second 10 tests will pass and you should now also see the output 'Operand Tests OK.' when testing your program.
COMMANDS: Your fourth task is to implement the void obey (display *d, state *s, byte op) {. . . }
function. Given pointers to the display and drawing state, and a single-byte command it first has to call the functions to get opcode and operand, and then it has to carry out the command represented. This will mean that you will need to implement each of the three commands. As described above, these may change the drawing state and/or call the
line (... ) function of the display module. You may want to create a helper function for each of the three commands to keep the obey (. . . ) function small. MAke sure you exactly understand what each command should do by reading the description of basic sketch files again before starting to implement this, every detail matters. Once you have implemented this function correctly and the drawing state is allocated, initialised and realeased correctly then the tests up to test 35 should pass and you should now see another line of success output when testing your program.
READING/VIEWING SKETCH FILES: Your final task in the creation of a basic sketch viewer is to implement the body of the processSketch (. . . ) function. Every time the function is called it should open the sketch file as a binary file, read the command bytes and for each byte read ask the obey (. . . ) function to carry out the command. As said before, the getName (. . . ) function of the display module can be used to get the filename of the sketch file to open. Make sure you call the show(... ) function of the display module exactly once after processing the file and obeying the commands in it so that all drawing done can actually be seen in the display window. Also reset the current position, target position and tool of the drawing state back to their initial values either after or before processing your file to comply with the standard. Everytime the processSketch (. . . ) function is called the entire file is processed again. Once you have implemented this function correctly you should see 'ALL BASIC TESTS PASS.' when testing your program. Use the tests in test. c to understand and check the exact call sequences to the display module that your code should produce. You should be able to correctly view