Top-down design (TDD)

In a TDD approach, we split a large problem into smaller problems. Once we’ve broken down the problem, implement it from the bottom up

Programming is a design problem: there is no single correct solution, but there are many bad solutions.

Signs a design is successful:

  • The program is easily built and tested in pieces

  • Functions perform a single, well-designed task

  • It’s easy to change the program to support new features, or modify an existing one

  • No code blocks are cut and paste

Heuristics for doing a TDD:

  • Create/obtain the list of features your program needs to have

  • From the feature list, identify verbs (and later nouns). The verbs will be the functions of your program. The nouns will be your classes.

  • Outline (at a high-level) when your functions will be used. This will be your algorithm in main()

  • Identify parameters and return types for each function

  • Implement your design

    • Create placeholders (e.g. stubs) for each function. If a function has a return value, return a value with the correct type. (This is called a dummy value)

    • Implement main to call your stubs

    • Your program should run. You should test that your functions are called.

Example saucer.py

Step 1: Create/obtain the list of features your program needs to have

The list of features are given to us in the program comments:

  • Draw a simple background

  • Animate N randomized asteroids to move from right to left

  • Asteroids should loop back to the right when the reach the end of the screen

  • Asteroids should start at the right of the screen

  • Use circles to represent the player and the asteroids

  • The up/down/left/right keys control the player

  • Typing "q" quits the game

  • Crashing into an asteroid ends the game

Step 2: Identify verbs from the feature list. These will become our functions

Verbs from the feature list:

  • draw the background

  • create and draw the asteroids

  • create and draw the player

  • move the asteroids

  • move the player

  • check for crashes

Step 3: Outline how these functions will be used. This will become our main()

In this step, we write psuedocode to outline how our functions will be used together. Psuedocode is code-like text that allows us to write an algorithm without worrying about having the correct Python syntax.

We do not need to worry about how we will implement our functions yet.

create the window
draw the background
create and draw the asteroids
create and draw the player

timeToQuit = False
while not timeToQuit:
   keyPressed = get the key from the user
   move the asteroids
   if the player crashes with an asteroid:
      timeToQuit = True
   elif keyPressed == "q":
      timeToQuit = True
   elif keyPressed == "Left":
      move the player to the left
   elif keyPressed == "Right":
      move the player to the right
   elif keyPressed == "Up":
      move the player up
   elif keyPressed == "Down":
      move the player down

Step 4: Identify names, inputs and outputs for each function

You can do this step in your Python design program. I will list our choices below:

  • drawBackground

    • purpose: draw a background

    • input: win (type: GraphWin) - a graphic window for drawing

    • output: none needed

  • createAsteroids

    • purpose: draw gray circles with random sizes and positions

    • input: win (type: GraphWin) - a graphic window for drawing

    • input: (maybe) the number of asteroids to create

    • output: list of circles (needed so we can move them later)

  • createPlayer

    • purpose: draw a green circle for the player

    • input: win (type: GraphWin) - a graphic window for drawing

    • output: a circle (needed so we can move it later)

  • moveAsteroids

    • purpose: move the circles from right to left; wrap back to right when x < 0

    • input: list of circles to move

    • output: none

  • movePlayer

    • purpose: move the player

    • input: the circle representing the player

    • input: the x and y directions to move the player

    • output: none

  • checkForCrash

    • purpose: check whether the circle representing the player overlaps with an asteroid

    • input: the circle representing the player

    • input: the list of circles representing asteroids

    • output: boolean which is True if the player overlaps an asteroid

Step 5: Implement your design

Please see your inclass examples for saucer-design.py.

Notice the following important features of our TDD:

  • The design runs with no errors

  • The design calls all our functions

  • Functions have correct arguments and return type

  • Functions contain placeholder, aka dummy, code. Placeholder code might be a pass statement or a print statement.

  • main() is complete. We may need to modify it slightly for the final program, but not significantly

  • Functions have comments

Step 6: Implement your program

Let’s copy our design to a new file saucer.py and implement each function. I recommend an incremental development approach where you implement each function one at a time. Test each function works before implementing the next one.

Please see your inclass directory for saucer-soln.py

Extra challenge extensions:

  • Use more than one circle to represent the player

  • Try changing the window size, say to (600, 400)

  • Allow the player to shoot bullets at teh asteroids

  • Load images for the player or asteroids