{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2025-10-09T18:00:00+00:00","date_modified":"2025-10-23T14:21:04.196812+00:00","extra_instructor_files":"{}","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":2537,"instructions":"# Ghost Hunt\n\n## A Longer Example\n\n-   Ghost Hunt: Find All the Ghosts!\n\nLet's look at a longer example of a Drafter program. This program is called \"Ghost Hunt\" and it is a simple tile-based game where we click on tiles to find hidden ghosts.\n\n## Trying this Program\n\n-   [ghost_hunt.py](https://gist.githubusercontent.com/acbart/8bfb48903b6e5a5073a32d602afdff4a/raw/be48bf598b7a6312a5b261e4578ed11a4e035565/ghost_hunt.py)\n\nAlthough you can try running this program directly here in the textbook, we strongly recommend that you download the code and run it in Thonny. This is a longer program and has a strong graphical element. It will be easier to read the code in Thonny, and it will be easier to interact with the program that way too.\n\n**Note**: You will need to update your Drafter package to the latest version to run this program. In Thonny, click the Tools menu, then select Manage packages. Search for \"drafter\" and then click the Upgrade button.\n\n## The Program\n\n```python ghost-hunt\nfrom drafter import *\nfrom dataclasses import dataclass\nfrom random import choice, shuffle, seed\n\n\n@dataclass\nclass Tile:\n    \"\"\"\n    Represents a tile on the game board.\n\n    Attributes:\n        text (str): The text displayed on the tile (e.g., ghost, treat, or empty).\n        flipped (bool): Whether the tile has been flipped or not.\n        x (int): The x-coordinate of the tile on the grid.\n        y (int): The y-coordinate of the tile on the grid.\n    \"\"\"\n\n    text: str\n    flipped: bool\n    x: int\n    y: int\n\n\n@dataclass\nclass State:\n    \"\"\"\n    Represents the state of the game.\n\n    Attributes:\n        grid (list[list[Tile]]): The game board represented as a grid of tiles.\n        seed (int): The seed value used for randomizing the game board.\n        score (int): The current score of the player.\n        size (int): The size of the game board (size x size).\n        ghosts (int): The total number of ghosts hidden on the board.\n    \"\"\"\n\n    grid: list[list[Tile]]\n    seed: int\n    score: int\n    size: int\n    ghosts: int\n\n\nGHOST = \"\ud83d\udc7b\"\nEMPTY = \"\u2b1c\"\nTREAT = \"\ud83c\udf6c\"\n\n\ndef make_ghost_grid(seed_value: int, size: int, ghosts: int) -> list[list[Tile]]:\n    \"\"\"\n    Generates a game board with ghosts and treats.\n\n    Args:\n        seed_value (int): The seed value for randomization.\n        size (int): The size of the grid (size x size).\n        ghosts (int): The number of ghosts to place on the board.\n    Returns:\n        list[list[Tile]]: The generated game board as a grid of tiles.\n    \"\"\"\n    seed(seed_value)\n    grid = make_empty_grid(size)\n    positions = choose_ghost_spots(grid, ghosts)\n    fill_in_board(grid, positions)\n    return grid\n\n\ndef make_empty_grid(size: int) -> list[list[Tile]]:\n    \"\"\"\n    Creates an empty game board of the specified size.\n\n    Args:\n        size (int): The size of the grid (size x size).\n    Returns:\n        list[list[Tile]]: An empty grid of tiles.\n    \"\"\"\n    grid = []\n    for y in range(size):\n        row = []\n        for x in range(size):\n            row.append(Tile(EMPTY, False, x, y))\n        grid.append(row)\n    return grid\n\n\ndef choose_ghost_spots(grid: list[list[Tile]], ghosts: int) -> list[list[int]]:\n    \"\"\"\n    Randomly selects positions on the grid to place ghosts.\n\n    Args:\n        grid (list[list[Tile]]): The game board grid.\n        ghosts (int): The number of ghosts to place.\n    Returns:\n        list[list[int]]: A list of [x, y] positions for the ghosts.\n            The inner list will always have length 2.\n    \"\"\"\n    size = len(grid)\n    ys = list(range(size))\n    xs = list(range(size))\n    shuffle(xs)\n    shuffle(ys)\n    positions = []\n    for ghost in range(ghosts):\n        positions.append([xs.pop(), ys.pop()])\n    return positions\n\n\ndef add_treat_if_empty(grid: list[list[Tile]], x: int, y: int):\n    \"\"\"\n    Adds a treat to the specified position on the grid if it is empty.\n\n    Args:\n        grid (list[list[Tile]]): The game board grid.\n        x (int): The x-coordinate of the position.\n        y (int): The y-coordinate of the position.\n    \"\"\"\n    if x < 0 or y < 0 or y >= len(grid) or x >= len(grid):\n        return\n    if grid[y][x].text == EMPTY:\n        grid[y][x].text = TREAT\n\n\ndef fill_in_board(grid: list[list[Tile]], positions: list[tuple[int, int]]):\n    \"\"\"\n    Fills in the game board with ghosts and treats based on the specified ghost positions.\n\n    Args:\n        grid (list[list[Tile]]): The game board grid.\n        positions (list[tuple[int, int]]): A list of [x, y]\n            positions where ghosts should be placed.\n    \"\"\"\n    for x, y in positions:\n        grid[y][x].text = GHOST\n        add_treat_if_empty(grid, x + 1, y)\n        add_treat_if_empty(grid, x - 1, y)\n        add_treat_if_empty(grid, x, y + 1)\n        add_treat_if_empty(grid, x, y - 1)\n        add_treat_if_empty(grid, x + 1, y + 1)\n        add_treat_if_empty(grid, x - 1, y - 1)\n        add_treat_if_empty(grid, x + 1, y - 1)\n        add_treat_if_empty(grid, x - 1, y + 1)\n\n\n@route\ndef index(state: State) -> Page:\n    \"\"\"\n    The main page of the game, where players can start a new game.\n\n    Args:\n        state (State): The current state of the game.\n    Returns:\n        Page: The main game page.\n    \"\"\"\n    return Page(\n        state,\n        [\n            Header(\"Ghost Hunt\"),\n            \"Find all the ghosts by flipping tiles!\",\n            Button(\"New Game\", \"new_game\"),\n        ],\n    )\n\n\n@route\ndef new_game(state: State) -> Page:\n    \"\"\"\n    Starts a new game by initializing the game state.\n\n    Args:\n        state (State): The current state of the game.\n    Returns:\n        Page: The game page with the new game state.\n    \"\"\"\n    state.grid = make_ghost_grid(state.seed, state.size, state.ghosts)\n    state.score = 0\n    return play_game(state)\n\n\ndef make_tile_button(tile: Tile) -> Button:\n    \"\"\"\n    Creates a button for a tile that can be flipped.\n\n    Args:\n        tile (Tile): The tile to create a button for.\n    Returns:\n        Button: A button that, when clicked, will flip the tile.\n    \"\"\"\n    return Button(\n        \"\",\n        \"flip_tile\",\n        arguments=[Argument(\"x\", tile.x), Argument(\"y\", tile.y)],\n    )\n\n\ndef draw_grid(grid: list[list[Tile]]) -> list[list[PageContent]]:\n    \"\"\"\n    Draws the game board grid, showing flipped tiles and buttons for unflipped tiles.\n\n    Args:\n        grid (list[list[Tile]]): The game board grid.\n    Returns:\n        list[list[PageContent]]: A grid representation where flipped tiles show their text\n            and unflipped tiles are represented as buttons.\n    \"\"\"\n    display = []\n    for row in grid:\n        display_row = []\n        for tile in row:\n            if tile.flipped:\n                display_row.append(tile.text)\n            else:\n                display_row.append(make_tile_button(tile))\n        display.append(display_row)\n    return display\n\n\n@route\ndef play_game(state: State) -> Page:\n    \"\"\"\n    Plays the game by displaying the current state of the grid.\n\n    Args:\n        state (State): The current state of the game.\n    Returns:\n        Page: The game page showing the current grid.\n    \"\"\"\n    grid_display = draw_grid(state.grid)\n    return Page(\n        state,\n        [\n            \"Press a button to look for ghosts.\",\n            \"Candy means a ghost is nearby!\",\n            Table(grid_display),\n        ],\n    )\n\n\n@route\ndef flip_tile(state: State, x: int, y: int) -> Page:\n    \"\"\"\n    Flips a tile at the specified coordinates and updates the game state.\n\n    Args:\n        state (State): The current state of the game.\n        x (int): The x-coordinate of the tile to flip.\n        y (int): The y-coordinate of the tile to flip.\n    Returns:\n        Page: The updated game page after flipping the tile.\n    \"\"\"\n    tile = state.grid[y][x]\n    tile.flipped = True\n    if tile.text == GHOST:\n        state.score += 1\n    if state.score == state.ghosts:\n        return game_won(state)\n    return play_game(state)\n\n\n@route\ndef game_won(state: State) -> Page:\n    \"\"\"\n    Displays the game won page when all ghosts have been found.\n\n    Args:\n        state (State): The current state of the game.\n    Returns:\n        Page: The game won page.\n    \"\"\"\n    return Page(\n        state,\n        [\n            Header(\"You found all the ghosts!\"),\n            GHOST * state.ghosts,\n            \"Congratulations!\",\n            Button(\"Play Again\", \"new_game\"),\n        ],\n    )\n\n\nstart_server(State([], 42, 0, 5, 3))\n```\n\n## Some Notes\n\n```python seed-example\nfrom bakery import assert_equal\nfrom random import seed, randint\n\nprint(randint(0, 10))\n# This will print a different number each time\n\nseed(1)\nprint(randint(0, 10))\n# This will always print 4\n\nseed(5)\nprint(randint(0, 10))\n# This will always print 2\n\nseed(1)\nprint(randint(0, 10))\n# This will always print 4 again\n```\n\nIn the Ghost Hunt game, we use the `seed` function from the `random` module to make sure that the random numbers generated are the same each time we run the program. This is important for testing and debugging, as it allows us to have a consistent experience.\n\nComputers use complicated algorithms to generate random numbers, but these algorithms are _deterministic_, meaning that if you start with the same initial value (the seed), you will get the same sequence of random numbers. By setting the seed to a specific value at the start of our program, we ensure that every time we run the program, we get the same sequence of random numbers, which means the ghosts will be in the same places each time.\n\nIn this example, we set the seed to `1` before we generate the second \"random\" number, which is why it always prints `4`. When we set the seed to `5`, it always prints `2`. Setting the seed back to `1` again gives us `4` once more. If we didn't set the seed, the random numbers would be different each time we run the program.\n\n## Grid and Coordinates\n\n```python\ndef make_ghost_grid(seed_value: int, size: int, ghosts: int) -> list[list[Tile]]:\n    grid = make_empty_grid(size)\n    positions = choose_ghost_spots(grid, ghosts)\n    fill_in_board(grid, positions)\n```\n\nThe game is played on a grid of tiles. Each tile can either be empty or contain a ghost. The player clicks on tiles to reveal whether they contain a ghost or not.\nTo represent this grid in our program, we use a list of lists (a 2D list). Each inner list represents a row of tiles, and each element in the inner list represents a tile in that row.\n\nThe `make_ghost_grid` function creates the grid by first making an empty grid of the specified size, then choosing random positions for the ghosts, and finally filling in the board with ghosts and treats. It uses its helper functions to decompose the tricky logic into smaller, more manageable pieces.\n\n## Possible Future Additions\n\n-   Keep track of how many clicks the user needs\n-   Hide other things behind the buttons\n-   Make the grid size configurable\n\nWe hope you enjoyed this longer example of a Drafter program! Here are some ideas for how this game could be extended in the future. We encourage you to try implementing some of these features on your own.","ip_ranges":"","name":"9A1) Ghost Hunt Reading","on_change":"","on_eval":"","on_run":"","owner_id":1,"owner_id__email":"acbart@udel.edu","points":0,"public":true,"reviewed":false,"sample_submissions":[],"settings":"{\"summary\": \"This is an example of a longer Drafter program.\", \"small_layout\": true, \"disable_timeout\": \"true,\"}","starting_code":"","subordinate":true,"tags":[],"type":"reading","url":"bakery_project2_example1_read","version":1},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":2537,"assignment_version":1,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T12:38:51.694975+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T12:38:51.694975+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036725,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-2764da34-bf4e-477f-9676-ae09e25b06f6","user_id":2044658,"user_id__email":"","version":0},"success":true}
