CPSC 120 | Principles of Computer Science | Fall 2024 |
The project is due at the time stated. You should plan to complete the project on time. See the policy on late work and extensions in the case of extenuating circumstances.
The goal of the projects is to integrate the skills you've gained to build something a bit larger than you create in the labs.
Successfully completing this project means that you are able to:
As with the labs, you may get help with how to solve the problem and with writing and debugging code, but you must document any help received (including from TFs) and any resources used other than the textbook and posted course materials and you may not work together with other students to write code, shortcut to a solution by copying code or using someone else's program as a guide or example, or be in possession of someone else's program or solution before you have handed in your own.
Review the discussion in previous lab handouts and the full collaboration policy for more details.
To hand in your work:
Make sure that your name and a short description of the sketch are included in a comment at the beginning of your sketch.
Make sure that you've auto-formatted your sketch.
Copy the entire midterm directory from your sketchbook (~/cs120/sketchbook) to your handin directory (found inside /classes/cs120/handin).
In this project, you'll create an animation inspired by Rube Goldberg — a Rube Goldberg machine is something that performs a simple task in a complicated, chain-reaction fashion. (For more about Rube Goldberg and his inventions and comics, see www.rubegoldberg.org.) Your task will be to create a sketch with multiple stages that link together a series of actions, such as in the example below. (Position the mouse near the left side of the window to raise the elevator and get everything started; click to restart.)
Name your sketch midterm.
There is considerable flexibility as to the details of your sketch, but for full credit, it must contain the elements listed below. Note that the demo above does not satisfy all of the requirements! (And it also includes a few elements that have not been discussed in class, though you could investigate how to use them for extra credit.) It is intended only as an example to clarify some of the points below — you should not simply copy the demo, but rather come up with your own ideas.
A chain reaction of events with at least five different stages. In the demo, the five stages are:
Note that it is not necessary for all of the stages to be about motion. For example, a possibility would be for a shape to grow until it reaches a certain point and triggers the next stage. A stage can also be triggered by user interaction, such as a drifting hot air balloon that drops a ball when the mouse clicked or a key is pressed.
At least three actions that last for two or more stages (but not every stage). In the demo, the these actions are:
It is not necessary for a multi-stage action, once started, to last for all of the remaining stages (though that is the case in the demo). For example, it would be fine for an action to only last for stages 2 and 3.
An end result or goal. In the demo, this is the balloon floating away.
A theme that ties everything together, with appropriate scenery. Note that the demo does not meet this requirement!
At least two instances of some compound thing (three or more shapes) drawn by a function with appropriate parameters. The instances must be interactive or animated in different ways; not interactive or animated is a possibility for one instance. (Not satisfied by the demo.)
Physically-based motion, including both gravity and bouncing along with air resistance and/or damping. (*)
Speeding up or slowing down, other than in physically-based motion. This can involve motion (change in position) or some other kind of change (such as size, color, or orientation). (*) (Not satisfied by the demo.)
Constrained motion, using parametric equations. "Motion" can be motion (change in position) or change in something else (e.g. size, color). (Note that "constrained" means that you have two values changing in a related way — x and y if you animate position or width and height if you animate size or at least two of the three color components if you animate color.) (*)
"Natural"/"smooth" random motion, using Perlin noise. "Motion" can be motion (change in position) or something else (e.g. size, color, orientation). (*)
At least one instance of animating something other than position and speed e.g. size or color or orientation. (*)
At least two forms of interaction: resetting the animation to its starting point when the mouse is clicked, and something else (*). In the demo, the "something else" is that the elevator only lifts the red ball when the mouse is near the left side of the window.
At least one "complex element". (*) This is something that extends the basics discussed in class and done in examples, in-class exercises, or labs. There are a number of possibilities for this:
Constrained change other than along a ramp or in a circle — combine the x equation from one curve and the y equation from another (less math), or vary the center and/or radius of the circle to get spiral motion or loop-de-loops (less math), or look up the parametric equations for another kind of curve such as a parabola, cycloid (other than the hypotrochoid and epitrochoid from lab 6), or Lissajous curve (less-to-medium math).
Acceleration or deceleration (whether due to gravity or otherwise) along a constrained path, such as a ramp or circle or other curve. If you are modeling gravity, you will need to be careful to make sure the acceleration is constant in the y direction — how much the gravity affects the speed of the object along the motion path depends on the shape of the curve at that point, and requires more math to get right. Or you can imagine a thruster or brakes, which directly speed up or slow down the object along the motion path (less math).
Rotation coordinated with something else — acceleration/deceleration, moving along a constrained path, etc. (medium-to-more math) Examples include a rolling ball that speeds up, a ball rolling down a ramp (with or without speeding up), a ball rolling around the inside or outside of a circle, or a pendulum bob whose top always points towards the center of the circle. The math needs to be right here — looking OK isn't enough!
Bouncing on more complex surfaces, such as a ball bouncing down stairs (medium math). Be sure to handle both the horizontal and vertical surfaces! Or handle bouncing off of circles (more math).
In addition, you must:
Include a comment at the beginning of the sketch with your name and a description of your sketch — identify the sequence of actions, what triggers each new stage, and the final goal. Also identify how you satisfy the requirements marked with (*) above — include a comment at the beginning of the sketch or just before the relevant section of code, as appropriate.
Comment each function definition (describe what the function does and what each of its parameters are for), and comment variable declarations where needed (if the name of the variable isn't sufficient to understand its purpose).
Keep your code organized —
Include comments in your code to identify what each set of statements is doing.
Choose descriptive variable names — lots of one-letter or x1, x2, etc names are going to get difficult to keep track of. Include something about what element the variable is associated with (e.g. ball, pendulum, platform1) as well as what the value is (e.g. x coordinate, y coordinate, speed, color) in each variable name. Name variables that all go with one thing consistently e.g. if you have two balls, use ball1x, ball1y, etc for one ball and ball2x, ball2y, etc for other ball — and just leave out a variable if it isn't relevant e.g. no ball2y if ball 2 only moves horizontally.
Create drawing functions for all compound things (three or more shapes), with appropriate parameters.
Put only drawing into drawing functions — do updates in draw(), not in a drawing function.
Use Auto Format so that the code you hand in is properly formatted.
You can earn extra credit by going substantially beyond the required elements. Some possibilities:
An elegant code organization which avoids repeated code for multi-stage actions. This can be achieved with a variation of the state machine pattern — instead of a "more than two choices" on-the-spot conditional using the state variable to decide on the action(s), use a "to do or not to do" conditional for each action (do it if it is a stage when it is active, do nothing otherwise).
An especially elaborate or clever scene. This could include appropriate use of loops.
More stages and/or "complex elements".
More complex "complex elements" (ones requiring more math).
Handle bouncing correctly — compute where the ball actually should have ended up after a mid-frame wall hit. (See the "Physics Quirks" section below.)
Create fancy shapes using vertex shapes (see section 14.3 in the textbook).
Use the transformations rotate() and/or scale() to draw and/or animate non-axis-aligned rectangles and other shapes (see sections 14.5, 14.7, and 14.8 in the textbook). (The demo uses transformations for the purple ball's swinging platform and the deflating balloon.) Be sure to surround usage of rotate() and scale() with pushMatrix() and popMatrix() to limit their effects to the desired elements.
Include a brief description of what you've done for extra credit in a comment at the beginning of your sketch. More creative and challenging elements will earn more points.
Plan first. Sketch out what the scene will look like (on a piece of paper). Decide what the five stages will be — what will happen in each stage and what the trigger points are for the transitions from one stage to the next. (Keeping in mind #2 in lab 5, consider transition triggers of the form "in stage 2 and the red ball hits the pendulum bob" rather than just the "the red ball hits the pendulum bob" — this helps avoid confusing situations where the rest of the condition happens to be true in another stage as well.) Identify the multi-stage action(s) and the final goal. Write what happens in each stage, what triggers each transition, and the final goal down in comments in your sketch. Check the list of requirements to make sure you've covered everything. Note how you satisfy each of the (*) requirements in comments in your sketch.
Practice incremental development — write one small piece at a time and test your sketch to make sure it works as you want after each piece.
Utilize patterns. When you start to implement a piece of your sketch, first identify what structure you need — is there interaction, animation, conditionals, a drawing function, or just a list of statements? Each of these gives you a code template to fill in with the specifics for your particular task. For the questions to ask yourself to identify when a structure is applicable, the code templates, and the questions to ask yourself to fill in the templates, review the slides, handouts, and posted examples for each topic.
Start with the parts you are most confident in, and work on consecutive stages rather than disconnected ones. For example:
Start with drawing the stationary elements that are the same in every stage. (In the demo, this is most of the black shapes that act as surfaces.)
Add any interactive or animated elements that are present in every stage. (The demo doesn't have any of these.)
Set up the code structure for handling multiple stages (see the hints and tips below) — declare and initialize the state variable for the current stage and write the structure for the "usage" if statements, with empty bodies for each of the branches. (The "transition" if can wait until the first trigger point is implemented.)
Pick the stage that is simplest or that you are most confident about to start with, and implement the elements of that stage one at a time — add animation variables as needed and draw the relevant elements. Initialize animated elements to their positions at the beginning of that stage, and don't worry about the ending of the stage yet. Put this code into the appropriate branch of the "usage" ifs so that it happens only when in this stage, and initialize the stage state variable to start the sketch in this stage. (In the demo, this might be stage 2 — the red ball sliding down a ramp. Write code for an animated red circle which starts at the top of the ramp and slides down it. It'll keep going past the end of the ramp — that's fine for now.)
Next, work on the stage immediately before or immediately after the one already completed along with the trigger for the transition between stages. Put this code in the appropriate branch of the "usage" ifs so that it only happens in the right state, start the "transition" if for the trigger point and state change, and update the initialization of the stage state variable if needed. (In the demo, this would be either stage 1 or stage 3. Say stage 1 is picked — change the initialization of the red ball to be at the bottom of the elevator, add the animation for stage 1, and include the trigger for the transition to stage 2.)
Repeat, continuing to add a stage immediately before or after already completed ones. (In the demo, this would mean stages 3, 4, and 5, in that order.)
The chain-reaction nature of a Rube Goldberg machine means that it is natural to think of the sketch as a series of stages with trigger points that define the transition from one stage to the next — (stage 1) the red ball is lifted by the elevator until it reaches (trigger point) the top of the ramp, then (stage 2) it rolls down the ramp until (trigger point) it hits the pendulum bob, etc.
This is similar to #2 in lab 5 (the circle moving around the edges of the drawing window) — the circle moved in one direction until it reached a certain point, when it started moving in another direction. The code structure for multiple stages will be the same — this is a state machine pattern with a state variable for the current stage number. Combined with organizing draw() so that all the drawing is done before the updating, this means that you'll have (at least) two "usage" if statements (one for drawing the elements for that stage and one for updating the animation variables in that stage) and (at least) one "transition" if with a case for each of the trigger points and resulting stages.
// "usage" if structure for two stages if ( stage == 1 ) { } else if ( stage == 2 ) { }
// "transition" if structure for three stages if ( stage == 1 && trigger condition ) { stage = 2; } else if ( stage == 2 && trigger condition ) { stage = 3; }
The logic of "if the ball has moved past the wall, flip the sign of the speed" for handling bouncing is simplified — the ball actually hit the wall sometime between the previous frame and this one, and should have actually bounced a little bit away from the wall instead of moving past the wall. This fact means that some strange effects can occur when the ball's speed is low — it can get stuck or seem to escape if its speed drops enough that it can't move back to the proper side of the wall by the next frame.
The correct solution is to work out exactly where the ball should be after a mid-frame wall hit — this takes a bit more math (though it isn't all that complicated) and can be done for extra credit.
A band-aid solution is to do two things when a wall is hit: flip the sign of the speed (incorporating damping if desired) and set the position of the ball so that it is just touching the wall. This creates other quirks (such a movement in effect gives the ball a little more energy each time, so the bouncing tends to not die out completely) but at least avoids the stuck or escaping problem.