Workbook

Modern application development is usually done through web development. We can use Drafter to make web applications that are easy to write and test. This workbook takes you through developing a few different web applications, piece-by-piece. We provide unit tests that you can use to check that you are building the software correctly.

Important

Read over all the instructions and text as you go. Many of your questions will be answered directly in the instructions! If you just copy/paste code, you will not learn much from this activity.

Bank Account

A bank account let’s you withdraw and deposit money. We’ll make a simple bank account application to support this. Preview this website at https://drafter-edu.github.io/examples/bank/.

Screenshot of the Bank Account application

Step 1: Download bank.py and put it into the same directory as the cookie clicker game.

The file contains the incomplete State, five incomplete routes, six correct unit tests, and a call to start_server. You might be wondering why there are five routes when the image above only shows three pages. The reason is because two of the routes (finish_withdraw and finish_deposit) are routes that modify the state and then return the result of calling the index route. The diagram below visualizes the relationships between the routes.

        flowchart
    start(("Start"))
    index["index"]
    start_withdraw["start_withdraw"]
    finish_withdraw[/"finish_withdraw"/]
    start_deposit["start_deposit"]
    finish_deposit[/"finish_deposit"/]
    start --> index
    index -- Withdraw --> start_withdraw
    start_withdraw -- "Withdraw(amount)" --> finish_withdraw
    start_withdraw -- Cancel --> index
    finish_withdraw -.-> index
    index -- Deposit --> start_deposit
    start_deposit -- "Deposit(amount)" --> finish_deposit
    start_deposit -- Cancel --> index
    finish_deposit -.-> index
    

To explain a little further:

  • The index route has buttons to go to the start_withdraw and start_deposit routes.

  • The start_withdraw and start_deposit have buttons to go to the finish_withdraw and finish_deposit routes, respectively, and also have a button to go back to the index route (“Cancel”). The start_withdraw and start_deposit routes also have a TextBox to input the amount to withdraw or deposit, which is passed as a parameter to the finish_withdraw and finish_deposit routes.

  • When the finish_withdraw and finish_deposit routes are called, they modify the state and then return the result of calling the index route.

Step 2: Inside the State class, add a balance field (an integer).

Step 3: You will need to create five routes:

  1. The index route will consume a State object and return a Page (like most routes). The state in the returned Page will be unchanged. The content of the Page will be (in order):

    • The text "Your current balance is:: X" except instead of X it will be the balance of the State object.

    • A Button with the text "Withdraw" that links to a start_withdraw route.

    • A Button with the text "Deposit" that links to a start_deposit route.

  2. The start_withdraw route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will be (in order):

    • The text "How much would you like to withdraw?".

    • A TextBox() that will be used to input the amount to withdraw, initially 10. Make sure you match the name for the TextBox to the name of the parameter in the finish_withdraw route.

    • A Button with the text "Withdraw" that links to a finish_withdraw route.

    • A Button with the text "Cancel" that links to the index route.

  3. The finish_withdraw route will consume a State object and an amount (an int). The balance of the state in the returned Page will be changed to reflect the withdrawal. The function should return the result of calling the index function with the modified state. Note: Use the index function inside of this function to avoid code duplication.

  4. The start_deposit route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will be (in order):

    • The text "How much would you like to deposit?".

    • A TextBox() that will be used to input the amount to deposit, initially 10. Make sure you match the name for the TextBox to the name of the parameter in the finish_deposit route.

    • A Button with the text "Deposit" that links to a finish_deposit route.

    • A Button with the text "Cancel" that links to the index route.

  5. The finish_deposit route will consume a State object and an amount (an int). The balance of the state in the returned Page will be changed to reflect the deposit. The function should return the result of calling the index function with the modified state.

Step 4: Run the application and check the tests to see if you have implemented the routes correctly.

This example has shown you how to have multiple pages that link together, with some routes that do not modify the state and some routes that do modify the state. You also have now seen a textbox, which is a way to get input from the user, and how that input can be passed to another route as a parameter in order to update the state.

Simple Adventure Game

Now we’ll make a little adventure game with multiple pages that link to each other in a more complicated way, and also an item that you can pick up and use in of the rooms. To make things more exciting, we’ll have images of the screens in the game (generated using ChatGPT). Preview this website at https://drafter-edu.github.io/examples/adventure/.

Screenshot of the Simple Adventure Game

Step 1: Download the images and the starting file for the game from the following links:

Important

Make sure the files are in the same folder as the python file. You can change the images but make sure they have the same filenames, or the tests won’t run!

Once again, the number of routes and the number of pages do not match. The diagram below shows the flow of the game. The game starts with the index route, where the player enters their name. The player then moves to the small_field route, where they can choose to go to the woods or the cave. In the woods route, the player can pick up a key, which will allow them to unlock the door in the cave route. If the player does not have the key, they will be unable to unlock the door and will have to leave the cave. The game ends in the ending route, where the player finds a treasure chest and wins the game.

        flowchart
    start(("Start"))
    index["index"]
    begin[/"begin"/]
    small_field["small_field"]
    woods["woods"]
    check_has_key{has_key}
    check_missing_key{not has_key}
    cave["cave"]
    take_key[/"take_key"/]
    ending["ending"]

    start --> index
    index -- "Begin(name)" --> begin
    begin -.-> small_field
    small_field -- "Woods" --> woods
    small_field -- "Cave" --> cave
    woods --- check_missing_key
    check_missing_key -- "Take key" --> take_key
    take_key -.-> woods
    woods -- "Leave" --> small_field
    check_has_key -- "Unlock door" --> ending
    cave -.- check_has_key
    cave -- "Leave" --> small_field
    

Notice the diamond shaped nodes in the diagram. These are decision nodes. The game will have two different paths depending on whether the player has the key or not. The Cave only shows the “Unlock door” button if the player has the key. The Woods only shows the “Take key” button if the player does not have the key. This means that you will need an if statement in your route to decide which content to show.

Step 2: Add two fields to the State class.

  • The has_key field is a boolean

  • The name field is a string

Step 3: Finish implementing the following routes:

  1. The index route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will be (in order):

    • The text "Welcome to the adventure! What is your name?"

    • A TextBox that will take the user’s name (the default value is "Adventurer").

    • A Button with the text "Begin" that links to a begin route.

  2. The begin route will consume a State object and return a Page. The state in the returned Page will have the name field set to the value of the TextBox in the previous page. Use the small_field route to return the next page, rather than defining a new Page object.

  3. The small_field route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will be (in order):

    • The text "You are NAME." except replacing NAME with the value of the name field in the State object.

    • The text "You are in a small field."

    • The text "You see paths to the woods and a cave.

    • A Button with the text Cave that links to the cave route.

    • A Button with the text Woods that links to the woods route.

    • An Image with the filename "field.png".

  4. The cave route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will depend on whether or not the state has the has_key field set to True. If it is is True, then the content of the page should be:

    • The text "You enter the cave."

    • The text "You see a locked door."

    • A Button with the text "Unlock door" that links to the ending route.

    • A Button with the text "Leave" that links to the small_field route.

    • An Image with the filename "cave.png".

    Otherwise, if the has_key field is False, then the content of the page should be:

    • The text "You enter the cave."

    • The text "You see a locked door."

    • A Button with the text "Leave" that links to the small_field route.

    • An Image with the filename "cave.png".

  5. The woods route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will depend on whether or not the state has the has_key field set to True. If it is is True, then the content of the page should be:

    • The text "You are in the woods."

    • A Button with the text "Leave" that links to the small_field route.

    • An Image with the filename "woods.png".

    Otherwise, if the has_key field is False, then the content of the page should be:

    • The text "You are in the woods."

    • The text "You see a key on the ground."

    • A Button with the text "Take key" that links to the take_key route.

    • A Button with the text "Leave" that links to the small_field route.

    • An Image with the filename "woods.png".

  6. The take_key route will consume a State object and return a Page. The state in the returned Page will have the has_key field set to True. Use the woods route to return the next page, rather than defining a new Page object.

  7. The ending route will consume a State object and return a Page. The state in the returned Page will be unchanged. The content of the Page will be:

    • The text "You unlock the door.",

    • The text "You find a treasure chest."

    • The text "You win!"

    • An Image with the filename "victory.png".

Step 4: Run the application and check the tests to see if you have implemented the routes correctly.

This application showed you how to make multiple pages that link in more complicated ways, including some pages that had different content depending on the state of the State object. You also learned how to use images in your application.

Store

This activity is a little store where you can purchase items for a game. Preview the activity at https://drafter-edu.github.io/examples/store/.

In this example, we’ve already written the state and all the routes. You just need to make all the tests for us!

This is most easily done by just running the site, and then interacting with the shop. As you explore unique pages, tests will be generated at the bottom in the “Combined Page History” section at the bottom of the page. This will only retain unique visits, so you can’t just spam the same page over and over again.

You can also manually create tests by writing them yourself. Ultimately, though, you need to make at least 10 unique tests!

Step 1: Download store.py and put it into an appropriate directory, where you placed everything else.

Step 2: Run the server, and then try purchasing things.

Step 3: Write tests for the store. Or rather, just copy the tests that have been generated for you at the bottom of the page.

Step 4: Run the tests with python store.py and make sure they all pass.