seed: curriculum content
This commit is contained in:
31
1.solar-system/0.index.md
Normal file
31
1.solar-system/0.index.md
Normal 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 1–2 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 |
|
||||||
|
|------|--------|-------|
|
||||||
|
| 1–2 | Welcome | Shell + Git basics, paired battle |
|
||||||
|
| 3–18 | Python Fundamentals | Variables, control flow, functions, data |
|
||||||
|
| 19–20 | 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
|
||||||
30
1.solar-system/1.welcome/0.index.md
Normal file
30
1.solar-system/1.welcome/0.index.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
type: module
|
||||||
|
title: "Welcome"
|
||||||
|
description: "Your first 1–2 days. Crash course in the terminal and Git, then a paired battle."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome
|
||||||
|
|
||||||
|
You've landed. The next 1–2 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 10–12 hours of focused work. Some cadets finish in a day, some
|
||||||
|
take two. Either is fine. Do not skip blocks — the checkpoint catches
|
||||||
|
gaps.
|
||||||
84
1.solar-system/1.welcome/01.welcome/index.md
Normal file
84
1.solar-system/1.welcome/01.welcome/index.md
Normal 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]
|
||||||
35
1.solar-system/1.welcome/02.hello-world/index.md
Normal file
35
1.solar-system/1.welcome/02.hello-world/index.md
Normal 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
|
||||||
@@ -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.
|
||||||
16
1.solar-system/1.welcome/02.hello-world/testing/test.sh
Normal file
16
1.solar-system/1.welcome/02.hello-world/testing/test.sh
Normal 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
|
||||||
46
1.solar-system/1.welcome/03.first-repo/index.md
Normal file
46
1.solar-system/1.welcome/03.first-repo/index.md
Normal 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`.
|
||||||
38
1.solar-system/1.welcome/03.first-repo/review/general.md
Normal file
38
1.solar-system/1.welcome/03.first-repo/review/general.md
Normal 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 2–3 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?
|
||||||
27
1.solar-system/1.welcome/03.first-repo/review/review.json
Normal file
27
1.solar-system/1.welcome/03.first-repo/review/review.json
Normal 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'"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
40
1.solar-system/1.welcome/04.the-maker/index.md
Normal file
40
1.solar-system/1.welcome/04.the-maker/index.md
Normal 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
|
||||||
13
1.solar-system/1.welcome/04.the-maker/starter/starter.sh
Normal file
13
1.solar-system/1.welcome/04.the-maker/starter/starter.sh
Normal 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.
|
||||||
17
1.solar-system/1.welcome/04.the-maker/testing/test.sh
Normal file
17
1.solar-system/1.welcome/04.the-maker/testing/test.sh
Normal 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"
|
||||||
39
1.solar-system/1.welcome/05.the-mover/index.md
Normal file
39
1.solar-system/1.welcome/05.the-mover/index.md
Normal 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
|
||||||
20
1.solar-system/1.welcome/05.the-mover/starter/starter.sh
Normal file
20
1.solar-system/1.welcome/05.the-mover/starter/starter.sh
Normal 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.
|
||||||
25
1.solar-system/1.welcome/05.the-mover/testing/test.sh
Normal file
25
1.solar-system/1.welcome/05.the-mover/testing/test.sh
Normal 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"
|
||||||
39
1.solar-system/1.welcome/06.the-reader/index.md
Normal file
39
1.solar-system/1.welcome/06.the-reader/index.md
Normal 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
|
||||||
13
1.solar-system/1.welcome/06.the-reader/starter/starter.sh
Normal file
13
1.solar-system/1.welcome/06.the-reader/starter/starter.sh
Normal 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.
|
||||||
39
1.solar-system/1.welcome/06.the-reader/testing/test.sh
Normal file
39
1.solar-system/1.welcome/06.the-reader/testing/test.sh
Normal 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"
|
||||||
48
1.solar-system/1.welcome/07.the-editor/index.md
Normal file
48
1.solar-system/1.welcome/07.the-editor/index.md
Normal 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)
|
||||||
19
1.solar-system/1.welcome/07.the-editor/starter/starter.sh
Normal file
19
1.solar-system/1.welcome/07.the-editor/starter/starter.sh
Normal 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.
|
||||||
19
1.solar-system/1.welcome/07.the-editor/testing/test.sh
Normal file
19
1.solar-system/1.welcome/07.the-editor/testing/test.sh
Normal 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"
|
||||||
36
1.solar-system/1.welcome/08.the-searcher/index.md
Normal file
36
1.solar-system/1.welcome/08.the-searcher/index.md
Normal 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`
|
||||||
13
1.solar-system/1.welcome/08.the-searcher/starter/starter.sh
Normal file
13
1.solar-system/1.welcome/08.the-searcher/starter/starter.sh
Normal 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.
|
||||||
22
1.solar-system/1.welcome/08.the-searcher/testing/test.sh
Normal file
22
1.solar-system/1.welcome/08.the-searcher/testing/test.sh
Normal 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"
|
||||||
45
1.solar-system/1.welcome/09.the-scripter/index.md
Normal file
45
1.solar-system/1.welcome/09.the-scripter/index.md
Normal 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>`
|
||||||
17
1.solar-system/1.welcome/09.the-scripter/starter/starter.sh
Normal file
17
1.solar-system/1.welcome/09.the-scripter/starter/starter.sh
Normal 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.
|
||||||
21
1.solar-system/1.welcome/09.the-scripter/testing/test.sh
Normal file
21
1.solar-system/1.welcome/09.the-scripter/testing/test.sh
Normal 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'"
|
||||||
89
1.solar-system/1.welcome/10.how-to-learn/index.md
Normal file
89
1.solar-system/1.welcome/10.how-to-learn/index.md
Normal 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]
|
||||||
38
1.solar-system/1.welcome/11.the-identity/index.md
Normal file
38
1.solar-system/1.welcome/11.the-identity/index.md
Normal 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
|
||||||
12
1.solar-system/1.welcome/11.the-identity/starter/starter.sh
Normal file
12
1.solar-system/1.welcome/11.the-identity/starter/starter.sh
Normal 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.
|
||||||
13
1.solar-system/1.welcome/11.the-identity/testing/test.sh
Normal file
13
1.solar-system/1.welcome/11.the-identity/testing/test.sh
Normal 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"
|
||||||
43
1.solar-system/1.welcome/12.the-initiator/index.md
Normal file
43
1.solar-system/1.welcome/12.the-initiator/index.md
Normal 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`
|
||||||
14
1.solar-system/1.welcome/12.the-initiator/starter/starter.sh
Normal file
14
1.solar-system/1.welcome/12.the-initiator/starter/starter.sh
Normal 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.
|
||||||
13
1.solar-system/1.welcome/12.the-initiator/testing/test.sh
Normal file
13
1.solar-system/1.welcome/12.the-initiator/testing/test.sh
Normal 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"
|
||||||
41
1.solar-system/1.welcome/13.the-saver/index.md
Normal file
41
1.solar-system/1.welcome/13.the-saver/index.md
Normal 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
|
||||||
14
1.solar-system/1.welcome/13.the-saver/starter/starter.sh
Normal file
14
1.solar-system/1.welcome/13.the-saver/starter/starter.sh
Normal 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.
|
||||||
20
1.solar-system/1.welcome/13.the-saver/testing/test.sh
Normal file
20
1.solar-system/1.welcome/13.the-saver/testing/test.sh
Normal 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"
|
||||||
40
1.solar-system/1.welcome/14.the-historian/index.md
Normal file
40
1.solar-system/1.welcome/14.the-historian/index.md
Normal 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
|
||||||
15
1.solar-system/1.welcome/14.the-historian/starter/starter.sh
Normal file
15
1.solar-system/1.welcome/14.the-historian/starter/starter.sh
Normal 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.
|
||||||
24
1.solar-system/1.welcome/14.the-historian/testing/test.sh
Normal file
24
1.solar-system/1.welcome/14.the-historian/testing/test.sh
Normal 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"
|
||||||
45
1.solar-system/1.welcome/15.the-cloner/index.md
Normal file
45
1.solar-system/1.welcome/15.the-cloner/index.md
Normal 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
|
||||||
15
1.solar-system/1.welcome/15.the-cloner/starter/starter.sh
Normal file
15
1.solar-system/1.welcome/15.the-cloner/starter/starter.sh
Normal 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.
|
||||||
29
1.solar-system/1.welcome/15.the-cloner/testing/test.sh
Normal file
29
1.solar-system/1.welcome/15.the-cloner/testing/test.sh
Normal 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"
|
||||||
43
1.solar-system/1.welcome/16.the-pusher/index.md
Normal file
43
1.solar-system/1.welcome/16.the-pusher/index.md
Normal 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`
|
||||||
15
1.solar-system/1.welcome/16.the-pusher/starter/starter.sh
Normal file
15
1.solar-system/1.welcome/16.the-pusher/starter/starter.sh
Normal 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.
|
||||||
27
1.solar-system/1.welcome/16.the-pusher/testing/test.sh
Normal file
27
1.solar-system/1.welcome/16.the-pusher/testing/test.sh
Normal 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"
|
||||||
63
1.solar-system/1.welcome/17.team-build/index.md
Normal file
63
1.solar-system/1.welcome/17.team-build/index.md
Normal 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.
|
||||||
70
1.solar-system/1.welcome/17.team-build/review/general.md
Normal file
70
1.solar-system/1.welcome/17.team-build/review/general.md
Normal 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.
|
||||||
38
1.solar-system/1.welcome/17.team-build/review/review.json
Normal file
38
1.solar-system/1.welcome/17.team-build/review/review.json
Normal 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'"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
27
1.solar-system/2.first-light/0.index.md
Normal file
27
1.solar-system/2.first-light/0.index.md
Normal 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.
|
||||||
71
1.solar-system/2.first-light/01.first-light/index.md
Normal file
71
1.solar-system/2.first-light/01.first-light/index.md
Normal 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]
|
||||||
35
1.solar-system/2.first-light/02.the-greeting/index.md
Normal file
35
1.solar-system/2.first-light/02.the-greeting/index.md
Normal 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"`
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
def greet():
|
||||||
|
"""Return the string 'Hello, Python'."""
|
||||||
|
pass
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from solution import greet
|
||||||
|
|
||||||
|
|
||||||
|
def test_greet_returns_hello_python():
|
||||||
|
assert greet() == "Hello, Python"
|
||||||
43
1.solar-system/2.first-light/03.the-repl/index.md
Normal file
43
1.solar-system/2.first-light/03.the-repl/index.md
Normal 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
|
||||||
10
1.solar-system/2.first-light/03.the-repl/starter/starter.py
Normal file
10
1.solar-system/2.first-light/03.the-repl/starter/starter.py
Normal 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
|
||||||
@@ -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
|
||||||
31
1.solar-system/2.first-light/04.the-script/index.md
Normal file
31
1.solar-system/2.first-light/04.the-script/index.md
Normal 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"`
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
def hello():
|
||||||
|
"""Return the string 'From the script'."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def goodbye():
|
||||||
|
"""Return the string 'Out'."""
|
||||||
|
pass
|
||||||
@@ -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"
|
||||||
40
1.solar-system/2.first-light/05.the-vault/index.md
Normal file
40
1.solar-system/2.first-light/05.the-vault/index.md
Normal 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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
37
1.solar-system/2.first-light/06.the-translator/index.md
Normal file
37
1.solar-system/2.first-light/06.the-translator/index.md
Normal 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 0–100
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
40
1.solar-system/2.first-light/07.the-prompt/index.md
Normal file
40
1.solar-system/2.first-light/07.the-prompt/index.md
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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."
|
||||||
44
1.solar-system/2.first-light/08.the-formatter/index.md
Normal file
44
1.solar-system/2.first-light/08.the-formatter/index.md
Normal 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`
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
44
1.solar-system/2.first-light/09.the-temperature/index.md
Normal file
44
1.solar-system/2.first-light/09.the-temperature/index.md
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
42
1.solar-system/2.first-light/10.the-calculator/index.md
Normal file
42
1.solar-system/2.first-light/10.the-calculator/index.md
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
27
1.solar-system/3.strings/0.index.md
Normal file
27
1.solar-system/3.strings/0.index.md
Normal 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.
|
||||||
42
1.solar-system/3.strings/01.the-message/index.md
Normal file
42
1.solar-system/3.strings/01.the-message/index.md
Normal 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
|
||||||
13
1.solar-system/3.strings/01.the-message/starter/starter.py
Normal file
13
1.solar-system/3.strings/01.the-message/starter/starter.py
Normal 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
|
||||||
@@ -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}
|
||||||
45
1.solar-system/3.strings/02.the-slicer/index.md
Normal file
45
1.solar-system/3.strings/02.the-slicer/index.md
Normal 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
|
||||||
16
1.solar-system/3.strings/02.the-slicer/starter/starter.py
Normal file
16
1.solar-system/3.strings/02.the-slicer/starter/starter.py
Normal 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
|
||||||
@@ -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")
|
||||||
43
1.solar-system/3.strings/03.the-methods/index.md
Normal file
43
1.solar-system/3.strings/03.the-methods/index.md
Normal 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
|
||||||
15
1.solar-system/3.strings/03.the-methods/starter/starter.py
Normal file
15
1.solar-system/3.strings/03.the-methods/starter/starter.py
Normal 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
|
||||||
@@ -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"
|
||||||
44
1.solar-system/3.strings/04.the-validator/index.md
Normal file
44
1.solar-system/3.strings/04.the-validator/index.md
Normal 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
|
||||||
17
1.solar-system/3.strings/04.the-validator/starter/starter.py
Normal file
17
1.solar-system/3.strings/04.the-validator/starter/starter.py
Normal 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
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
43
1.solar-system/3.strings/05.the-mirror/index.md
Normal file
43
1.solar-system/3.strings/05.the-mirror/index.md
Normal 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
|
||||||
19
1.solar-system/3.strings/05.the-mirror/starter/starter.py
Normal file
19
1.solar-system/3.strings/05.the-mirror/starter/starter.py
Normal 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
|
||||||
@@ -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}
|
||||||
35
1.solar-system/3.strings/06.the-counter/index.md
Normal file
35
1.solar-system/3.strings/06.the-counter/index.md
Normal 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
|
||||||
16
1.solar-system/3.strings/06.the-counter/starter/starter.py
Normal file
16
1.solar-system/3.strings/06.the-counter/starter/starter.py
Normal 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
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
47
1.solar-system/3.strings/07.the-report/index.md
Normal file
47
1.solar-system/3.strings/07.the-report/index.md
Normal 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
|
||||||
19
1.solar-system/3.strings/07.the-report/starter/starter.py
Normal file
19
1.solar-system/3.strings/07.the-report/starter/starter.py
Normal 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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user