Code Correctness
In CS 3, we expect you to pay careful attention to the quality of your code (not just if it “works”). Each week, we will expect you to apply all the items we’ve been looking for in every previous week in addition to the new ones.
So, far, we’ve asked you to pay attention to:
- good variable and function names
- procedural decomposition
This week, we will focus on all of the previous items as well as the following:
- “overcommenting” (e.g., the comments do not explain things that are already clear from the code itself)
- usage of whitespace
- overmodularization (e.g., modularizing into functions that actually make the code less clear)
Goals and Deliverables
In this project, you will begin writing the codebase you will be working with over the next several weeks. As groups combine, we will ask you to choose pieces of each person’s codebase; so, it is entirely possible that you will still be using some of this code in week 10 of the term. This week, you will build primitive physics engine objects, a data structure, and a simulation that uses your work.
- The two most primitive things to represent are two-dimensional vectors and polygons. You will implement the base of both of these this week.
- The main data structure we will need is an
ArrayList
. This week, you will write a simple wrapper for an array, and you will extend it to be more useful in future weeks. - We think it is important to “see” your work as you go. So, every week, we will ask you to write a small simulation that uses the work you’ve completed that week. These will get ever closer to “correct” physics as we go! This week, you will make a star that bounces off of the walls of the screen.
vector_t
Last week, you wrote a simple set of functions that dynamically allocated memory for a (2D) vector_t
using malloc
, freed this memory, and added two vector_t
s. For all the reasons
discussed in class, it is not ideal to allocate all the vector_t
s this way. So, this week, you’ll be writing a more extensive set of functions that act directly on vector_t
s that are
not pointers. Your vector_t
will eventually represent position, velocity, acceleration, and other vector quantities in your physics engine.
Task 0.
Using the documentation in vector.h
, fill in vector.c
with the implementations of the functions outlined in the header files.
vec_list_t
In CS 2, you may remember that we created an ArrayList
in Java from scratch, making it auto-resizable and generic. We will eventually get to the same place in C, but, because C
doesn’t provide as nice abstractions as Java, it’s going to take us a few weeks. This week, since we only need to represent a single polygon, all we need is a fixed-size
ArrayList
of vector_t *
s. Notice that we are using pointers, not values here! This will be important later.
Task 1.
Using the documentation in vec_list.h
as a reference, implement the functionality of vec_list.c
. Your code, as always, should not have any runtime errors or memory leaks.
Polygons
A polygon can be represented as a fixed-size vec_list_t
. We will take the convention that a polygon’s vertices are stored in a counter-clockwise order.
Task 2.
Using the documentation in polygon.h
as a reference, implement the functionality of polygon.c
.
Bouncing Star
Finally, the fun part! Now, you get to build a graphical simulation of a star bouncing around the screen!
Ticks and the Main Run Loop
We’re going to be working through a lot of graphics-related code in this class, so it’s important to understand the basic concepts behind drawing graphical content to the screen.
When we talk about graphics in the context of writing software, we are dealing with computing the contents of individual frames in real-time, clearing the canvas of the previous frame, and drawing the new computed frame, all at regular intervals. If you want your graphics to look realistic, you need to have a reasonable measurement of the time between frames, since our goal is to generate a graphical representation that is independent of the speed at which frames are processed by the computer. This is why low frame rates in graphics-intensive games result in a scene that is choppy – each frame encompasses a large amount of real-world time, causing objects to move large distances in each frame. The most basic implementation of a graphical application, then, looks as follows:
while app_is_running:
dt = time_since_last_frame()
object_pos = compute_new_positions(object_pos, dt)
clear_screen()
draw_frame_offscreen()
display_frame()
We’ve provided a few helper functions to help facilitate writing these functions, you can see how they interface with the lower-level SDL2 library in sdl_wrapper.c
.
Files of Importance
The above basic loop is implemented in emscripten.c
, which relies on a number of functions you will write in your demos - namely, emscripten_init
, emscripten_main
, and emscripten_free
. emscripten.c
serves as the basic functionality of all our demos - you do not need to modify this file. Notably, because emscripten.c
implements the main while loop described above, your bounce.c
file should not have a main loop.
The header file for the functions you write in your demo is state.h
, which tells emscripten.c
that thse three functions are properly defined elsewhere. You do not need to write a state.c
file - you should write the three functions in the header in your bounce.c
demo as mentioned above. Notably, state.h
also includes an opaque type definition for a “state”. This is a struct that you will define in each of your demos (here, in bounce.c
). This state struct is how you will pass information between your three functions - anything you initialize in emscripten-init
that you need in your emscripten_main
loop should be put in this state accordingly and returned. You are responsible for defining the state’s parameters, malloc’ing it in emscripten_init
and freeing it in emscripten_free
.
Using the basic breakdown of the files above, and the documentation in sdl_wrapper.h
and state.h
, you should be able to get a good idea of how to implement a graphical application in C, using our wrapper library. For a reference of a working demo that uses this overarching structure, please consult https://gitlab.caltech.edu/cs3-22sp/emscripten-demo. Although it does not use the same abstractions, the circle.c
demo works with emscripten.c
and state.h
in the same way you will.
A Bouncing Polygon
We are going to specifically be implementing a simulation consisting of a 5-sided star bouncing around the window while rotating. However, since we have not implemented the “physics” part of our physics engine, yet, we’ll be implementing collisions with the window as perfectly elastic collisions that reverse the velocity in the collision direction. In other words, if the star is moving towards the top wall and collides with it, its y-velocity will change direction.
In your bounce file, you should write your emscripten_init
to initialize everything you need for the demo, including the SDL window itself (you will find functions in sdl_wrapper.c
to be useful here). Then, in emscripten_main
, you should update the position and velocity of your star at each frame using this rule, and the time elapsed since the last frame. You should also have your star rotate at a constant rate throughout your animation. When the demo exits, emscripten_free
is called, so you should write this function to clean up all the memory you allocated in the demo.
As always, you should practice good procedural decomposition. Hard-coding a star shape is not acceptable; write code to generate the star for you. Make sure this code is extensible– we might ask you to make a small alteration to your demo during the code review!
Task 3.
After carefully reading the APIs we provide for you in sdl-wrapper.h
and state.h
, create a bouncing star animation that respects the mechanics outlined above. For reference, here is a short
video of the reference solution in action:
You do not need to match our solution exactly (e.g., the velocities may be different), but it should do approximately the same thing. For correctness purposes, we will ask you to demo your simulation during the code review.
Running and Testing your Code: make
and the Makefile
In project 0, you used clang
to compile your one-file C programs. Going forward, our project will consist of many C files. It is important to organize projects carefully
to help reduce the complexity that comes with larger programs. Your physics library will have the following directory structure:
- include: header files go here
-
library: source files for your libraries (e.g.,
vec_list
,polygon
, etc.) - demo: source files for the demos go here
- tests: test source files go here
- out: built object files will be generated here
- bin: built binaries will be generated ere
We have provided you with a Makefile
which is set up to build the project. It allows you to run the following commands:
-
make all
: compile all libraries, demos, and tests, and run the web server -
make NO_ASAN=true all
compiles everything without asan - this is recommended for running demos -
make test
: compile all libraries and tests and run all the tests -
make server
: Run the web server with the current binaries -
make clean
: remove all generated binaries
This year, we will be testing demos via a web interface hosted on labradoodle, using a library called emscripten
.
Every student has been assigned a port, which you can see after running make all
or make server
, or by running cs3-port
in a shell on labradoodle.
To run your demo, you should run make all
or make server
and then navigate to the link printed to the shell (the last line before “Serving HTTP on …”) and click the compiled demo .html
file.
To run all the tests, all you need to type is make test
. To run compile and run an individual test suite, run the following commands (replacing vector
with the test suite you want to run)
:
make bin/test_suite_vector
./bin/test_suite_vector