Week 7, Wednesday: File IO, Top-Down Design



Idea of Top-Down Design (TDD):

First, think about the tic-tac-toe game, and how it proceeds. To make things easier, just assume the user always goes first. The basic outline is:

Now look at each step and decide if you can write a function for that step. If so, think about what the function needs from main(), what the function will do, and what (if anything) it will return to main(). For example, if we wrote a function to handle the user's turn, it might do something like this:

Obviously, if this is a function, we need the board from main() in order to check if the user picked a valid square, and to update the board. So for this function, we can call it from main() like this:

userturn(board)

Keep doing that for all functions you might call from main(). Here is one possible design of main() for the tic-tac-toe game:

def main():
  """run one game of tic-tac-toe"""

  # print rules of game??
  board = [" "]*9
  display(board)

  while True:
    userturn(board)
    display(board)
    result = checkIfGameOver(board)
    if result != None:
      break
    computerTurn(board)
    display(board)
    result = checkIfGameOver(board)
    if result != None:
      break

And here are two function stubs for the above main():

def checkIfGameOver(board):
  """
  check if someone has won, or a draw.
  return "x" if X wins, "o" if O wins, "d" if draw, 
         None if game not over
  """
  return None

def computerTurn(board):
  """find best spot and play it"""

  board[5] = "o"
  return

I may or may not know how to code up these functions, but I at least have an idea of what they should do, including what they need from main() (the parameters), what the are going to do, and what they are going to return to main(). Plus, since I have written the function stubs, the whole design runs, and I can type in each function (one at a time!) and test them as I go.

For example, when implementing the computerTurn() function, a simple first step might be to have it choose a random location. That at least allows me to play the game and test out other functions. Once I know the rest of the game works, I can come back to computerTurn() and make it better.

Your turn: implement a function from the design

See if you can implement one or two functions from the tic-tac-toe design shown above. Pick one, write it and test it, then move on to the next one.

using assert() statements

One way to test your functions is to use python assert() statements. Here's a quick example:

>>> assert(range(4) == [0,1,2,3])
>>> assert(range(4) == [0,1,2,3,4])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

The assert() statement is a comparison, and if the comparison succeeds (is True), nothing happens. If, however, the comparison fails, an AssertionError occurs.

These kinds of statements are very useful when testing code. And having them built-in, as part of the development process, can help you know for sure if your code is working.

Here's another example, based on this week's lab writeup, where we want you to have main() call a test() function:

def main():
  test()

def test():
  """use this space to test out all of my functions"""
  assert(sumList([1,2,3,4]) == 10)
  assert(sumList([]) == 0)
  assert(sumList([326]) == 326)
  assert(sumList([-1,-2,-3]) == -6)
  assert(maxOfList([-1,-2,-3]) == -1)
  assert(maxOfList([100,0,200,5]) == 200)
  assert(maxOfList([]) == "not a valid list!")
  assert(maxOfList(["cat","dog","zebra","pony"]) == "zebra")
  print("All tests passed!")

def maxOfList(L):
  """find and return the largest item in list L"""
  if len(L) == 0:
    return "not a valid list!"
  biggest = L[0]
  for item in L:
    if item > biggest:
      biggest = item
  return biggest

def sumList(L):
  """sum values of list, return sum"""
  total = 0
  for num in L:
    total = total + num
  return total

In the above, once I've tested all functions, I would remove the call to test() from main() (or comment it out), then add the real code to main().