pub struct Level {Show 16 fields
pub id: LevelId,
pub title: String,
pub kind: LevelKind,
pub text: String,
pub hints: Vec<String>,
pub start: Start,
pub optional: bool,
pub success: String,
pub fixture: Fixture,
pub answer: Option<Answer>,
pub requirements: Vec<Requirement>,
pub allow_empty: bool,
pub cd: Option<CdPolicy>,
pub on_fd: Option<fn(&Fds) -> bool>,
pub resets: Vec<Marker>,
pub solution: Option<Solution>,
}Expand description
A level. Build one with level or intro and chain the methods below.
use shellgame::engine::prelude::*;
let l = level(2, "Čtení souboru")
.text("Přečtěte si `message.txt`.")
.start("")
.hints(["Použijte `cat`.", "Zadejte první slovo."])
.file("message.txt", "Secret is the key.\n")
.answer(exact("secret").ignore_case())
.solution("shellgame submit \"$(cut -d' ' -f1 message.txt)\"");
assert_eq!(l.id.number, 2);Fields§
§id: LevelIdThe stable id. The section part is assigned when the level joins a Section.
title: StringThe title, without the id.
kind: LevelKindRegular task or section intro.
text: StringThe assignment, as Markdown. Pages of an intro are separated by \n---\n.
hints: Vec<String>Plain-text hints, revealed one at a time. Printed verbatim, never as Markdown.
start: StartWhere the player is placed when the level starts.
optional: boolA bonus level never gates progress.
success: StringShown when the level is completed.
fixture: FixtureWhat the level’s files look like (applied after the section’s fixture).
answer: Option<Answer>How a submitted answer is judged, if the level takes one.
requirements: Vec<Requirement>Conditions that are always checked, before the answer.
allow_empty: boolWhether shellgame submit without an answer may complete the level.
cd: Option<CdPolicy>The level’s cd policy, if it teaches one movement command.
on_fd: Option<fn(&Fds) -> bool>Decides whether the hooked command’s output redirection counts as the drill.
resets: Vec<Marker>Extra markers cleared whenever the level is prepared.
solution: Option<Solution>A script that completes the level.
Implementations§
Source§impl Level
impl Level
Sourcepub fn start(self, path: impl Into<String>) -> Self
pub fn start(self, path: impl Into<String>) -> Self
Place the player in this section-relative directory when the level starts ("" or
"." is the section root).
Sourcepub fn start_at_workspace(self) -> Self
pub fn start_at_workspace(self) -> Self
Place the player at the workspace root, above the section.
Sourcepub fn success(self, message: impl Into<String>) -> Self
pub fn success(self, message: impl Into<String>) -> Self
The text shown on completion, instead of messages::CORRECT.
Sourcepub fn dirs<I, S>(self, paths: I) -> Self
pub fn dirs<I, S>(self, paths: I) -> Self
Declare directories; see Fixture::dirs.
Sourcepub fn dir(self, path: impl Into<String>) -> Self
pub fn dir(self, path: impl Into<String>) -> Self
Declare a directory; see Fixture::dir.
Sourcepub fn file(self, path: impl Into<String>, content: impl Into<Content>) -> Self
pub fn file(self, path: impl Into<String>, content: impl Into<Content>) -> Self
Declare a file; see Fixture::file.
Sourcepub fn file_mode(
self,
path: impl Into<String>,
content: impl Into<Content>,
mode: u32,
) -> Self
pub fn file_mode( self, path: impl Into<String>, content: impl Into<Content>, mode: u32, ) -> Self
Declare a file with an explicit mode; see Fixture::file_mode.
Sourcepub fn keep_file(
self,
path: impl Into<String>,
content: impl Into<Content>,
) -> Self
pub fn keep_file( self, path: impl Into<String>, content: impl Into<Content>, ) -> Self
Declare a file that is only written when missing; see Fixture::keep_file.
Sourcepub fn dir_mode(self, path: impl Into<String>, mode: u32) -> Self
pub fn dir_mode(self, path: impl Into<String>, mode: u32) -> Self
Declare a directory mode; see Fixture::dir_mode.
Sourcepub fn clean<I, S>(self, paths: I) -> Self
pub fn clean<I, S>(self, paths: I) -> Self
Declare paths to remove before anything is created; see Fixture::clean.
Sourcepub fn fixture(self, fixture: Fixture) -> Self
pub fn fixture(self, fixture: Fixture) -> Self
Merge a prebuilt or generated fixture into this level’s.
Sourcepub fn require(self, requirement: impl Into<Requirement>) -> Self
pub fn require(self, requirement: impl Into<Requirement>) -> Self
Add a condition that is always checked, in declaration order, before the answer.
Sourcepub fn allow_empty(self) -> Self
pub fn allow_empty(self) -> Self
Let shellgame submit without an answer complete the level (once the requirements
hold).
Sourcepub fn on_fd(self, predicate: fn(&Fds) -> bool) -> Self
pub fn on_fd(self, predicate: fn(&Fds) -> bool) -> Self
Record the level’s fd evidence when this predicate holds for the hooked
command’s descriptors; pair it with fd_evidence(..).
Sourcepub fn resets<I>(self, markers: I) -> Selfwhere
I: IntoIterator<Item = Marker>,
pub fn resets<I>(self, markers: I) -> Selfwhere
I: IntoIterator<Item = Marker>,
Markers to clear whenever the level is prepared, in addition to the ones the level
already references (evidence, its cd/fd markers, after rules).
Sourcepub fn solution(self, script: impl Into<String>) -> Self
pub fn solution(self, script: impl Into<String>) -> Self
A reference solution: a shell script valid in both bash and fish.
Sourcepub fn solution_bash(self, script: impl Into<String>) -> Self
pub fn solution_bash(self, script: impl Into<String>) -> Self
A reference solution that needs bash syntax; it is not replayed in fish.
Sourcepub fn solution_with(self, generate: fn(&Ctx<'_>) -> String) -> Self
pub fn solution_with(self, generate: fn(&Ctx<'_>) -> String) -> Self
A reference solution generated from the machine (such as a walk down $HOME); the
script must be valid in both bash and fish.
Sourcepub fn display_title(&self) -> String
pub fn display_title(&self) -> String
The title as shown to the player: "1.4: Návrat na základnu" for a regular level,
the bare title for an intro.
Sourcepub fn referenced_markers(&self) -> Vec<Marker>
pub fn referenced_markers(&self) -> Vec<Marker>
Every marker this level reads or writes, without duplicates: evidence
requirements, its own cd marker if it has a policy, its fd marker if it has an
on_fd rule, the markers of after rules and the explicit Level::resets.
Sourcepub fn prepare(
&self,
section: &Section,
workspace: &Path,
) -> Result<(), FixtureError>
pub fn prepare( &self, section: &Section, workspace: &Path, ) -> Result<(), FixtureError>
Put the level into its starting state: clear its markers and rebuild its files. Containment is verified first, so nothing is touched below a workspace or section directory that is a symlink.
- Every marker the level references is removed, so evidence from an earlier attempt (or an earlier visit) cannot complete it.
- The section root is repaired: a file in its place is made removable and deleted, a directory the player locked is made usable. (A symlink is refused. Section 0, whose root is the workspace itself, is never repaired this way.)
- The section’s fixture is applied, then the level’s own.
Used for the first visit, for shellgame reset and for jumps; see Fixture::apply
for the repair rules that make it work after anything the player may have done.
Sourcepub fn validate(&self, ctx: &Ctx<'_>, answer: Option<&str>) -> Check
pub fn validate(&self, ctx: &Ctx<'_>, answer: Option<&str>) -> Check
Judge a submission: the requirements first, in order and unconditionally (the first failure wins), then the answer.
With no answer (None): the level passes if it allows empty submissions or takes no
answer, and otherwise fails with the answer’s “required” message. With an answer, it
is judged by the answer rule, or accepted when the level has none. On Pass, the
session shows Level::success.
Sourcepub fn start_dir(
&self,
workspace: &Path,
section_root: &Path,
) -> Option<PathBuf>
pub fn start_dir( &self, workspace: &Path, section_root: &Path, ) -> Option<PathBuf>
The directory the player is placed in when the level starts, if it has one.
Sourcepub fn returns_to_start(&self, mv: &CdMove<'_>) -> bool
pub fn returns_to_start(&self, mv: &CdMove<'_>) -> bool
True when this cd would bring the player back to the level’s start directory.
Strict cd rules always permit the recovery move, so a player can never be locked
out of the level’s own workspace. The destination is resolved the way the shell
will (CdMove::destination); where it cannot be (an unknown cd -), the answer
is no.
Sourcepub fn cd_enforcement_lifted(&self, mv: &CdMove<'_>) -> bool
pub fn cd_enforcement_lifted(&self, mv: &CdMove<'_>) -> bool
True when a strict cd rule must not block this move: the level’s cd evidence
exists (the player has shown the command) or the move returns to the start
directory.
Without this, one extra step could leave a player in a directory from which every permitted move leads further away. Lifting waives blocking only; it never grants or denies evidence.
Sourcepub fn cd_pre_move(&self, mv: &CdMove<'_>) -> CdDecision
pub fn cd_pre_move(&self, mv: &CdMove<'_>) -> CdDecision
Decide whether a cd about to happen is allowed. Levels without a cd policy allow
everything. Printing the red panel and the recovery tip is the UI’s job.
Sourcepub fn cd_post_move(&self, mv: &CdMove<'_>)
pub fn cd_post_move(&self, mv: &CdMove<'_>)
Record the outcome of a cd that succeeded (evidence, on_move).
Sourcepub fn cd_legacy_pre_move(&self, mv: &CdMove<'_>) -> CdDecision
pub fn cd_legacy_pre_move(&self, mv: &CdMove<'_>) -> CdDecision
Level::cd_pre_move for a shell started by the Python version of the game, which
only reports the move before it happens; see CdPolicy::legacy_pre_move.
Sourcepub fn cd_legacy_post_move(&self, ctx: &Ctx<'_>, dest: &Path)
pub fn cd_legacy_post_move(&self, ctx: &Ctx<'_>, dest: &Path)
The post-move report of a Python-era shell; see CdPolicy::legacy_post_move.