Skip to content

PPP Worksheet

Ian MacIntosh edited this page Jun 24, 2020 · 15 revisions

Pseudocode Programming Process

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:

Begin

Record your understanding of the problem you're trying to solve.

In a sentence, what does this program do?

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.

What does the client need to provide?

Player movement and actions

How does the client provide it?

Keyboard at first, or "swipes" on phone in phase 2

How does the client know they've provided it?

The hero responds to feedback, and the player is given info about the controls

What does the client expect to get?

The hero does what the player wants, and monsters are cleared as expected

How does the client know they got it?

Visual response

What are the design constraints?

Not sure yet

What's the hardest step and why?

Not sure yet

What's the easiest step and why?

Not sure yet

Create a General Design for the Class

Components

  • App
  • Scoreboard
  • Field
  • Queue
  • Monster
  • Homebase
  • Hero
  • Counter

States

App

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;

Hero

let hero = {
    color: 0,
    x: 0,
    y: 2,
    orientation: north
  }

Can this process be broken up into steps?

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
  • 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, multiply monsterDuration by accelerationRate
  • 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
          • Queue.reportElimination sets streak to on
        • If color does not match monster color, report streak over via App.endStreak() and update hero color via Hero.changeColor()
  • Step 4 Update score and elimination counter

  • Step 5 End level when elimination counter hits 0

What methods line up with those steps?

  • Handle movement
  • Add monster
  • Update interval
  • Strike queue
    • Strike monster
    • Update character color
  • Update score
  • Update elimination counter
    • End level

Open Questions

  • How should queues be represented? What data structure?

Construct the Routines within the Class

App Class

Add monster

  • 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

End Level

  • Preconditions: Elimination counter is zero
  • Postconditions: Level is restarted
  • Input: None
  • Output: None

Hero Class

Walk

  • 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

Update character color

  • Preconditions: None
  • Postconditions: State for hero reflects new color
  • Input: New color
  • Output: Hero icon is new color

Queue Class

Strike

  • Preconditions: We know status of affected queue
  • Postconditions: Queue is updated
  • Input: Queue, color
  • Output: Visual representation of hero and queue is updated

Monster Class

Strike

  • 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)

Scoreboard Class

Update

  • 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

Counter Class

Update

  • Preconditions: None
  • Postconditions: Elimination counter is updated, end level if <= 0
  • Input: Number of monsters eliminated
  • Output: None

Review and Test the Class as a Whole

Previous Notes

  1. Begin
  2. Create a general design for the class
  3. Construct the routines within the class
  4. Review and test the class as a whole
  5. 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
Thoughts on PPP during PS3.1

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.

FAQ

How do I handle indentation? How close to code is too close?