# COMP1010 Exam Notes

---

## Python Fundamentals

### Variables & Types
```python
x = 5          # int
y = 3.14       # float
s = "hello"    # str
b = True       # bool

int("5")       # -> 5
float("3.14")  # -> 3.14
str(5)         # -> "5"
```

### Input / Output
```python
name = input("Enter name: ")   # always returns str
print("Hello", name)
print(f"Hello {name}")         # f-string
```

### Conditionals
```python
if x > 0:
    print("positive")
elif x == 0:
    print("zero")
else:
    print("negative")
```

### Loops
```python
for i in range(5):        # 0, 1, 2, 3, 4
    print(i)

for item in my_list:
    print(item)

while x > 0:
    x -= 1
```

### Functions
```python
def greet(name):
    return "Hello " + name

result = greet("Aaran")
```

### String Methods
```python
s = "hello world"
s.upper()           # "HELLO WORLD"
s.lower()           # "hello world"
s.strip()           # removes whitespace from ends
s.split(" ")        # ["hello", "world"]
s.replace("o", "0") # "hell0 w0rld"
len(s)              # 11
s.startswith("he")  # True
" ".join(["a","b"]) # "a b"
```

---

## Data Structures

### Lists
```python
items = [1, 2, 3]
items.append(4)       # [1, 2, 3, 4]
items.remove(2)       # [1, 3, 4]
items.pop()           # removes last item
items[0]              # 1  (first)
items[-1]             # last item
len(items)            # length

for item in items:
    print(item)

# check membership
if 3 in items:
    print("found")
```

### Dictionaries
```python
user = {"name": "Aaran", "age": 20}
user["name"]           # "Aaran"
user["age"] = 21       # update
user["email"] = "a@b"  # add new key

user.get("phone", "N/A")   # safe get with default
user.keys()                # all keys
user.values()              # all values
user.items()               # key-value pairs

for key, value in user.items():
    print(key, value)

if "name" in user:
    print("has name")

del user["age"]        # delete key
```

### List of Dicts (common pattern)
```python
users = [
    {"name": "Alice", "score": 90},
    {"name": "Bob",   "score": 75},
]

for user in users:
    print(user["name"], user["score"])
```

### Choosing a Data Structure
| Need | Use |
|------|-----|
| Ordered sequence of items | list |
| Key-value lookup | dict |
| Multiple records with same fields | list of dicts |
| Persist data across requests | file (JSON) |
| Per-user data across requests | session (cookie) |

**Pros/Cons:**
- **List:** simple, ordered, good for sequences. No named fields — hard to read with complex data.
- **Dict:** named fields, fast lookup. Unordered (Python 3.7+ insertion order). Harder to have multiple records.
- **List of dicts:** best of both — multiple records with named fields. Slightly more verbose to access.

---

## JSON (File Persistence)

### Read / Write
```python
import json
import os

FILE = "data.json"

def read_data():
    if not os.path.exists(FILE):
        return {}                        # return empty default
    with open(FILE, "r") as f:
        return json.load(f)

def write_data(data):
    with open(FILE, "w") as f:
        json.dump(data, f, indent=4)
```

### Common Pattern — load on startup, save on change
```python
data = read_data()   # at top of file, after Flask init

@app.post("/add")
def add():
    item = request.form["item"]
    data["items"].append(item)
    write_data(data)
    return redirect("/")
```

### JSON stores: dicts, lists, strings, ints, floats, bools, None
```python
# list of records
records = [{"name": "Alice", "score": 90}]

# nested dict
state = {
    "users": ["alice", "bob"],
    "scores": {"alice": 90, "bob": 75}
}
```

---

## Flask Basics

### Minimal App
```python
from flask import Flask, request, redirect
import pyhtml as p

app = Flask(__name__)

@app.get("/")
def home():
    page = p.html(
        p.head(p.title("My App")),
        p.body(p.h1("Hello"))
    )
    return str(page)

if __name__ == "__main__":
    app.run(port=5001)
```

### Routes
```python
@app.get("/page")        # handles GET request
def page():
    return str(...)

@app.post("/submit")     # handles POST (form submission)
def submit():
    name = request.form["name"]   # read form field
    return redirect("/")          # redirect after POST
```

### Forms in pyhtml
```python
p.form(
    p.label("Name:"),
    p.input(type="text", name="username"),
    p.input(type="submit", value="Submit"),
    method="post",
    action="/submit"
)
```

### Common pyhtml tags
```python
p.html(...)
p.head(p.title("Title"))
p.body(...)
p.h1("Heading"), p.h2(...), p.h3(...)
p.p("Paragraph text")
p.a("Click here", href="/page")
p.ul(p.li("item1"), p.li("item2"))
p.ol(p.li("first"), p.li("second"))
p.table(p.tr(p.th("Col1"), p.th("Col2")))
p.tr(p.td("data1"), p.td("data2"))
p.form(..., method="post", action="/route")
p.input(type="text", name="field")
p.input(type="submit", value="Go")
p.textarea(name="msg")
p.select(p.option("A"), p.option("B"), name="choice")
p.br()
p.hr()
p.div(..., id="main", class_="container")  # note: class_ not class
p.span("text")
p.img(src="/static/img.png", alt="desc")
p.script("...")            # inline JS
```

### Linking CSS
```python
p.head(
    p.title("App"),
    p.link(rel="stylesheet", href="/static/style.css")
)
```

### Building dynamic HTML (loop example)
```python
items = ["apple", "banana", "cherry"]
li_tags = []
for item in items:
    li_tags.append(p.li(item))

page = p.html(p.body(p.ul(*li_tags)))   # * unpacks list
```

---

## Sessions (Cookies)

### Setup
```python
from flask import Flask, request, redirect, session

app = Flask(__name__)
app.secret_key = "some_secret_key"   # required for sessions
```

### Read / Write / Delete
```python
session["username"] = "aaran"         # store
name = session.get("username")        # read (None if missing)
name = session.get("username", "Guest")  # with default
del session["username"]               # delete one key
session.clear()                       # clear all
```

### Persistent cookie (survives browser close)
```python
session.permanent = True
```

### Login / Logout pattern
```python
@app.post("/login")
def login():
    username = request.form["username"]
    session["username"] = username
    session.permanent = True
    return redirect("/")

@app.post("/logout")
def logout():
    session.clear()
    return redirect("/")

@app.get("/")
def home():
    username = session.get("username")
    if username is None:
        return redirect("/login")
    return str(p.html(p.body(p.p("Hello " + username))))
```

### When to use sessions vs files
- **Session:** data specific to one user, temporary (e.g. who is logged in, current cart)
- **File:** data that should persist across server restarts, shared across users (e.g. all posts, all scores)

---

## Web App Patterns

### App with file persistence
```python
import json, os
from flask import Flask, request, redirect
import pyhtml as p

app = Flask(__name__)
FILE = "items.json"

def load():
    if not os.path.exists(FILE):
        return []
    with open(FILE) as f:
        return json.load(f)

def save(data):
    with open(FILE, "w") as f:
        json.dump(data, f, indent=4)

@app.get("/")
def home():
    data = load()
    items = []
    for item in data:
        items.append(p.li(item))
    return str(p.html(p.body(
        p.ul(*items) if items else p.p("Nothing yet"),
        p.form(
            p.input(type="text", name="item"),
            p.input(type="submit", value="Add"),
            method="post", action="/add"
        )
    )))

@app.post("/add")
def add():
    data = load()
    data.append(request.form["item"])
    save(data)
    return redirect("/")

if __name__ == "__main__":
    app.run(port=5001)
```

### App with sessions + file persistence
```python
# Store per-user data in session, shared data in file
# session["username"] -> who is logged in
# json file -> all users' records
```

---

## HTML & CSS Reference

### Basic HTML structure
```html
<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Heading</h1>
    <p>Paragraph</p>
</body>
</html>
```

### Common tags
```html
<h1> to <h6>          headings
<p>                   paragraph
<a href="/page">      link
<img src="" alt="">   image
<ul><li>              unordered list
<ol><li>              ordered list
<table><tr><th><td>   table
<form method="post" action="/route">  form
<input type="text" name="x">         text input
<input type="submit" value="Go">     submit button
<textarea name="msg"></textarea>     multi-line input
<div>                 block container
<span>                inline container
<br>                  line break
<hr>                  horizontal rule
```

### Forms
```html
<form method="post" action="/submit">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name">
    <input type="submit" value="Submit">
</form>
```
- `method="post"` — sends data in request body (use for creating/changing data)
- `method="get"` — sends data in URL (use for search/filter)
- `name` attribute is what Flask reads via `request.form["name"]`

### CSS basics
```css
/* selector { property: value; } */

body {
    font-family: sans-serif;
    background-color: #f0f0f0;
    color: #333;
}

h1 {
    color: navy;
    font-size: 2rem;
}

.container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

#main {
    background: white;
}

a {
    color: blue;
    text-decoration: none;
}

/* class selector: .classname    used in HTML as class="classname" */
/* id selector:    #idname       used in HTML as id="idname"       */
```

---

## Usability (Nielsen's Heuristics)

Key heuristics to know and apply:

| # | Heuristic | What it means in practice |
|---|-----------|--------------------------|
| 1 | **Visibility of system status** | Tell the user what's happening — e.g. "Saved!" confirmation, loading indicators |
| 2 | **Match between system and real world** | Use familiar language and concepts, not technical jargon |
| 3 | **User control and freedom** | Provide undo, back buttons, cancel options |
| 4 | **Consistency and standards** | Same words, same buttons, same layout across all pages |
| 5 | **Error prevention** | Validate input before submitting, confirm destructive actions |
| 6 | **Recognition over recall** | Show options/menus rather than making users remember commands |
| 7 | **Flexibility and efficiency** | Shortcuts for expert users, defaults for new users |
| 8 | **Aesthetic and minimalist design** | Don't show irrelevant info — every extra element competes for attention |
| 9 | **Help users recognise/recover from errors** | Clear error messages that say what went wrong and how to fix it |
| 10 | **Help and documentation** | If users need docs, make them easy to find and action-oriented |

### For "discuss usability" questions:
- Name the heuristic violated or satisfied
- Describe specifically what the interface does
- Explain why it helps or hurts the user

**Example:** "The form shows no confirmation after submission — this violates heuristic 1 (visibility of system status) because the user doesn't know if their data was saved."

---

## Designing a Web App (for "design" questions)

### Data structure design
1. What data needs to be stored?
2. Does it need to persist across server restarts? → use a file
3. Is it per-user? → use session (cookie)
4. Is it shared across users? → use a file
5. What fields does each record need?

**Example:** A to-do app needs:
- A list of tasks (shared, persistent) → JSON file: `[{"task": "...", "done": false}]`
- Who is logged in → session: `session["username"]`

### UI design
- Every page should have a clear purpose
- Navigation should be consistent
- Forms should have labels on every input
- Feedback after every action (redirect or confirmation message)
- Error messages should be helpful

---


## Misc Python

### Random
```python
import random
random.randint(1, 10)      # random int between 1 and 10 inclusive
random.choice(["a","b"])   # random item from list
random.shuffle(my_list)    # shuffle list in place
```

### os module
```python
import os
os.path.exists("file.txt")   # True/False
os.path.join("folder","file.txt")  # safe path joining
```

### Common mistakes
- `input()` always returns a string — convert with `int()` or `float()` if needed
- `request.form["key"]` crashes if key missing — use `request.form.get("key", "")`
- `session.get("key")` is safer than `session["key"]` — won't crash if missing
- Must call `json.dump` to write (not just modify the variable)
- `app.secret_key` must be set before using sessions
- `class` is reserved in Python — pyhtml uses `class_` instead

---

---

# EXAMPLE EXAM QUESTIONS & ANSWERS

---

## Q1 — Python Fundamentals

**Question:** Write a function `is_passing(score)` that returns `True` if score is 50 or above, otherwise `False`. Then write a loop that reads 3 scores from the user and prints "Pass" or "Fail" for each.

```python
# function takes a score and checks if it's passing
def is_passing(score):
    if score >= 50:
        return True
    else:
        return False

# range(3) means loop runs 3 times: i = 0, 1, 2
for i in range(3):
    score = int(input("Enter score: "))  # input() always returns str, int() converts it
    if is_passing(score):                # call the function and check result
        print("Pass")
    else:
        print("Fail")
```

---

## Q2 — Strings

**Question:** Write a function `initials(full_name)` that takes a full name like `"john smith"` and returns `"J.S."`.

```python
def initials(full_name):
    parts = full_name.split(" ")  # split on space -> ["john", "smith"]
    result = ""
    for part in parts:
        result = result + part[0].upper() + "."  # part[0] = first letter, .upper() capitalises
    return result

print(initials("john smith"))   # "J.S."
```

---

## Q3 — Lists

**Question:** Write a function `above_average(scores)` that takes a list of numbers and returns a new list containing only the numbers above the average.

```python
def above_average(scores):
    # first pass: add up everything to find the average
    total = 0
    for score in scores:
        total = total + score
    avg = total / len(scores)  # len() gives how many items are in the list

    # second pass: only keep scores that beat the average
    result = []
    for score in scores:
        if score > avg:
            result.append(score)  # append adds to the end of the list
    return result

print(above_average([40, 60, 80, 100]))   # avg = 70, so returns [80, 100]
```

---

## Q4 — Dictionaries

**Question:** You have a list of student dicts. Write a function `find_student(students, name)` that returns the student dict with that name, or `None` if not found.

```python
# list of dicts — each student is a dict with "name" and "grade" keys
students = [
    {"name": "Alice", "grade": "A"},
    {"name": "Bob",   "grade": "B"},
    {"name": "Carol", "grade": "A"},
]

def find_student(students, name):
    for student in students:             # loop through every student dict
        if student["name"] == name:      # check the "name" key of this dict
            return student               # found it — return the whole dict
    return None                          # looped through everything, nothing matched

result = find_student(students, "Bob")
print(result)   # {"name": "Bob", "grade": "B"}
```

---

## Q5 — Data Structure Design (written)

**Question:** You are building a recipe app. Users can save their own recipes. Each recipe has a name, ingredients list, and instructions. What data structure would you use and where would you store it?

**Answer:**

Each recipe is a dict with three fields. All recipes go in a list of dicts.

```python
# list of dicts — one dict per recipe
# ingredients is a list inside the dict (nested)
recipes = [
    {
        "name": "Pasta",
        "ingredients": ["pasta", "tomato sauce", "cheese"],
        "instructions": "Boil pasta, add sauce, top with cheese."
    }
]
```

Storage: recipes need to survive server restarts, so store in a JSON file. Since each recipe belongs to a user, store `"owner"` in each dict and filter by `session["username"]` when displaying. Who is logged in lives in the session cookie.

---

## Q6 — JSON File Persistence

**Question:** Write a Flask app where users submit a message via a form, messages are saved to a file, and all messages display on the home page.

```python
import json, os
from flask import Flask, request, redirect
import pyhtml as p

app = Flask(__name__)
FILE = "messages.json"  # all messages saved here

# load messages from file — returns empty list if file doesn't exist yet
def load_messages():
    if not os.path.exists(FILE):  # first run: file hasn't been created yet
        return []
    with open(FILE) as f:
        return json.load(f)       # reads JSON and converts it to a Python list

# write the updated messages list back to the file
def save_messages(messages):
    with open(FILE, "w") as f:
        json.dump(messages, f, indent=4)  # indent=4 makes the file human-readable

@app.get("/")
def home():
    messages = load_messages()  # load current messages every time the page loads
    items = []
    for msg in messages:
        items.append(p.li(msg))  # wrap each message in an <li> tag
    return str(p.html(
        p.head(p.title("Messages")),
        p.body(
            p.h1("Messages"),
            p.ul(*items) if items else p.p("No messages yet."),  # * unpacks the list
            p.form(
                p.label("New message:"),
                p.input(type="text", name="message"),
                p.input(type="submit", value="Post"),
                method="post",
                action="/post"  # form sends data to the /post route
            )
        )
    ))

@app.post("/post")
def post():
    messages = load_messages()                # load what's already saved
    msg = request.form.get("message", "")    # read what the user typed, "" if nothing
    if msg != "":                             # don't save empty submissions
        messages.append(msg)
        save_messages(messages)              # write updated list back to file
    return redirect("/")                     # always redirect after a POST

if __name__ == "__main__":
    app.run(port=5001)
```

---

## Q7 — Sessions / Cookies

**Question:** Build a Flask app where a user enters their name on a login page and is greeted by name on the home page. If they visit home without logging in, redirect to login. Include a logout button.

```python
from flask import Flask, request, redirect, session
import pyhtml as p

app = Flask(__name__)
app.secret_key = "secret"  # required to use sessions — Flask won't work without this

@app.get("/")
def home():
    username = session.get("username")  # returns None if not logged in
    if username is None:
        return redirect("/login")       # kick them to login page if no session exists
    return str(p.html(
        p.head(p.title("Home")),
        p.body(
            p.h1("Hello, " + username + "!"),
            p.form(
                p.input(type="submit", value="Logout"),
                method="post",
                action="/logout"  # clicking logout submits to POST /logout
            )
        )
    ))

# GET /login — just shows the login form
@app.get("/login")
def login_page():
    return str(p.html(
        p.head(p.title("Login")),
        p.body(
            p.h1("Login"),
            p.form(
                p.label("Name:"),
                p.input(type="text", name="username"),
                p.input(type="submit", value="Login"),
                method="post",
                action="/login"  # form submits to POST /login below
            )
        )
    ))

# POST /login — processes the form and saves to session
@app.post("/login")
def login():
    username = request.form.get("username", "")  # read what user typed
    if username != "":
        session["username"] = username   # save into the session cookie
        session.permanent = True         # cookie survives browser close
    return redirect("/")                 # go to home page

@app.post("/logout")
def logout():
    session.clear()           # wipe all session data
    return redirect("/login") # send back to login

if __name__ == "__main__":
    app.run(port=5001)
```

---

## Q8 — Sessions + File (combined)

**Question:** Build a note-taking app where each user sees only their own notes, and notes persist across server restarts.

```python
import json, os
from flask import Flask, request, redirect, session
import pyhtml as p

app = Flask(__name__)
app.secret_key = "secret"
FILE = "notes.json"

# notes stored as a dict keyed by username:
# {"alice": ["note1", "note2"], "bob": ["note3"]}
def load_notes():
    if not os.path.exists(FILE):
        return {}  # empty dict — no users have notes yet
    with open(FILE) as f:
        return json.load(f)

def save_notes(notes):
    with open(FILE, "w") as f:
        json.dump(notes, f, indent=4)

@app.get("/")
def home():
    username = session.get("username")
    if username is None:
        return redirect("/login")   # not logged in — go to login

    notes = load_notes()
    user_notes = notes.get(username, [])  # get this user's list, or [] if they're new
    items = []
    for note in user_notes:
        items.append(p.li(note))    # each note becomes a list item
    return str(p.html(
        p.head(p.title("Notes")),
        p.body(
            p.h1(username + "'s Notes"),
            p.ul(*items) if items else p.p("No notes yet."),
            p.form(
                p.input(type="text", name="note"),
                p.input(type="submit", value="Add Note"),
                method="post",
                action="/add"
            ),
            p.form(
                p.input(type="submit", value="Logout"),
                method="post",
                action="/logout"
            )
        )
    ))

@app.post("/add")
def add():
    username = session.get("username")
    if username is None:
        return redirect("/login")   # safety check — must be logged in to add

    note = request.form.get("note", "")
    if note != "":
        notes = load_notes()
        if username not in notes:
            notes[username] = []    # first note for this user — create their list
        notes[username].append(note)
        save_notes(notes)           # write everything back to file
    return redirect("/")

@app.get("/login")
def login_page():
    return str(p.html(
        p.head(p.title("Login")),
        p.body(
            p.form(
                p.label("Username:"),
                p.input(type="text", name="username"),
                p.input(type="submit", value="Login"),
                method="post",
                action="/login"
            )
        )
    ))

@app.post("/login")
def login():
    username = request.form.get("username", "")
    if username != "":
        session["username"] = username
        session.permanent = True
    return redirect("/")

@app.post("/logout")
def logout():
    session.clear()
    return redirect("/login")

if __name__ == "__main__":
    app.run(port=5001)
```

---

## Q9 — Usability (written)

**Question:** A web form submits successfully but shows no message and the page looks the same. What usability heuristic does this violate? How would you fix it?

**Answer:** Violates **Heuristic 1 — Visibility of system status**. The user has no way of knowing whether their submission worked. Fix: after a successful POST, redirect to a page that shows the new data, or display a "Saved!" confirmation. Reloading the same blank form gives zero feedback.

---

## Q10 — Usability (written)

**Question:** A form field is labelled `usr_inp_01`. What heuristic does this violate? What should it say instead?

**Answer:** Violates **Heuristic 2 — Match between system and real world**. Labels should use plain language, not internal variable names. It should say something like "Username", "Email address", or "Your name" depending on what it collects.

---



## Q12 — HTML form

**Question:** Write an HTML form that collects a username and password and submits via POST to `/login`.

```html
<form method="post" action="/login">
    <!-- for= links the label to the input with matching id -->
    <label for="username">Username:</label>
    <input type="text" id="username" name="username">

    <!-- type="password" hides the characters as the user types -->
    <label for="password">Password:</label>
    <input type="password" id="password" name="password">

    <input type="submit" value="Login">
</form>
```

---

## Q13 — CSS

**Question:** Write CSS that makes all `<h1>` tags red, gives the body a light grey background, and makes links have no underline.

```css
body {
    background-color: #f0f0f0;  /* light grey — # means hex colour code */
}

h1 {
    color: red;  /* must spell it "color" not "colour" */
}

a {
    text-decoration: none;  /* removes the default underline from links */
}
```

---

---

# PRACTICE EXAM-STYLE QUESTIONS

---

## PE1 — Tipping Calculator (easy warmup)

**Spec:** User enters a bill amount and tip percentage. App displays tip amount and total.

```python
from flask import Flask, request, redirect
import pyhtml as p

app = Flask(__name__)

# GET / — show the input form
@app.get("/")
def home():
    return str(p.html(
        p.head(p.title("Tip Calculator")),
        p.body(
            p.h1("Tip Calculator"),
            p.form(
                p.label("Bill amount ($):"),
                p.input(type="text", name="bill"),
                p.br(),
                p.label("Tip percentage (%):"),
                p.input(type="text", name="tip_pct"),
                p.br(),
                p.input(type="submit", value="Calculate"),
                method="post",
                action="/calculate"
            )
        )
    ))

# POST /calculate — do the maths and show result
@app.post("/calculate")
def calculate():
    bill = float(request.form.get("bill", 0))        # convert str -> float for maths
    tip_pct = float(request.form.get("tip_pct", 0))  # e.g. "15" -> 15.0
    tip_amount = bill * (tip_pct / 100)               # e.g. 50 * 0.15 = 7.50
    total = bill + tip_amount

    return str(p.html(
        p.head(p.title("Result")),
        p.body(
            p.h1("Result"),
            p.p("Tip: $" + str(round(tip_amount, 2))),   # round to 2 decimal places
            p.p("Total: $" + str(round(total, 2))),
            p.a("Calculate again", href="/")              # link back to form
        )
    ))

if __name__ == "__main__":
    app.run(port=5001)
```


# EXTRA NOTES (from lecturer examples)

---

## Extra String Methods

```python
s = "meow"
s.capitalize()      # "Meow" — first letter uppercase, rest lowercase
s.casefold()        # "meow" — stronger version of .lower(), handles accented chars
s.count("m")        # 1 — how many times "m" appears in the string
s.index("e")        # 1 — position of first "e" (crashes if not found — use carefully)
s.isalpha()         # True — every character is a letter (no digits, spaces, symbols)
s.isdigit()         # False — not all digits
s.isalnum()         # True — all characters are letters or digits (no spaces or symbols)
s.center(10)        # "   meow   " — pads the string to be centred in a width-10 field
```

---

## Extra List Methods

```python
items = [1, 2, 4]

items.insert(2, 3)   # insert value 3 at index 2 -> [1, 2, 3, 4]
                     # first arg is the index, second is the value to insert
items.pop(0)         # remove and return item at index 0 -> removes 1
items.pop()          # no index = removes and returns the last item
items.remove(2)      # remove the first occurrence of value 2
                     # (remove() uses value, pop() uses index — don't mix them up)
```

---

## Operators (full list)

```python
# arithmetic
x + y    # add
x - y    # subtract
x * y    # multiply
x / y    # divide — always returns a float, e.g. 4 / 2 = 2.0
x % y    # modulus — remainder after division, e.g. 10 % 3 = 1
x // y   # floor division — rounds down to whole number, e.g. 10 // 3 = 3

# compound assignment — shorthand for updating a variable
x += 3   # same as: x = x + 3
x -= 3   # same as: x = x - 3
x *= 3   # same as: x = x * 3
x /= 2   # same as: x = x / 2
x %= 3   # same as: x = x % 3

# comparison — always return True or False
x == y   # equal (two equals signs — one = is assignment, not comparison)
x != y   # not equal
x > y    # greater than
x < y    # less than
x >= y   # greater than or equal to
x <= y   # less than or equal to

# logical — combine conditions
x > 5 and x < 20   # True only if BOTH are true
x > 5 or x < 3     # True if AT LEAST ONE is true
not x > 5           # flips True to False and False to True

# membership — check if a value is inside something
"m" in "meow"       # True — "m" is in the string
3 in [1, 2, 3]      # True — 3 is in the list
"m" not in "meow"   # False — opposite of in
```

---

## Looping over a string (char by char)

```python
# a for loop over a string gives you one character at a time
for char in "meow":
    print(char)
# output: m (newline) e (newline) o (newline) w
```

---

## Usability — 5 Quality Components (Nielsen)

These may appear instead of or alongside the 10 heuristics in written questions:

| Component | What it means |
|-----------|---------------|
| **Learnability** | Can a new user figure it out without being told? |
| **Efficiency** | Once learned, how fast can tasks be done? |
| **Memorability** | After not using it for a while, can the user remember how? |
| **Errors** | How often do users make errors? Are they easy to recover from? |
| **Satisfaction** | Is it pleasant to use? |

**Colour conventions in UI:**
- Red = warning / error / destructive action
- Green = success / safe
- Blue = neutral / informational

**Visual consistency checklist:**
- Same button and input style across all pages
- Consistent spacing, alignment, and layout
- Colour palette matches the app's purpose and audience
- No jarring layout changes between pages


---

## HTML — extra tags

```html
<!-- semantic layout tags — tell the browser what each section IS -->
<header>...</header>      <!-- top of page / site banner -->
<footer>...</footer>      <!-- bottom of page -->
<section>...</section>    <!-- a distinct chunk of content on the page -->
<article>...</article>    <!-- self-contained piece (e.g. a blog post) -->
<nav>...</nav>            <!-- navigation links area -->

<!-- text formatting -->
<b>bold text</b>          <!-- makes text bold -->
<i>italic text</i>        <!-- makes text italic -->
<br>                      <!-- line break — moves to next line (no closing tag) -->
<hr>                      <!-- horizontal line divider (no closing tag) -->

<!-- media -->
<img src="cat.jpg" alt="a cat">   <!-- alt text shows if image fails to load -->
<video>
    <source src="video.mp4" type="video/mp4">
</video>

<!-- containers -->
<div class="box">...</div>           <!-- block container — takes up full width -->
<span class="highlight">...</span>   <!-- inline container — sits within text -->
```

### HTML attributes quick ref

```
href="url"    link destination              <a href="/page">click here</a>
src="file"    file source                   <img src="cat.jpg">
alt="text"    fallback if image fails       <img src="cat.jpg" alt="a cat">
id="name"     unique identifier — one only  <div id="main-content">
class="name"  group elements for CSS        <p class="intro">
type="text"   input type                    <input type="password">
```

### Basic signup form

```html
<!-- for= on label links it to the input with the matching id -->
<form method="post" action="/signup">
    <label for="fname">First name:</label>
    <input type="text" id="fname" name="fname">

    <label for="lname">Last name:</label>
    <input type="text" id="lname" name="lname">

    <input type="submit" value="Sign Up">
</form>
```

---

## CSS — extra

```css
/* box model — controls the space around and inside elements */
margin: 10px;            /* space OUTSIDE the element (pushes other things away) */
padding: 10px;           /* space INSIDE the element (between border and content) */
border: 1px solid black; /* draws a border around the element */
width: 200px;            /* how wide the element is */
height: 100px;           /* how tall the element is */

/* text styling */
color: red;              /* text colour — must spell it "color" not "colour" */
font-family: Arial;      /* what font to use */
font-size: 24px;         /* how big the text is */
text-align: center;      /* aligns text: left / center / right */

/* background */
background-color: pink;  /* background colour of the element */
```
### CSS basics
```css
/* syntax: selector { property: value; }         */
/* selector = what to style, property = what to change */

body {
    font-family: sans-serif;    /* font for the whole page */
    background-color: #f0f0f0;  /* light grey background — # is a hex colour code */
    color: #333;                /* dark grey text — applies to all text inside body */
    margin: 0 auto;             /* centres the page content */
    padding: 20px;              /* space inside the body on all sides */
}

h1 {
    color: navy;                /* colour of all h1 headings */
    font-size: 2rem;            /* rem = relative to browser default (1rem = 16px) */
    text-align: center;         /* centres the heading text */
}

h2 {
    color: #333;
    font-size: 1.5rem;
}

p {
    font-size: 1rem;
    color: #333;
}

a {
    color: blue;                /* link text colour */
    text-decoration: none;      /* removes the default underline on links */
}

a:hover {
    text-decoration: underline; /* underline appears when mouse hovers */
}

/* class selector — dot prefix, targets elements with class="container" */
.container {
    max-width: 800px;           /* won't grow wider than 800px */
    margin: 0 auto;             /* centres the block on the page */
    padding: 20px;              /* space inside the element on all sides */
    background-color: white;
    border: 1px solid #ccc;     /* light grey border */
}

/* id selector — hash prefix, targets the ONE element with id="main" */
#main {
    background: white;
    padding: 20px;
}

/* forms */
input {
    font-size: 1rem;
    padding: 5px;               /* space inside the input box */
    margin: 5px;                /* space outside the input box */
    border: 1px solid #ccc;
}

input[type="submit"] {
    background-color: navy;     /* button background */
    color: white;               /* button text */
    border: none;               /* removes default border */
    cursor: pointer;            /* shows hand cursor on hover */
    padding: 8px 16px;          /* 8px top/bottom, 16px left/right */
}

/* tables */
table {
    border-collapse: collapse;  /* removes gap between cell borders */
    width: 100%;
}

th, td {
    border: 1px solid #ccc;
    padding: 8px;
    text-align: left;
}

th {
    background-color: navy;     /* header row background */
    color: white;               /* header row text */
}

/* box model:                                    */
/* margin  = space OUTSIDE the element           */
/* padding = space INSIDE the element            */
/* border  = line drawn around the element       */

/* .classname targets class="classname"          */
/* #idname   targets id="idname"                 */
```
### Inline styles (style attribute on the tag)

```html
<!-- you can put CSS directly on a tag using style="" -->
<h1 style="color: red; font-size: 80px;">Big red heading</h1>
<p style="text-align: center;">Centred paragraph</p>
```

### Classes vs IDs in CSS

```css
/* class — can apply to MANY elements, use a dot */
.my-class { color: blue; }

/* id — should apply to ONE element only, use a hash */
#my-id { background-color: yellow; }
```

```html
<p class="my-class">This text is blue</p>
<div id="my-id">This has a yellow background</div>
```

## Miscellaneous (from Chess exam)

### Dynamic routes — URL parameters
```python
# <player_id> in the route becomes a variable passed into the function
@app.get("/player/<player_id>")
def player_page(player_id):
    # player_id is whatever was in the URL, e.g. /player/magnus -> "magnus"
    return str(p.html(p.body(p.p("Player: " + player_id))))
```

### Finding the max in a list of dicts (manual loop)
```python
# no max() shortcut — loop through and track the highest yourself
def find_highest_elo(data):
    best = data[0]               # start by assuming first player is best
    for player in data:
        if player["elo"] > best["elo"]:
            best = player        # found someone better, update best
    return best
```

### Counting occurrences inside a list of dicts
```python
# e.g. count wins, draws, losses from a player's past_games list
def get_wins_draws_losses(player):
    wins = 0
    draws = 0
    losses = 0
    for game in player["past_games"]:   # loop through each game dict
        if game["result"] == "win":
            wins += 1
        elif game["result"] == "draw":
            draws += 1
        else:
            losses += 1
    return f"{wins} wins, {draws} draws, {losses} losses"
```

### Modifying a dict in place (no return)
```python
# instead of returning a new value, you change the dict directly
# this works because dicts are passed by reference in Python
def update_elo(player, amount):
    player["elo"] += amount     # modifies the original dict — no return needed

magnus = {"name": "Magnus", "elo": 2831}
update_elo(magnus, -17)
print(magnus["elo"])            # 2814 — the original dict was changed
```

### Adding a player to a list of dicts
```python
# build the dict and append it to the list
def add_player(data, id, name, faculty):
    data.append({
        "id": id,
        "name": name,
        "faculty": faculty,
        "elo": 1500,        # default starting elo
        "past_games": []    # empty list — no games yet
    })
    # no return needed — data list is modified in place
```
### Dict lookup table + running total
```python
# dict used as a lookup table — key maps to a value (e.g. colour -> cost per cm)
colour_costs = {
    "red": 0.5,
    "blue": 1.5,
    "purple": 2.0,
    "pink": 1.4,
}

# scarf_info is a list of dicts, each with "colour" and "length"
def calculate_scarf_cost(scarf_info):
    total = 0.0
    for segment in scarf_info:
        cost_per_cm = colour_costs[segment["colour"]]  # look up cost using colour as key
        total += cost_per_cm * segment["length"]        # add cost of this segment
    return round(total, 2)                              # round to 2 decimal places

# example:
# pink 10cm = 1.4 * 10 = 14
# purple 5cm = 2.0 * 5 = 10
# blue 10cm = 1.5 * 10 = 15
# total = 39.0
```
