CSC230 Homework 4

This homework is to be done individually.

You may use any functions in the standard library on this assignment.

Updates:

This assignment includes extra credit -- all portions of this document relating to the extra credit will be in a green box like this one. You can safely skip these boxes until you're ready to look into pursuing the extra credit. Warning: Don't let your pursuit of extra credit mess up your primary program!

Note: this is the first semester this particular assignment is being used. Please report any unclear explanations, mistakes, or issues to the instructors.

Learning Outcomes

Introduction

When studying a process, such as a fire fighting training simulator, one can break apart the world into a group of smaller pieces (that may also be called “cells” or “sites”) that are somehow related. Each piece corresponds to an area (or volume) in the world. Each piece can be associated with one of several possible states at any given time. One convenient way to lay out the world is as a rectangular grid of cells. Rules specify how a cell changes state over time based on the states of the cells around it.


Conway's Game of Life -- the 'machine' near the top is manufacturing 'gliders' that fly into the distance. Source.

A computer simulation involving such a system is a cellular automaton. Cellular automata have shown that it is possible to give rise to fantastically complex systems just by following a set of simple rules. The first well-known cellular automaton was Conway's Game of Life (see animation, right). This complexity that sprung forth from that simple system gave rise to the entire research field of cellular automata.

Cellular automata are dynamic computational models that are discrete in space, state, and time. We picture space as a one-, two-, or three-dimensional (also sometimes called an array or lattice). A site (or cell), of the grid has a state, and the number of states is finite. Rules (or transition rules) specifying local relationships and indicating how cells are to change state, regulate the behavior of the system. An advantage of such grid-based models is that we can visualize through informative animations the progress of events. For example, we can view a simulation of the movement of ants toward a food source, the propagation of infectious diseases, heat diffusion, distribution of pollution, the motion of gas molecules in a container, or fire propagation.

For this assignment, we will not be implementing Conway's Game of Life, because computer science students have been writing this thing for over 40 years now, and I'm sick of it.

Instead, we will be implementing another two-dimensional cellular automaton called Brian's Brain, which looks more like this:


A demo of the "fancy" extra-credit console animation version of the Brian's Brain assignment, which looks pretty cool.

Program Requirements

Program Input

The program will be able to be launched two different ways, as described by its usage message: "./brain <max_steps> <size_x> <size_y> [probability_alive] [seed]\n" "\n" " Random mode: Run a Brian's Brain simulation on a random matrix.\n" " - max_steps: Maximum number of timesteps to run for. Negative values disable\n" " the limit, and a value of 0 just displays the initial state before exiting.\n" " - size_x, size_y: Dimensions of the matrix.\n" " - probability_alive: initial probability of a cell being CELL_ALIVE,\n" " a floating point number between 0 and 1 (optional, default: 0.50).\n" " - seed: the randomization seed to use in generating the matrix, a\n" " decimal integer (optional, default: current epoch time).\n" "\n" "./brain <max_steps> <input_filename>\n" "\n" " File mode: Run a Brian's Brain simulation on the matrix in the given file.\n" " - max_steps: Maximum number of timesteps to run for. Negative values disable\n" " the limit, and a value of 0 just displays the initial state before exiting.\n" " - input_filename: Binary file containing the matrix information.\n" "\n" There are of course a number of invalid inputs the user is capable of inputing:

Specifically, if an invalid input is attempted, an error message should be displayed, and the proper exit code returned:

Input Error Error Message Exit Code
Insufficient (or too many) command line arguments (The usual usage message shown above) 1
Unable to parse max_steps as an integer "Error: Invalid max_steps value (must be an integer)\n" 2
Unable to parse either size_x or size_y as an integer "Error: Invalid size value(s) (must be integers)\n" 3
Either size_x or size_y is outside of legal bounds (<1 or >120) "Error: Size out of range (must be in [1,120])\n" 4
Unable to parse probability_alive as a double "Error: Invalid probability_alive (must be a floating point value)\n" 5
Probability outside of legal bounds (<0 or >1) "Error: Probability out of range (must be in [0,1])\n" 6
Unable to parse seed as an integer "Error: Invalid seed (must be an integer)\n" 7
The given input_filename cannot be opened (not found, permission error, etc.) "<input_filename>: <Standard UNIX error message>\n"
(i.e., the common UNIX behavior of calling perror(filename). Also, this message is be printed on standard error, whereas the rest go on standard output -- this oddity is due to an oversight on my part which I am now formalizing)
8
The given input file has an invalid format "<input_filename>: Invalid file format\n" 9
The given file has a size value outside of range (<1 or >120), or has a state entry not in the set {0,1,2} "<input_filename>: Illegal value in file\n" 10
Another unacceptable condition occurs not specified in this document due to a mistake or omission in these requirements (other) 11

Cellular Automation Initialization

The program will simulate the Brian's Brain cellular automaton. The world is modeled as an size_x × size_y grid. The cells in the grid may contain one of three values: 0 (dead), 1 (dying), or 2 (alive).

Random mode initialization

In random mode, the random number generator will be initialized using the provided seed, or the current epoch time in seconds if no seed is provided. Then, a grid of the requisite size will be initialized. Each cell has a probability of probability_alive of being alive (2), else it is dead (0). No cells are initialized to the dying state (1).

Note that we will be testing your random initialization functionality by exact-equality testing, meaning that your output must precisely match ours. This means that your random initialization function must generate and consume random numbers according to a specific algorithm (see "Generating Probabilities" later in this document). This means that there are many ways to correctly implement the base requirement above while still not passing the assignment test cases. Therefore, the following is also a design requirement of your random initializer:

Random initialization must proceed in sequence of increasing sequential memory addresses, with each cell being alive if and only if rand()<RAND_MAX*probability_alive.

File mode initialization

The matrix file is in a binary file format as follows, given in an old-school ASCII art table:
    OFFSET   LENGTH (bytes)   TYPE      VALUE
   =========================================================
    0x0      4                int       size_x
    0x4      4                int       size_y
    0x8      size_x*size_y    char[]    cells of the matrix
The cells are given in row-major order.

Simulation

The simulation models how the state of the cells in the world change over time. Simulated time is represented in discrete units, such as one minute, one hour, or one day. Note that simulated time is not the same as actual time (often called wall-clock time). It may take the computer only a few seconds to run through a simulation of thousands or millions of simulated timesteps.

During each timestep, the simulation calculates what the state of each cell will be at the beginning of the next timestep, cell by cell. The simulation ends either when the current timestep number is equal to max_steps, or if all the cells in the simulation are in the dead state. The simulation will display what the world looks like at each timestep as shown in the sample output below (notice at timestep 6, there are no cells with a value other than 0 (dead), which is the terminating condition).


The Moore neighborhood. Source.
When modeling cellular automata, the cell will update based on the state of its neighbors (each cell maintains itself). The rules are as follows:
  1. If a cell is ALIVE (2) at a given timestep, it becomes DYING (1) at the next timestep.
  2. If a cell is DYING (1) at a given timestep, it becomes DEAD (2) at the next timestep.
  3. If a cell is DEAD (0) at a given timestep, it becomes ALIVE (2) at the next timestep if and only if the number of living neighbors is exactly 2.
The concept of neighbors in this case refers to adjacent cells in what is known as the Moore neighborhood, which is just a fancy way of saying "the 8 cells within one hop of this one, including diagonally".

Further, references at the edges shall wrap around (like in Pac-Man), meaning that a reference to position one left of (0, 0) will yield the position (size_x-1, 0).

Note that each simulation timestep takes place all at once, i.e., the matrix at once timestep creates an entirely new matrix for the next. Thus, the outcome does not depend on the order in which cells are evaluated.

Output format

Output for each timestep shall be formatted as follows:
  1. The first line is a header line showing the current timestep using the format "Timestep %d\n". Zero is the initial state.
  2. Each cell should be displayed using the format "%2d ".
  3. A single newline should be emitted after each row.
  4. One final newline should be emitted after the full matrix.

Sample Execution


The same sample output, animated.

A sample execution is shown below. The output must match exactly to facilitate automated grading. Typed content is shown in glowy blue.

This run shows a random-mode 6×4 matrix running a maximum of 100 steps. Each cell as a 10% probability of starting alive, and the random seed is 102. Note that because the random seed has been specified, your program's output should match this output exactly.

An animation of the extra-credit variant of the program running the same scenario is shown to the right, slowed down for easier review. $ ./brain 100 6 4 0.1 102 Timestep 0 2 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Timestep 1 1 0 0 0 0 1 2 0 0 0 0 2 0 0 0 0 0 0 2 0 0 0 0 2 Timestep 2 0 2 0 0 2 0 1 0 0 0 0 1 0 2 0 0 2 0 1 0 0 0 0 1 Timestep 3 0 1 0 0 1 0 0 2 2 2 2 0 0 1 0 0 1 0 0 2 2 2 2 0 Timestep 4 2 0 0 0 0 2 0 1 1 1 1 0 2 0 0 0 0 2 0 1 1 1 1 0 Timestep 5 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 Timestep 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Extra credit: console animation

This requirement is optional, and provides up to +10 additional points. Because this is a stretch goal, less detailed how-to is provided; students are invited to explore how to implement this feature only after they have mastered the rest of the assignment.

What's the point in doing a cellular automaton if you're not going to get cool graphics out of it? We aren't quite ready for graphics programming, but we can cheat with the time honored method of console art. You will receive up to 10 points extra credit if you submission includes code to do the following on the console:

More specifically, applicants for this extra credit will have an additional Makefile target called brain_fancy which compiles using the -DFANCY option. Internally, the code will contain preprocessor directives (such as #ifdef) to perform the alternate animation behavior instead of the default numeric output tables. Console animation will be achived using ANSI escape codes. This functionality will be tested manually -- no Jenkins tasks exist to evaluate it. For more information, see "Controlling the console" under "Implementation" later in this document.

To be precise, brain_fancy will:

  1. Take the usual command line parameters as specified above.
  2. Clear the screen.
  3. Display the initial state of the simulation (timestep 0) as follows:
    • First the timestep number is shown in the default terminal coloring (code 0) in the same manner as the standard program.
    • Each cell will be represented by two spaces, colorized as described below.
    • Dead cells will have a black background (code 40).
    • Dying cells will have a blue background (code 44).
    • Alive cells will have a cyan background (code 46).
  4. If all cells are dead or the timestep is equal to max_steps, exit.
  5. Advance the state of the simulation ot the next timestep.
  6. Delay execution for 0.1 seconds.
  7. Home the cursor to the upper-left of the screen.
  8. Display the current state of the simulation as described in step 3 above.
  9. Loop to step 4.

NOTE: Do not let your pursuit of extra credit damage the primary program! Your normal program and your test program must build normally and emit no escape codes. You must reasonably complete the primary objectives to be eligible for this extra credit.

Design

The following function prototypes are REQUIRED // Print the matrix the format described. // (For extra credit, if FANCY is defined, this prints in the // format described in the extra credit section.) void print_matrix(char* mtx, int size_x, int size_y, int timestep); // Take the given matrix of the given dimensions and initialize it using the // given probability of cells being alive vs. dead. void init_matrix_random(char* mtx, int size_x, int size_y, double probability_alive); // Update the provided matrix of the given dimensions to the next timestep. void matrix_advance(char* mtx, int size_x, int size_y); // Simulate a sequence of timesteps based on the given initial matrix of the // given dimensions, printing the matrix after each timestep, 0 being the // initial timestep. This should iterate until all cells are dead or the current // timestep equals the given max_timesteps. // (For extra credit, it seems likely this function will vary depending on FANCY) void simulate(char* mtx, int size_x, int size_y, int max_timesteps);

You may design the rest of your program however you like. You must have at least 3 other functions in addition to your main function and the functions required above.

Implementation

Matrix representation

The matrix shall be represented as an array of chars. The size of this array shall be size_x × size_y. You may declare it as a two-dimensional array, but in that case you'll have to cast it to a one-dimensional pointer before you can use it, because of the pointer constraint below. Alternately, you may declare it as a one-dimensional array of size_x*size_y elements.

Constraint - Pointers ONLY!

IMPORTANT: The brain and brain_test programs MUST be implemented using ONLY pointers, except for array declaration.

We will grep for array brackets and remove up to 40 points depending on the level of array bracket usage.

An example of an array declaration would be a statement like: int array[4][5];

An example of an array usage, WHICH IS NOT ALLOWED, would be statements like the following:

To help check for this, a scripted check for brackets is included in the Jenkins test script; it should warn you if you have used brackets in a non-array-declaring way. Note that it is a simple script, and so if your code is weird enough, it may have false positives or false negatives. If potentially illegal brackets are found, the script will report the filename and line number.

Generating Probabilities

To illustrate how probability can be determined during a simulation run, suppose the probability of a cell being alive is 55% = 0.55. We can use rand() to return a number that is equally likely to be anywhere between 0 and RAND_MAX. Dividing the result by RAND_MAX generates a uniformly distributed random number between 0.0 and 1.0. If the number is less than 0.55, we assume that the cell is alive. If the generated value is greater than or equal to 0.55, the cell will be declared dead.

Further, recall from the requirements that "Random initialization must proceed in sequence of increasing sequential memory addresses". This implies that your algorithm should look something like the following:

for i in range [0,size_x * size_y): if rand() < RAND_MAX * probability_alive then Mi ← ALIVE else Mi ← DEAD end if next

Magic Numbers

All "magic numbers" that you use (for example, the default values, cell states, and exit codes) must be defined as constants. For example:

#define CONSTANT_NAME 3

Get your environment set up

Note: If you successfully cloned your GitHub repo for HW2, you can skip to the section "Get the starter files". Make sure that you're developing in the 4_homework directory!

Start by cloning the provided CSC230 GitHub repo in your local AFS (or development space) using the following commands:

$ unset SSH_ASKPASS
$ git clone https://<unity_id>@github.ncsu.edu/engr-csc230-summer2015/<repo_name>.git

This will create a directory with your repo's name. If you cd into the directory, you should see directories for each of the homeworks for the class. You'll want to do all of your development in 4_homework. If you do NOT see the 4_homework directory, enter the following command to make the directory:

$ mkdir 4_homework

Get the starter files

Next, you will need to copy the hw4_starter.tgz from the course locker into your 4_homework directory. Use the following command:

$ wget

Untar using the command:

$ tar xvzf hw4_starter.tgz

Set up your Makefile

No Makefile is provided -- you will also need to write this. For automated grading to work, the Makefile must have the following characteristics:

Write your code

Your code should be in a single file called brain.c. You must use the appropriate name for automated grading.

We have provided some files to help you start your program:

In addition, to complete the assignment, you will need to create (at least) the following new files:

NOTE: The provided files are NOT fully commented and may not conform to all style rules for the course. You must update the file(s) that are part of your brain program to meet the style guidelines! (You need not document the create_brain program, and the brain_test.c code only needs a top-of-file comment.) You MAY use magic numbers in your test program. There will be no magic number check on brain_test.c.

Testing

This program will require both unit testing and system testing; see the Testing section below for full details.

Pushing changes


This lad knows the value of keeping his github repository up to date! Source.

When you are ready to commit to your local repository, execute:

$ git add .
$ git commit -am "a meaningful message for future you"

When you are ready to submit to the remote repository for automated grading or teaching staff feedback, execute:

$ git push

NOTE: It is considered good practice in industry to commit locally very frequently, and to push whenever you have a functioning unit of work. This is doubly true when you have a continuous integration system like Jenkins to constantly provide feedback on compilation and test completion!

Extra credit: Controlling the console

This information pertains ONLY to the extra credit console animation requirement -- you can skip this until you decide you want to try to tackle it.

Escape codes and colors

There are a series of special sequences that, if printed to the console, can be used to clear the screen or change colors. These are called ANSI escape codes. Here's a screenshot of a little program I use to look up codes:

As described above, '\e' is a special character called the escape character. Like '\n' or '\a', it's a special signal to the terminal to do something. This characater is ASCII value 27, and when followed by a left bracket ('['), it indicates that an escape sequence consisting of numeric codes separated by semicolons follows, which is then terminated by an 'm' character. For example:

With this, you can draw a pretty colorized version of the simulation. For example, the following code emits the image below.

int main() { printf("\e[37;1m\e[40m 0 \e[40m 0 \e[40m 0 \e[40m 0 \e[40m 0 \e[m\n"); printf("\e[37;1m\e[40m 0 \e[40m 0 \e[40m 0 \e[42m 1 \e[40m 0 \e[m\n"); printf("\e[37;1m\e[40m 0 \e[41m 2 \e[42m 1 \e[42m 1 \e[40m 0 \e[m\n"); printf("\e[37;1m\e[40m 0 \e[40m 0 \e[42m 1 \e[41m 2 \e[40m 0 \e[m\n"); printf("\e[37;1m\e[40m 0 \e[40m 0 \e[40m 0 \e[40m 0 \e[40m 0 \e[m\n"); return EXIT_SUCCESS; }

Obviously this code has no loops, arrays, pointers, etc., but you should be able to use it to see how to draw a colorized simulation.

Clearing the screen

Next, we need to be able to clear the console between frames. There's another escape sequence to do this. I never bother remembering it, because the UNIX command "clear" emits it. We can discover this "magic" sequence of characters by feeding the output of "clear" to our old friend "hexdump":

% clear | hexdump -C -v 00000000 1b 5b 48 1b 5b 32 4a |.[H.[2J| 00000007

We can translate this to the C string "\e[H\e[2J". Now we can clear the terminal at any time by printing that special string.

Re-homing the cursor

While it is true that we could do the animation by clearing the screen between each frame, that can induce "flicker", as the split second of time between the clear and rendering the next frame can be visible. Therefore, we'll do a slightly different technique between frames -- re-homing the cursor. This means moving the cursor to the upper left of the console without affecting the current content.

In fact, we've actually already learned how to do this -- it's part of the process of clearing the screen. It turns out the that clearing the screen actually consists of two steps: the \e[H re-homes the cursor, and the \e[2J actually removes the text from the screen. It's possible to do either of these operations independant of the other.

Therefore, re-homing the cursor is as simple as printing "\e[H".

Adding a delay

Finally, we need to be able to pause execution of our program to show one frame at a time for a while. There's no function for this in core ANSI C99, but there are a few functions to do so in the POSIX standard (a standard of basic system calls many operating systems have agreed upon).

For example, the sleep() function is extremely common -- it can pause the program for an integer number of seconds. Unfortunately, this function won't work for us, as we're supposed to pause for 0.1 seconds. Instead, you should investigate the usleep() function.

To learn how to use this function, look up "man 3 usleep" and read the man page -- be sure to include the approprite .h file. Man pages are also available online -- I usually look them up at linux.die.net. HINT: If you're getting warnings about implicit declaration, you may want to read this.

Testing

For Homework 4, you will write unit and system tests and run them against your Brian's Brain application.

Unit Testing

C only allows a single main per compiled executable. When unit testing, we typically create a separate file for our tests; however, with C, we're unable to compile together two files that each have a main. To mitigate the problem, we will do something a bit weird -- we're going to compile brain.c, rip the main out of the binary using the strip utility, then compile what's left against a separate brain_test.c file containing your unit tests. The recipe for this unholy abomination is given above in the section titled "Set up your Makefile".

In the future, we will learn how to develop proper multi-file programs, and this weird technique will no longer be needed.

You will write the following unit tests:

Function Number of Additional Unit Tests Notes
matrix_advance() 4 Each unit test must consider a different equivalence class for the Brian's Brain functionality. Don't forget to consider corner cases, such as all alive, all dead, etc., as well as literal corner cases, i.e. situations where wrapping occurs.
init_matrix_random() 4 Build a test function that examines the results statistically, i.e. is the produced number of alive cells close to the expected number? Without getting too deep into statistics, if you run 5000 calls of the function to populate a 5×10 matrix of 50 cells, the odds are overwhelmingly good that the average number of alive cells over all the runs is within 1.0 of the expected value, probability_alive*size_x*size_y.

A more rigorous approach would be to determine the confidence level that the measured distribution matches the expected one, but that is beyond the scope of this course -- a pass/fail check that the average is within a margin of the expected value will suffice.

Once you have such a test function, use it for at least 4 different values of probability_alive.

Use the provided unit tests to help you write your own. Each unit test should be in its own function, and that function should call the relevant brain.c function, then use one of the given assert_* functions to check results. You may also write new assert_* functions to do other kinds of comparisons if needed. You should not modify main except to add calls to the tests you write.

Your System Testing

You will also submit at least two additional test cases that will be stored in files similar to the teaching staff system tests, described below. Adding a test case entails:

Each of the tests that you submit must come from different equivalence classes or boundaries AND at least one test must come from different equivalence classes or boundaries than the instructor provided tests (hint: the instructor provided tests don't consider all boundaries). Of course, if your test is from the same equivalence class as an instructor provided one, you should use different inputs. You will lose 5 points for each test that doesn't meet the above restriction even if the test is passing.

Remember, you can create an extensionless file by typing the Linux command touch filename.

Teaching Staff System Testing

Some test cases for the program are provided. We have also provided a script, test.sh, that will execute the provided tests and report the results. There should be no difference between your actual output and the expected outputs. Grading will test additional inputs and outputs, so you should test your program with inputs and outputs beyond the provided example. You should edit test.sh to add your student test cases.

When you are ready to run the full suite of tests, execute:

$ chmod +x test.sh
$ ./test.sh

The first command, chmod, sets the permissions of test.sh to allow for execution. You only have to run this command once.

Checking Jenkins Feedback

We have created a Jenkins build job for you. Jenkins is a continuous integration server that is used in industry to compile, build, and test applications under development. We will be using Jenkins to compile, build, and test your homework submissions to GitHub, which will provide early feedback on the completeness (does your implementation meet the requirements) and quality (does your implementation correctly implement the requirements).

Your Jenkins job is associated with your GitHub repository and will poll or query GitHub every two minutes for changes. After you have pushed code to GitHub, Jenkins will notice the change and automatically start a build process on your code. The following actions will occur:

Jenkins will record the results of each execution. To obtain your Jenkins feedback, do the following tasks:

NOTE: Jenkins will NOT execute any tests without evidence that you have run the tests locally. We require that at least one file matching the regular expression output*-actual is pushed to GitHub for automated testing to run.

No Jenkins tests exist to evaluate the extra credit console animation program.

Instructions for Submission

The following files MUST be pushed to your assigned CSC230 GitHub repository:

By submitting the actual results from the tests, you will prove that you have tested your program with the minimum set of the provided acceptance tests. Automated grading will not run on your program without at least one generated actual result file (it doesn't mean your tests have to pass, just that you've attempted the tests locally before pushing to your repo).

Additional Considerations

Follow the CSC 230 Style Guidelines.  Make sure your program compiles on the common platform and Jenkins cleanly (no errors or warnings), with the required compile options.

There are certain learning outcomes and basic software engineering skills that this assignment is assessing. See the rubric below for deductions may be applied to your submission as enforcement of good software engineering practices and assignment intentions.

Make sure that you push your code to the GitHub repository provided for you! Pushing your code to GitHub is your submission!

There is a 24 hour window for late submissions.  After the main deadline, continue to submit to GitHub. We will use the last commit to GitHub before the late deadline for grading and the timestamp of that commit will determine a deduction, if any.

Rubric

brain.c

System Level Evaluation (45 points)

+5 for brain compiling on the common platform with gcc –Wall –std=c99 options with no warnings

+20 for passing teaching staff system test cases (provided tests + additional tests), which demonstrates a correct implementation.

+20 for your two system tests working on your solution

-3 per test not following test rule: Each of the tests that you submit must come from different equivalence classes or boundaries AND at least one test must come from different equivalence classes or boundaries than the instructor provided tests (hint: the instructor provided tests don't consider any boundaries). Of course, if your test is from the same equivalence class as an instructor provided one, you should use different inputs.

Unit Level Evaluation (40 points)

+5 for brain_test compiling on the common platform with gcc -DTESTING -Wall -std=c99 options with no warnings.

+20 for passing teaching staff unit test cases, which demonstrates a correct design.

+15 for good, passing unit test cases

Global Evaluation (20 points)

+20 for comments, style, and constants

-5 for meaningless or missing comments

-5 for inconsistent formatting

-5 for magic numbers

-5 for header comments that are missing, incomplete, or lacking a name

Extra Credit (+10 points)

+5 for partial completion (use of color and/or delayed display of frames)

+5 for full completion (works as shown in the animation in the requirements)

Note: You must reasonably complete the primary objectives to be eligible for this extra credit.

Total: 105 points

Global deductions FROM the score you earn above:

Up to -40 points for using array indexing rather than pointer arithmetic as described in the implementation section.

-10 points for files (brain.h, brain.c, brain_test.c, output*-expected, Makefile, and all of the actual outputs generated through testing (output*-actual) that are named incorrectly.

-20 points for late work, even if you submit part of the assignment on time.

You can not receive less than a 0 for this assignment unless an academic integrity violation occurs.