seed: curriculum content

This commit is contained in:
2026-05-07 14:32:44 +00:00
parent 9258534803
commit ec76f4f56b
100 changed files with 2846 additions and 0 deletions

31
1.solar-system/0.index.md Normal file
View File

@@ -0,0 +1,31 @@
---
type: module
title: "Solar System"
description: "Your launchpad — 4 weeks to land your first lines of Python, with a fast crash course in the shell and Git on day one."
---
# Solar System
Welcome aboard. Over the next four weeks you'll go from wherever you are now to writing real Python programs. Day one drops you into the shell and Git so you can move and save your work; everything after that is Python.
## How It Works
The Solar System runs on three modules.
- **Welcome** — your first 12 days. Crash course in the terminal and Git, the cultural ground rules, and a paired battle to close it out.
- **Python Fundamentals** — the bulk of the stage. From your first `print` to writing real programs with functions, loops, and data structures.
- **The Checkpoint** — proves you're ready for what comes next.
## The Schedule
| Days | Module | Focus |
|------|--------|-------|
| 12 | Welcome | Shell + Git basics, paired battle |
| 318 | Python Fundamentals | Variables, control flow, functions, data |
| 1920 | The Checkpoint | Final assessment |
## What You'll Need
- A workstation on the campus (already configured for you)
- A GitHub account (you'll create one in Welcome)
- Curiosity and persistence — you'll get stuck, and that's the point

View File

@@ -0,0 +1,30 @@
---
type: module
title: "Welcome"
description: "Your first 12 days. Crash course in the terminal and Git, then a paired battle."
---
# Welcome
You've landed. The next 12 days are about getting you off the ground:
the shell, Git, and how things work here. Sixteen blocks, one battle,
then you're cleared for Python.
This module gives you the bare minimum tools to write code, save your
work, and ship it. Every command you learn here will reappear, in some
form, every day for the rest of the program.
## What You'll Do
- Walk the file system from a blank prompt
- Make, move, and read files
- Write your first shell script
- Configure Git and make your first commits
- Push your work to GitHub and open your first pull request
- Pair with another cadet to close out the module
## Pace
Roughly 1012 hours of focused work. Some cadets finish in a day, some
take two. Either is fine. Do not skip blocks — the checkpoint catches
gaps.

View File

@@ -0,0 +1,84 @@
---
type: story
title: "Welcome"
xp: 25
duration: 20
difficulty: 1
---
# Welcome
> **[INCOMING — Mission Control, Earth]**
>
> Cadet.
>
> Welcome to the program.
>
> You have just enrolled in twelve months of training. By the end, if
> you finish, you will be a working developer — not a graduate of
> something, but a person who can sit in front of a blank file and
> produce software that does what it's supposed to do.
>
> We will not teach you the way you've been taught before. There will
> be no lectures. No instructor at a board. No videos to play at 2x.
> You will read, you will type, and you will build. Most of the
> learning happens between your ears while your fingers move.
>
> Before you touch the terminal — and you will, in the very next block
> — we want you to know where you are and what's about to happen.
> Read this to the end.
>
> [TRANSMISSION CONTINUES]
## Where You Are
You have entered Stage I: **Solar System**. Your launchpad. Over the
next four weeks, you will move from "I have never written code" to
"I can write small programs that solve real problems."
Past Solar System, four more stages await:
- **Milky Way** — deeper Python, data structures, your first real projects.
- **Andromeda** — web, databases, applications other people can use.
- **Deep Space** — specialize. Backend, frontend, data, or DevOps.
- **New Horizon** — your capstone, and the bridge to a job.
Don't worry about anything past Solar System right now. The path
exists. Trust it. Walk one stage at a time.
## How Content Works
Every module in every stage is built from three kinds of blocks:
- **Stories** like this one. They give you context — the *why* before
the *do*. They are short. Skim them and you'll pay for it later.
- **Challenges** are where you build. Each one drops you a starter pack
and a mission. You write code, submit, and an automated grader tells
you pass or iterate.
- **Battles** put you in a pair. One of you executes, the other
reviews. You learn twice — once doing, once watching.
Every block earns XP when you complete it. XP is not a leaderboard.
It's a measure of how much you have actually done. Skipping ahead does
not work — checkpoints catch it, and they catch it in front of your
peers.
## What This Module Holds
The next block is `hello-world` — your first line of code. After that,
twelve more challenges teach you the shell and Git, the tools every
cadet on Earth uses every day. One paired battle closes the module.
One to two days of work, depending on your pace. Then Python begins.
> **[CLOSING — Mission Control]**
>
> One more thing before we let you go.
>
> Everyone who finishes the program got stuck where you are now. Every
> single one. The terminal you're about to open scared them too. They
> kept going. So will you.
>
> Your training begins on the next block.
>
> [END TRANSMISSION]

View File

@@ -0,0 +1,35 @@
---
type: challenge
title: "Hello, World"
xp: 25
duration: 15
difficulty: 1
---
# Hello, World
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, first signal. Write a script that creates `hello.txt`
> containing exactly:
>
> ```
> Hello, World
> ```
>
> Two commands:
>
> - `echo` prints text
> - `>` redirects what would print into a file
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, write the command(s) that create `hello.txt`
with the content `Hello, World`.
## Objectives
- `hello.txt` exists in the working directory
- File contents match `Hello, World` exactly

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Hello, World — your first script.
# Write the line "Hello, World" into a file called hello.txt
# in the current directory.
# Your code here.

View File

@@ -0,0 +1,16 @@
#!/bin/bash
bash solution.sh > /dev/null 2>&1
if [ -f hello.txt ]; then
echo "ok 1 - hello.txt is created"
else
echo "not ok 1 - hello.txt is created"
fi
ACTUAL=$(cat hello.txt 2>/dev/null)
ACTUAL="${ACTUAL%$'\n'}"
if [ "$ACTUAL" = "Hello, World" ]; then
echo "ok 2 - hello.txt contains 'Hello, World'"
else
echo "not ok 2 - hello.txt contains 'Hello, World'"
fi

View File

@@ -0,0 +1,46 @@
---
type: battle
title: "First Repo"
xp: 200
duration: 30
difficulty: 1
---
# First Repo
> **[INCOMING — Mission Control, Earth]**
>
> Cadets, your first paired exercise. One of you will set up a fresh
> Git project; the other will verify it. Then swap.
>
> Pair up. Decide who goes first as **defender** and who goes first
> as **attacker**.
>
> [END TRANSMISSION]
## The Task
The **defender** writes a single shell script — `setup.sh` — that,
when run inside an empty workspace:
1. Initializes a fresh Git repo on branch `main`.
2. Sets `user.name` and `user.email` on the repo.
3. Creates a file called `hello-world.txt` containing exactly:
```
Hello, World
```
4. Stages and commits it with the message `add hello-world`.
## Battle Rules
- **Defender**: writes `setup.sh` in a clean workspace. Does not show
the file until done.
- **Attacker**: receives the script, runs it in a clean folder,
inspects the resulting repo, and submits a review.
- **Then swap**: roles flip; the new defender writes their own
`setup.sh` (no copy-pasting).
- Both pass the battle if both scripts pass review.
## Tools
`git init`, `git config`, `echo`, `>`, `git add`, `git commit -m`.

View File

@@ -0,0 +1,38 @@
# First Repo — Extended Notes
*Not rendered yet. For instructors and a future "learn more" surface.*
## Purpose
The first paired exercise of the program. Cadets have just finished
the bash basics; this introduces them to the battle pattern (defender
writes, attacker reviews, then swap) using a tiny but real Git
workflow: init, commit, log.
## Skills Demonstrated
- Composing learned commands (`git init`, `git config`, `git add`,
`git commit`) into a single script
- Reading another cadet's script to verify behavior
- Filling out the review checklist constructively
## Common Pitfalls
- **Skipping `git config`** — the commit either fails or uses a
fallback identity that's not the cadet's. Reviewer should run
`git config --local user.name` to confirm.
- **Wrong file content** — `echo "hello world"` (lowercase) doesn't
match `Hello, World`. Reviewer must `cat hello-world.txt` and check
byte-for-byte.
- **Wrong commit message** — `add hello world` (no dash) is *not*
`add hello-world`. Reviewer should run `git log --format=%s`.
- **Multiple commits** — sometimes a cadet stages files twice and
ends up with two commits. The spec asks for exactly one.
## Discussion Prompts (post-battle)
After both rounds, pair discusses for 23 minutes:
1. Whose script was easier to read? Why?
2. What did you check first when reviewing? Why?
3. If this script ran on a server, what could go wrong?

View File

@@ -0,0 +1,27 @@
[
{
"title": "Repo Setup",
"icon": "ph:git-branch-duotone",
"items": [
".git/ exists after running setup.sh",
"user.name is set on the repo",
"user.email is set and looks like an email"
]
},
{
"title": "File Content",
"icon": "ph:file-text-duotone",
"items": [
"hello-world.txt exists at the repo root",
"File contains exactly the line 'Hello, World'"
]
},
{
"title": "Commit",
"icon": "ph:git-commit-duotone",
"items": [
"Exactly one commit in git log",
"Commit message is exactly 'add hello-world'"
]
}
]

View File

@@ -0,0 +1,40 @@
---
type: challenge
title: "The Maker"
xp: 50
duration: 25
difficulty: 2
---
# The Maker
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, you've walked the field. Now build something.
>
> When your script runs, a stale file `cargo-old.txt` is sitting in
> your working directory. Build a cargo bay with three rooms — `food/`,
> `water/`, `tools/` — each holding an empty `manifest.txt`. Then
> delete the stale file.
>
> Three commands:
>
> - `mkdir` — make a directory (use `-p` to make nested ones at once)
> - `touch` — create an empty file
> - `rm` — delete (be careful — there is no trash)
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, write the commands to:
1. Create `cargo/food/`, `cargo/water/`, `cargo/tools/`
2. Create an empty `manifest.txt` in each room
3. Remove `cargo-old.txt`
## Objectives
- All three room directories exist
- Each contains an empty `manifest.txt`
- `cargo-old.txt` no longer exists

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# The Maker — build a cargo bay.
#
# When this script runs, a stale file `cargo-old.txt` is in your
# working directory. Your script must:
#
# 1. Create directories: cargo/food, cargo/water, cargo/tools
# 2. Create an empty file `manifest.txt` in each of those three rooms
# 3. Remove cargo-old.txt
#
# Tools: mkdir (use -p for nested), touch, rm
# Your code here.

View File

@@ -0,0 +1,17 @@
#!/bin/bash
echo "stale entry" > cargo-old.txt
bash solution.sh > /dev/null 2>&1
N=0
check() {
N=$((N+1))
if eval "$1"; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi
}
check '[ -d cargo/food ]' "cargo/food exists"
check '[ -d cargo/water ]' "cargo/water exists"
check '[ -d cargo/tools ]' "cargo/tools exists"
check '[ -f cargo/food/manifest.txt ] && [ ! -s cargo/food/manifest.txt ]' "cargo/food/manifest.txt exists and is empty"
check '[ -f cargo/water/manifest.txt ] && [ ! -s cargo/water/manifest.txt ]' "cargo/water/manifest.txt exists and is empty"
check '[ -f cargo/tools/manifest.txt ] && [ ! -s cargo/tools/manifest.txt ]' "cargo/tools/manifest.txt exists and is empty"
check '[ ! -f cargo-old.txt ]' "cargo-old.txt removed"

View File

@@ -0,0 +1,39 @@
---
type: challenge
title: "The Mover"
xp: 50
duration: 25
difficulty: 2
---
# The Mover
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, your cargo bay is built. Now stock it.
>
> When your script runs, an `inbox/` of fresh supplies has arrived.
> Each item is labeled by category — `food-`, `water-`, or `tool-`.
> Sort them into the right rooms. Then make a backup of the food
> manifest.
>
> Two commands:
>
> - `mv` — move (or rename)
> - `cp` — copy
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, write the commands to:
1. Move each `food-*` file into `cargo/food/`
2. Move each `water-*` file into `cargo/water/`
3. Move each `tool-*` file into `cargo/tools/`
4. Copy `cargo/food/manifest.txt` to `cargo/food/manifest.backup`
## Objectives
- All `food-*`, `water-*`, `tool-*` files moved to the correct rooms
- `cargo/food/manifest.backup` exists

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# The Mover — sort the supplies.
#
# When this script runs:
# - cargo/food/, cargo/water/, cargo/tools/ already exist
# - cargo/food/manifest.txt exists (empty)
# - inbox/ contains files with prefixes:
# food-apple.txt, food-bread.txt
# water-bottle.txt, water-canteen.txt
# tool-wrench.txt, tool-hammer.txt
#
# Your script must:
# 1. Move all food-*.txt files into cargo/food/
# 2. Move all water-*.txt files into cargo/water/
# 3. Move all tool-*.txt files into cargo/tools/
# 4. Copy cargo/food/manifest.txt to cargo/food/manifest.backup
#
# Tools: mv, cp
# Your code here.

View File

@@ -0,0 +1,25 @@
#!/bin/bash
mkdir -p cargo/food cargo/water cargo/tools inbox
touch cargo/food/manifest.txt cargo/water/manifest.txt cargo/tools/manifest.txt
echo "1 apple" > inbox/food-apple.txt
echo "1 loaf" > inbox/food-bread.txt
echo "500ml" > inbox/water-bottle.txt
echo "1L" > inbox/water-canteen.txt
echo "10mm" > inbox/tool-wrench.txt
echo "claw" > inbox/tool-hammer.txt
bash solution.sh > /dev/null 2>&1
N=0
check() {
N=$((N+1))
if eval "$1"; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi
}
check '[ -f cargo/food/food-apple.txt ]' "food-apple in cargo/food"
check '[ -f cargo/food/food-bread.txt ]' "food-bread in cargo/food"
check '[ -f cargo/water/water-bottle.txt ]' "water-bottle in cargo/water"
check '[ -f cargo/water/water-canteen.txt ]' "water-canteen in cargo/water"
check '[ -f cargo/tools/tool-wrench.txt ]' "tool-wrench in cargo/tools"
check '[ -f cargo/tools/tool-hammer.txt ]' "tool-hammer in cargo/tools"
check '[ -f cargo/food/manifest.backup ]' "cargo/food/manifest.backup exists"

View File

@@ -0,0 +1,39 @@
---
type: challenge
title: "The Reader"
xp: 50
duration: 30
difficulty: 2
---
# The Reader
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, an old mission log was recovered. We need three things from it.
>
> When your script runs, `mission-log.txt` is sitting in the working
> directory. Pull these out:
>
> 1. The first 5 lines → save to `first-five.txt`
> 2. The last 3 lines → save to `last-three.txt`
> 3. The total line count → save to `line-count.txt`
>
> Three commands:
>
> - `head -n N file` — first N lines
> - `tail -n N file` — last N lines
> - `wc -l file` — count lines
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, produce the three output files using `head`,
`tail`, `wc`, and redirect.
## Objectives
- `first-five.txt` matches the first 5 lines of the log
- `last-three.txt` matches the last 3 lines of the log
- `line-count.txt` contains the line count

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# The Reader — pull stats from a log.
#
# When this script runs, mission-log.txt is in your working directory.
# Your script must produce three files:
#
# - first-five.txt — the first 5 lines of mission-log.txt
# - last-three.txt — the last 3 lines of mission-log.txt
# - line-count.txt — the line count (output of `wc -l`)
#
# Tools: head, tail, wc, redirect (>)
# Your code here.

View File

@@ -0,0 +1,39 @@
#!/bin/bash
cat > mission-log.txt <<'LOG'
[1969-07-20 13:32] Apollo touchdown confirmed.
[1969-07-20 13:33] Surface stable.
[1969-07-20 13:34] Cabin sealed.
[1969-07-20 13:35] EVA prep started.
[1969-07-20 13:40] Hatch open check.
[1969-07-20 13:42] Suit pressurization complete.
[1969-07-20 13:50] Ladder deployed.
[1969-07-20 13:55] First step recorded.
[1969-07-20 14:00] Sample collection begun.
[1969-07-20 14:10] All systems nominal.
[1969-07-20 14:20] Communication test passed.
[1969-07-20 14:30] Second sample box sealed.
[1969-07-20 14:40] Solar panel deployed.
[1969-07-20 14:50] Flag planted.
[1969-07-20 15:00] Phone call inbound.
[1969-07-20 15:10] Phone call complete.
[1969-07-20 15:20] Sample box stowed.
[1969-07-20 15:30] EVA wrap-up started.
[1969-07-20 15:35] Hatch closed.
[1969-07-20 15:40] Cabin re-pressurized.
LOG
bash solution.sh > /dev/null 2>&1
N=0
report() {
N=$((N+1))
if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi
}
[ -f first-five.txt ]; report $? "first-five.txt exists"
diff -q <(head -n 5 mission-log.txt) first-five.txt > /dev/null 2>&1; report $? "first-five.txt matches head -n 5"
[ -f last-three.txt ]; report $? "last-three.txt exists"
diff -q <(tail -n 3 mission-log.txt) last-three.txt > /dev/null 2>&1; report $? "last-three.txt matches tail -n 3"
[ -f line-count.txt ]; report $? "line-count.txt exists"
COUNT=$(awk '{print $1}' line-count.txt 2>/dev/null | tr -d ' ')
[ "$COUNT" = "20" ]; report $? "line-count.txt contains 20"

View File

@@ -0,0 +1,48 @@
---
type: challenge
title: "The Editor"
xp: 50
duration: 25
difficulty: 2
---
# The Editor
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, single-line `echo` works for short messages. Real files
> need multiple lines. Two options:
>
> - Multiple `echo` lines, each with its own redirect (use `>>` after
> the first one to append)
> - A *heredoc* — write a whole block in one go:
>
> ```bash
> cat > journal.md <<EOF
> line one
> line two
> EOF
> ```
>
> Write a script that creates `journal.md` containing exactly:
>
> ```
> # Cadet Log — Day 1
>
> Today I learned to navigate the shell.
> Tomorrow I will write code.
> ```
>
> Mind the empty line between the header and the body.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, produce `journal.md` with the exact content
above.
## Objectives
- `journal.md` exists
- Contents match the template exactly (4 lines + the blank line)

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# The Editor — write a multi-line journal entry.
#
# Your script must create a file `journal.md` containing exactly:
#
# # Cadet Log — Day 1
#
# Today I learned to navigate the shell.
# Tomorrow I will write code.
#
# Note the empty line between the header and the body.
#
# A heredoc writes multiple lines at once:
#
# cat > journal.md <<EOF
# ...lines here...
# EOF
# Your code here.

View File

@@ -0,0 +1,19 @@
#!/bin/bash
bash solution.sh > /dev/null 2>&1
EXPECTED=$(cat <<'EXP'
# Cadet Log — Day 1
Today I learned to navigate the shell.
Tomorrow I will write code.
EXP
)
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -f journal.md ]; report $? "journal.md exists"
ACTUAL=$(cat journal.md 2>/dev/null)
ACTUAL="${ACTUAL%$'\n'}"
[ "$ACTUAL" = "$EXPECTED" ]; report $? "journal.md matches the template exactly"

View File

@@ -0,0 +1,36 @@
---
type: challenge
title: "The Searcher"
xp: 75
duration: 30
difficulty: 3
---
# The Searcher
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, an archive arrived. Somewhere in it, a single log file
> mentions the word `BREACH`. We need it found.
>
> Two commands:
>
> - `find <path> -name "<pattern>"` — locate files by name
> - `grep -rl "<text>" <path>` — search file contents recursively,
> list only the matching file paths
>
> Your script must produce two files:
>
> 1. `logs.txt` — every `.log` file under `archive/`
> 2. `breach.txt` — the path to the file containing `BREACH`
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, write the two commands.
## Objectives
- `logs.txt` lists every `.log` file under `archive/`
- `breach.txt` contains the path to the file mentioning `BREACH`

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# The Searcher — find things in an archive.
#
# When this script runs, an `archive/` directory tree exists with
# many files. Somewhere in it, a single .log file mentions BREACH.
#
# Your script must produce two files:
# - logs.txt — every `.log` file path under archive/, one per line
# - breach.txt — the path to the file containing the word BREACH
#
# Tools: find, grep -rl
# Your code here.

View File

@@ -0,0 +1,22 @@
#!/bin/bash
mkdir -p archive/sectors/a archive/sectors/b archive/sectors/c archive/data archive/old
echo "[03:30] Sector A nominal." > archive/sectors/a/sector-a.log
echo "[03:32] Sector B nominal." > archive/sectors/b/sector-b.log
printf '[03:30] Sector C nominal.\n[03:42] BREACH detected at perimeter.\n' > archive/sectors/c/sector-c.log
echo "[00:00] Logger online." > archive/data/raw.log
echo "Notes" > archive/data/notes.txt
echo "[01:00] Legacy." > archive/old/old.log
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -f logs.txt ]; report $? "logs.txt exists"
EXPECTED=$(find archive -name "*.log" | sort)
ACTUAL=$(sort logs.txt 2>/dev/null)
[ "$EXPECTED" = "$ACTUAL" ]; report $? "logs.txt lists all .log files"
[ -f breach.txt ]; report $? "breach.txt exists"
grep -q "sector-c.log" breach.txt 2>/dev/null; report $? "breach.txt references sector-c.log"

View File

@@ -0,0 +1,45 @@
---
type: challenge
title: "The Scripter"
xp: 100
duration: 35
difficulty: 3
---
# The Scripter
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, time to combine commands. Bash lets you embed the output of
> one command inside another using `$(...)` — *command substitution*:
>
> ```bash
> echo "Today is $(date '+%Y-%m-%d')"
> # → Today is 2026-05-04
> ```
>
> When your script runs, a directory `supplies/` exists with several
> files. Produce `report.txt` containing exactly two lines:
>
> ```
> Date: <today in YYYY-MM-DD format>
> Files in supplies: <count of entries in supplies/>
> ```
>
> Two helpers:
>
> - `date '+%Y-%m-%d'` — today in the right format
> - `ls supplies | wc -l | tr -d ' '` — entry count, whitespace stripped
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, use `echo` + `$(...)` substitution to
produce the two-line report.
## Objectives
- `report.txt` exists
- Line 1 matches `Date: <today>`
- Line 2 matches `Files in supplies: <count>`

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# The Scripter — combine commands into a script.
#
# When this script runs, a directory `supplies/` exists with several
# files. Your script must produce `report.txt` containing exactly two
# lines:
#
# Date: <today, in YYYY-MM-DD format>
# Files in supplies: <number of entries in supplies/>
#
# Use command substitution `$(...)` to embed command output inside
# strings:
#
# $(date '+%Y-%m-%d')
# $(ls supplies | wc -l | tr -d ' ')
# Your code here.

View File

@@ -0,0 +1,21 @@
#!/bin/bash
mkdir -p supplies
touch supplies/oxygen-tank.txt \
supplies/ration-pack.txt \
supplies/medkit.txt \
supplies/repair-kit.txt \
supplies/comms-unit.txt
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -f report.txt ]; report $? "report.txt exists"
TODAY=$(date '+%Y-%m-%d')
LINE1=$(sed -n '1p' report.txt 2>/dev/null)
LINE2=$(sed -n '2p' report.txt 2>/dev/null)
[ "$LINE1" = "Date: $TODAY" ]; report $? "line 1 is 'Date: $TODAY'"
[ "$LINE2" = "Files in supplies: 5" ]; report $? "line 2 is 'Files in supplies: 5'"

View File

@@ -0,0 +1,89 @@
---
type: story
title: "How to Learn Here"
xp: 25
duration: 25
difficulty: 1
---
# How to Learn Here
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, take a breath. You've spent the morning in the shell.
> Before we take you to Git, we need you to know how learning actually
> works in this program.
>
> Cadets who finish the program share three habits. Cadets who quit
> share three different ones. We're going to tell you which is which.
>
> [TRANSMISSION CONTINUES]
## Rule 1 — Understand Every Line
You will use AI here. Claude, ChatGPT, our onboard NAV-7 — whatever
you reach for. We don't forbid it. We *expect* it. The world you're
training to enter assumes AI fluency.
But there is one rule that does not bend:
**You must be able to explain every line of code you submit.**
If AI writes a function and you can't say what each line does and why,
you have not learned. You've outsourced thinking. The checkpoint will
catch the gap. The first real project will catch it harder. Use AI as
a tutor and a sparring partner — never as a ghostwriter.
## Rule 2 — Help the Cadet Next to You
This is a peer-learning environment. There are no professors here.
There are people who landed last month, last week, and yesterday — and
right now, you.
Two things follow from that:
- **When you understand something, teach it.** Explaining a concept to
someone else cements it in your own head better than reading it twice.
- **When you don't understand something, ask.** Your peers solved this
exact problem hours, days, or weeks ago. Their explanation will
usually beat ours.
The cohort is the curriculum. Treat it that way and the program runs
on rails. Ignore it and you'll fall behind alone.
## Rule 3 — Struggle First, for Fifteen Minutes
When you get stuck — and you will, every day — the instinct is to ask
right away. Resist it.
For fifteen minutes, struggle. Re-read the briefing. Read the error
message slowly. Try one thing. Try a second thing. Notice what you
expected versus what actually happened.
Most of your real learning happens in those fifteen minutes. Skip them
and you don't just miss the answer — you miss the *thinking*. After
fifteen, ask. Not before.
## Asking a Good Question
When you do ask — peer, AI, or staff — three things make a good
question:
1. **Context.** What were you trying to do?
2. **What you tried.** What approach did you take? What error did you
see?
3. **The exact error.** Copy it. Don't summarize it.
A good question gets answered in ten minutes. A bad question gets
ignored for an hour, or worse, gets the wrong answer because nobody
understood what you meant.
> **[CLOSING — Mission Control]**
>
> Two days in, you'll have habits. Make sure they're the right ones.
>
> Six Git challenges follow. Then a paired battle. Then Python.
>
> Keep going.
>
> [END TRANSMISSION]

View File

@@ -0,0 +1,38 @@
---
type: challenge
title: "The Identity"
xp: 50
duration: 15
difficulty: 2
---
# The Identity
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, before Git records any of your work, it needs to know who's
> making the changes. Every commit you'll ever make will carry your
> name and email.
>
> When your script runs, a fresh Git repository already exists in the
> current directory. Set the local identity:
>
> ```bash
> git config user.name "Your Name"
> git config user.email "you@example.com"
> ```
>
> Without `--global`, these settings only apply to this one repo —
> exactly what we want for a contained challenge.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, set `user.name` and `user.email` on the
current Git repo.
## Objectives
- `user.name` is set on the repo (non-empty)
- `user.email` is set on the repo and looks like an email

View File

@@ -0,0 +1,12 @@
#!/bin/bash
# The Identity — tell Git who you are.
#
# When this script runs, a fresh Git repo is already initialized in
# the current directory. Your script must set both:
#
# - user.name (any non-empty string)
# - user.email (must contain @ and a dot)
#
# Tools: git config <key> "<value>"
# Your code here.

View File

@@ -0,0 +1,13 @@
#!/bin/bash
git init -q -b main
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
NAME=$(git config --local --get user.name 2>/dev/null)
EMAIL=$(git config --local --get user.email 2>/dev/null)
[ -n "$NAME" ]; report $? "user.name is set on the repo"
[[ "$EMAIL" == *@*.* ]]; report $? "user.email is set and looks like an email"

View File

@@ -0,0 +1,43 @@
---
type: challenge
title: "The Initiator"
xp: 50
duration: 20
difficulty: 2
---
# The Initiator
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, ground zero. Take an empty directory and turn it into a
> Git project.
>
> Two commands today:
>
> - `git init` — turns the current directory into a Git repository
> (creates a hidden `.git/` folder)
> - `git status` — shows what Git sees: branch, tracked, untracked
>
> Your script must:
>
> 1. Run `git init` (use `-b main` to set the default branch)
> 2. Capture `git status` to `status-before.txt`
> 3. Create `intro.md` containing `Hello, Git`
> 4. Capture `git status` to `status-after.txt`
>
> Notice the second status sees the new file as *untracked* — that
> distinction is the heart of how Git works.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, write the four steps above.
## Objectives
- `.git/` exists
- `intro.md` contains `Hello, Git`
- `status-before.txt` looks like git status output
- `status-after.txt` mentions `intro.md`

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# The Initiator — turn an empty directory into a Git repo.
#
# Your script must:
# 1. Initialize a Git repo (use main as the default branch)
# 2. Capture `git status` to status-before.txt
# 3. Create intro.md containing exactly "Hello, Git"
# 4. Capture `git status` to status-after.txt
#
# Notice how the second status sees intro.md as untracked.
#
# Tools: git init, git status, echo
# Your code here.

View File

@@ -0,0 +1,13 @@
#!/bin/bash
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -d .git ]; report $? ".git/ exists"
[ -f intro.md ]; report $? "intro.md exists"
grep -q "Hello, Git" intro.md 2>/dev/null; report $? "intro.md contains 'Hello, Git'"
[ -f status-before.txt ]; report $? "status-before.txt exists"
grep -qi "branch" status-before.txt 2>/dev/null; report $? "status-before.txt looks like git status output"
[ -f status-after.txt ]; report $? "status-after.txt exists"
grep -q "intro.md" status-after.txt 2>/dev/null; report $? "status-after.txt mentions intro.md"

View File

@@ -0,0 +1,41 @@
---
type: challenge
title: "The Saver"
xp: 75
duration: 25
difficulty: 2
---
# The Saver
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, snapshots. Git's whole purpose is to capture *commits* —
> snapshots of your work — that you can return to later.
>
> The flow per commit:
>
> 1. Create or modify a file
> 2. `git add <file>` — stage it
> 3. `git commit -m "<message>"` — lock it in
>
> When your script runs, a clean Git repo with identity already set
> sits in the working directory. Make three commits, in this exact
> order:
>
> 1. Create `a.txt` with `alpha`. Commit message: `add alpha`
> 2. Create `b.txt` with `beta`. Commit message: `add beta`
> 3. Create `c.txt` with `gamma`. Commit message: `add gamma`
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, run six commands per the flow above (one
echo + add + commit per file).
## Objectives
- `a.txt`, `b.txt`, `c.txt` exist with `alpha`, `beta`, `gamma`
- `git log` shows exactly 3 commits
- Commit messages: `add alpha`, `add beta`, `add gamma` in order

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# The Saver — make three commits.
#
# When this script runs, a fresh Git repo with identity already set
# is in your working directory. Make three commits, in this exact
# order:
#
# 1. Create a.txt containing "alpha". Commit message: "add alpha"
# 2. Create b.txt containing "beta". Commit message: "add beta"
# 3. Create c.txt containing "gamma". Commit message: "add gamma"
#
# Tools: echo, git add, git commit -m
# Your code here.

View File

@@ -0,0 +1,20 @@
#!/bin/bash
git init -q -b main
git config user.name "Setup"
git config user.email "setup@learnroom.local"
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
{ [ -f a.txt ] && grep -q alpha a.txt 2>/dev/null; }; report $? "a.txt exists with 'alpha'"
{ [ -f b.txt ] && grep -q beta b.txt 2>/dev/null; }; report $? "b.txt exists with 'beta'"
{ [ -f c.txt ] && grep -q gamma c.txt 2>/dev/null; }; report $? "c.txt exists with 'gamma'"
COUNT=$(git log --oneline 2>/dev/null | wc -l | tr -d ' ')
[ "$COUNT" = "3" ]; report $? "exactly 3 commits in git log"
MSGS=$(git log --reverse --format='%s' 2>/dev/null)
EXPECTED=$'add alpha\nadd beta\nadd gamma'
[ "$MSGS" = "$EXPECTED" ]; report $? "commit messages: 'add alpha', 'add beta', 'add gamma' in order"

View File

@@ -0,0 +1,40 @@
---
type: challenge
title: "The Historian"
xp: 75
duration: 25
difficulty: 3
---
# The Historian
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, the repo in your working directory contains five commits
> from a previous mission. We need a historian's eye on it.
>
> Two commands today:
>
> - `git log --oneline` — compact one-line-per-commit history
> - `git show <hash>` — show what a commit changed
>
> Your script must produce two files:
>
> 1. `log.txt` — full one-line log
> 2. `breach-commit.txt` — short hash of the commit whose message
> mentions `BREACH`
>
> Hint: pipe the log through `grep BREACH | awk '{print $1}'` to
> extract the short hash from the matching line.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`, capture the full log and the breach commit's
short hash.
## Objectives
- `log.txt` lists all 5 commits, one per line
- `breach-commit.txt` contains the short hash of the BREACH commit

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# The Historian — read git history.
#
# When this script runs, the current Git repo already has 5 commits
# from a prior mission. One of them mentions BREACH.
#
# Your script must produce two files:
# - log.txt — `git log --oneline` output (one line per commit)
# - breach-commit.txt — the short hash of the BREACH commit
#
# Hint: `git log --oneline | grep BREACH | awk '{print $1}'`
# pipes the matching log line and prints the leftmost column (the
# short hash).
# Your code here.

View File

@@ -0,0 +1,24 @@
#!/bin/bash
git init -q -b main
git config user.name "Setup"
git config user.email "setup@learnroom.local"
echo "manifest" > manifest.txt; git add manifest.txt; git commit -q -m "add manifest"
echo "sensor a online" > sensor-a.txt; git add sensor-a.txt; git commit -q -m "add sensor A"
echo "breach details" > breach.txt; git add breach.txt; git commit -q -m "investigate BREACH at sector C"
echo "sensor b online" > sensor-b.txt; git add sensor-b.txt; git commit -q -m "add sensor B"
echo "resolution applied" > resolution.txt; git add resolution.txt; git commit -q -m "apply resolution"
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -f log.txt ]; report $? "log.txt exists"
LINES=$(wc -l < log.txt | tr -d ' ')
[ "$LINES" = "5" ]; report $? "log.txt has 5 lines (one per commit)"
[ -f breach-commit.txt ]; report $? "breach-commit.txt exists"
ACTUAL=$(tr -d '[:space:]' < breach-commit.txt 2>/dev/null)
EXPECTED=$(git log --grep BREACH --format=%h)
[ "$ACTUAL" = "$EXPECTED" ]; report $? "breach-commit.txt contains the BREACH commit's short hash"

View File

@@ -0,0 +1,45 @@
---
type: challenge
title: "The Cloner"
xp: 75
duration: 25
difficulty: 3
---
# The Cloner
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, real work doesn't start from scratch. You join an existing
> project — clone it, make changes, commit.
>
> When your script runs, `remote.git` is a *bare repository* (think
> of it as a project living on a server) sitting in your working
> directory. Clone it into a folder called `project`:
>
> ```bash
> git clone remote.git project
> ```
>
> Then inside it, set your identity (Git won't let you commit
> without one), create `log-entry.txt` containing exactly
> `Cadet log entry — checked in.`, and commit with message
> `add cadet log entry`.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`:
1. Clone `remote.git` into `project`
2. `cd project`
3. Set `user.name` and `user.email`
4. Create `log-entry.txt` with the exact line above
5. Add and commit with the exact message
## Objectives
- `project/.git` exists
- `project/log-entry.txt` matches the exact content
- `project/` has at least 2 commits in its log

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# The Cloner — clone a remote and add a commit.
#
# When this script runs, a bare repository `remote.git` is in your
# working directory. Your script must:
#
# 1. Clone remote.git into a folder called `project`
# 2. Inside project/, set user.name and user.email
# 3. Create log-entry.txt containing exactly:
# Cadet log entry — checked in.
# 4. Stage and commit it with message: "add cadet log entry"
#
# Tools: git clone, cd, git config, echo, git add, git commit -m
# Your code here.

View File

@@ -0,0 +1,29 @@
#!/bin/bash
TMP=$(mktemp -d)
(
cd "$TMP"
git init -q -b main
git config user.name "Setup"
git config user.email "setup@learnroom.local"
echo "# Mission Project" > README.md
echo "Initial mission setup." > mission.txt
git add .
git commit -q -m "initial mission setup"
)
git clone -q --bare "$TMP" remote.git
rm -rf "$TMP"
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
[ -d project/.git ]; report $? "project/.git exists (clone happened)"
[ -f project/log-entry.txt ]; report $? "project/log-entry.txt exists"
ACTUAL=$(cat project/log-entry.txt 2>/dev/null)
ACTUAL="${ACTUAL%$'\n'}"
[ "$ACTUAL" = "Cadet log entry — checked in." ]; report $? "log-entry.txt contains the exact line"
COUNT=$(git -C project log --oneline 2>/dev/null | wc -l | tr -d ' ')
[ "$COUNT" -ge 2 ] 2>/dev/null
report $? "project has at least 2 commits"

View File

@@ -0,0 +1,43 @@
---
type: challenge
title: "The Pusher"
xp: 100
duration: 30
difficulty: 3
---
# The Pusher
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, your commits live only on your machine until you *push* them
> somewhere shared. That's how teams work.
>
> When your script runs, you'll find:
>
> - `local-repo/` — a working repo with two commits already, identity set
> - `remote.git/` — an empty bare repo (your shared destination)
>
> Two commands:
>
> - `git remote add <name> <path>` — register a remote and give it a
> short name (by convention, `origin`)
> - `git push -u <remote> <branch>` — send your branch's commits to that
> remote (`-u` sets it as default for next time)
>
> Wire `local-repo` to `remote.git` and push.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.sh`:
1. `cd local-repo`
2. Add `../remote.git` as a remote named `origin`
3. Push `main` to `origin` with `-u`
## Objectives
- `local-repo` has a remote `origin` configured
- `remote.git` has the same commits on `main` as `local-repo`

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# The Pusher — send your commits to a remote.
#
# When this script runs:
# - local-repo/ is a working repo with commits + identity already set
# - remote.git/ is a bare empty repo waiting for your push
#
# Your script must:
# 1. Inside local-repo, register remote.git as a remote named `origin`
# pointing at ../remote.git
# 2. Push the main branch to origin (use -u to set upstream)
#
# Tools: cd, git remote add, git push
# Your code here.

View File

@@ -0,0 +1,27 @@
#!/bin/bash
mkdir -p local-repo
(
cd local-repo
git init -q -b main
git config user.name "Cadet Vega"
git config user.email "cadet.vega@learnroom.local"
echo "# Mission" > README.md
git add README.md; git commit -q -m "initial commit"
echo "Day 1 log" > log.txt
git add log.txt; git commit -q -m "add log"
)
git init -q --bare remote.git
bash solution.sh > /dev/null 2>&1
N=0
report() { N=$((N+1)); if [ "$1" = "0" ]; then echo "ok $N - $2"; else echo "not ok $N - $2"; fi; }
ORIGIN_URL=$(git -C local-repo remote get-url origin 2>/dev/null)
[ -n "$ORIGIN_URL" ]; report $? "'origin' remote configured on local-repo"
LOCAL_COUNT=$(git -C local-repo log --oneline main 2>/dev/null | wc -l | tr -d ' ')
REMOTE_COUNT=$(git -C remote.git log --oneline main 2>/dev/null | wc -l | tr -d ' ')
[ -n "$REMOTE_COUNT" ] && [ "$REMOTE_COUNT" != "0" ]; report $? "remote.git has commits on main"
[ "$LOCAL_COUNT" = "$REMOTE_COUNT" ]; report $? "remote.git has same number of commits as local-repo"

View File

@@ -0,0 +1,63 @@
---
type: battle
title: "Team Build"
xp: 200
duration: 90
difficulty: 3
---
# Team Build
> **[INCOMING — Mission Control, Earth]**
>
> Cadets, this is your final task before Python. You and a partner will
> automate a mission setup together — one writing, the other
> reviewing. Then swap.
>
> Pair up. Decide who goes first as **defender** and who goes first as
> **attacker**.
>
> [END TRANSMISSION]
## The Task
The **defender** writes a single shell script — `launch.sh` — that,
when run inside an empty workspace, does the following in order:
1. Creates a folder called `mission/` and enters it.
2. Initializes a fresh Git repo on branch `main`.
3. Sets `user.name` and `user.email` on the repo (use your own).
4. Creates `manifest.txt` containing exactly:
```
Mission Apollo
```
5. Creates `crew.txt` containing exactly:
```
Cadet A, Cadet B
```
6. Creates `coords.txt` containing exactly:
```
lat: 0.0, lon: 0.0
```
7. Commits each file separately, in this exact order, with messages:
- `add manifest`
- `add crew`
- `add coords`
8. Prints `MISSION READY` to stdout when done.
## Battle Rules
- **Defender**: writes `launch.sh` from scratch in a clean workspace.
Does not show the file until done. Time limit: 30 minutes.
- **Attacker**: receives the script, runs it in a clean folder,
inspects the resulting `mission/`, and submits a review through the
platform's review form.
- **Then swap**: roles flip. The new defender writes their own
`launch.sh` (no copy-pasting). 30 more minutes.
- Both pass the battle if both scripts pass review.
## Tools You'll Use
Everything you've learned this module:
`mkdir`, `cd`, `echo`, `>`, `git init`, `git config`, `git add`,
`git commit`, and a shebang to make the script run.

View File

@@ -0,0 +1,70 @@
# Team Build — Extended Notes
*Not rendered in the platform yet. Reference for instructors and for
future "learn more" surfaces.*
## Purpose
The capstone of `1.welcome`. By this point a cadet has used every
tool they need: `mkdir`, `echo`, `git init`, `git config`, `git add`,
`git commit`, and shell scripting with `chmod +x`. This battle forces
them to combine all of it into a single artifact — a working
automation script — and to read someone else's script for the first
time.
## Skills Demonstrated
- Composing learned commands into a sequential program
- Writing a script with a correct shebang and execute bit
- Producing exact output from automation
- Reading another cadet's code well enough to score it
- Giving and receiving honest feedback under a time limit
## Common Pitfalls
- **One commit covering all three files** — defenders forget that the
spec says *each file commits separately*. Three commits, not one.
- **File contents with extra whitespace** — `echo` adds a trailing
newline by default, which is fine; trailing spaces on the line are
not. Reviewers should `cat -A` to spot invisible characters.
- **Missing or wrong identity** — `git commit` will succeed even
without `user.name` set if a fallback is configured globally;
reviewers should check `git -C mission config user.name` directly.
- **Hardcoded absolute paths** — `cd /Users/...` breaks the moment
the script runs on someone else's machine. Use relative paths.
- **`MISSION READY` printed inside another message** — the spec says
"exactly". `echo "Done. MISSION READY now."` should fail review.
- **Forgetting `chmod +x`** — script runs fine via `bash launch.sh`
but fails when executed directly. Reviewers should test both.
## Discussion Prompts (post-battle)
Ask the pair to discuss for 5 minutes after both rounds:
1. Whose script was easier to read? Why?
2. Was anything in the spec ambiguous? How would you tighten it?
3. What would you do differently if you wrote this from scratch
today?
4. Did you find a bug your partner missed?
## Alternative Approaches
A cadet who's seen heredocs might write:
```bash
cat > manifest.txt <<EOF
Mission Apollo
EOF
```
Both `echo "..." > file` and heredoc are acceptable. The spec
doesn't require a specific style — only that the resulting file
content match exactly.
## Why This Is the Last Block of Welcome
This is the bridge between mechanical skill (typing commands) and
real engineering (composing them, reading others' code, judging
quality). Cadets who pass this battle have proven they can act as
both author and reviewer — the foundational loop of every
collaborative codebase. Python is built on this foundation.

View File

@@ -0,0 +1,38 @@
[
{
"title": "Script Quality",
"icon": "ph:code-duotone",
"items": [
"Script starts with #!/bin/bash shebang",
"Script is executable (chmod +x applied)",
"Script runs without errors",
"Prints exactly 'MISSION READY' at the end"
]
},
{
"title": "Repo State",
"icon": "ph:git-branch-duotone",
"items": [
"mission/.git/ exists after running the script",
"user.name is set on the mission repo",
"user.email is set and looks like an email"
]
},
{
"title": "Files",
"icon": "ph:files-duotone",
"items": [
"manifest.txt contains exactly 'Mission Apollo'",
"crew.txt contains exactly 'Cadet A, Cadet B'",
"coords.txt contains exactly 'lat: 0.0, lon: 0.0'"
]
},
{
"title": "Commits",
"icon": "ph:list-duotone",
"items": [
"Three separate commits exist (not one combined commit)",
"Commit messages, in order: 'add manifest', 'add crew', 'add coords'"
]
}
]

View File

@@ -0,0 +1,27 @@
---
type: module
title: "First Light"
description: "Your first Python — REPL, scripts, variables, and the syntax to write small real programs."
---
# First Light
Bash and Git got you off the ground. Now we put a real programming
language in your hands. Python is small enough to learn fast and big
enough to power half the systems running in production right now.
Ten blocks. About four and a half hours.
## What You'll Do
- Print your first Python output
- Use the interactive REPL
- Run a `.py` script
- Declare variables of every basic type
- Read input from a cadet, transform it, print it back
- Write a small calculator
## Pace
About 4.5 hours. After this you'll know enough Python syntax to
write real programs in the next module.

View File

@@ -0,0 +1,71 @@
---
type: story
title: "First Light"
xp: 25
duration: 25
difficulty: 1
---
# First Light
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, you've got the ground beneath you. Bash, Git, the cohort —
> all there. Now we hand you a language.
>
> Python. We picked it because it's small enough to learn fast, big
> enough to power half the systems running in production right now,
> and clear enough that you can read it like prose. The trade for
> "easy to read" is that some advanced concepts come later than they
> would in C or Java. We'll get to them.
>
> [TRANSMISSION CONTINUES]
## Why Python
Three reasons:
1. **Syntax that gets out of your way.** No semicolons. No braces.
Indentation is structure. You'll spend more time thinking about
the problem than fighting the language.
2. **Batteries included.** A standard library that lets you do
networking, data, files, math, dates, and more without installing
anything.
3. **It runs everywhere.** Web servers, data pipelines, scripts,
embedded systems, machine learning. The skills transfer.
## How You'll Run It
You'll meet Python in three ways:
- **`python3 -c 'expression'`** — run a one-liner from the shell
- **The REPL** — type `python3` with no argument; you get an
interactive prompt where every line runs immediately
- **A script** — write code in a `.py` file and run it with
`python3 file.py`
The next three blocks walk you through all three.
## What's Coming
After this module:
- `strings` — text processing
- `flow` — conditionals and loops
- `collections` — lists, dicts, sets
- `functions` — your own building blocks
- `files-errors` — read/write files, handle failures
- `algorithms` — recursion, classic problems
- `oop` — classes
- `capstone` — the hard ones (8 queens, sudoku, more)
Then the checkpoint.
> **[CLOSING — Mission Control]**
>
> You already know how to learn here. The hard part — the courage to
> open a terminal — is behind you. The rest is practice and patience.
>
> Open Python on the next block.
>
> [END TRANSMISSION]

View File

@@ -0,0 +1,35 @@
---
type: challenge
title: "The Greeting"
xp: 25
duration: 15
difficulty: 1
---
# The Greeting
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, your first Python. Implement a function called `greet` that
> returns the exact string:
>
> ```
> Hello, Python
> ```
>
> Two pieces of syntax:
>
> - `def name():` defines a function
> - `return value` sends a value back to whoever called it
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Replace `pass` with a `return` statement
that returns `"Hello, Python"`.
## Objectives
- A function `greet` exists
- `greet()` returns `"Hello, Python"`

View File

@@ -0,0 +1,3 @@
def greet():
"""Return the string 'Hello, Python'."""
pass

View File

@@ -0,0 +1,5 @@
from solution import greet
def test_greet_returns_hello_python():
assert greet() == "Hello, Python"

View File

@@ -0,0 +1,43 @@
---
type: challenge
title: "The REPL"
xp: 25
duration: 25
difficulty: 1
---
# The REPL
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, the *REPL* — Read, Evaluate, Print, Loop — is Python's
> interactive shell. Open it on your machine with `python3` and
> every line you type runs immediately:
>
> ```
> >>> 5 + 7
> 12
> >>> "Hello".upper()
> 'HELLO'
> ```
>
> Use the REPL to figure out the answers. Then write them into a
> function `compute` that returns a tuple of all five, in order:
>
> 1. `5 + 7`
> 2. `100 - 25`
> 3. `6 * 8`
> 4. `"Hello" + " " + "World"`
> 5. `2 ** 10`
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Replace `pass` with a `return` of a
5-element tuple containing the values above.
## Objectives
- `compute()` returns a 5-element tuple
- Each element matches the corresponding expression

View File

@@ -0,0 +1,10 @@
def compute():
"""Return a tuple of 5 computed values, in this order:
1. 5 + 7
2. 100 - 25
3. 6 * 8
4. "Hello" + " " + "World"
5. 2 ** 10
"""
pass

View File

@@ -0,0 +1,25 @@
from solution import compute
def test_returns_five_values():
assert len(compute()) == 5
def test_first_is_addition():
assert compute()[0] == 12
def test_second_is_subtraction():
assert compute()[1] == 75
def test_third_is_multiplication():
assert compute()[2] == 48
def test_fourth_is_string_concat():
assert compute()[3] == "Hello World"
def test_fifth_is_power():
assert compute()[4] == 1024

View File

@@ -0,0 +1,31 @@
---
type: challenge
title: "The Script"
xp: 25
duration: 20
difficulty: 1
---
# The Script
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, real Python files hold *multiple* functions. Each one does
> a single, named job. The grader can call any of them by name.
>
> Implement two functions in the same file:
>
> - `hello()` returns `"From the script"`
> - `goodbye()` returns `"Out"`
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Replace each `pass` with the right
`return` statement.
## Objectives
- `hello()` returns `"From the script"`
- `goodbye()` returns `"Out"`

View File

@@ -0,0 +1,8 @@
def hello():
"""Return the string 'From the script'."""
pass
def goodbye():
"""Return the string 'Out'."""
pass

View File

@@ -0,0 +1,9 @@
from solution import hello, goodbye
def test_hello_returns_correct_string():
assert hello() == "From the script"
def test_goodbye_returns_correct_string():
assert goodbye() == "Out"

View File

@@ -0,0 +1,40 @@
---
type: challenge
title: "The Vault"
xp: 50
duration: 25
difficulty: 1
---
# The Vault
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, every program needs to remember things. Variables hold
> values; types tell Python what kind of value:
>
> - `int` — whole numbers, like `3`
> - `float` — decimals, like `4500.5`
> - `str` — text in quotes, like `"Apollo"`
> - `bool` — `True` or `False`
>
> A **dictionary** maps keys to values. Implement a function `vault`
> that returns a dictionary with exactly these four keys and values:
>
> ```
> "mission": "Apollo"
> "crew_size": 3
> "fuel_kg": 4500.5
> "ready": True
> ```
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Replace `pass` with `return { ... }`.
## Objectives
- `vault()` returns a dict
- Each key holds the exact value above (and the right type)

View File

@@ -0,0 +1,9 @@
def vault():
"""Return a dictionary with these four keys and exact values:
"mission": "Apollo" (str)
"crew_size": 3 (int)
"fuel_kg": 4500.5 (float)
"ready": True (bool)
"""
pass

View File

@@ -0,0 +1,29 @@
from solution import vault
def test_returns_dict():
assert isinstance(vault(), dict)
def test_mission_is_correct_string():
v = vault()
assert v["mission"] == "Apollo"
assert isinstance(v["mission"], str)
def test_crew_size_is_correct_int():
v = vault()
assert v["crew_size"] == 3
assert isinstance(v["crew_size"], int)
assert not isinstance(v["crew_size"], bool)
def test_fuel_kg_is_correct_float():
v = vault()
assert v["fuel_kg"] == 4500.5
assert isinstance(v["fuel_kg"], float)
def test_ready_is_true_bool():
v = vault()
assert v["ready"] is True

View File

@@ -0,0 +1,37 @@
---
type: challenge
title: "The Translator"
xp: 50
duration: 25
difficulty: 2
---
# The Translator
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, when input comes in as text (a `str`), you can't do math
> with it directly. You have to *translate* it to a number.
>
> Three converters:
>
> - `int("42")` → `42`
> - `float("3.14")` → `3.14`
> - `str(42)` → `"42"`
>
> Implement `years_until(age)` that returns `100 - age` as an `int`.
> The argument may arrive as either `int` or `str` — handle both
> by calling `int()` on it first.
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Convert `age` with `int()`, then return
`100 - age`.
## Objectives
- `years_until(25)` returns `75`
- `years_until("25")` also returns `75` (handles string input)
- Works for any integer age 0100

View File

@@ -0,0 +1,11 @@
def years_until(age):
"""Return how many years remain until age 100.
The argument may arrive as an int OR as a string (like "25"),
so convert it to int first.
years_until(25) -> 75
years_until("7") -> 93
years_until(60) -> 40
"""
pass

View File

@@ -0,0 +1,21 @@
from solution import years_until
def test_int_input_25():
assert years_until(25) == 75
def test_int_input_7():
assert years_until(7) == 93
def test_int_input_60():
assert years_until(60) == 40
def test_string_input_converted():
assert years_until("25") == 75
def test_zero():
assert years_until(0) == 100

View File

@@ -0,0 +1,40 @@
---
type: challenge
title: "The Prompt"
xp: 50
duration: 25
difficulty: 2
---
# The Prompt
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, every program takes input. In this stage, your functions
> receive input as *parameters* — values passed in by whoever called
> you.
>
> *f-strings* (formatted strings) embed values directly into text:
>
> ```python
> name = "Vega"
> f"Hello, {name}!" # → "Hello, Vega!"
> ```
>
> Implement `welcome(name, planet)` that returns:
>
> ```
> Welcome, <name> from <planet>.
> ```
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Use an f-string to return the welcome
message.
## Objectives
- `welcome("Vega", "Mars")``"Welcome, Vega from Mars."`
- Works for any name and planet

View File

@@ -0,0 +1,8 @@
def welcome(name, planet):
"""Return a welcome string of the form:
"Welcome, <name> from <planet>."
welcome("Vega", "Mars") -> "Welcome, Vega from Mars."
"""
pass

View File

@@ -0,0 +1,13 @@
from solution import welcome
def test_vega_mars():
assert welcome("Vega", "Mars") == "Welcome, Vega from Mars."
def test_halo_earth():
assert welcome("Halo", "Earth") == "Welcome, Halo from Earth."
def test_orbit_seven_europa():
assert welcome("Orbit-7", "Europa") == "Welcome, Orbit-7 from Europa."

View File

@@ -0,0 +1,44 @@
---
type: challenge
title: "The Formatter"
xp: 50
duration: 25
difficulty: 2
---
# The Formatter
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, raw numbers like `9.99` print fine, but `29.970000000000002`
> doesn't. Real programs *format* numbers. f-strings can do it inline:
>
> ```python
> price = 9.99
> f"${price:.2f}" # → "$9.99"
> ```
>
> The `:.2f` is a *format spec*: two decimal places, fixed-point.
>
> Implement `format_receipt(price, qty)` that returns a 3-line string:
>
> ```
> Item price: $<price>
> Quantity: <qty>
> Total: $<price * qty>
> ```
>
> Both prices use 2-decimal formatting. Use `\n` to join lines.
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Compute `total`, then return a single
f-string with `\n` separators.
## Objectives
- `format_receipt(9.99, 3)` returns the exact 3-line string
- Prices format to 2 decimal places
- Works for any positive `price` and `qty`

View File

@@ -0,0 +1,13 @@
def format_receipt(price, qty):
"""Return a 3-line formatted receipt as a single string.
Format (note the dollar sign and 2-decimal places on prices):
Item price: $<price>
Quantity: <qty>
Total: $<price * qty>
format_receipt(9.99, 3) returns:
"Item price: $9.99\nQuantity: 3\nTotal: $29.97"
"""
pass

View File

@@ -0,0 +1,13 @@
from solution import format_receipt
def test_basic():
assert format_receipt(9.99, 3) == "Item price: $9.99\nQuantity: 3\nTotal: $29.97"
def test_round_numbers():
assert format_receipt(100, 1) == "Item price: $100.00\nQuantity: 1\nTotal: $100.00"
def test_decimal_quantity_one():
assert format_receipt(1.5, 10) == "Item price: $1.50\nQuantity: 10\nTotal: $15.00"

View File

@@ -0,0 +1,44 @@
---
type: challenge
title: "The Temperature"
xp: 75
duration: 30
difficulty: 2
---
# The Temperature
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, half the world reads temperature in Fahrenheit, the other
> half in Celsius. Build a converter.
>
> Formula: `C = (F - 32) * 5 / 9`
>
> Implement `f_to_c(f)` that returns a string of the form:
>
> ```
> <F>°F = <C>°C
> ```
>
> Where `<C>` is rounded to one decimal place using `:.1f`. Convert
> the input with `float()` first so the output is consistent for
> int and float inputs.
>
> Examples:
>
> - `f_to_c(32)` → `"32.0°F = 0.0°C"`
> - `f_to_c(212)` → `"212.0°F = 100.0°C"`
> - `f_to_c(98.6)` → `"98.6°F = 37.0°C"`
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, convert `f` to float, compute Celsius,
return the formatted string.
## Objectives
- All three examples produce the exact expected string
- Works for any numeric F input

View File

@@ -0,0 +1,13 @@
def f_to_c(f):
"""Convert Fahrenheit to Celsius and return a formatted string.
Formula: C = (F - 32) * 5 / 9
The Celsius value should be formatted with 1 decimal place.
Convert the Fahrenheit input with float() so output is consistent.
f_to_c(32) -> "32.0°F = 0.0°C"
f_to_c(212) -> "212.0°F = 100.0°C"
f_to_c(98.6) -> "98.6°F = 37.0°C"
"""
pass

View File

@@ -0,0 +1,17 @@
from solution import f_to_c
def test_freezing():
assert f_to_c(32) == "32.0°F = 0.0°C"
def test_boiling():
assert f_to_c(212) == "212.0°F = 100.0°C"
def test_body_temp():
assert f_to_c(98.6) == "98.6°F = 37.0°C"
def test_zero_fahrenheit():
assert f_to_c(0) == "0.0°F = -17.8°C"

View File

@@ -0,0 +1,42 @@
---
type: challenge
title: "The Calculator"
xp: 100
duration: 35
difficulty: 2
---
# The Calculator
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, capstone. Combine everything you've learned in this module
> into one program.
>
> Implement `calculate(a, b)` that returns a dictionary with all four
> basic operations:
>
> ```python
> {
> "sum": a + b,
> "difference": a - b,
> "product": a * b,
> "quotient": a / b,
> }
> ```
>
> Convert both inputs with `float()` so results are consistent — and
> so string inputs work too.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, convert both inputs to float, then return
the dictionary with the four keys above.
## Objectives
- `calculate(10, 5)` returns the four operations as a dict
- Works with int, float, and string inputs
- All values are floats

View File

@@ -0,0 +1,15 @@
def calculate(a, b):
"""Return a dict with the four basic operations on (a, b).
Convert both inputs with float() so output is consistent.
Returned dict has these exact keys:
"sum" — a + b
"difference" — a - b
"product" — a * b
"quotient" — a / b
calculate(10, 5) -> {"sum": 15.0, "difference": 5.0,
"product": 50.0, "quotient": 2.0}
"""
pass

View File

@@ -0,0 +1,34 @@
from solution import calculate
def test_returns_dict_with_four_keys():
r = calculate(10, 5)
assert set(r.keys()) == {"sum", "difference", "product", "quotient"}
def test_basic_ten_five():
r = calculate(10, 5)
assert r["sum"] == 15.0
assert r["difference"] == 5.0
assert r["product"] == 50.0
assert r["quotient"] == 2.0
def test_seven_two():
r = calculate(7, 2)
assert r["sum"] == 9.0
assert r["difference"] == 5.0
assert r["product"] == 14.0
assert r["quotient"] == 3.5
def test_decimals():
r = calculate(3.5, 1.5)
assert r["sum"] == 5.0
assert r["difference"] == 2.0
assert r["product"] == 5.25
def test_string_inputs_converted():
r = calculate("10", "5")
assert r["sum"] == 15.0

View File

@@ -0,0 +1,27 @@
---
type: module
title: "Strings"
description: "Text is half of every program — index it, slice it, transform it, count it, format it."
---
# Strings
Half of what programs do is move text around. Reading user input,
formatting reports, parsing log lines, validating passwords — all
strings. This module hands you the toolkit.
Seven blocks. About three and a half hours. No conditionals or loops
yet — those come next module — so every challenge here works through
indexing, slicing, methods, and built-in functions.
## What You'll Do
- Index and slice strings
- Apply the most-used string methods (`upper`, `strip`, `split`,
`replace`, ...)
- Validate, count, mirror, and format text
- Build a small mission report from raw fields
## Pace
About 3.5 hours. Each block is a small real program — no drills.

View File

@@ -0,0 +1,42 @@
---
type: challenge
title: "The Message"
xp: 50
duration: 25
difficulty: 2
---
# The Message
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, a string is a *sequence of characters*. You can pick any
> one out by its position (its *index*).
>
> Indexes start at 0:
>
> ```
> M I S S I O N
> 0 1 2 3 4 5 6
> ```
>
> Negative indexes count back from the end. `s[-1]` is the last
> character.
>
> Strings are *immutable* — you can read any character, but you can't
> change one in place. To "modify" a string, you build a new one.
>
> Implement `info(s)` that returns a dict with four keys: `first`,
> `sixth`, `last`, `length`.
>
> [END TRANSMISSION]
## Your Task
Open `starter/starter.py`. Use `s[0]`, `s[5]`, `s[-1]`, and `len(s)`
to fill in the dict.
## Objectives
- `info("MISSION-CONTROL-7")` returns `{"first": "M", "sixth": "O", "last": "7", "length": 17}`
- Works for any non-empty string of length ≥ 6

View File

@@ -0,0 +1,13 @@
def info(s):
"""Return a dict describing the string s.
Keys:
"first" — the character at index 0
"sixth" — the character at index 5
"last" — the character at index -1
"length" — total number of characters
info("MISSION-CONTROL-7") -> {"first": "M", "sixth": "O",
"last": "7", "length": 17}
"""
pass

View File

@@ -0,0 +1,16 @@
from solution import info
def test_mission_control():
r = info("MISSION-CONTROL-7")
assert r == {"first": "M", "sixth": "O", "last": "7", "length": 17}
def test_andromeda():
r = info("andromeda")
assert r == {"first": "a", "sixth": "m", "last": "a", "length": 9}
def test_alphabet():
r = info("ABCDEFGH")
assert r == {"first": "A", "sixth": "F", "last": "H", "length": 8}

View File

@@ -0,0 +1,45 @@
---
type: challenge
title: "The Slicer"
xp: 50
duration: 25
difficulty: 2
---
# The Slicer
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, *slicing* grabs a chunk of a string instead of one character.
> The syntax is `s[start:end]` — `start` is included, `end` is not.
>
> ```python
> s = "ANDROMEDA"
> s[0:3] # "AND"
> s[3:7] # "ROME"
> s[-2:] # "DA"
> ```
>
> Telemetry packets always arrive in this exact format (length 30):
>
> ```
> TIMESTAMP=YYYY-MM-DDTHH:MM:SSZ
> ```
>
> Implement `extract(header)` that returns a tuple `(date, time)`:
>
> - `date` = `YYYY-MM-DD` (10 chars)
> - `time` = `HH:MM:SS` (8 chars)
>
> Figure out which slice positions hold each piece.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, return `(date, time)` extracted with slicing.
## Objectives
- `extract("TIMESTAMP=2026-05-04T14:32:01Z")``("2026-05-04", "14:32:01")`
- Works for any header in the same fixed format

View File

@@ -0,0 +1,16 @@
def extract(header):
"""Extract date and time from a fixed-format packet header.
Header format (always exactly this shape):
TIMESTAMP=YYYY-MM-DDTHH:MM:SSZ
Length 30. Example:
TIMESTAMP=2026-05-04T14:32:01Z
Return a tuple (date, time) where:
date = "YYYY-MM-DD" (10 chars)
time = "HH:MM:SS" (8 chars)
extract("TIMESTAMP=2026-05-04T14:32:01Z")
-> ("2026-05-04", "14:32:01")
"""
pass

View File

@@ -0,0 +1,13 @@
from solution import extract
def test_basic():
assert extract("TIMESTAMP=2026-05-04T14:32:01Z") == ("2026-05-04", "14:32:01")
def test_apollo():
assert extract("TIMESTAMP=1969-07-20T13:32:00Z") == ("1969-07-20", "13:32:00")
def test_end_of_century():
assert extract("TIMESTAMP=2099-12-31T23:59:59Z") == ("2099-12-31", "23:59:59")

View File

@@ -0,0 +1,43 @@
---
type: challenge
title: "The Methods"
xp: 50
duration: 30
difficulty: 2
---
# The Methods
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, every string carries a toolkit of methods. The most-used:
>
> - `s.upper()` — uppercase copy
> - `s.lower()` — lowercase copy
> - `s.strip()` — drops leading/trailing whitespace
> - `s.replace(a, b)` — every `a` becomes `b`
>
> Methods can be *chained* — each returns a new string you can call
> the next on:
>
> ```python
> " Hello World ".strip().lower().replace(" ", "_")
> # → "hello_world"
> ```
>
> Implement `transform(s)` that returns a dict with three keys:
>
> - `upper` — input uppercased
> - `lower` — input lowercased
> - `clean` — input stripped, lowercased, spaces replaced by `_`
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, build the dict using the four methods.
## Objectives
- `transform(" Hello World ")["clean"]` returns `"hello_world"`
- All three keys present and correct for any string

View File

@@ -0,0 +1,15 @@
def transform(s):
"""Apply three string transformations and return them in a dict.
Keys:
"upper" — s.upper()
"lower" — s.lower()
"clean" — s stripped of leading/trailing whitespace,
lowercased, with spaces replaced by underscores
transform(" Hello World ") returns:
{"upper": " HELLO WORLD ",
"lower": " hello world ",
"clean": "hello_world"}
"""
pass

View File

@@ -0,0 +1,22 @@
from solution import transform
def test_padded_hello_world():
r = transform(" Hello World ")
assert r["upper"] == " HELLO WORLD "
assert r["lower"] == " hello world "
assert r["clean"] == "hello_world"
def test_mission_ready():
r = transform("MISSION READY")
assert r["upper"] == "MISSION READY"
assert r["lower"] == "mission ready"
assert r["clean"] == "mission_ready"
def test_andromeda_sector():
r = transform("andromeda sector")
assert r["upper"] == "ANDROMEDA SECTOR"
assert r["lower"] == "andromeda sector"
assert r["clean"] == "andromeda_sector"

View File

@@ -0,0 +1,44 @@
---
type: challenge
title: "The Validator"
xp: 75
duration: 30
difficulty: 2
---
# The Validator
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, every system that takes user input has to *validate* it.
>
> Comparison operators (`>=`, `<`, `==`) return `True` or `False`
> directly. String methods like `.isupper()` and `.isdigit()` also
> return booleans:
>
> ```python
> len("hello") >= 8 # False
> "Hello"[0].isupper() # True
> "abc1"[-1].isdigit() # True
> ```
>
> Implement `validate(pw)` that returns a dict with four checks on
> a non-empty password:
>
> - `length` — char count (int)
> - `long_enough` — `length >= 8` (bool)
> - `starts_capital` — first char uppercase (bool)
> - `ends_digit` — last char a digit (bool)
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, return the dict using `len()`, `>=`,
`.isupper()`, `.isdigit()`.
## Objectives
- All four keys present with correct types
- `validate("Andromeda1")` matches the expected dict exactly
- Works for any non-empty password

View File

@@ -0,0 +1,17 @@
def validate(pw):
"""Check four properties of a non-empty password and return a dict.
Keys:
"length" — number of characters (int)
"long_enough" — True if length >= 8 (bool)
"starts_capital" — True if first char is uppercase (bool)
"ends_digit" — True if last char is a digit (bool)
Helpers:
pw[0].isupper() — True if first char is uppercase
pw[-1].isdigit() — True if last char is a digit
validate("Andromeda1") -> {"length": 10, "long_enough": True,
"starts_capital": True, "ends_digit": True}
"""
pass

View File

@@ -0,0 +1,41 @@
from solution import validate
def test_strong_password():
r = validate("Andromeda1")
assert r == {
"length": 10,
"long_enough": True,
"starts_capital": True,
"ends_digit": True,
}
def test_short_lowercase_no_digit():
r = validate("abc")
assert r == {
"length": 3,
"long_enough": False,
"starts_capital": False,
"ends_digit": False,
}
def test_capital_no_digit():
r = validate("Hello")
assert r == {
"length": 5,
"long_enough": False,
"starts_capital": True,
"ends_digit": False,
}
def test_long_lowercase_with_digit():
r = validate("spaceX2026")
assert r == {
"length": 10,
"long_enough": True,
"starts_capital": False,
"ends_digit": True,
}

View File

@@ -0,0 +1,43 @@
---
type: challenge
title: "The Mirror"
xp: 75
duration: 30
difficulty: 3
---
# The Mirror
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, a *palindrome* reads the same forwards and backwards.
> `racecar`. `level`. `madam`.
>
> A reverse-slice flips a string in one move:
>
> ```python
> "Hello"[::-1] # "olleH"
> ```
>
> A string is a palindrome when it equals its reverse. The `==`
> operator returns `True` or `False` directly.
>
> Implement `mirror(s)` that returns a dict with three keys:
>
> - `original` — the input
> - `reversed` — the input reversed
> - `palindrome` — `True` if equal to its reverse
>
> Match case exactly — `racecar` and `RaceCar` are different inputs.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, build the dict using `[::-1]` and `==`.
## Objectives
- `mirror("racecar")["palindrome"]` is `True`
- `mirror("hello")["palindrome"]` is `False`
- All three keys correct for any input string

View File

@@ -0,0 +1,19 @@
def mirror(s):
"""Detect if s is a palindrome and return a dict.
Keys:
"original" — s unchanged
"reversed" — s reversed
"palindrome" — True if s equals its reverse, False otherwise
Reverse a string with [::-1]:
"Hello"[::-1] -> "olleH"
mirror("racecar") -> {"original": "racecar",
"reversed": "racecar",
"palindrome": True}
mirror("hello") -> {"original": "hello",
"reversed": "olleh",
"palindrome": False}
"""
pass

View File

@@ -0,0 +1,27 @@
from solution import mirror
def test_palindrome():
r = mirror("racecar")
assert r == {"original": "racecar", "reversed": "racecar", "palindrome": True}
def test_not_palindrome():
r = mirror("hello")
assert r == {"original": "hello", "reversed": "olleh", "palindrome": False}
def test_level():
r = mirror("level")
assert r["palindrome"] is True
def test_andromeda():
r = mirror("andromeda")
assert r["palindrome"] is False
assert r["reversed"] == "ademordna"
def test_noon():
r = mirror("noon")
assert r == {"original": "noon", "reversed": "noon", "palindrome": True}

View File

@@ -0,0 +1,35 @@
---
type: challenge
title: "The Counter"
xp: 75
duration: 30
difficulty: 3
---
# The Counter
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, time to count things in text. Three counts:
>
> - **Total length** — `len(s)`
> - **Word count** — `len(s.split())` (split breaks on whitespace)
> - **`a` count** — `s.lower().count("a")` returns how many `a`s
>
> Implement `count(s)` that returns a dict with three keys:
>
> - `length` — total chars including spaces
> - `words` — word count
> - `a_count` — number of lowercase `a`s after lowercasing
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, build the dict using `len()`, `.split()`,
`.lower()`, `.count()`.
## Objectives
- `count("Hello andromeda")``{"length": 15, "words": 2, "a_count": 2}`
- All three keys present and correct for any sentence

View File

@@ -0,0 +1,16 @@
def count(s):
"""Count three things in a sentence and return a dict.
Keys:
"length" — total characters in s (including spaces)
"words" — number of whitespace-separated words
"a_count" — count of lowercase 'a's after lowercasing s
Helpers:
len(s) — char count
len(s.split()) — word count
s.lower().count("a") — number of 'a's, case-insensitive
count("Hello andromeda") -> {"length": 15, "words": 2, "a_count": 2}
"""
pass

View File

@@ -0,0 +1,21 @@
from solution import count
def test_hello_andromeda():
assert count("Hello andromeda") == {"length": 15, "words": 2, "a_count": 2}
def test_all_systems_nominal():
assert count("All systems nominal") == {"length": 19, "words": 3, "a_count": 2}
def test_caps_with_a():
assert count("MISSION READY ALPHA") == {"length": 19, "words": 3, "a_count": 3}
def test_quick_brown_fox():
assert count("the quick brown fox jumps over") == {
"length": 30,
"words": 6,
"a_count": 0,
}

View File

@@ -0,0 +1,47 @@
---
type: challenge
title: "The Report"
xp: 100
duration: 35
difficulty: 3
---
# The Report
> **[INCOMING — Mission Control, Earth]**
>
> Cadet, capstone for the strings module. Combine f-strings,
> repetition, methods, and `\n` joining into one formatted output.
>
> Repetition: `"=" * 30` produces a string of 30 equals signs.
>
> Implement `format_report(name, status, location, time)` that
> returns this exact 8-line report as a single string (lines joined
> with `\n`):
>
> ```
> ==============================
> MISSION REPORT
> ==============================
> Name: <NAME IN UPPERCASE>
> Status: <status as entered>
> Location: <location as entered>
> Time: <time as entered>
> ==============================
> ```
>
> Only the name is uppercased; other fields print as entered.
>
> [END TRANSMISSION]
## Your Task
In `starter/starter.py`, build the report string. Use `"=" * 30`
for borders, `name.upper()` for the name, and join lines with `\n`.
## Objectives
- `format_report("apollo", "ok", "moon", "13:32")` returns the
exact 8-line string
- Only the name is uppercased
- Borders are 30 `=` characters

View File

@@ -0,0 +1,19 @@
def format_report(name, status, location, time):
"""Return a formatted mission report as a single string.
The report is 8 lines, joined by '\n'. The borders are 30 equals
signs ("=" * 30). Only the name is uppercased; other fields are
inserted as-is.
format_report("apollo", "ok", "moon", "13:32") returns:
==============================
MISSION REPORT
==============================
Name: APOLLO
Status: ok
Location: moon
Time: 13:32
==============================
"""
pass

View File

@@ -0,0 +1,36 @@
from solution import format_report
def test_apollo():
expected = (
"==============================\n"
"MISSION REPORT\n"
"==============================\n"
"Name: APOLLO\n"
"Status: ok\n"
"Location: moon\n"
"Time: 13:32\n"
"=============================="
)
assert format_report("apollo", "ok", "moon", "13:32") == expected
def test_voyager():
result = format_report("voyager", "active", "interstellar", "1977-09-05")
assert "Name: VOYAGER" in result
assert "Status: active" in result
assert "Location: interstellar" in result
assert "Time: 1977-09-05" in result
assert result.startswith("=" * 30 + "\n")
assert result.endswith("\n" + "=" * 30)
def test_perseverance():
result = format_report("perseverance", "roving", "jezero crater", "2026-05-04")
assert "Name: PERSEVERANCE" in result
assert "Location: jezero crater" in result
def test_eight_lines():
result = format_report("apollo", "ok", "moon", "13:32")
assert result.count("\n") == 7