1use 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
312pub 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 let name = ui::request("Enter a name for your warrior: ");
356 Profile::new(name)
357}
358
359pub 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
370pub 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}