From 743f767cb9d3156ae2878e6600b1cd120df0b8b9 Mon Sep 17 00:00:00 2001 From: Ty Tremblay Date: Mon, 27 Jul 2026 10:44:52 -0400 Subject: [PATCH] Derive lesson numbers from position; de-numbered titles; reorder-proof cross-refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make lesson ordering cheap to change (see docs/adr/0001): - Gapped `order` (position * 10) so a lesson can be inserted without renumbering every later one. - Stripped the "Lesson N: " prefix from all 34 titles — titles are the concept only; the number is derived from position in the sorted list and rendered by the site (LessonCard/LessonView/AppSidebar). - Cross-references resolve the target's current number/title at render time from its slug, so moving or inserting a lesson never edits another lesson's prose. - ADR 0001 + ordering-refactor handoff added under docs/. Co-Authored-By: Claude Opus 4.8 --- .../0001-derive-lesson-order-from-position.md | 60 ++++++++++++++++ docs/handoffs/ordering-refactor.md | 68 +++++++++++++++++++ lessons/01-flowcharts/README.md | 4 +- lessons/02-conditionals/README.md | 6 +- lessons/03-loops/README.md | 4 +- lessons/04-functions/README.md | 4 +- lessons/05-what-is-java/README.md | 8 +-- lessons/06-hello-world/README.md | 12 ++-- lessons/07-variables/README.md | 8 +-- lessons/08-numbers-and-text/README.md | 12 ++-- lessons/09-if-statements/README.md | 16 ++--- lessons/10-loops/README.md | 14 ++-- lessons/11-writing-methods/README.md | 16 ++--- lessons/12-arrays/README.md | 14 ++-- lessons/13-arraylists/README.md | 11 +-- lessons/14-maps/README.md | 10 +-- lessons/15-sets/README.md | 12 ++-- lessons/16-booleans/README.md | 6 +- lessons/17-classes-and-objects/README.md | 6 +- lessons/18-multiple-objects/README.md | 12 ++-- lessons/19-encapsulation/README.md | 4 +- lessons/20-state-machines/README.md | 4 +- lessons/21-state-machines-in-code/README.md | 16 ++--- lessons/22-events-and-transitions/README.md | 10 +-- lessons/23-organizing-a-machine/README.md | 8 +-- lessons/24-coordinating-machines/README.md | 10 +-- lessons/25-capstone-state-machine/README.md | 8 +-- .../26-decisions-in-code-revisited/README.md | 14 ++-- lessons/27-enums-revisited/README.md | 18 ++--- lessons/28-interfaces-as-diagrams/README.md | 4 +- lessons/29-interfaces-in-code/README.md | 6 +- lessons/30-encapsulation-revisited/README.md | 10 +-- lessons/31-lambdas/README.md | 6 +- .../32-builder-pattern-as-diagrams/README.md | 4 +- lessons/33-builder-pattern-in-code/README.md | 10 +-- lessons/34-capstone-2/README.md | 20 +++--- site/src/components/AppSidebar.tsx | 6 +- site/src/lib/lessons.ts | 10 +++ site/src/routes/LessonView.tsx | 40 ++++++++++- 39 files changed, 341 insertions(+), 170 deletions(-) create mode 100644 docs/adr/0001-derive-lesson-order-from-position.md create mode 100644 docs/handoffs/ordering-refactor.md diff --git a/docs/adr/0001-derive-lesson-order-from-position.md b/docs/adr/0001-derive-lesson-order-from-position.md new file mode 100644 index 0000000..9b8d9b4 --- /dev/null +++ b/docs/adr/0001-derive-lesson-order-from-position.md @@ -0,0 +1,60 @@ +# 1. Derive lesson order from position + +Status: Accepted + +Date: 2026-07-20 + +## Context + +A lesson's **absolute position** used to be stored as *content* in three places, +which made inserting or reordering a lesson a ~250-edit chore: + +- `order:` frontmatter was a dense consecutive integer sequence (1, 2, 3, …), so + inserting a lesson shifted the `order` of every later lesson. +- `title:` embedded the ordinal (`"Lesson 20: State machines as diagrams"`). +- Cross-references named the ordinal in visible link text and prose + (`[lesson 12](#/lesson/12-arrays)`, and bare "in lesson 20" mentions) — 103 + hash-links plus dozens of prose mentions across 34 files. + +Sorting is driven **only** by `order` (`site/src/lib/lessons.ts`); the numeric +prefix on a folder/slug name is cosmetic and never read. So the displayed number +can be computed from a lesson's position in the sorted list instead of stored. + +## Decision + +Derive the displayed lesson number from position; get ordinals out of stored +content. Specifically (Option B — we did **not** rename folders or change any +`#/lesson/` href, which was the rejected Option C): + +1. **Gapped `order`.** Each lesson's `order` is `position * 10` (1st → 10, 2nd → + 20, … 34th → 340). Same sequence as before, but with room to insert a lesson + between neighbors (e.g. a future maze module at 191–195) without touching any + other lesson's `order`. +2. **Concept-only titles.** The `Lesson N: ` prefix is stripped from all `title:` + values (`title: "State machines as diagrams"`). +3. **Number rendered from position.** The `lessons` array is sorted by `order`; + the displayed number is its 1-based index. `lessonNumber(slug)` in + `site/src/lib/lessons.ts` is the single source of truth; the index cards, + sidebar, and lesson-view header all read it. +4. **Slug-keyed cross-references.** Cross-reference links carry no stored number. + Their visible text uses `{n}` / `{title}` tokens (e.g. + `[lesson {n}](#/lesson/09-if-statements)`) that a custom react-markdown link + renderer in `LessonView.tsx` fills in from the target's **current** + `lessonNumber` / title at render time. Bare-prose "lesson NN" mentions were + converted to the same token-link form (or rephrased to a concept reference, + e.g. "the Objects lessons"). + +## Consequences + +- Inserting or reordering a lesson touches **only** `order:` values. No title, + prose, or cross-reference edits are needed; every displayed number and + reference re-labels itself automatically from the new positions. +- Slugs keep their numeric prefix (`12-arrays`). It is now an opaque, harmless id + that users never see — it is not the lesson's displayed number and need not + match it. A lesson can sit at any position regardless of its slug prefix. +- Lesson READMEs remain valid Markdown, but the `{n}` / `{title}` tokens and + `#/lesson/` hrefs only resolve inside the site renderer; on GitHub the + tokens render literally and the hash links do not navigate. This was already + true of the hash links and is an accepted cost of Option B. +- Anything that reads a lesson's number must derive it (`lessonNumber`), never + parse it from a title or slug. diff --git a/docs/handoffs/ordering-refactor.md b/docs/handoffs/ordering-refactor.md new file mode 100644 index 0000000..228bcc1 --- /dev/null +++ b/docs/handoffs/ordering-refactor.md @@ -0,0 +1,68 @@ +# Handoff: make lesson ordering cheap to change (Option B) + +## Why +Today, a lesson's **absolute position** is stored as *content* in three places, so +inserting or moving a lesson is a ~250-edit chore: + +- `order:` frontmatter is dense consecutive integers → inserting shifts every later lesson. +- `title:` embeds the ordinal (`"Lesson 20: State machines as diagrams"`). +- Cross-references name the ordinal in visible prose and link text + (`[lesson 12](#/lesson/12-arrays)`, and bare "in lesson 20" mentions). + +Sorting is driven **only** by `order` (`site/src/lib/lessons.ts:92`); the numeric +prefix on folder names is cosmetic. The fix: **derive the displayed number from a +lesson's position in the sorted list**, and get ordinals out of stored content. + +Measured footprint (lessons 1–34): 34 titles, 103 hash-links, 128 prose "lesson NN" +mentions, 34 numbered folders. + +## Scope: Option B (do), Option C (do NOT) +**In scope (B):** +1. **Gapped `order`.** Rewrite each lesson's `order` to `position * 10` (current + 1st → 10, 2nd → 20, … 34th → 340), preserving today's exact sequence but + leaving room to insert. (A future maze module will slot in at, e.g., 191–195 + between Objects and State Machines — nothing else will need to move.) +2. **De-numbered titles.** Strip the `Lesson N: ` prefix from all 34 `title:` + values → concept only (`title: "State machines as diagrams"`). +3. **Site renders the number from position.** The `lessons` array is already + sorted by `order`; display a 1-based index as the lesson number everywhere a + number should appear — index cards (`LessonCard.tsx`), the lesson header + (`LessonView`), and the sidebar (`AppSidebar.tsx`, whose `shortTitle` regex on + line 18 becomes obsolete — titles no longer carry the prefix; prepend the + derived number instead if a number is wanted there). Numbers must stay 1..34 + in the same order after the refactor — this is a no-visible-reorder change. +4. **Reorder-proof cross-references.** This is the point of the whole exercise: + after this, moving/inserting a lesson must NOT require editing any other + lesson's prose. Recommended mechanism: a custom link renderer (the site + renders lesson markdown — find the react-markdown/MD renderer in + `site/src/`) that, for any `#/lesson/` link, resolves the target + lesson's **current** derived number (and/or title) at render time via + `getLesson(slug)` + its index. Convert the 103 existing + `[lesson N](#/lesson/)` links to a canonical slug-keyed form the + renderer fills in, so the visible "lesson N" is always computed, never stored. + For the bare-prose "lesson NN" mentions that are not links, prefer turning them + into such links, or rephrase to a concept reference ("the arrays lesson"). No + stored ordinal may remain in prose. + +**Out of scope (Option C — do NOT do):** +- Do **not** rename lesson folders or strip numeric prefixes from slugs. +- Do **not** change any `#/lesson/` href target. Slugs stay exactly as they + are (their numeric prefix is now a harmless opaque id users never see). + +## Deliverables +- All 34 lessons updated (order + title) and cross-references converted. +- Site code renders derived numbers and resolves cross-ref numbers at render time. +- `docs/adr/0001-derive-lesson-order-from-position.md` recording the decision + (context: ordinal-as-content churn; decision: derive from position, concept-only + titles, slug-keyed cross-refs; consequence: inserts/reorders touch only `order`). +- `cd site && npm run build && npm run lint` both pass. + +## Acceptance check +- Index and sidebar show lessons 1..34 in the identical order and with the same + visible numbers as before this change. +- Every cross-reference link still resolves, and its visible number matches the + target's current position (test by temporarily bumping one lesson's `order` + past a neighbor — the reference text should follow automatically, with no + content edit — then revert). +- No `title:` contains `Lesson \d+:`; no lesson prose contains a hard-coded + "lesson NN" ordinal. diff --git a/lessons/01-flowcharts/README.md b/lessons/01-flowcharts/README.md index 3ea420a..2abd87b 100644 --- a/lessons/01-flowcharts/README.md +++ b/lessons/01-flowcharts/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 1: Programs are step-by-step" +title: "Programs are step-by-step" goal: "See a program as a flowchart of blocks and watch the computer evaluate it one step at a time." -order: 1 +order: 10 section: "Programming with Blocks" --- diff --git a/lessons/02-conditionals/README.md b/lessons/02-conditionals/README.md index 38aca9c..06180f0 100644 --- a/lessons/02-conditionals/README.md +++ b/lessons/02-conditionals/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 2: Making decisions" +title: "Making decisions" goal: "Use a condition to make a program choose between two paths." -order: 2 +order: 20 section: "Programming with Blocks" --- @@ -30,7 +30,7 @@ asks `a > b?` — that's `true` here (`7 > 4`) — so the program follows the Press **▶ Run step by step**. Watch the diamond answer `true` and the program head down the `true` arrow — while the other branch **grays out and gets skipped**. Those blocks never run. This fork is exactly what Java writes as -`if / else` — you'll type it yourself in lesson 9. +`if / else` — you'll type it yourself in [lesson {n}](#/lesson/09-if-statements). ```blocks preset: cond-demo diff --git a/lessons/03-loops/README.md b/lessons/03-loops/README.md index fcea85d..3890fe5 100644 --- a/lessons/03-loops/README.md +++ b/lessons/03-loops/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 3: Doing things over and over" +title: "Doing things over and over" goal: "Use a loop to repeat a step many times without copying it out by hand." -order: 3 +order: 30 section: "Programming with Blocks" --- diff --git a/lessons/04-functions/README.md b/lessons/04-functions/README.md index ae98496..55c76d9 100644 --- a/lessons/04-functions/README.md +++ b/lessons/04-functions/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 4: Reusable blocks" +title: "Reusable blocks" goal: "Package a flowchart into a named block you can reuse instead of rebuilding it." -order: 4 +order: 40 section: "Programming with Blocks" --- diff --git a/lessons/05-what-is-java/README.md b/lessons/05-what-is-java/README.md index fff2d81..f4ec8ab 100644 --- a/lessons/05-what-is-java/README.md +++ b/lessons/05-what-is-java/README.md @@ -1,14 +1,14 @@ --- -title: "Lesson 5: What is Java?" +title: "What is Java?" goal: "Recognize Java's basic punctuation and program shape before writing or running any code." -order: 5 +order: 50 section: "Java Fundamentals" --- # A language, not a magic spell You've been building programs already — as flowcharts of blocks -([lesson 1](#/lesson/01-flowcharts) onward). From here on we write those same +([lesson {n}](#/lesson/01-flowcharts) onward). From here on we write those same ideas as **text**, in a language called **Java**. It's the language FRC robot code is written in, and one of the most widely used languages in the world. @@ -72,7 +72,7 @@ public class Main { Don't worry about memorizing this — the playground writes it for you for now, and you'll learn to write it yourself in -[lesson 11](#/lesson/11-writing-methods). For today, just recognize the shapes +[lesson {n}](#/lesson/11-writing-methods). For today, just recognize the shapes in it with the vocabulary from above: - `public class Main { ... }` — a **class** named `Main`, with everything it diff --git a/lessons/06-hello-world/README.md b/lessons/06-hello-world/README.md index 759243e..80a7f36 100644 --- a/lessons/06-hello-world/README.md +++ b/lessons/06-hello-world/README.md @@ -1,16 +1,16 @@ --- -title: "Lesson 6: Hello, World!" +title: "Hello, World!" goal: "Explain what a program actually is, then write and run your first one." -order: 6 +order: 60 section: "Java Fundamentals" --- # What is a program? You've already been building programs — as flowcharts of blocks. Everything you -did there carries over: doing steps in order ([lesson 1](#/lesson/01-flowcharts)), making decisions -([lesson 2](#/lesson/02-conditionals)), repeating steps ([lesson 3](#/lesson/03-loops)), and packaging steps into reusable pieces -([lesson 4](#/lesson/04-functions)). From here on we write those same ideas as **text** instead of wiring +did there carries over: doing steps in order ([lesson {n}](#/lesson/01-flowcharts)), making decisions +([lesson {n}](#/lesson/02-conditionals)), repeating steps ([lesson {n}](#/lesson/03-loops)), and packaging steps into reusable pieces +([lesson {n}](#/lesson/04-functions)). From here on we write those same ideas as **text** instead of wiring boxes together. The concepts don't change; only the notation does — and [last lesson](#/lesson/05-what-is-java) you already met that notation: braces, parentheses, commas, semicolons, quotes. @@ -48,7 +48,7 @@ Two things to know about the playground before you press Run: - Java instructions normally live inside a small "program shell" of wrapper code. The playground writes that shell for you so you can focus on the instructions themselves — you'll learn to write the shell yourself in - [lesson 11](#/lesson/11-writing-methods). + [lesson {n}](#/lesson/11-writing-methods). Press **Run** and watch the output panel below the code. diff --git a/lessons/07-variables/README.md b/lessons/07-variables/README.md index 668252e..331a8d4 100644 --- a/lessons/07-variables/README.md +++ b/lessons/07-variables/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 7: Naming things" +title: "Naming things" goal: "Store a value under a typed name so you can reuse it and change it in one place." -order: 7 +order: 70 section: "Java Fundamentals" --- @@ -24,7 +24,7 @@ the computer reaches in and uses that value. The word in front of the name — `String` — is the variable's **type**: what *kind* of value it holds. `String` means text. Java makes you say the type up front, and in exchange it will catch you the moment you try to put the wrong -kind of value in — more on this in [lesson 8](#/lesson/08-numbers-and-text). +kind of value in — more on this in [lesson {n}](#/lesson/08-numbers-and-text). The `+` between `"Hello, "` and `name` **joins** text together into one string, so this program prints `Hello, Ada`. @@ -88,7 +88,7 @@ and stores `10`. Then `score = score + 5` looks confusing until you remember `=` means "gets set to," not "equals": the computer works out the right side first (`10 + 5 = 15`), then stores that back into `score`. This "take the value, change it, put it back" pattern is exactly the running total the **repeat** block kept -in [lesson 3](#/lesson/03-loops) — a value that updates itself, one step at a time. +in [lesson {n}](#/lesson/03-loops) — a value that updates itself, one step at a time. Your turn: add a line that gives a `10` penalty (subtract, don't add), then print the score again. Predict the final number before you run it. diff --git a/lessons/08-numbers-and-text/README.md b/lessons/08-numbers-and-text/README.md index 135e3a6..d1e5f2c 100644 --- a/lessons/08-numbers-and-text/README.md +++ b/lessons/08-numbers-and-text/README.md @@ -1,14 +1,14 @@ --- -title: "Lesson 8: Types — numbers and text" +title: "Types — numbers and text" goal: "Tell the basic kinds of data apart, and convert between them." -order: 8 +order: 80 section: "Java Fundamentals" --- # Numbers are not text The block editor quietly hid something from you: it only ever dealt with -numbers. Real programs juggle different **kinds** of data — [lesson 7](#/lesson/07-variables) already +numbers. Real programs juggle different **kinds** of data — [lesson {n}](#/lesson/07-variables) already made you name the kind every time you declared a variable. That kind is the **type**, and the two you'll meet first are: @@ -24,7 +24,7 @@ System.out.println("3" + "4"); ``` The first line prints `7` — that's math. The second prints `34`, because with -text, `+` means **stick these together** (you used it that way in [lesson 7](#/lesson/07-variables)), +text, `+` means **stick these together** (you used it that way in [lesson {n}](#/lesson/07-variables)), not "add." `"3"` and `"4"` aren't numbers to Java; they're just characters that happen to look like digits, so it glues them into `"34"`. @@ -58,12 +58,12 @@ System.out.println("Next year you will be " + (age + 1)); ``` Notice we needed a **second variable**. `ageText` is a `String` and its type -can never change — that's the deal Java made with you in [lesson 7](#/lesson/07-variables). So the +can never change — that's the deal Java made with you in [lesson {n}](#/lesson/07-variables). So the converted number gets its own name, `age`, with its own type, `int`. One name per kind of thing keeps the program honest: you can always tell what's text and what's a number just by reading the declarations. (The parentheses around `(age + 1)` make the math happen *before* the joining — remember inner-first -from [lesson 1](#/lesson/01-flowcharts).) +from [lesson {n}](#/lesson/01-flowcharts).) Conversion goes the other way too: `String.valueOf(42)` turns a number into text — though as you saw, gluing a number onto a string with `+` usually does diff --git a/lessons/09-if-statements/README.md b/lessons/09-if-statements/README.md index ef18c2f..45242aa 100644 --- a/lessons/09-if-statements/README.md +++ b/lessons/09-if-statements/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 9: Decisions in code" +title: "Decisions in code" goal: "Use if, else if, and else to make a program choose between paths." -order: 9 +order: 90 section: "Java Fundamentals" --- # The decision diamond, in text -Remember the **decision diamond** from [lesson 2](#/lesson/02-conditionals)? It asked a true/false question and then +Remember the **decision diamond** from [lesson {n}](#/lesson/02-conditionals)? It asked a true/false question and then took one path or the other, skipping the branch it didn't take. Java writes that same idea with the word **`if`**: @@ -35,7 +35,7 @@ but the "good day" line always prints. # Both paths: else -[Lesson 2's](#/lesson/02-conditionals) diamond always had *two* arrows — a `true` arrow and a `false` arrow. +[Lesson {n}'s](#/lesson/02-conditionals) diamond always had *two* arrows — a `true` arrow and a `false` arrow. Java spells the second one **`else`**: ```java @@ -49,12 +49,12 @@ if (score >= 6) { Exactly one of those two blocks runs, every time. If the question is true, you get the first; otherwise you get the second. The other is skipped — "the road -not taken" from [lesson 2](#/lesson/02-conditionals), in text form. +not taken" from [lesson {n}](#/lesson/02-conditionals), in text form. One thing to watch closely: that's `>=` ("greater than or equal to"), and the "is it equal?" test is `==` with **two** equals signs. A single `=` means -"store a value" ([lesson 7](#/lesson/07-variables)), so Java uses `==` to *ask* about equality. Mixing -them up is the code version of the `>` vs `≥` bug [lesson 2](#/lesson/02-conditionals) warned about. +"store a value" ([lesson {n}](#/lesson/07-variables)), so Java uses `==` to *ask* about equality. Mixing +them up is the code version of the `>` vs `≥` bug [lesson {n}](#/lesson/02-conditionals) warned about. Change `score` to `6`, then `5`, and run each time. Convince yourself `>= 6` lets a `6` pass but a `5` doesn't. @@ -81,7 +81,7 @@ Java checks each question **top to bottom** and takes the **first** one that's true — then skips all the rest. That order matters: a score of `95` is also `>= 80`, but because `>= 90` is checked first and wins, you never reach the B line. This is the same "inputs before the things that use them, one step at a -time" discipline from [lesson 1](#/lesson/01-flowcharts). +time" discipline from [lesson {n}](#/lesson/01-flowcharts). Run it, then change `score` to `85`, `72`, and `40` and run each time to see a different branch win. Then your challenge: build a program that sets a number diff --git a/lessons/10-loops/README.md b/lessons/10-loops/README.md index e1a3484..722ed1e 100644 --- a/lessons/10-loops/README.md +++ b/lessons/10-loops/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 10: Loops in code" +title: "Loops in code" goal: "Repeat steps in Java with for and while instead of copying lines." -order: 10 +order: 100 section: "Java Fundamentals" --- # Repeat, in text -In [lesson 3](#/lesson/03-loops) the **repeat** block did a step over and over so you didn't have to +In [lesson {n}](#/lesson/03-loops) the **repeat** block did a step over and over so you didn't have to copy it out by hand. Java's version is the **`for` loop**, and it comes with a built-in counter. Meet the counter first: @@ -26,11 +26,11 @@ The header has **three slots**, separated by semicolons: for `i = i + 1`). Those are the same three ideas the repeat block had — a start, a count, and a -step. The braces hold the step that repeats, just like `if` in [lesson 9](#/lesson/09-if-statements). +step. The braces hold the step that repeats, just like `if` in [lesson {n}](#/lesson/09-if-statements). Run it and watch `i` climb: `0`, `1`, `2`, `3`, `4` — five passes, and the loop hands you the "which time around am I?" number on every one, free of charge. -(Programmers start counting at zero; you'll see why it's handy in [lesson 12](#/lesson/12-arrays).) +(Programmers start counting at zero; you'll see why it's handy in [lesson {n}](#/lesson/12-arrays).) Once the counting makes sense, put the loop to work: change the middle slot to `i < 3`, then `i < 10`, and watch the count follow. Then swap the print line @@ -40,9 +40,9 @@ repeating. # A running total -The real power of [lesson 3's](#/lesson/03-loops) repeat block was the **running total** — a value it +The real power of [lesson {n}'s](#/lesson/03-loops) repeat block was the **running total** — a value it remembered and updated each time around. You build that yourself in a `for` -loop by combining it with a variable ([lesson 7](#/lesson/07-variables)): +loop by combining it with a variable ([lesson {n}](#/lesson/07-variables)): ```java int total = 0; diff --git a/lessons/11-writing-methods/README.md b/lessons/11-writing-methods/README.md index 0c5b122..8c0224e 100644 --- a/lessons/11-writing-methods/README.md +++ b/lessons/11-writing-methods/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 11: Writing your own methods" +title: "Writing your own methods" goal: "Package steps into a named method and hand back a result with return." -order: 11 +order: 110 section: "Java Fundamentals" --- # The program shell, revealed -Since [lesson 6](#/lesson/06-hello-world), the playground has been quietly wrapping your code in a small +Since [lesson {n}](#/lesson/06-hello-world), the playground has been quietly wrapping your code in a small "program shell." Time to see it, because the thing you're about to build — a reusable block of your own — has to live inside it. Here is what a complete Java program really looks like: @@ -23,19 +23,19 @@ public class Main { Two layers, outside-in: - `public class Main { ... }` — every Java program is a **class**, a named - container for code. (Much more on classes in [lesson 17](#/lesson/17-classes-and-objects).) + container for code. (Much more on classes in [lesson {n}](#/lesson/17-classes-and-objects).) - `public static void main(String[] args) { ... }` — the **main method**: the agreed-upon starting point. When a Java program runs, the computer finds `main` and follows its instructions top to bottom. Every line you've written so far was living here. -Run it — it behaves exactly like lesson 6's one-liner, because it *is* that +Run it — it behaves exactly like [lesson {n}](#/lesson/06-hello-world)'s one-liner, because it *is* that program, shell and all. From now on, when a snippet shows the full shell, the playground runs it exactly as written; you're seeing the whole truth. # Define a block of your own -[Lesson 4](#/lesson/04-functions) gave you reusable blocks — a **double** block with a little flowchart +[Lesson {n}](#/lesson/04-functions) gave you reusable blocks — a **double** block with a little flowchart hidden inside, that you could drop in wherever you needed it. In Java, a reusable block is called a **method**, and now you can build one yourself: @@ -65,7 +65,7 @@ Read the new method piece by piece: Methods live inside the class, *next to* `main` — not inside it. Defining one doesn't run it; it just teaches Java the block exists. It only does its work when you *call* it. Run this, then change `5` and run again — same block, -different input, just like [lesson 4](#/lesson/04-functions). +different input, just like [lesson {n}](#/lesson/04-functions). The `n` is called a **parameter** — a placeholder that gets filled in with whatever value you pass when you call. A method can take more than one: @@ -80,7 +80,7 @@ Add that next to `doubleIt` and call `add(2, 3)` from `main`. # Order matters (again) -[Lesson 4](#/lesson/04-functions) ended with the big idea that **order matters**: `add 1` then `double` +[Lesson {n}](#/lesson/04-functions) ended with the big idea that **order matters**: `add 1` then `double` gave a different answer than `double` then `add 1`. Let's prove it in code with two methods of your own: diff --git a/lessons/12-arrays/README.md b/lessons/12-arrays/README.md index 5296d93..848ef4a 100644 --- a/lessons/12-arrays/README.md +++ b/lessons/12-arrays/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 12: Arrays" +title: "Arrays" goal: "Hold many values in one place, reach any of them by position, and walk through them with a loop." -order: 12 +order: 120 section: "Data Structures" --- @@ -36,7 +36,7 @@ batch. That's the whole idea of an array. You pull a single value out of an array by its **position**, written in square brackets after the name. And here's the catch that trips up everyone: Java counts positions starting from **0**, not 1 — the same "programmers start at -zero" rule the loop counter followed back in [lesson 10](#/lesson/10-loops). +zero" rule the loop counter followed back in [lesson {n}](#/lesson/10-loops). ```java int[] readings = {12, 47, 3, 88, 21}; @@ -67,7 +67,7 @@ System.out.println(Arrays.toString(readings)); # Walking the whole array -[Lesson 10's](#/lesson/10-loops) `for` loop counted with `i`. There's a second form of `for` whose +[Lesson {n}'s](#/lesson/10-loops) `for` loop counted with `i`. There's a second form of `for` whose real job is walking an array — handing you each item, one per pass. It's called the **for-each** loop: @@ -82,7 +82,7 @@ No indexes, no counting — the loop takes care of visiting every item in order. Read the colon as "in": "for each `score` in `scores`, print it." Run it and you get one line per match. -Now combine it with the **running total** from [lesson 10](#/lesson/10-loops) to add an array up: +Now combine it with the **running total** from [lesson {n}](#/lesson/10-loops) to add an array up: ```java int[] scores = {10, 25, 5, 40}; @@ -101,6 +101,6 @@ slots and keeps them forever. [Next lesson](#/lesson/13-arraylists) covers a fixed row of numbered slots. Your turn: make an array of five sensor `readings`, then loop through and print -only the ones **above 20** (you'll need an `if` inside the `for` — [lesson 9](#/lesson/09-if-statements) -meets lesson 12). Then use the running-total pattern and `.length` to print the +only the ones **above 20** (you'll need an `if` inside the `for` — [lesson {n}](#/lesson/09-if-statements) +meets [lesson {n}](#/lesson/12-arrays)). Then use the running-total pattern and `.length` to print the **average**. Predict it first, then check. diff --git a/lessons/13-arraylists/README.md b/lessons/13-arraylists/README.md index 006af9a..4635941 100644 --- a/lessons/13-arraylists/README.md +++ b/lessons/13-arraylists/README.md @@ -1,14 +1,15 @@ --- -title: "Lesson 13: ArrayLists" +title: "ArrayLists" goal: "Hold a group that grows and shrinks while the program runs, instead of a fixed number of slots." -order: 13 +order: 130 section: "Data Structures" --- # A row that can grow [Last lesson's](#/lesson/12-arrays) array is great, but it commits to a size up -front: `new int[5]` is five slots, forever. That's a problem the moment you +front: the five values you write between the braces are five slots, forever. +That's a problem the moment you don't know the count ahead of time — how many game pieces will you score this match? You don't know until the match is over. @@ -53,7 +54,7 @@ System.out.println(scores.get(1)); ``` `.get(1)` is the second item (`25`) — indexes still start at **0**, same rule -as [lesson 12](#/lesson/12-arrays). To change a slot without touching the +as [lesson {n}](#/lesson/12-arrays). To change a slot without touching the others, use `.set(index, value)`: ```java @@ -83,7 +84,7 @@ list's does, on demand. # Looping and checking membership -A for-each loop ([lesson 12](#/lesson/12-arrays)) walks a list exactly like it +A for-each loop ([lesson {n}](#/lesson/12-arrays)) walks a list exactly like it walks an array: ```java diff --git a/lessons/14-maps/README.md b/lessons/14-maps/README.md index 0d9b9ed..7359844 100644 --- a/lessons/14-maps/README.md +++ b/lessons/14-maps/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 14: Maps" +title: "Maps" goal: "Store values under names you choose instead of numbered positions, and look them up by those names." -order: 14 +order: 140 section: "Data Structures" --- @@ -26,8 +26,8 @@ System.out.println(motorSpeeds.get("intake")); ``` The type `HashMap` names both halves: keys are `String`s, -values are `Double`s (that's `double` from [lesson 8](#/lesson/08-numbers-and-text), dressed up so it can -live in a collection — same idea as `ArrayList` in [lesson 13](#/lesson/13-arraylists)). +values are `Double`s (that's `double` from [lesson {n}](#/lesson/08-numbers-and-text), dressed up so it can +live in a collection — same idea as `ArrayList` in [lesson {n}](#/lesson/13-arraylists)). `motorSpeeds.get("shooter")` hands back `1.0`. It reads like plain English, and you never have to remember which position is which. A map is perfect for **robot configuration**: settings filed under names you'll recognize six @@ -82,7 +82,7 @@ That prints `true` then `false`. It's the safe way to look before you leap. # Looping over a map -Like an array, a map can be walked with a for-each loop ([lesson 12](#/lesson/12-arrays)). Loop +Like an array, a map can be walked with a for-each loop ([lesson {n}](#/lesson/12-arrays)). Loop over its `keySet()` — the collection of every key — and use each key to fetch its value: diff --git a/lessons/15-sets/README.md b/lessons/15-sets/README.md index 78f7acc..67d4ee4 100644 --- a/lessons/15-sets/README.md +++ b/lessons/15-sets/README.md @@ -1,15 +1,15 @@ --- -title: "Lesson 15: Sets" +title: "Sets" goal: "Track a group where duplicates aren't allowed and all you need is membership, not order or position." -order: 15 +order: 150 section: "Data Structures" --- # Just "is it in here?" Three data structures in, and each one answers a different question. An array -or `ArrayList` ([lessons 12](#/lesson/12-arrays)–[13](#/lesson/13-arraylists)) -answers "what's at position `i`?" A map ([lesson 14](#/lesson/14-maps)) +or `ArrayList` ([lessons {n}](#/lesson/12-arrays)–[{n}](#/lesson/13-arraylists)) +answers "what's at position `i`?" A map ([lesson {n}](#/lesson/14-maps)) answers "what's filed under this key?" A **set** throws position and keys out entirely and answers just one question: **"is this value in the group, or not?"** @@ -34,7 +34,7 @@ duplicates" for you, so you never have to check yourself before adding. # Checking membership The move you'll use constantly is `.contains(...)` — same method name as -`ArrayList` ([lesson 13](#/lesson/13-arraylists)), but here it's the *reason +`ArrayList` ([lesson {n}](#/lesson/13-arraylists)), but here it's the *reason the type exists*, not an extra trick: ```java @@ -76,7 +76,7 @@ for (String alliance : alliances) { Run it a couple of times. The three lines always appear, but not necessarily in the order you added them — same warning as `HashMap` in -[lesson 14](#/lesson/14-maps): a `HashSet` files things for fast lookup, not +[lesson {n}](#/lesson/14-maps): a `HashSet` files things for fast lookup, not for tidy printing. If you need "no duplicates" *and* "keeps the order I added them," Java has `LinkedHashSet` — same methods, different filing system — but plain `HashSet` is the one you'll reach for first. diff --git a/lessons/16-booleans/README.md b/lessons/16-booleans/README.md index c73abd3..bed62ba 100644 --- a/lessons/16-booleans/README.md +++ b/lessons/16-booleans/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 16: true, false, and combining conditions" +title: "true, false, and combining conditions" goal: "Treat yes/no answers as real values and combine them with &&, ||, and ! to make safe decisions." -order: 16 +order: 160 section: "Java Fundamentals" --- # Yes and no are values too -Back in [lesson 9](#/lesson/09-if-statements), an `if` asked a question like `score > 10` and acted on the +Back in [lesson {n}](#/lesson/09-if-statements), an `if` asked a question like `score > 10` and acted on the answer. That answer is a real value with a type of its own: a **`boolean`**, which is either `true` or `false` — the only two it can ever be. Every comparison *produces* one: diff --git a/lessons/17-classes-and-objects/README.md b/lessons/17-classes-and-objects/README.md index 1f52c09..22ca8a3 100644 --- a/lessons/17-classes-and-objects/README.md +++ b/lessons/17-classes-and-objects/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 17: Classes and objects" +title: "Classes and objects" goal: "Bundle related data and the actions on it into one thing you can create with new." -order: 17 +order: 170 section: "Objects" --- @@ -134,6 +134,6 @@ is exactly why robot code is built this way. Your turn: write a `Shooter` class. In its constructor set `this.rpm = 0` and `this.ready = false`. Add a `spinUp()` method that sets `rpm` to `5000` and `ready` to `true`, and a `fire()` method that prints `"Fired!"` **only if** -`this.ready` is true (an `if` inside a method — [lesson 9](#/lesson/09-if-statements) lives here too). +`this.ready` is true (an `if` inside a method — [lesson {n}](#/lesson/09-if-statements) lives here too). Make one, try to `fire()` before spinning up, then `spinUp()` and fire for real. diff --git a/lessons/18-multiple-objects/README.md b/lessons/18-multiple-objects/README.md index 1b665c2..e32ed37 100644 --- a/lessons/18-multiple-objects/README.md +++ b/lessons/18-multiple-objects/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 18: Multiple objects, one blueprint" +title: "Multiple objects, one blueprint" goal: "See that every object made from a class keeps its own independent data, and hold a whole group of them in a list." -order: 18 +order: 180 section: "Objects" --- @@ -38,14 +38,14 @@ public class Main { ``` `Intake(String name)` takes a value in, exactly like a method parameter -([lesson 11](#/lesson/11-writing-methods)) — `new Intake("left")` fills it in. Run it: `left.start()` sets +([lesson {n}](#/lesson/11-writing-methods)) — `new Intake("left")` fills it in. Run it: `left.start()` sets `left`'s speed to `0.8` and leaves `right` completely untouched, at `0.0`. One blueprint, two independent objects, no way for one to reach into the other's fields by accident. # static vs. instance: who does a method belong to? -Every method you wrote before [lesson 17](#/lesson/17-classes-and-objects) had `static` in front of it, and +Every method you wrote before [lesson {n}](#/lesson/17-classes-and-objects) had `static` in front of it, and you probably didn't think twice about it — there was nothing yet for a method to belong to. Now that objects exist, the difference matters: @@ -84,7 +84,7 @@ whole rule: no `static` and no object, no call. # A list of objects Real robots don't have one intake — they have a handful of mechanisms, all -built from a small number of classes. An `ArrayList` ([lesson 13](#/lesson/13-arraylists)) +built from a small number of classes. An `ArrayList` ([lesson {n}](#/lesson/13-arraylists)) holds objects exactly the way it holds numbers or strings: ```java @@ -119,7 +119,7 @@ public class Main { ``` `ArrayList` says exactly what it holds: not numbers this time, but -whole `Intake` objects. The for-each loop ([lesson 10](#/lesson/10-loops)) hands you one object at +whole `Intake` objects. The for-each loop ([lesson {n}](#/lesson/12-arrays)) hands you one object at a time — `intake` is a real object each pass, so `intake.start()` and `intake.name` work exactly as they did above. Run it and watch both mechanisms start independently. diff --git a/lessons/19-encapsulation/README.md b/lessons/19-encapsulation/README.md index 7a7a660..c2bd371 100644 --- a/lessons/19-encapsulation/README.md +++ b/lessons/19-encapsulation/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 19: Encapsulation" +title: "Encapsulation" goal: "Hide an object's fields behind public methods so outside code can't force it into a broken state." -order: 19 +order: 190 section: "Objects" --- diff --git a/lessons/20-state-machines/README.md b/lessons/20-state-machines/README.md index ad28077..940fa76 100644 --- a/lessons/20-state-machines/README.md +++ b/lessons/20-state-machines/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 20: State machines as diagrams" +title: "State machines as diagrams" goal: "See a machine that's always in exactly one state and moves between states when events happen — before writing a line of code." -order: 20 +order: 200 section: "State Machines" --- diff --git a/lessons/21-state-machines-in-code/README.md b/lessons/21-state-machines-in-code/README.md index 5d06923..960e504 100644 --- a/lessons/21-state-machines-in-code/README.md +++ b/lessons/21-state-machines-in-code/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 21: State machines in code" +title: "State machines in code" goal: "Turn the state diagram from last lesson into Java — a state variable, transitions, and the loop that drives them." -order: 21 +order: 210 section: "State Machines" --- @@ -24,10 +24,10 @@ if (state.equals("red")) { } ``` -That's the traffic light from [lesson 20](#/lesson/20-state-machines). One variable, `state`, holds which state -the machine is in right now; an `if/else if` ([lesson 9](#/lesson/09-if-statements)) picks the behavior for +That's the traffic light from [lesson {n}](#/lesson/20-state-machines). One variable, `state`, holds which state +the machine is in right now; an `if/else if` ([lesson {n}](#/lesson/09-if-statements)) picks the behavior for that state. The glowing box in the diagram *is* this variable — nothing more. -(The states are text, so we ask with `.equals`, the habit from [lesson 16](#/lesson/16-booleans).) +(The states are text, so we ask with `.equals`, the habit from [lesson {n}](#/lesson/16-booleans).) Run it, then change `state` to `"green"` and run again. Same code, different state, different behavior. Notice you can only be in one state at a time, @@ -36,7 +36,7 @@ because a variable holds one value — exactly the rule the diagram enforced. # Moving between states A diagram's arrows said how to move. In code, a transition is just **changing -the variable**. Let's package "what comes next" as a method ([lesson 11](#/lesson/11-writing-methods)) — +the variable**. Let's package "what comes next" as a method ([lesson {n}](#/lesson/11-writing-methods)) — and since methods live in the shell, from here on the snippets show the whole program: @@ -65,7 +65,7 @@ public class Main { Each `state = nextState(state)` is one press of **timer done** from the diagram: it looks at where we are and hands back where we go. Run it and watch -`red → green → yellow`. The reassignment trick from [lesson 7](#/lesson/07-variables) is doing the +`red → green → yellow`. The reassignment trick from [lesson {n}](#/lesson/07-variables) is doing the real work — the machine "moves" because we overwrite `state` with its next value. @@ -73,7 +73,7 @@ value. Here's the idea the whole season rests on: a robot doesn't run its code once and stop. It runs the same update **over and over, many times a second**. -That's a loop ([lesson 10](#/lesson/10-loops)) — and dropping our machine inside one makes it *go*: +That's a loop ([lesson {n}](#/lesson/10-loops)) — and dropping our machine inside one makes it *go*: ```java public class Main { diff --git a/lessons/22-events-and-transitions/README.md b/lessons/22-events-and-transitions/README.md index 2027cd7..5da258f 100644 --- a/lessons/22-events-and-transitions/README.md +++ b/lessons/22-events-and-transitions/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 22: Events and transitions in code" +title: "Events and transitions in code" goal: "Drive a machine from events, and make each state ignore the events it has no transition for." -order: 22 +order: 220 section: "State Machines" --- @@ -12,7 +12,7 @@ it. Real mechanisms wait for **specific events**: a button press, a sensor tripping, a shot finishing. So a transition depends on **two things**: which state you're in, *and* which event just happened. -This is the game-piece handler you drove in [lesson 20](#/lesson/20-state-machines). Here's a single +This is the game-piece handler you drove in [lesson {n}](#/lesson/20-state-machines). Here's a single transition from it: ```java @@ -26,7 +26,7 @@ if (state.equals("empty") && event.equals("button")) { System.out.println(state); ``` -The `&&` ([lesson 16](#/lesson/16-booleans)) is the key: the machine only moves when it's in `empty` +The `&&` ([lesson {n}](#/lesson/16-booleans)) is the key: the machine only moves when it's in `empty` **and** the driver's `button` event happened. Run it — `empty` becomes `intaking`. Change `event` to `"sensor"` and run again: nothing changes, because `empty` has no transition for `sensor`. That's the diagram's "ignore" @@ -74,7 +74,7 @@ there. # Feeding it a stream of events A robot gets a new event every tick. We can simulate a whole match by putting -events in an **array** ([lesson 12](#/lesson/12-arrays)) and running them through the machine one +events in an **array** ([lesson {n}](#/lesson/12-arrays)) and running them through the machine one at a time with a for-each loop: ```java diff --git a/lessons/23-organizing-a-machine/README.md b/lessons/23-organizing-a-machine/README.md index 5aff492..f5f9ac2 100644 --- a/lessons/23-organizing-a-machine/README.md +++ b/lessons/23-organizing-a-machine/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 23: Organizing a state machine" +title: "Organizing a state machine" goal: "Make a machine safe and tidy with an enum for the states, enter actions, and a class to hold it all together." -order: 23 +order: 230 section: "State Machines" --- @@ -52,7 +52,7 @@ Two details worth noticing: states" is now enforced by the compiler. - Enums are compared with `==` — the one place text-like values get to use it safely, because there's exactly one `HandlerState.LOADED` in the whole - program. (Strings still need `.equals`, as [lesson 16](#/lesson/16-booleans) warned.) + program. (Strings still need `.equals`, as [lesson {n}](#/lesson/16-booleans) warned.) This is exactly how real FRC robot code names its states, so everything from here on is the real-world shape. @@ -93,7 +93,7 @@ while you sit in `SHOOTING` — a distinction that matters a lot on a real robot # Bundle it into a class Right now the state lives in a loose variable and the logic in loose methods. -You already know the fix for that from [lessons 17–19](#/lesson/17-classes-and-objects): a +You already know the fix for that from [the Objects lessons](#/lesson/17-classes-and-objects): a **field** for the data, a **constructor** to set its starting value, and **methods** that read and write it through `this`. Applied to a state machine, it looks like this: diff --git a/lessons/24-coordinating-machines/README.md b/lessons/24-coordinating-machines/README.md index bb2beed..99e746a 100644 --- a/lessons/24-coordinating-machines/README.md +++ b/lessons/24-coordinating-machines/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 24: Coordinating machines" +title: "Coordinating machines" goal: "Run several small state machines in one loop and let them make decisions based on each other." -order: 24 +order: 240 section: "State Machines" --- @@ -9,7 +9,7 @@ section: "State Machines" One giant state machine for a whole robot would be a tangle. Real robot code uses **several small machines** — one per mechanism — each an object -([lesson 18](#/lesson/18-multiple-objects)) with its own state. Because a class remembers its own state +([lesson {n}](#/lesson/18-multiple-objects)) with its own state. Because a class remembers its own state field, you can make as many as you want and they don't interfere: ```java @@ -55,7 +55,7 @@ and easy to reason about on its own — that's the point of splitting them up. (One shorthand to spot: `IntakeState state = IntakeState.CLEAR;` sets the field's starting value right where it's declared, so these little classes -don't need a constructor. [Lesson 17's](#/lesson/17-classes-and-objects) constructor form is what you'll +don't need a constructor. [Lesson {n}'s](#/lesson/17-classes-and-objects) constructor form is what you'll want when starting up takes real work.) # Machines that watch each other @@ -92,7 +92,7 @@ public class Main { } ``` -The extra `&& intakeState == IntakeState.CLEAR` ([lesson 16](#/lesson/16-booleans) again) is a +The extra `&& intakeState == IntakeState.CLEAR` ([lesson {n}](#/lesson/16-booleans) again) is a **guard**: the shooter refuses to fire unless the intake reports it's out of the way. Run it — the first attempt is ignored because the intake was `HOLDING`; the second works. This cross-machine guard is how you keep two diff --git a/lessons/25-capstone-state-machine/README.md b/lessons/25-capstone-state-machine/README.md index f915d5c..85bb155 100644 --- a/lessons/25-capstone-state-machine/README.md +++ b/lessons/25-capstone-state-machine/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 25: Capstone 1 — design a state machine" +title: "Capstone 1 — design a state machine" goal: "Take a real mechanism from words to a diagram to working code you can simulate." -order: 25 +order: 250 section: "State Machines" --- @@ -26,7 +26,7 @@ EXTENDED --button---> RETRACTING RETRACTING --at_bottom-> STOWED ``` -That's the exact diagram you'd build with blocks in [lesson 20](#/lesson/20-state-machines) — states, events, +That's the exact diagram you'd build with blocks in [lesson {n}](#/lesson/20-state-machines) — states, events, arrows, and (just as important) *no* arrow for anything unsafe. Notice there's no way to go straight from `STOWED` to `EXTENDED`: you must pass through `EXTENDING`. The design forbids the dangerous shortcut. @@ -34,7 +34,7 @@ no way to go straight from `STOWED` to `EXTENDED`: you must pass through # Translate the diagram to code Now it's mechanical — the diagram maps straight onto the class shape from -[lesson 23](#/lesson/23-organizing-a-machine): an enum for the states, a constructor for the start, an `update` +[lesson {n}](#/lesson/23-organizing-a-machine): an enum for the states, a constructor for the start, an `update` with one branch per arrow, and enter actions for what should fire once. ```java diff --git a/lessons/26-decisions-in-code-revisited/README.md b/lessons/26-decisions-in-code-revisited/README.md index 6925ec2..2820cc4 100644 --- a/lessons/26-decisions-in-code-revisited/README.md +++ b/lessons/26-decisions-in-code-revisited/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 26: Decisions in code, revisited" +title: "Decisions in code, revisited" goal: "Rewrite if/else-if chains as switch statements — including over an enum — and use the ternary operator for one-line decisions." -order: 26 +order: 260 section: "Advanced Java" --- # switch: one question, many answers -[Lesson 22](#/lesson/22-events-and-transitions) dispatched on an event string with `if`/`else if`. That works, but +[Lesson {n}](#/lesson/22-events-and-transitions) dispatched on an event string with `if`/`else if`. That works, but when every branch is testing the *same* variable against different exact values, Java has a shape built for exactly that: **`switch`**. @@ -62,7 +62,7 @@ things to notice: `switch` gets even better once the thing you're checking is an **`enum`**, because Java already knows every value it could possibly be. -Here's [lesson 23's](#/lesson/23-organizing-a-machine) `onEnter`, rewritten: +Here's [lesson {n}'s](#/lesson/23-organizing-a-machine) `onEnter`, rewritten: ```java enum HandlerState { EMPTY, INTAKING, LOADED, SHOOTING } @@ -95,7 +95,7 @@ Run it: `SHOOTING` prints the spin-up message, and `LOADED` prints nothing — it falls to `default`, which does nothing on purpose. Notice the case labels are just `INTAKING`, not `HandlerState.INTAKING` — once you `switch (state)`, Java already knows the type, so the enum name would be redundant. Compare -this to the `if (state == HandlerState.INTAKING)` chain from lesson 23: same +this to the `if (state == HandlerState.INTAKING)` chain from [lesson {n}](#/lesson/23-organizing-a-machine): same logic, less repetition, and far easier to scan when a state list grows long. # The ternary operator: a decision in one line @@ -111,9 +111,9 @@ System.out.println(status); Read it as *"ready? then `"GO"`, otherwise `"WAIT"`."* Change `ready` to `false` and run again — `status` flips. It's exactly the two-branch `if`/`else` -from [lesson 9](#/lesson/09-if-statements), just squeezed onto one line +from [lesson {n}](#/lesson/09-if-statements), just squeezed onto one line because both branches are a single value, not a block of statements. A -realistic use — the ready check from [lesson 19's](#/lesson/19-encapsulation) +realistic use — the ready check from [lesson {n}'s](#/lesson/19-encapsulation) `Flywheel`: ```java diff --git a/lessons/27-enums-revisited/README.md b/lessons/27-enums-revisited/README.md index 28837f3..5f887b4 100644 --- a/lessons/27-enums-revisited/README.md +++ b/lessons/27-enums-revisited/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 27: Enums, revisited" +title: "Enums, revisited" goal: "Loop over every value an enum can hold, and give an enum a field so each state carries its own number." -order: 27 +order: 270 section: "Advanced Java" --- # Every value an enum can hold -You've had `HandlerState` since [lesson 23](#/lesson/23-organizing-a-machine) +You've had `HandlerState` since [lesson {n}](#/lesson/23-organizing-a-machine) — four fixed values, nothing else legal. Here's a trick you haven't seen yet: an enum can hand you **every value it has**, with `.values()`. @@ -24,8 +24,8 @@ public class Main { ``` Run it — all four states print, in the order they were declared. `.values()` -returns an array of every constant ([lesson 12's](#/lesson/12-arrays) array, -[lesson 10's](#/lesson/10-loops) for-each) — handy anywhere you'd otherwise +returns an array of every constant ([lesson {n}'s](#/lesson/12-arrays) array +and for-each) — handy anywhere you'd otherwise have to list the states yourself and hope you didn't forget one. If you add a fifth state to the enum, this loop picks it up automatically; a hand-written list would silently miss it. @@ -34,7 +34,7 @@ list would silently miss it. So far an enum constant has only ever been a name. But a constant can also carry its **own data** — say, the motor speed each state should run at. Give -`HandlerState` a field and a constructor, the same way [lesson 17](#/lesson/17-classes-and-objects) +`HandlerState` a field and a constructor, the same way [lesson {n}](#/lesson/17-classes-and-objects) gave `Flywheel` one: ```java @@ -64,7 +64,7 @@ Run it. Each constant now calls the constructor with its own number — `INTAKING(0.5)` means "build the `INTAKING` constant, passing `0.5` to its constructor" — exactly like `new Flywheel(0.5)` would, just written before the semicolon that ends the constant list. `this.motorSpeed = motorSpeed;` inside -the constructor is the same assignment pattern from lesson 17; it just runs +the constructor is the same assignment pattern from [lesson {n}](#/lesson/17-classes-and-objects); it just runs once per constant instead of once per `new`. Two details worth naming: @@ -88,8 +88,8 @@ piling more fields onto an enum once you've seen it hold one — a second number, then a third, then a `String` label — and that's a trap. The moment a constant needs several fields, or the list of constants grows past what you can hold in your head at once, you've stopped modeling *a choice* and started -modeling *a small database*, and a `class` ([lesson 17](#/lesson/17-classes-and-objects)) -or a `Map` ([lesson 14](#/lesson/14-maps)) usually fits that better. Keep an +modeling *a small database*, and a `class` ([lesson {n}](#/lesson/17-classes-and-objects)) +or a `Map` ([lesson {n}](#/lesson/14-maps)) usually fits that better. Keep an enum's values doing what they're for — naming the options — and keep what they carry small enough that the set of options is still the point. diff --git a/lessons/28-interfaces-as-diagrams/README.md b/lessons/28-interfaces-as-diagrams/README.md index f9d0ec3..09bbf8e 100644 --- a/lessons/28-interfaces-as-diagrams/README.md +++ b/lessons/28-interfaces-as-diagrams/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 28: Interfaces, as diagrams" +title: "Interfaces, as diagrams" goal: "See that a contract can have more than one implementation, and that plugging in something that isn't one shows up as a visible, broken wire — before writing a line of Java." -order: 28 +order: 280 section: "Advanced Java" --- diff --git a/lessons/29-interfaces-in-code/README.md b/lessons/29-interfaces-in-code/README.md index bba0c25..2fcccea 100644 --- a/lessons/29-interfaces-in-code/README.md +++ b/lessons/29-interfaces-in-code/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 29: Interfaces, in code" +title: "Interfaces, in code" goal: "Write an interface as a contract with no body, implement it two ways, and see one method work unchanged against either implementation." -order: 29 +order: 290 section: "Advanced Java" --- @@ -28,7 +28,7 @@ empty on purpose; the first real output shows up once something `implements` it, below.) That's it — no `{ }` body on `getSpeed()`, just a signature ending in a -semicolon, like a method header from [lesson 11](#/lesson/11-writing-methods) +semicolon, like a method header from [lesson {n}](#/lesson/11-writing-methods) with the body torn off. `MotorIO` is a promise: "whatever `implements` me can answer `getSpeed()`" — nothing about *how* it answers. diff --git a/lessons/30-encapsulation-revisited/README.md b/lessons/30-encapsulation-revisited/README.md index 3bf0612..0a4bc60 100644 --- a/lessons/30-encapsulation-revisited/README.md +++ b/lessons/30-encapsulation-revisited/README.md @@ -1,13 +1,13 @@ --- -title: "Lesson 30: Encapsulation, revisited" +title: "Encapsulation, revisited" goal: "Add the default (package-private) access level to private and public, and see how a real robot project uses all three." -order: 30 +order: 300 section: "Advanced Java" --- # A third option: no modifier at all -[Lesson 19](#/lesson/19-encapsulation) gave you two access levels: `private` +[Lesson {n}](#/lesson/19-encapsulation) gave you two access levels: `private` (only this class can see it) and `public` (anything can see it). There's a third, and you've actually been writing it by accident — leave the modifier off entirely, and you get **default** access, also called **package-private**: @@ -69,7 +69,7 @@ to me," not the entire codebase. Every field or method you write is really a decision among three doors: - **`private`** — only this class. The default choice for fields - ([lesson 19](#/lesson/19-encapsulation)). + ([lesson {n}](#/lesson/19-encapsulation)). - **(no modifier)** — this class, plus anything else in the same package. Use it for helpers that other closely-related classes legitimately need, but that have no business being called from anywhere else in the robot. @@ -77,7 +77,7 @@ Every field or method you write is really a decision among three doors: outside code genuinely needs — `getSpeed()`, `setSpeed()`, not `clampSpeed()`. -Your turn: go back to [lesson 23's](#/lesson/23-organizing-a-machine) +Your turn: go back to [lesson {n}'s](#/lesson/23-organizing-a-machine) `GamePieceHandler`. Make its `state` field `private` with a public `getState()` method, keep `update(String event)` `public` (outside code needs to drive it), and add a package-private helper method, diff --git a/lessons/31-lambdas/README.md b/lessons/31-lambdas/README.md index 393b68c..e6ba9a0 100644 --- a/lessons/31-lambdas/README.md +++ b/lessons/31-lambdas/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 31: Lambdas" +title: "Lambdas" goal: "Write a lambda that hands back a value, then one that just does something, and pass either into a method as a parameter." -order: 31 +order: 310 section: "Advanced Java" --- @@ -119,7 +119,7 @@ public class Main { ``` `checkSensor` doesn't know or care *how* its `sensor` decides — it just calls -`.isReady()` once and reacts to whatever comes back ([lesson 9's](#/lesson/09-if-statements) +`.isReady()` once and reacts to whatever comes back ([lesson {n}'s](#/lesson/09-if-statements) `if`, doing exactly what it always does). Three calls, three different lambdas, and `checkSensor` itself never changes. A method can take a `Runnable` the same way: diff --git a/lessons/32-builder-pattern-as-diagrams/README.md b/lessons/32-builder-pattern-as-diagrams/README.md index 0df3849..ed76283 100644 --- a/lessons/32-builder-pattern-as-diagrams/README.md +++ b/lessons/32-builder-pattern-as-diagrams/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 32: The builder pattern, as diagrams" +title: "The builder pattern, as diagrams" goal: "See a configuration accumulate one field at a time through a chain of steps, before writing a line of code." -order: 32 +order: 320 section: "Advanced Java" --- diff --git a/lessons/33-builder-pattern-in-code/README.md b/lessons/33-builder-pattern-in-code/README.md index 54247e8..f4d0750 100644 --- a/lessons/33-builder-pattern-in-code/README.md +++ b/lessons/33-builder-pattern-in-code/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 33: The builder pattern, in code" +title: "The builder pattern, in code" goal: "Write a chain of fluent setters that each return this, in the exact shape PhoenixLib uses to configure a motor." -order: 33 +order: 330 section: "Advanced Java" --- @@ -41,7 +41,7 @@ diagram chained steps together instead. Here's how to make that legal Java. # Setters that return this The fix is one word: instead of `void`, a setter returns **`this`** — *the -object it was just called on* ([lesson 17](#/lesson/17-classes-and-objects)). +object it was just called on* ([lesson {n}](#/lesson/17-classes-and-objects)). That lets you call the *next* method directly on what the last one handed back: @@ -86,7 +86,7 @@ Run it — all three settings land in one chained expression. Read `new MotorConfig().withMaxSpeed(80)` as "build a config, then call `withMaxSpeed` on it" — and because `withMaxSpeed` **returns that very object**, `.withCurrentLimit(40)` can be called directly on whatever it just -returned, and so on. The fields stay `private` ([lesson 19](#/lesson/19-encapsulation)) +returned, and so on. The fields stay `private` ([lesson {n}](#/lesson/19-encapsulation)) — nothing outside `MotorConfig` ever touches `maxSpeed` directly, only through a `with*` method that sets it on purpose. @@ -97,7 +97,7 @@ of it. Real motor configs aren't just speed and current limit — they cover dozens of settings — but every one of them is set the same way: one `with*` method, one field, `return this;`. -Your turn: go back to your capstone mechanism from [lesson 25](#/lesson/25-capstone-state-machine). +Your turn: go back to your capstone mechanism from [lesson {n}](#/lesson/25-capstone-state-machine). Write a fluent config class for it with at least three `with*` methods (pick settings that make sense for your mechanism — a max height, a hold current, a speed, whatever fits) and a `report()` method. Build one with a chained call diff --git a/lessons/34-capstone-2/README.md b/lessons/34-capstone-2/README.md index 3bb4fb7..8e3a486 100644 --- a/lessons/34-capstone-2/README.md +++ b/lessons/34-capstone-2/README.md @@ -1,7 +1,7 @@ --- -title: "Lesson 34: Capstone 2 — an advanced mechanism" +title: "Capstone 2 — an advanced mechanism" goal: "Return to your Capstone 1 mechanism and upgrade it with enums with fields, switch, interfaces, access modifiers, and the builder pattern — so it runs identically in simulation and on real hardware." -order: 34 +order: 340 section: "Advanced Java" --- @@ -18,7 +18,7 @@ end. When a page adds a field to `ClimberState`, add one to your enum. When a page gives `Climber` an interface, give yours one too. By the last page, `Climber` and your mechanism should have grown up together. -Start with [lesson 27's](#/lesson/27-enums-revisited) trick, applied to the +Start with [lesson {n}'s](#/lesson/27-enums-revisited) trick, applied to the states from Capstone 1: give every state its own motor speed, right on the enum. @@ -47,12 +47,12 @@ public class Main { Run it — the four states from Capstone 1 print out, each now carrying the number the motor should run at while the climber is in that state. No new -behavior otherwise, just [lesson 27's](#/lesson/27-enums-revisited) trick +behavior otherwise, just [lesson {n}'s](#/lesson/27-enums-revisited) trick applied a second time. Go add a field to your own enum before moving on. # Give it real and simulated hardware -Next, [lesson 29's](#/lesson/29-interfaces-in-code) pattern: a contract for +Next, [lesson {n}'s](#/lesson/29-interfaces-in-code) pattern: a contract for "whatever moves the actual motor," plus a `Sim` and a `Real` that both keep it. @@ -82,14 +82,14 @@ public class Main { ``` Run it — `SIM: setSpeed(0.6)` prints. Neither implementation talks to actual -hardware — they're stand-ins, exactly like lesson 29's `MotorIO`, so you can +hardware — they're stand-ins, exactly like [lesson {n}](#/lesson/29-interfaces-in-code)'s `MotorIO`, so you can see which one ran without needing a real motor controller plugged in. Write the same `-IO` interface, plus `Sim` and `Real`, for your own mechanism now. # Recap: the config builder Before combining everything, rebuild one more piece on its own: -[lesson 33's](#/lesson/33-builder-pattern-in-code) fluent config, this time +[lesson {n}'s](#/lesson/33-builder-pattern-in-code) fluent config, this time guarding the climber's speed instead of a generic motor's. ```java @@ -121,7 +121,7 @@ public class Main { Run it: `0.5`, `-0.5`, `0.2` — the first two get reined in to `maxSpeed`, the third was already safely inside it and passes through untouched. `private double maxSpeed` plus one `with*` method that returns `this` is the -whole builder — nothing new here, just the exact class from lesson 33, +whole builder — nothing new here, just the exact class from [lesson {n}](#/lesson/33-builder-pattern-in-code), renamed for this mechanism. Keep this shape in mind; it's about to plug straight into the next piece. @@ -129,8 +129,8 @@ straight into the next piece. Now combine the pieces from the first two pages — the enum with a field, and the `Sim`/`Real` hardware — into an actual state-machine class, using -[lesson 26's](#/lesson/26-decisions-in-code-revisited) `switch` for the -transitions and [lesson 30's](#/lesson/30-encapsulation-revisited) access +[lesson {n}'s](#/lesson/26-decisions-in-code-revisited) `switch` for the +transitions and [lesson {n}'s](#/lesson/30-encapsulation-revisited) access modifiers for the fields. No config yet — `update` hands the enum's speed straight to `io`: diff --git a/site/src/components/AppSidebar.tsx b/site/src/components/AppSidebar.tsx index 55423bb..712e5c9 100644 --- a/site/src/components/AppSidebar.tsx +++ b/site/src/components/AppSidebar.tsx @@ -13,10 +13,6 @@ import { } from '@/components/ui/sidebar' import { lessons, lessonSections } from '@/lib/lessons' -// The lesson titles already read "Lesson N: ..."; the sidebar shows its own -// number badge, so strip the redundant prefix from the label. -const shortTitle = (title: string) => title.replace(/^Lesson\s+\d+:\s*/, '') - export function AppSidebar() { const { pathname } = useLocation() @@ -52,7 +48,7 @@ export function AppSidebar() { {lessons.indexOf(lesson) + 1} - {shortTitle(lesson.title)} + {lesson.title} diff --git a/site/src/lib/lessons.ts b/site/src/lib/lessons.ts index f86a7de..6724ef9 100644 --- a/site/src/lib/lessons.ts +++ b/site/src/lib/lessons.ts @@ -105,6 +105,16 @@ export function getLesson(slug: string): Lesson | undefined { return lessons.find((lesson) => lesson.slug === slug) } +// The displayed lesson number is derived from position in the sorted `lessons` +// array (1-based), never stored in content. This is the single source of truth +// for "what number is this lesson" - cards, the sidebar, and cross-reference +// links all read it, so inserting/reordering a lesson (change its `order`) is +// the only edit needed to renumber everything. +export function lessonNumber(slug: string): number { + const index = lessons.findIndex((lesson) => lesson.slug === slug) + return index === -1 ? 0 : index + 1 +} + // The lesson that follows `slug` in `order`, or undefined if it's the last one. export function nextLesson(slug: string): Lesson | undefined { const index = lessons.findIndex((lesson) => lesson.slug === slug) diff --git a/site/src/routes/LessonView.tsx b/site/src/routes/LessonView.tsx index a695597..6103758 100644 --- a/site/src/routes/LessonView.tsx +++ b/site/src/routes/LessonView.tsx @@ -1,8 +1,16 @@ +import type { ReactNode } from 'react' import { Link, Navigate, useNavigate, useParams } from 'react-router-dom' import ReactMarkdown from 'react-markdown' import type { Components } from 'react-markdown' import remarkGfm from 'remark-gfm' -import { blocksPreset, firstJavaSnippet, getLesson, nextLesson, stripBlocksFence } from '@/lib/lessons' +import { + blocksPreset, + firstJavaSnippet, + getLesson, + lessonNumber, + nextLesson, + stripBlocksFence, +} from '@/lib/lessons' import { JavaRunner } from '@/components/JavaRunner' import { CodeBlock } from '@/components/CodeBlock' import { BlockPlayground } from '@/components/BlockPlayground' @@ -15,7 +23,33 @@ import { PageNav } from '@/components/PageNav' // the first snippet still lives in the playground panel. Inline `code` stays as // a plain styled element. `pre` is a pass-through so CodeBlock owns its own // container instead of nesting inside a prose
.
+// Cross-reference links point at `#/lesson/` and carry no stored lesson
+// number - their visible text uses `{n}` / `{title}` tokens that we fill in
+// here from the target's CURRENT position, so reordering a lesson (its `order`)
+// re-labels every reference to it automatically, with no content edit.
+const LESSON_HREF = /^#\/lesson\/([a-z0-9-]+)$/
+
+function flattenText(node: ReactNode): string {
+  if (typeof node === 'string' || typeof node === 'number') return String(node)
+  if (Array.isArray(node)) return node.map(flattenText).join('')
+  return ''
+}
+
 const markdownComponents: Components = {
+  a: ({ href, children }) => {
+    const match = LESSON_HREF.exec(href ?? '')
+    if (match) {
+      const slug = match[1]
+      const text = flattenText(children)
+      if (text.includes('{n}') || text.includes('{title}')) {
+        const filled = text
+          .replace(/\{n\}/g, String(lessonNumber(slug)))
+          .replace(/\{title\}/g, getLesson(slug)?.title ?? '')
+        return {filled}
+      }
+    }
+    return {children}
+  },
   pre: ({ children }) => <>{children},
   code: ({ className, children }) => {
     const language = /language-(\w+)/.exec(className ?? '')?.[1]
@@ -115,7 +149,9 @@ export function LessonView() {
             
               Next lesson
             
-            {next.title} →
+            
+              Lesson {lessonNumber(next.slug)}: {next.title} →
+            
           
         ) : (