{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2025-08-17T17:31:11.361366+00:00","date_modified":"2025-09-09T21:50:05.760569+00:00","extra_instructor_files":"","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":2464,"instructions":"## Exploring Large Programs\n\n-   Warning: Bugs Ahead!\n\nIn the previous lesson, we tried running a simple word guessing game.\nEventually, we will see the real source code for this game.\nBut first, we're going to take a look at an initial attempt to implement this game.\nUnfortunately, this initial attempt will be incorrect.\n\n## Don't Panic\n\n-   Do not panic\n-   Breathe\n-   Take breaks\n-   Get adequate sleep\n-   Stay hydrated and nourished\n-   Stay organized\n-   Ask for help\n\nWe are about to show you a very large program. It may look overwhelming at first, but we will break it down together.\nIn this chapter, we are going to explore a lot of strategies that can make it much easier to tackle a new codebase.\nBut first, you must learn to manage your own emotions.\n\nSoftware Development, like any creative engineering task, can be stressful. It's important to take breaks, ask for help, and remember that it's okay to feel overwhelmed.\nAs you get more experience, you'll develop your own strategies for managing stress and staying productive.\nAt the minimum, you should aim to make sure you are well-rested, hydrated, and nourished.\nTrying to read complicated code on an empty stomach will make things much harder.\nYou also need to know when to take a walk and clear your mind.\nOf course, you should not do so immediately, or you'll never get anything done.\nBut pay attention to your body and mind, and take breaks when you need them.\n\nFinally, know when and how to ask for help.\nYou should always be focused on learning and finding resources that will explain things, not just answer questions for you.\nYou should always spend at least a little time trying to solve the problem on your own before asking for help, but do not hesitate to reach out when you are truly stuck.\nNovices often hesitate to ask for help, fearing it may make them look incompetent. However, seeking assistance is a vital part of the learning process.\nEven professional software engineers seek help.\nDoing so correctly is absolutely key.\n\nOnce you are ready, take a look at the code below.\n\n## The Incorrect Guessing Game\n\n```python incorrect-guess-game\n\"\"\"\nWord Guessing Game\nVersion: Incorrect 1.0.0\nAuthor: acbart\nPublished: August 17, 2025\n\"\"\"\n\nfrom bakery import assert_equal\nfrom string import printable\nfrom drafter import *\nfrom random import choice\nfrom dataclasses import dataclass\n\n\ndef choose_word(level: int) -> str:\n    levels = [\"heat\", \"dogs\", \"jazz\"]\n    return levels[level]\n\n\ndef reveal(secret: str, guesses: list[str]) -> str:\n    if secret[0] in guesses:\n        c0 = secret[0]\n    else:\n        c0 = \"_\"\n    if secret[1] in guesses:\n        c1 = secret[1]\n    else:\n        c1 = \"_\"\n    if secret[2] in guesses:\n        c2 = secret[1]\n    else:\n        c2 = \"_\"\n    if secret[3] in guesses:\n        c3 = secret[3]\n    else:\n        c3 = \"_\"\n    return c0 + c1 + c2 + c3\n\n\nassert_equal(reveal(\"heat\", [\"h\"]), \"h___\")\n\n\ndef is_win(secret: str, guesses: list[str]) -> bool:\n    if secret[0] not in guesses:\n        return False\n    if secret[1] not in guesses:\n        return False\n    if secret[2] not in guesses:\n        return False\n    if secret[3] not in guesses:\n        return False\n    return True\n\n\ndef is_valid_word(word: str) -> bool:\n    first = word[0] in printable\n    second = word[1] in printable\n    third = word[2] in printable\n    fourth = word[3] in printable\n    return len(word) == 4 and first and second and third and fourth\n\n\ndef is_loss(wrong: int) -> bool:\n    return wrong >= 6\n\n\ndef in_secret(secret: str, character: str) -> bool:\n    return character in secret\n\n\ndef format_guess(guess: str) -> str:\n    return guess.lower().strip()\n\n\ndef check_if_valid_guess(guess1: str, guess2: list[str]) -> bool:\n    \"\"\"\n    Check if the user's letter guess is valid.\n\n    Args:\n        guess1 (str): The first guess, should be a single letter.\n        guess2 (list[str]): The list of previous guesses.\n\n    Returns:\n        bool: True if the guess is valid, False otherwise.\n    \"\"\"\n    return not len(guess1) > 1 and guess1 in printable and guess2 not in guess1\n\n\ndef is_game_over(level: int) -> bool:\n    return level > 4\n\n\n#### Drafter UI\n\n\n@dataclass\nclass State:\n    secret: str\n    guesses: list[str]\n    guesses_made: int\n    wrong: int\n    level: int\n    score: int\n\n\n@route\ndef index(state: State) -> Page:\n    return Page(\n        state,\n        [\n            Header(\"Guess the Word\"),\n            \"Level: \" + str(state.level),\n            Button(\"Start\", \"next_level\"),\n        ],\n    )\n\n\n@route\ndef next_level(state: State) -> Page:\n    state.level += 1\n    state.secret = choose_word(state.level)\n    state.wrong = 0\n    if is_game_over(state.level):\n        return game_end(state)\n    return play_level(state)\n\n\n@route\ndef play_level(state: State) -> Page:\n    so_far = reveal(state.secret, state.guesses)\n    return Page(\n        state,\n        [\n            Header(\"Level \" + str(state.level)),\n            \"Word: \" + so_far,\n            \"Guesses so far: \",\n            BulletedList(state.guesses),\n            \"Guess a letter:\",\n            TextBox(\"guess\"),\n            Button(\"Submit\", \"submit_guess\"),\n        ],\n    )\n\n\n@route\ndef submit_guess(state: State, guess: str) -> Page:\n    guess = format_guess(guess)\n    if not check_if_valid_guess(guess, state.guesses):\n        return play_level(state)\n\n    state.guesses.append(guess)\n    if not in_secret(state.secret, guess):\n        state.wrong += 1\n\n    if is_win(state.secret, state.guesses):\n        return win_level(state)\n\n    if is_loss(state.wrong):\n        return lose_level(state)\n\n    return play_level(state)\n\n\n@route\ndef win_level(state: State) -> Page:\n    state.score += state.level\n    return Page(\n        state,\n        [\n            Header(\"You Win!\"),\n            \"The word was: \" + state.secret,\n            Button(\"Play Again\", \"next_level\"),\n        ],\n    )\n\n\n@route\ndef lose_level(state: State) -> Page:\n    return Page(\n        state,\n        [\n            Header(\"You Lost!\"),\n            \"The word was: \" + state.secret,\n            Button(\"Play Again\", \"next_level\"),\n        ],\n    )\n\n\n@route\ndef game_end(state: State) -> Page:\n    return Page(\n        state,\n        [\n            Header(\"Game Over\"),\n            \"The word was: \" + state.secret,\n            \"Your final score is: \" + str(state.score),\n        ],\n    )\n\n\nstart_server(State(\"\", [], 0, 0, -1, 0))\n```\n\nHere is the incorrect version of the guessing game.\nIf you try to run this game, it will not immediately crash.\nHowever, there are several issues with the code that will prevent it from working as intended.\nIf you try to play the game, you will encounter an error very early on.\n\nPlease do take a moment to read over the code, and think about how you might approach it.\nYou might immediately see some issues with the code, such as missing or incorrect logic.\nFeel free to try fixing it on your own.\nWhen you are ready, however, we will go through several strategies that you can use to explore and understand the code more deeply.\n\n## Top-Down or Bottom-Up?\n\n-   Top-Down: Start with the big picture and break it down into smaller parts.\n-   Bottom-Up: Start with the details and build up to the big picture.\n\nThere are two general directions you can take when exploring a codebase. First is top-down, where you find the main starting point of the application, and then work your way down into the details. This can help you understand how the different parts of the program fit together.\n\nOn the other hand, bottom-up exploration involves starting with the details and building up to the big picture. This can be useful when you need to understand specific functions or components before seeing how they fit into the overall program.\n\nMechanically, this is the difference between reading through all the function definitions first (bottom-up) versus reading through the main program flow first (top-down).\nHowever, there is no one-size-fits-all approach. Depending on the codebase and your familiarity with it, you may find one method more effective than the other.\nYou might also switch between the two approaches as needed.\nYou could explore the top-level program logic first, and then start looking around at specific functions and bounce around a little.\n\nIn general, when exploring a new program (especially one that you suspect is not correct), we suggest starting with the top-level logic. Identify which, if any, functions are responsible for the main flow of the program.\nThis might help you determine that some functions are not even used.\n\n## The Top Level Logic\n\n-   Imports\n-   Function definitions\n-   State dataclass\n-   Route function definitions\n-   `start_server` function call\n\nWalking through the code from top-to-bottom, we look at what happens at the top level.\nFirst, we import the necessary modules and functions.\nThen, we define quite a few functions that make up the game logic.\nWe also define a `State` dataclass to keep track of the game state.\nIn the second half, we define a bunch of functions using the `@route` decorator from the Drafter library, to make our user interface.\nFinally, at the end of the file, we start the server with an initial state.\n\nIt is only at the very end that the program really \"begins\", which is often a source of confusion.\nTechnically, the execution starts from the very first line of the file, but the main game logic doesn't kick in until the server is started.\nEverything before that point is just setup and definitions for things that will happen later.\nAfter that point, the `index` function will be executed by Drafter and the application will flow from there.\nLet's take a closer look at the functions defined in the code.\n\n## Listing the Functions\n\n-   Routes:\n    -   index\n    -   next_level\n    -   play_level\n    -   submit_guess\n    -   win_level\n    -   lose_level\n    -   game_end\n-   Helper Functions:\n    -   choose_word\n    -   reveal\n    -   is_win\n    -   is_valid_word\n    -   is_loss\n    -   in_secret\n    -   format_guess\n    -   check_if_valid_guess\n    -   is_game_over\n\nWe've identified all the functions in the codebase and categorized them into routes and helper functions.\nThis implementation has a whopping 16 functions.\n7 of those are route functions, and the remaining 9 are helper functions.\nThis may seem like a lot, but it is actually a relatively small program.\nStill, to make sense of the program's flow, we need to understand how these functions interact with each other.\n\n## The Flow of the Functions\n\n![A diagram showing the flow of the functions in the guessing game, starting from the index route, going through all the other routes, and then also showing all of the helper functions being used.](bakery_projects_exploring_function_flow.png)\n\nUsing our list of functions, we can inspect each function further to see which other functions it calls. We use this information to construct a pictorial representation of the function calls.\nThis is known as a \"Call Graph\", or a \"Flow Diagram\", since it shows the flow of function calls.\n\nThe first real call in our program is Drafter's `start_server`, which will load the `index` page. Therefore, our diagram starts from the (heavily bolded) `index` function.\nTechnically, the routes are not called directly in our code, but when they are linked with buttons in the user interface, Drafter takes care of calling the appropriate route functions based on user interactions.\nSo the first connection from `index` is to the `next_level` function, which is called when the user progresses to the next level.\nThat function has a button that connects to `play_level` and also has logic to connect to `game_end`, but also uses two helper functions: `choose_word` and `is_game_over`.\nThe `play_level` function uses the `reveal` helper function, and then has a link to the `submit_guess` function.\n\nThe `submit_guess` function is one of the most complicated functions in the game, just from looking at the call graph. It has 5 helper functions and links to 3 other routes (`win_level`, `lose_level`, and `play_level`).\nTwo of those routes (`win_level` and `lose_level`) are connect back to the `next_level`.\nThose are all of the functions that are reachable through function calls and links.\nA major insight in this diagram is that the `is_valid_word` function is never actually called.\nThe function may have been useful at one point, but it has since been replaced by other logic in the application.\nWe can safely ignore that function as we dive deeper into exploring this codebase.\n\n## Looking at the State\n\n```python\n@dataclass\nclass State:\n    secret: str\n    guesses: list[str]\n    guesses_made: int\n    wrong: int\n    level: int\n    score: int\n```\n\nAs we explore the flow of control through the application, we also need to consider the state of the application at any given time.\nIn a Drafter application in particular, there is inevitably a dataclass named `State`. This class is used to keep track of the current state of the application.\nSometimes, the `State` will be quite complicated and incorporate additional dataclasses.\nFortunately, for this game, the `State` is relatively simple and only contains a few fields.\n\nNot every application will make its state management this explicit, so you must be prepared to think critically at every level of the application.\nFurther, we cannot assume that the `State` will be used correctly.\nWe already know this application has some issues with state management, as evidenced by the presence of unused functions and potential inconsistencies in the game logic.\nIndeed, we will eventually see that at least one of these state fields is unused!\n\n## Summary\n\n-   You can read through a program either top-down or bottom-up.\n    -   In a top-down approach, you start with the high-level structure and break it down into smaller components.\n    -   In a bottom-up approach, you start with the details and build up to the larger structure.\n-   Both approaches have their merits, and the best choice depends on the specific program and your goals as a reader.\n-   As you read through a program, pay attention to the flow of control and the state of the application at each step.\n-   Make a list of all the functions and diagram their relationships to each other.\n-   Pay attention for functions that are actually not used in the program.\n-   Always be sure to manage your emotions and physicality as you engage with the software engineering process.\n","ip_ranges":"","name":"5A2) Exploring","on_change":"","on_eval":"","on_run":"","owner_id":1,"owner_id__email":"acbart@udel.edu","points":1,"public":true,"reviewed":false,"sample_submissions":[],"settings":"{\n  \"small_layout\": true,\n  \"disable_timeout\": true,\n  \"header\": \"Exploring\"\n}","starting_code":"","subordinate":false,"tags":[],"type":"reading","url":"bakery_projects_exploring_read","version":5},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":2464,"assignment_version":5,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T12:38:53.506510+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T12:38:53.506510+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036729,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-7d2ca052-64f4-4a5d-9220-6d7abff944c1","user_id":2044658,"user_id__email":"","version":0},"success":true}
