Separate Suitable Games from Similar Games

PythonBeginner
Practice Now

Introduction

A small game library needs alternatives for tonight. A game can resemble your favorite yet be unavailable or unsuitable for the group. In this lab, you will keep those two questions separate: which games can be used, and how their numeric features should be compared.

You will create a rerunnable suitability program and a real JSON result. You need basic Python functions and dictionaries, introductory NumPy arrays, and familiarity with reading tabular data. K-nearest-neighbor classification is useful background, but this project has no labels to predict. Python and NumPy are ready; scikit-learn is not needed. The 45-minute duration is an author estimate including reading and ordinary corrections.

Decide Which Games Tonight Can Use

In this step, you will inspect the real library catalog, then implement tonight's eligibility rules in one region of a prepared function.

Open the Terminal Tab. All commands run there, including the saved-file reader. Opening or reconnecting Terminal does not reinstall or reset your workspace. cd changes this terminal's current directory; another terminal has its own directory.

cd /home/labex/project/board-games

Start with the actual input. CSV has a header followed by comma-separated rows. cat prints the file without changing it. This is the complete, unfiltered catalog, not a list of games already qualified for tonight.

cat catalog.csv

Each row represents a distinct game edition. Its unique, nonempty game_id is the key used to find and associate that edition. title is display text: several Harbor editions have different IDs and must remain separate. minutes is a positive play-time estimate; complexity is a synthetic 1–5 rating, with larger values meaning more complicated. Integer min_players and max_players include both endpoints. The synthetic input is complete and has one row per ID; cleaning malformed data is outside this exercise.

cat availability.csv

The separate borrowing table has one row for every catalog ID, in another order. Its CSV text true/false becomes Python booleans True/False in the prepared loader. Joining by a key means finding the same game_id, not attaching row 1 to row 1. Unique keys and one lending-state row per ID prevent ambiguous or missing matches. You will implement that lookup.

JSON stores named values, lists and objects. Read tonight's request:

cat night.json

reference_id is the example to resemble, players is the group size, and k is the requested number of near alternatives, used in the next lab. The reference is a query example and can itself be unavailable or unsuitable; only returned alternatives need tonight's eligibility. Exclude only its exact ID, not all games with its title or identical features. The raw input overview has not applied these rules or generated a suitability result.

Consider three available games supporting four players: Harbor has 60 minutes and complexity 3, Quay has 61 and 5, and Bridge has 90 and 3. Original-unit differences are candidate minus reference: Quay is one minute longer and two complexity points higher; Bridge is thirty minutes longer and equally complex. Python's -c runs the quoted calculation as one argument. The semicolon separates two Python statements.

python3 -c "print('Quay minutes:', 61 - 60); print('Bridge minutes:', 90 - 60)"

Expect 1 and 30. Raw Euclidean distance squares and adds feature differences, then takes a square root. Thirty minutes can overwhelm two complexity points if those units are mixed directly. You will define a common scale next; first decide who is allowed to compete.

You may edit suitability.py, ranking.py, your result files and your own script backups. Preserve every supplied CSV/JSON input, planner.py, coursekit.py, show_results.py and both starter-*.py recovery copies. planner.py selects a public function and delegates file reading, type conversion and JSON writing to coursekit.py; neither makes the business decision. Leave ranking.py unfinished in this lab.

Read the prepared module:

cat suitability.py

choose_games(catalog, availability, request) receives the loaded lists of dictionaries and request dictionary. Its two labeled regions start with eligible = [] and scale = {}. These are unfinished placeholders, not a claim that no games qualify. The final return packages the IDs and scale for the runner. You will replace the eligibility region now and keep the scale placeholder for step 2. Setup and the input overview create no graded result.

Back up this module. cp SOURCE DESTINATION copies just the named file; .bak is a backup suffix. Do not overwrite this backup after a mistake.

cp suitability.py suitability.py.bak

nano edits an existing file inside Terminal. The command-local LC_ALL=C.UTF-8 selects UTF-8 for this editor only, without changing global shell settings. In nano's shortcut strip, ^ means hold Ctrl. Arrow keys move the cursor; typing inserts text. Ctrl+W, a search phrase, then Enter locates text. Home moves to the line start; Ctrl+K cuts that whole line. Python uses indentation: the lines below start with four spaces inside the function, with further indentation inside its loop and condition.

LC_ALL=C.UTF-8 nano suitability.py

First practice discarding an unwanted edit. Find ## Candidate identities with Ctrl+W and Enter, press End, and add temporary to that comment. Press Ctrl+X, then N at the save question. You leave without changing the file. Open it again:

LC_ALL=C.UTF-8 nano suitability.py

Find eligible = [], press Home, then Ctrl+K once to remove only that placeholder line. Keep the function header, scale region and final return. Insert the following indented lines there. The dictionary comprehension builds an ID-to-availability lookup. The loop uses and for three independent requirements; chained <= comparisons include both player limits. append adds the matching ID, not the display title.

    status = {row['game_id']: row['available'] for row in availability}
    eligible = []
    for game in catalog:
        different_id = game['game_id'] != request['reference_id']
        fits_group = game['min_players'] <= request['players'] <= game['max_players']
        if different_id and fits_group and status[game['game_id']]:
            eligible.append(game['game_id'])

Save with Ctrl+O, press Enter to confirm the unchanged filename, then Ctrl+X to exit. Saving does not run the function. Its existing return uses sorted(eligible) to produce an alphabetical identity list, independently of input row order, and leaves scale as an empty object.

The runner's --mode suitable selects choose_games; required --output suitable.json names its saved result. Defaults are catalog.csv, availability.csv and night.json in this directory. --catalog, --availability and --request can select other paths. These inputs remain read-only.

python3 planner.py --mode suitable --output suitable.json

The supplied reader opens that saved JSON only. It neither generates a missing file nor calculates anything from the catalog.

python3 show_results.py suitable.json

Expect Scale: {} and IDs left, near, raw, right, same, zero. Both same and zero are other Harbor editions; zero even has identical numeric features to the reference. blocked is unavailable, small cannot seat four, and ref is the exact reference. Compare these identities with the two input tables. Qualification is now complete, but scaling is deliberately not yet implemented.

The next wrong-directory failure is intentional. Move one directory up:

cd /home/labex/project
python3 planner.py --mode suitable --output suitable.json

Python reports can't open file and No such file or directory: it cannot find the runner here. Your existing program and result have not been reset. pwd reveals this terminal's directory; return to the workspace and rerun the actual command.

pwd
cd /home/labex/project/board-games
python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json

For an editing error, read Python's filename and line number, reopen the module in nano and correct just the affected region. A SyntaxError often points to a missing colon or quote; an IndentationError means the leading spaces need attention. Save, exit and rerun. Ctrl+X, N abandons unsaved changes. If necessary, cp suitability.py.bak suitability.py restores only this unfinished base, so you must reapply the eligibility region afterward. Neither a backup nor the protected starter-suitability.py is your completed solution. Do not rerun setup or change inputs to recover.

Keep One Scale for the Whole Catalog

In this step, you will implement the scale region while preserving your completed eligibility logic.

Continue in Terminal. An explicit directory also makes the command safe in a new terminal.

cd /home/labex/project/board-games

A feature is one numeric property used in comparison. Our order is always minutes, then complexity; player bounds and availability are eligibility rules, not distance features. Find each feature's minimum and maximum over the entire catalog, including the reference, unavailable games and unsuitable games. Its range is maximum minus minimum. MinMax scaling maps x to (x - minimum) / range, expressing each feature as a fraction of its catalog range.

This catalog's minima are [10, 1] and ranges [100, 4]. Quay's differences become [1/100, 2/4]; Bridge's become [30/100, 0/4]. Their scaled Euclidean distances are about 0.5001 and 0.3. The declared metric changes their order, but neither unit system predicts personal taste. Equal scaled feature weights are this exercise's transparent design choice.

The catalog defines a metric, not a supervised training set: there is no target label or train/test split. Changing only borrowing state must not change the scale or retained candidates' distances. Recomputing min/range after eligibility filtering would move the ruler when a game is borrowed.

For a feature constant throughout the catalog, save its true range 0. Later distance calculation uses divisor 1 for that feature. The query is itself a catalog member, so its constant-feature difference is zero for every candidate. This avoids division by zero without inventing information.

Keep a new backup of your working eligibility stage, leaving the original base backup intact:

cp suitability.py suitability-ids.py.bak
LC_ALL=C.UTF-8 nano suitability.py

Find scale = {} with Ctrl+W and Enter, press Home, and cut only that one placeholder line with Ctrl+K. Leave the completed ID lookup and loop above it unchanged. Insert the four indented lines below. np.array builds a two-column numeric table in the published feature order; axis=0 finds minima and maxima down each column. tolist() converts arrays to ordinary lists that JSON can save.

    features = np.array([[game['minutes'], game['complexity']] for game in catalog])
    minimum = features.min(axis=0)
    ranges = features.max(axis=0) - minimum
    scale = {'min': minimum.tolist(), 'range': ranges.tolist()}

The existing return now includes this computed scale and your same sorted IDs. Save with Ctrl+O, Enter, then Ctrl+X. You have added a region rather than replacing the preceding work or appending another program.

The result requires each eligible ID once, alphabetically, plus two numeric values in each scale.min and scale.range list. Save numbers, not formatted strings. Extra harmless explanation fields are allowed. Numerical comparisons use absolute and relative tolerances of 1e-8; this allows ordinary floating-point arithmetic, not a different ruler.

Regenerate only the result file from the unchanged original inputs:

python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json

Expect minima 10 and 1, ranges 100 and 4, and the same six eligible IDs. The reader may display 10.0 instead of 10; both mean numeric ten. Inspect the saved structure directly:

cat suitable.json

If an edit damages the eligibility region, cp suitability-ids.py.bak suitability.py restores that working stage only. Reopen nano and implement the scale region again. cp starter-suitability.py suitability.py instead restores the unfinished base and loses both regions; use it only when intentionally starting over. Ordinary corrections should fix the relevant lines, save, exit and rerun. Preserve both supplied starter files and all inputs. Reconnecting Terminal never runs setup or restores a backup for you.

Self-check: tonight's eligibility can change without changing this full-catalog ruler. In the next independent lab, the complete suitability function is supplied so you can focus on distances and selection.

Summary

You separated tonight's eligibility from content similarity, joined independent rows using stable game IDs, retained legitimate same-title and zero-difference alternatives, and derived a fixed full-catalog MinMax scale. Your suitable.json is a real, rerunnable result. The next lab uses this knowledge to measure and explain candidates; it starts in its own independent environment.