rust_warrior/
starter.rs

1//! contains some methods for generating the game files
2
3use std::fs;
4use std::io;
5use std::path::Path;
6use std::process;
7
8use crate::{profile::Profile, ui};
9
10fn generate_readme(level: usize, player: &str) -> String {
11    match level {
12        1 => format!(
13            "# Level 1
14
15You see before yourself a long hallway with stairs at the end.
16There is nothing in the way.
17
18Tip: Call `warrior.walk()` in the `Player::play_turn` method.
19
20```
21 --------
22|@      >|
23 --------
24
25  > = Stairs
26  @ = {player} (20 HP)
27```
28
29Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
30
31----------
32
33When you're ready, use `cargo run` to attempt this challenge.
34",
35            player = player
36        ),
37        2 => format!(
38            "# Level 2
39
40It is too dark to see anything, but you smell sludge nearby.
41
42Tip: Add a `rust_warrior::Tile` import and then call `warrior.check()`
43to see if there is anything in front of you or if it's `Empty`. Call
44`warrior.attack()` to fight the sludge.
45
46```
47 --------
48|@   s  >|
49 --------
50
51  > = Stairs
52  @ = {player} (20 HP)
53  s = Sludge (12 HP)
54```
55
56Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
57
58----------
59
60When you're ready, use `cargo run` to attempt this challenge.
61",
62            player = player
63        ),
64        3 => format!(
65            "# Level 3
66
67The air feels thicker than before. There must be a horde of sludge.
68
69Tip: Be careful not to die! Use `warrior.health()` to keep an eye on your health
70and `warrior.rest()` to earn 10% of max health back.
71
72```
73 ---------
74|@ s ss s>|
75 ---------
76
77  > = Stairs
78  @ = {player} (20 HP)
79  s = Sludge (12 HP)
80```
81
82Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
83
84----------
85
86When you're ready, use `cargo run` to attempt this challenge.
87",
88            player = player
89        ),
90        4 => format!(
91            "# Level 4
92
93You can hear bow strings being stretched.
94
95Tip: No new abilities this time, but you must be careful not to rest while
96taking damage. Add a `health` field to your `Player` struct and compare it on
97each turn to see if you're taking damage.
98
99```
100 --------
101|@ S  aS>|
102 --------
103
104  > = Stairs
105  @ = {player} (20 HP)
106  S = Thick Sludge (18 HP)
107  a = Archer (7 HP)
108```
109
110Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
111
112----------
113
114When you're ready, use `cargo run` to attempt this challenge.
115",
116            player = player
117        ),
118        5 => format!(
119            "# Level 5
120
121You hear cries for help. Captives must need rescuing.
122
123Tip: Add a `rust_warrior::UnitType` import and call `warrior.check()` to see
124if there is a captive (`Tile::Unit(UnitType::Captive)`). Call
125`warrior.rescue()` to rescue them. Don't attack captives.
126
127```
128 --------
129|@ CaaSC>|
130 --------
131
132  > = Stairs
133  @ = {player} (20 HP)
134  C = Captive (1 HP)
135  a = Archer (7 HP)
136  S = Thick Sludge (18 HP)
137```
138
139Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
140
141----------
142
143When you're ready, use `cargo run` to attempt this challenge.
144",
145            player = player
146        ),
147        6 => format!(
148            "# Level 6
149
150The wall behind you feels a bit further away in this room. And you hear more
151cries for help.
152
153Tip: Add a `rust_warrior::Direction` import and use directional actions.
154The directional action methods are `warrior.walk_toward(Direction)`,
155`warrior.check_toward(Direction)`, `warrior.attack_toward(Direction)`,
156and `warrior.rescue_toward(Direction)`. Archer attacks have a limited range.
157Walk backward if you are taking damage from afar and don't have enough health
158to engage. Consider backing up until you've reached a `Tile::Wall`.
159
160```
161 ---------
162|C @ S aa>|
163 ---------
164
165  > = Stairs
166  @ = {player} (20 HP)
167  C = Captive (1 HP)
168  S = Thick Sludge (18 HP)
169  a = Archer (7 HP)
170```
171
172Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
173
174----------
175
176When you're ready, use `cargo run` to attempt this challenge.
177",
178            player = player
179        ),
180        7 => format!(
181            "# Level 7
182
183You feel a wall right in front of you and an opening behind you.
184
185Tip: You are not as effective at attacking backward. Use
186`warrior.check_toward(Direction)` to see if you are facing a `Tile::Wall` and
187`warrior.pivot()` to turn around.
188
189```
190 ------
191|>a S @|
192 ------
193
194  > = Stairs
195  @ = {player} (20 HP)
196  a = Archer (7 HP)
197  S = Thick Sludge (18 HP)
198```
199
200Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
201
202----------
203
204When you're ready, use `cargo run` to attempt this challenge.
205",
206            player = player
207        ),
208        8 => format!(
209            "# Level 8
210
211You hear the mumbling of wizards. Beware of their deadly wands! Good thing you
212found a bow and some arrows.
213
214Tip: Use `warrior.look` to determine your surroundings, and `warrior.shoot` to
215fire an arrow. Wizards are deadly but low in health. Kill them before they
216have time to attack.
217
218```
219 -------
220|@ Cw w>|
221 -------
222
223  > = Stairs
224  @ = {player} (20 HP)
225  C = Captive (1 HP)
226  w = Wizard (3 HP)
227```
228
229Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
230
231----------
232
233When you're ready, use `cargo run` to attempt this challenge.
234",
235            player = player
236        ),
237        9 => format!(
238            "# Level 9
239
240Time to hone your skills and apply all of the abilities that you have learned.
241
242Tip: Watch your back.
243
244```
245 ------------
246|>Ca  @ S  wC|
247 ------------
248
249  > = Stairs
250  @ = {player} (20 HP)
251  C = Captive (1 HP)
252  a = Archer (7 HP)
253  S = Thick Sludge (18 HP)
254  w = Wizard (3 HP)
255```
256
257Warrior abilities: https://docs.rs/rust-warrior/latest/rust_warrior/warrior/struct.Warrior.html
258
259----------
260
261When you're ready, use `cargo run` to attempt this challenge.
262",
263            player = player
264        ),
265        _ => unimplemented!(),
266    }
267}
268
269fn generate_main_rs(player: &str) -> String {
270    format!(
271        "use rust_warrior::{{Game, Player, Warrior}};
272
273struct {player};
274
275impl Player for {player} {{
276    fn play_turn(&mut self, warrior: &Warrior) {{}}
277}}
278
279impl {player} {{
280    fn new() -> Self {{
281        {player} {{}}
282    }}
283
284    // don't change this method, this is what needs to be passed to `Game::play`
285    fn new_player() -> Box<dyn Player + Send + Sync> {{
286        Box::new({player}::new())
287    }}
288}}
289
290fn main() {{
291    Game::play({player}::new_player);
292}}
293",
294        player = player
295    )
296}
297
298fn generate_cargo_toml(name: &str) -> String {
299    format!(
300        "[package]
301name = \"rustwarrior-{name}\"
302version = \"0.1.0\"
303edition = \"2021\"
304
305[dependencies]
306rust-warrior = \"0.14.0\"
307",
308        name = name
309    )
310}
311
312/// Set up a new game directory and player profile
313///
314/// Creates a `rustwarrior` directory if one does not exist yet.
315/// Then creates a player directory for the chosen player name,
316/// with the following contents:
317///
318/// * `src/main.rs`
319/// * `Cargo.toml`
320/// * `.profile`
321///
322/// From there, the newly generated crate can be used to start level one.
323pub fn generate() -> io::Result<()> {
324    println!("Welcome to Rust Warrior");
325
326    create_game_directory()?;
327    let mut profile = create_profile();
328    create_game_files(&mut profile)?;
329
330    println!(
331        "Game files have been generated. See rustwarrior/{}/README.md for instructions.",
332        &profile.directory()
333    );
334
335    Ok(())
336}
337
338fn create_game_directory() -> io::Result<()> {
339    if Path::new("rustwarrior").exists() {
340        return Ok(());
341    }
342
343    if ui::ask("No rustwarrior directory found. Would you like to create one?") {
344        fs::create_dir("rustwarrior")?;
345    } else {
346        println!("Unable to continue without directory.");
347        process::exit(1);
348    }
349
350    Ok(())
351}
352
353fn create_profile() -> Profile {
354    // TODO: menu to select difficulty
355    let name = ui::request("Enter a name for your warrior: ");
356    Profile::new(name)
357}
358
359/// Write the README.md for the current level into the player's game directory
360pub fn write_readme(profile: &Profile, level: usize, directory: Option<&Path>) {
361    let readme = match directory {
362        Some(player_dir) => player_dir.join("README.md"),
363        _ => Path::new("README.md").to_path_buf(),
364    };
365    let contents = generate_readme(level, &profile.name);
366    fs::write(readme, contents)
367        .unwrap_or_else(|_| panic!("failed to generate level {} README.md", level));
368}
369
370/// Save the player's [`Profile`](crate::profile::Profile) to .profile in their
371/// game directory
372pub fn write_profile(profile: &Profile, directory: Option<&Path>) {
373    let profile_toml = match directory {
374        Some(player_dir) => player_dir.join(".profile"),
375        _ => Path::new(".profile").to_path_buf(),
376    };
377    fs::write(profile_toml, &profile.to_toml()).expect("failed to write .profile");
378}
379
380fn create_game_files(profile: &mut Profile) -> io::Result<()> {
381    let profile_dir = &profile.directory();
382    let player_dir = Path::new("rustwarrior").join(&profile_dir);
383    fs::create_dir(&player_dir)?;
384    let src_dir = player_dir.join("src");
385    fs::create_dir(&src_dir)?;
386    let main_rs = src_dir.join("main.rs");
387    fs::write(main_rs, generate_main_rs(&profile.name))?;
388    let cargo_toml = player_dir.join("Cargo.toml");
389    fs::write(cargo_toml, generate_cargo_toml(profile_dir))?;
390
391    write_profile(profile, Some(&player_dir));
392    write_readme(profile, 1, Some(&player_dir));
393
394    Ok(())
395}