Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit


Department of Computer Science

CMPT 214– Programming Principles and Practice

Assignment 5


General Submission Instructions

Assignments must be submitted using Canvas.

Programs must be written in C conforming to the C11 standard. Everything we teach you is compli-ant with the C11 standard. Things we haven’t taught you might not be, so use only the features of C that we have taught. If you try things you find on other websites (where you should definitely not be looking for solutions) they may not be C11 compliant. Everything you need to solve programming problems can be found in the course materials, or in the assignment itself.

Include the following identification in a comment at the top of all your code files: your name, NSID, student ID, instructor’s name, course name, and course section number.

Late assignments are accepted with a penalty depending on how late it is received. Assignments are not accepted more than 2 days late (the assignment submission will close 48 hours after the due date passes and you will not be able to submit). See the course syllabus for the full late assignment and assignment extension policies for this class.

VERY IMPORTANT: Canvas is very fragile when it comes to submitting multiple files. We insist that you package all of the files for all questions for the entire assignment into a single ZIP archive file. This can be done with a feature built into the Windows explorer (Windows), or with the zip terminal command (LINUX and Mac), or by selecting files in the Mac finder, right-clicking, and choosing "Compress ...". We cannot accept any other archive formats other than ZIP files. This means no tar, no gzip, no 7zip (.7zip), and no WinRAR (.rar) files. Non-ZIP archives will not be graded.

Instructions on "how to create zip archives" can be found here: https://canvas.usask.ca/courses/40242/pages/how-to-zip-slash-compress-your-files


Background

Additional Useful LINUX/UNIX Commands

A very useful command on LINUX/UNIX is the man command. It is discussed in the textbook in Sections 5.3, 8.7.3, and 17.2. It is very helpful in determining what functions exist in a family of C library functions, what arguments a particular function takes, what it returns, how it operates, what return value it has, what type of errors it can generate, etc. There are also man pages for standard commands that you input the shell.

        Commands that we will be using in this assignment are file and nm. file reports what type of information a file contains, while nm lists the symbols that are present in an object module. (An example of an object module is a .o file.) Read about these two commands by logging in to tuxworld and typing

and


Additional Useful C Library Functions

When a programmer wishes to output only a single character to the standard output, printf() is overkill. A useful alternative is the function putchar(). It takes only one argument, the character to output. Example usage is

which will output a single newline character on the standard output.

        The functions scanf() and fscanf() are members of a family of library functions that perform input conversion. They scan input according to a format specification, perform conversions, and store the results in locations pointed to by the arguments that follow the format specification. In the case of scanf() the input is the standard input stream stdin. Function fscanf() has an additional first argument which is the stream from which to read input. Function sscanf() is analogous to fscanf() except that it reads input from a string provided by the programmer as its first argument. Here is an example of using sscanf(). Assume that name, rate, and level are declared to be of the appropriate datatype and size.

After the sscanf(), level will have value 3, rate will have value 1.5, and the string name will have content “sam”. You can read about sscanf() by giving the command

on tuxworld.

        In general one should always be checking the return value from library functions. There are a few exceptions (such as calls to printf()) but for robust C code, one should never assume that a library function succeeded. It might not have. Further, the return value from a library function might have useful information. The return value from a scanf() or sscanf() is such an example. The function calls return the number of items successfully scanned. Therefore in the sample code above, if we had

then we could check the value of nfields to find out that only two items had been successfully scanned and stored (level and rate), and that nothing was scanned and stored in the location pointed to by name.


Environment Variables

The LINUX operating system needs to know where dynamically-linked shared libraries are located; i.e. in which directories to look for shared libraries. On LINUX/UNIX the set of locations (directories) is provided by the setting of the environment variable LD_LIBRARY_PATH. The setting of the variable is a colon-separated list of directories. The directories are searched in the order in which they are specified. You can find out the current setting of LD_LIBRARY_PATH by giving the command

on tuxworld. Note that environment variables are discussed in Chapter 22 of the text, and LD_LIBRARY_PATH is briefly described on page 277 (just prior to section 22.5).


Statically Linked Executables

By default gcc will create a dynamically-linked executable. To create a statically-linked executable, one only needs to provide the “-static” option. For example, if the command

produces the dynamically-linked executable fancy_prog, then

produces the statically-linked version.


Question 1 (11 points):

Purpose: To practice building dynamic and static libraries, and writing a makefile to automate building libraries.

For this question you will build both a dynamically-linked shared library and statically-linked library from source code provided to you. The source code is for two functions. One, rand_num(), generates a pseudo-random number in a specified range. The series of pseudo-random numbers generated by the function will always be the same, however. This is useful if one wishes to have a program use a consistent sequence of pseudo-random numbers, such as when testing or debugging. However, the series of pseudo-random numbers can be altered by changing the seed for the series. For a series of pseudo-random numbers that appears different each time a program is run, one can change the seed each time. The second function provided for you, rand_init(), does this. See the comments in rand_num.h for a description of the functions, the arguments they take, and the values they return.

If you are interested in the C library functions used by rand_num() and rand_init() you are welcome to check the man pages for gettimeofday() and random().

Before beginning this question, make sure that you have covered the Topic 14 and Topic 15 course material.

Some of the tasks below require that you record a log of your activities. Compile that log in a file named asn5q1.log.


Your Tasks

● By hand, compile rand_num.c creating file rand_num_s.o. Then create a static library named librn.a from rand_num_s.o. You do not have to record anything in asn5q1.log as part of this task.

● To verify that your static library is correctly constructed, issue the following commands and record the commands and their output in asn5q1.log.

● By hand, compile rand_num.c creating file rand_num_d.o. Remember that you will need the -fpic option when compiling. Then create a shared library called librn.so from rand_num_d.o. Note that the man page for gcc advocates that you give -fpic when creating the shared library (in addition to -shared). You do not have to record anything in asn5q1.log as part of this task.

● To verify that your shared library is correctly constructed, issue the following commands and record the commands and their output in asn5q1.log.

● Create a makefile that will automate the creation of librn.a and librn.so. The makefile is to be able to make two targets, “librn” and “clean”, in addition to being able to make the targets “librn.a” and “ librn.so”. Target “librn” depends on the targets “librn.a” and “librn.so”, while target “clean” has an empty dependency and dictates the removal of existing .o files in the current directory. Have “clean” as a “.PHONY”. To keep your files organized, make sure to compile to rand_num_s.o and rand_num_d.o, where rand_num_d.o is the object module from which the shared library will be created. Once your makefile is working correctly, make a copy of it called Makefile_q1. You do not need to record anything in asn5q1.log for this part of Question 1.


Implementation Notes

Do not modify the contents of rand_num.c or rand_num.h in any way.


Testing

Make sure to test that your makefile works correctly. However, you do not need to submit any proof of testing.


Question 2 (35 points):

Purpose: To practice creating a C program as multiple modules, gain exposure to additional C library functions, and to further practice controlling automatic building of the source code with a makefile.

In this question you will build software to implement a simple computer game. The game involves a playing field, a treasure located on the field, and a player who — supposedly — is interested in acquiring the treasure. The program is to consist of 3 separately compiled C modules. It will also make use of the functions provided by the library created in Question 1. For the most part, function headers, internal comments, or both are provided to guide your software development and program logic. Your code must adhere to (be consistent with) those provided headers or comments.

The treasure program takes one argument on the command line, the size of the playing field. The playing field is square. Somewhere on that field (i.e. at a random location) is a treasure marked with a ‘$’ character. The player’s starting position is also randomly determined, and is marked by a ‘@’ character. The program prints (on the standard output) the initial state of the playing field, then prompts the player (user) for a command. Commands are single characters. Possible commands are:

● D, d, 2 – move down one row

● U, u, 8 – move up one row

● R, r, 6 – move right one column

● L, l, 4 – move left one column

● E, e, q, Q – quit the game

● P, p – (re)print the state of the playing field

Any other command is diagnosed as invalid. Blank input is ignored. The game will also terminate if an end-of-file is indicated on the keyboard (usually the control-D character). After a legitimate move the game checks whether the player has reached the position of the treasure. If so, a congratulatory message is printed and the game ends.

The playing field is a 2D grid or rows and columns. Position (0,0) of that grid is at the upper left of the field (grid). Other than the markers for the player and the treasure, each position on the field is marked with an asterisk, ‘*’. There is a minimum size for the playing field.


Program Requirements

The program is called treasure. The treasure source code consists of three .c files and corresponding .h files. Those files are treasure.c, treasure.h, field.c, field.h, position.c, and position.h. Since treasure will also be making use of functions in your local librn, rand_num.h will also be relevant.

Input must be handled by a function named get_input() in treasure.c. The function takes no ar-guments, reads a one-character command from the standard input (stdin), and returns that character on success. The character is the first non-white space character found in the input stream. The logic of get_input() consists of the following steps in order:

1. Obtain one line of input via fgets(). If fgets() encountered an error or if an end-of-file was diagnosed, get_input() returns the character ‘q’.

2. Any trailing newline character in the input string is removed.

3. The input string is checked to see if it is empty. If so, get_input() returns the null character, ‘\0’.

4. Function sscanf() is called with the input string as the first argument and the format string "␣%c" as the second argument. If sscanf() was able to scan and store one argument (a character), then that character is returned as the value of get_input(). If sscanf() was not able to do this, get_input() returns the null character, ‘\0’.

Note the first character in the format string that is the second argument to sscanf(). It is a space character. You are welcome to read about the functionality of this character and the rest of the specified format string in the man page for sscanf.

Within function main(), the one-character command must be processed via a switch-case statement similar to that utilized in Question 5 of Assignment 2. Note that the

statement in the solution to that question should not be used in the code for treasure.

The files you must create and submit, and their contents or roles are as follows:

● field.h – defines the datatype field_t, which is a struct consisting of an integer grid_size and a pointer, grid, of type (char **). It also provides the function prototypes of (global) functions init_field() and print_field().

● field.c – implements global functions init_field() and print_field().

● position.h – defines the datatype position_t, which is a struct consisting of two int elements, r and c. Structures of type position_t represent positions on the playing field. This file also pro-vides the function prototypes of (global) functions position_treasure(), position_player(), position_same(), and move().

● position.c – implements global functions position_treasure(), position_player(), position_same(), and move(), as well as a local function random_position().

● treasure.h – defines symbols TREASURE_CHAR, PLAYER_CHAR, FIELD_CHAR.

● treasure.c – defines symbol FIELD_SIZE_MIN, local functions treasure_found() and get_input(), and global function main(). It can contain definitions of additional symbols and local functions. However, any such additional symbols or local functions must be kept to a minimum.

Skeletons of treasure.c, position.h, position.c, and field.h are provided for you in the starter pack. Note that these files will not compile in their initial state. Do not change of the provided code or comments.

Your programming solution must, in some way, make use of all of the functions and modules described above, and the modules in the local rn library. Further, your solution should strive to maximize code re-use and minimize code duplication by calling the prescribed functions in the prescribed modules and libraries rather than calling additional or alternative functions that are not specified here.

Regarding the datatype field_t, the grid (playing field) is implemented using an vector (1D array) of pointers, each of which points to a vector of grid_size elements of type char. If fieldp is a pointer to a structure of type field_t, then a particular position (r, c) on the field can be accessed as fieldp->grid[r][c], as well as many other ways.

You will need to determine what .h files are needed by which .c files. #Include-ing unnecessary .h files will be penalized when code is graded.

Concurrent with writing your source code, modify the makefile from Question 1 so that it will build both a statically-linked version of treasure named treasure_static as well as a dynamically-linked version named treasure. Add to your makefile a target “all” which depends on targets “treasure” and “librn”. Also add a target “deep_clean” which depends on “clean” and then removes any instances of treasure, treasure_static, librn.a, or librn.so in the current directory. Some targets will need to be declared to be .PHONY; make sure they are. If there are automatic rules for linking, make sure that LDFLAGS is appropriately defined. Similarly, if there are automatic rules for compiling, make sure that CFLAGS is appropriately defined. When your makefile is working to your satisfaction, make a copy of it in file Makefile_q2.


Implementation Notes

The task of implementing treasure might seem daunting. However, it can be effectively and efficiently tackled by breaking the overall problen down into logical stages that build towards the final solution. Here is a suggested methodology for implementation of the program modules in this question.

1. Write or complete field.c, field.h and treasure.h, and create a dummy program to test them. (Do not hand in the dummy program.)

2. Complete position.c and position.h, and create a dummy program to test them. (Do not hand in the dummy program.)

3. Write or complete treasure.c and Makefile_q2. Test the completed makefile. Verify that treasure is dynamically linked and treasure_static is statically linked. Test treasure and treasure_static (they should behave the same).

It is up to you to select a suitable value for FIELD_SIZE_MIN.


Testing

Normally, shared libraries would be placed in some system wide area. Unfortunately, this will not be the case for librn.so. To be able to test your dynamically-linked version of treasure you will need to set LD_LIBRARY_PATH to indicate where (in which directory) to find librn.so. Assuming that librn.so is in your current working directory, give the following command to the shell:

You will only need to give the above command once after you log in to tuxworld and cd to the directory with treasure and librn.so.

For the brave, you can also use “-Wl,-rpath=/path/to/dir/with/shared/library” when linking all the .o files instead of setting the LD_LIBRARY_PATH variable. Read about “-rpath” in the man page for gcc if you are interested in this alternative.

Make sure to test treasure as well as treasure_static.

As part of testing, it is instructive to look at the file size of treasure (i.e. the dynamically-linked version) and treasure_static. You should see a substantial difference in size. You should also make use of the file command as you did in Question 1. The output from file should confirm that treasure is dynamically linked and treasure_static is statically linked.


Sample Output

The file treasure.log is provided in the starter pack. It provides examples runs of treasure. The prompt printed by treasure is the ‘>’ character. “ˆD” indicates that a control-D character was typed at that point. The log also shows confirming that treasure is dynamically linked while treasure_static is statically linked.


Question 3 (8 points):

Purpose: To practice using cmake to build software, including function libraries.

In this question you will create a CMakeLists.txt file and use it, along with cmake, to build librn.so from source code, and then to build a dynamically-linked executable treasure from your source code in Question 2 and the newly built librn.so. You will need to provide your CMakeLists.txt as your response to this question, as well as a log showing the output of various commands. That log must be in a file named asn5q3.log.

Recall that cmake is covered in the course materials for Topic 15 and well as in Section 15.3 of the textbook.


Your Tasks

● Create a CMakeLists.txt file that will build librn.so from source code. Then add to CMakeLists.txt instructions to build a dynamically-linked version of treasure from your source code files (treasure.c, field.c, and position.c) from Question 2. In CMakeLists.txt make sure to set CMAKE_C_STANDARD and CMAKE_C_FLAGS to appropriate values.

● Remove, from your current working directory (the one with the source code for librn.so and treasure), any .o or .so files, as well as treasure. If your Makefile_q2 conforms to the specifi-cations described in Question 2, you should be able to do this with the command

Then verify the removal of the above specified files from your current working directory with an ls command. Provide a log of the output of ls in your asn5q3.log.

● Issue the commands

in your current working directory. Provide a log of the output from these two commands in asn5q3.log.

● Finally, using the file command as in Question 1, confirm that files treasure and librn.so have been created and have the correct content. Provide the output from the file command(s) in your asn5q3.log.


Implementation Notes

Remember

● to make use of target_link_libraries in your CMakeLists.txt file as described in Section 15.3.3 of the textbook.

● that you need to give the command “make all” after “cmake” completes.


Testing

Note that the cmake command will overwrite any existing Makefile in your current working directory.


Files Provided

A .zip file archive named asn5_starter_pack.zip containing

rand_num.c and rand_num.h: C source code for Question 1.

treasure-starter.c, position-starter.h, position-starter.c, field-starter.h: C source starter code for Question

2

treasure.log: A sample log of running treasure in Question 2.


What to Hand In

Hand in a .zip file archive that contains the following files:

asn5q1.log: Log of the output called for in Question 1.

Makefile_q1: Makefile called for in Question 1.

treasure.h, treasure.c, position.h, position.c, field.h, field.c: Source code called for in Question 2.

Makefile_q2: Makefile called for in Question 2.

CMakeListst.txt: CMakeLists.txt called for in Question 3.

asn5q3.log: Log of the output called for in Question 3.