-
Notifications
You must be signed in to change notification settings - Fork 0
PPP Worksheet
There are alternatives to the pseudocode programming process (like "test-first development" (TDD?) and design by contract), but PPP is what's described in this book. Class and routine construction is usually a messy and iterative process:
Record your understanding of the problem you're trying to solve.
This program is a browser-based clone of Zoop -- an action puzzle game where a player removes pieces from the board before they invade his home base.
Player movement and actions
Keyboard at first, or "swipes" on phone in phase 2
The hero responds to feedback, and the player is given info about the controls
The hero does what the player wants, and monsters are cleared as expected
Visual response
Not sure yet
Not sure yet
Not sure yet
- App
- Scoreboard
- Field
- Queue
- Monster
- Homebase
- Hero
- Counter
let board = {
north: [ // Northern and southern queues will end the game when their length > 5
[
0 // This character represents a monster, the digit is its color
],
[ ],
[ ],
[ ]
],
west: [ // Western and eastern queues will end the game when their length > 8
[ ],
[ ],
[ ],
[ ]
],
east: [
[ ],
[ ],
[ ],
[ ]
],
south: [
[ ],
[ ],
[ ],
[ ]
],
},
hero = {
color: 0,
x: 0,
y: 2,
orientation: north
},
streaking = true,
score = 200,
monstersRemaining = 28;
let hero = {
color: 0,
x: 0,
y: 2,
orientation: north
}
Yes.
-
Step 1 Render board
- Call App.setStage()
- Number of monsters in stage (e.g., 50)
- Rate of monster creation (interval at which a new monster created; e.g., 3)
- Wave duration (interval at which rate accelerates; e.g., 10)
- Rate of acceleration (multiplier to apply to rate; e.g. 0.75)
- In the example above: 50 monsters need to be eliminated to move to next stage. A new monster is provided every 3 seconds. Every 10 seconds, the duration decreases by 25% -- instead of monsters being created every 3 seconds, new monsters get created every 2.25 seconds. After 10 more seconds, a new monster gets created every 1.6875 seconds
- Call App.setStage()
-
Step 2 Add monsters periodically, going faster as level goes on
- Every
monsterDuration
seconds, call App.addMonster(randomField, randomQueue, randomColor)- Update state to add monster of randomColor to randomQueue of randomField
- Create new Monster component within the appropriate queue
- Every
waveDuration
seconds, multiplymonsterDuration
byaccelerationRate
- Every
-
Step 3 Remove monsters as player moves hero to remove them
- Player moves in field, calling Hero.move() for each movement
- Each movement updates app state for Hero
- CSS variables get updated to move Hero within grid (
grid-row
&grid-column
to reflect x and y)
- Player calls App.strike()
- Read hero coords and direction to determine which queue to strike
- Call Queue.strike() on appropriate queue
- Call Monster.strike() on front monster on appropriate queue, handling passing-through
- If color matches monster color, report elimination via Queue.reportElimination()
- Queue.reportElimination calls Monster.strike() on next monster (if exists)
- If no next monster exists on queue, call Counter.update() and Scoreboard.update()
- Scoreboard.update() manages streak tally? Need to figure out how to manage this
- If no next monster exists on queue, call Counter.update() and Scoreboard.update()
- Queue.reportElimination sets streak to on
- Queue.reportElimination calls Monster.strike() on next monster (if exists)
- If color does not match monster color, report streak over via App.endStreak() and update hero color via Hero.changeColor()
- If color matches monster color, report elimination via Queue.reportElimination()
- Player moves in field, calling Hero.move() for each movement
-
Step 4 Update score and elimination counter
-
Step 5 End level when elimination counter hits 0
- Handle movement
- Add monster
- Update interval
- Strike queue
- Strike monster
- Update character color
- Update score
- Update elimination counter
- End level
- How should queues be represented? What data structure?
- Preconditions: None
- Postconditions: A column has a new monster
- Inputs: Color, queue
- Outputs: State is updated with new monster in queue, we have determined game is still playable
- Preconditions: Elimination counter is zero
- Postconditions: Level is restarted
- Input: None
- Output: None
- Preconditions: We know hero's current location and orientation
- Postconditions: Hero's location and orientation are updated
- Input: Direction
- Output: Visual representation of hero within base is updated
- Preconditions: None
- Postconditions: State for hero reflects new color
- Input: New color
- Output: Hero icon is new color
- Preconditions: We know status of affected queue
- Postconditions: Queue is updated
- Input: Queue, color
- Output: Visual representation of hero and queue is updated
- Preconditions: The monster knows its queue, its position within that queue, and its color
- Postconditions: Monster is updated based on strike
- Input: Striker color
- Output: Monster is updated, remaining monster counter is decremented (if monster is eliminated)
- Preconditions: Streak is accurate
- Postconditions: Streak is accurate -- "false" if zero monsters were eliminated, "true" otherwise, score is updated
- Input: Number of monsters eliminated
- Output: None
- Preconditions: None
- Postconditions: Elimination counter is updated, end level if <= 0
- Input: Number of monsters eliminated
- Output: None
- Begin
- Create a general design for the class
- Construct the routines within the class
- Review and test the class as a whole
- Done
Steps 1, 2, and 3 must be performed in order, then 2-4 can go in any order any number of times until finally passing from 4 to 5.
To use pseudocode effectively:
- Use English-like statements that precisely describe specific operations
- Avoid syntax from target language -- pseudocode allows designing at higher level than code by virtue of higher language
- Write pseudocode at the level of intent, describing the meaning of the approach rather than implementation details
- To avoid glossing over problematic details, write pseudocode at low enough level that generating code from it will be nearly automatic. Refine pseudocode detail until it would be easier to simply write the code
Constructing routines using PPP:
- Design the routine
- Check prerequisites and confirm routine fits cleanly into overall design and aligns with project requirements
- Define the problem the routine will solve: info routine will hide, its inputs & outputs, preconditions that will be true before it is called, and post conditions it will guarantee before passing control back to caller
- Name the routine. If its name is crappy, its purpose is unclear. If you cannot make the name clearer, your design probably needs work
- Decide how to test the routine
- Research what's available in existing libraries
- Think about error handling and efficiency
- Research the algorithms and data types available to solve the problem
- Write high-level pseudocode in your IDE, working from general to more specific. To help clarify your understanding of the routine, write a concise statement describing the purpose of the routine.
- After you've written your pseudocode, think of how you'd explain it to someone else. Make sure you have an easy and comfortable understanding of what the routine does and how it does it
- Code the routine
- Start with pseudocode
- Write the first and last statements and turn pseudocode into high-level comments
- Fill in code below each comment (each comment generally expands to 2-10 lines of code
- Check the code
- Clean up leftovers
- Repeat all steps as needed, going back and forth as needed
- Done
- Check the code
- The earlier you find an error, the cheaper it is to fix
- A problem may not appear until the routine is fully coded. Mentally check the routine for errors by yourself and then with peers. Push compilation off until after reviewing it to avoid "Just One More Compile" syndrome where you have an internal stopwatch ticking and pressuring you to hack-and-compile your way out of problems. Step through the code in the debugger.
- Clean up loose ends
- Check input and output data
- Check for design quality and loose coupling
- Check your variables for bad names, unused objects, undeclared vars, etc
- Check statements and logic for infinite loops and off-by-one errors
- Make sure you've used white space to clarify logical structure
- Check your routine's documentation to confirm it's still accurate
- Remove redundant comments
- Repeat as needed
It all seemed kind of heavy-handed, but the general design seemed sound (even though I didn't come up with it) and the routines seemed logical and well thought-out. I don't know where to draw the line for when to do a thorough PPP, or how to speed things up when some questions don't apply. For example, if we're talking about algorithmic efficiency, I perform two iterations over the candidates ("N"?) in the print_winner
routine, bringing the order of growth for this class up to 3N from 2N (one iteration for adding votes, one iteration to identify the most votes, one iteration for identifying candidates with most votes) but efficiency is not an important design constraint. Nor one I can off-handedly think of a workaround for considering my instructions were to not modify the main routine. In short, it's hard to argue with the results, but this really seemed like a lot of planning for a trivial task. I can say I was resistant to the PPP approach, but it's hard to say how much of that was just discomfort from doing something I'm not used to vs genuinely well-founded concerns with the techniques.
How do I handle indentation? How close to code is too close?