From d11ac0f4f5277c1fce93c9edb371b24efac51aed Mon Sep 17 00:00:00 2001
From: Petr Janik <485122@mail.muni.cz>
Date: Mon, 24 Aug 2020 22:41:36 +0200
Subject: [PATCH 1/4] Init adventure game in week 4
---
04/adventure-game/Cargo.lock | 5 +++++
04/adventure-game/Cargo.toml | 9 +++++++++
04/adventure-game/src/main.rs | 3 +++
3 files changed, 17 insertions(+)
create mode 100644 04/adventure-game/Cargo.lock
create mode 100644 04/adventure-game/Cargo.toml
create mode 100644 04/adventure-game/src/main.rs
diff --git a/04/adventure-game/Cargo.lock b/04/adventure-game/Cargo.lock
new file mode 100644
index 0000000..c23c547
--- /dev/null
+++ b/04/adventure-game/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "adventure-game"
+version = "0.1.0"
diff --git a/04/adventure-game/Cargo.toml b/04/adventure-game/Cargo.toml
new file mode 100644
index 0000000..90fa6de
--- /dev/null
+++ b/04/adventure-game/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "adventure-game"
+version = "0.1.0"
+authors = ["Petr Janik <485122@mail.muni.cz>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/04/adventure-game/src/main.rs b/04/adventure-game/src/main.rs
new file mode 100644
index 0000000..e7a11a9
--- /dev/null
+++ b/04/adventure-game/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
From f348492bb64fad53bb244eb3484d0b3f0315059c Mon Sep 17 00:00:00 2001
From: Petr Janik <485122@mail.muni.cz>
Date: Mon, 24 Aug 2020 22:49:31 +0200
Subject: [PATCH 2/4] Add adventure game progress from last week to 04
---
04/adventure-game/src/main.rs | 3 -
.../.run/Run 04_adventure_game.run.xml | 14 +++
.../Cargo.lock | 2 +-
.../Cargo.toml | 2 +-
04/adventure_game/src/main.rs | 102 ++++++++++++++++++
04/adventure_game/story.txt | 20 ++++
6 files changed, 138 insertions(+), 5 deletions(-)
delete mode 100644 04/adventure-game/src/main.rs
create mode 100644 04/adventure_game/.run/Run 04_adventure_game.run.xml
rename 04/{adventure-game => adventure_game}/Cargo.lock (83%)
rename 04/{adventure-game => adventure_game}/Cargo.toml (89%)
create mode 100644 04/adventure_game/src/main.rs
create mode 100644 04/adventure_game/story.txt
diff --git a/04/adventure-game/src/main.rs b/04/adventure-game/src/main.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/04/adventure-game/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}
diff --git a/04/adventure_game/.run/Run 04_adventure_game.run.xml b/04/adventure_game/.run/Run 04_adventure_game.run.xml
new file mode 100644
index 0000000..6895359
--- /dev/null
+++ b/04/adventure_game/.run/Run 04_adventure_game.run.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/04/adventure-game/Cargo.lock b/04/adventure_game/Cargo.lock
similarity index 83%
rename from 04/adventure-game/Cargo.lock
rename to 04/adventure_game/Cargo.lock
index c23c547..7696895 100644
--- a/04/adventure-game/Cargo.lock
+++ b/04/adventure_game/Cargo.lock
@@ -1,5 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
-name = "adventure-game"
+name = "adventure_game"
version = "0.1.0"
diff --git a/04/adventure-game/Cargo.toml b/04/adventure_game/Cargo.toml
similarity index 89%
rename from 04/adventure-game/Cargo.toml
rename to 04/adventure_game/Cargo.toml
index 90fa6de..50ef64a 100644
--- a/04/adventure-game/Cargo.toml
+++ b/04/adventure_game/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "adventure-game"
+name = "adventure_game"
version = "0.1.0"
authors = ["Petr Janik <485122@mail.muni.cz>"]
edition = "2018"
diff --git a/04/adventure_game/src/main.rs b/04/adventure_game/src/main.rs
new file mode 100644
index 0000000..6589238
--- /dev/null
+++ b/04/adventure_game/src/main.rs
@@ -0,0 +1,102 @@
+use std::{env, io};
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{BufRead, BufReader, Error};
+
+type Choice = (String, u32);
+
+struct Scene {
+ story: String,
+ choices: Vec,
+}
+
+impl Scene {
+ fn from(story: String, choices: Vec) -> Scene {
+ Scene {
+ story,
+ choices,
+ }
+ }
+}
+
+fn main() -> std::io::Result<()> {
+ let args = get_args();
+
+ let scenes = parse_scenes(&args)?;
+
+ play(&scenes)?;
+
+ Ok(())
+}
+
+fn play(scenes: &HashMap) -> Result<(), Error> {
+ const INITIAL_SCENE_INDEX: u32 = 0;
+
+ let mut current_scene: &Scene = &scenes[&INITIAL_SCENE_INDEX];
+ while !current_scene.choices.is_empty() {
+ println!("{}", current_scene.story);
+ for (choice, _) in ¤t_scene.choices {
+ println!("{}", choice);
+ }
+ let choice = get_choice_from_user(current_scene.choices.len() as u32)?;
+ let next_scene: u32 = current_scene.choices[choice as usize - 1].1;
+ current_scene = &scenes[&next_scene];
+ }
+ println!("{}", current_scene.story);
+ Ok(())
+}
+
+pub fn get_choice_from_user(num_of_choices: u32) -> Result {
+ loop {
+ println!("What do you choose to do?");
+ let mut choice = String::new();
+
+ io::stdin().read_line(&mut choice)?;
+ match choice.trim().parse::() {
+ Ok(val) => {
+ if val < 1 || val > num_of_choices {
+ continue;
+ }
+ break Ok(val);
+ }
+ Err(_) => continue
+ };
+ }
+}
+
+fn parse_scenes(arguments: &[String]) -> Result, Error> {
+ let input_file = File::open(&arguments[1])?;
+ let input_reader = BufReader::new(input_file);
+
+ let mut scenes = HashMap::new();
+
+ let mut lines: Vec = Vec::new();
+ for line in input_reader.lines() {
+ let current_line = line?;
+
+ if current_line.is_empty() {
+ let id: u32 = lines[0].parse().unwrap();
+ let story = String::from(&lines[1]);
+ let choices = lines.iter().skip(2).map(|line| {
+ let choice_and_next_scene: Vec<&str> = line.split(';').collect();
+ (String::from(choice_and_next_scene[0]), choice_and_next_scene[1].trim().parse().unwrap())
+ }).collect();
+ let scene = Scene::from(
+ story,
+ choices);
+ scenes.insert(id, scene);
+ lines.clear();
+ } else {
+ lines.push(current_line);
+ }
+ }
+ Ok(scenes)
+}
+
+fn get_args() -> Vec {
+ let arguments: Vec<_> = env::args().collect();
+ if arguments.len() != 2 {
+ panic!("Usage: {} ", arguments[0])
+ };
+ arguments
+}
\ No newline at end of file
diff --git a/04/adventure_game/story.txt b/04/adventure_game/story.txt
new file mode 100644
index 0000000..2f5f76a
--- /dev/null
+++ b/04/adventure_game/story.txt
@@ -0,0 +1,20 @@
+0
+The adventure begins!
+1) Go left; 1
+2) Go right; 2
+
+1
+You see a dragon.
+1) Attack him; 3
+2) Pet him; 1
+3) Run away; 4
+
+2
+You went right. You win!
+
+3
+You attacked the dragon. He ate you. Game over!
+
+4
+Coward! Game over!
+
From 5ed11aab016071811cf3b3207f87affb6945e2b4 Mon Sep 17 00:00:00 2001
From: Petr Janik <485122@mail.muni.cz>
Date: Wed, 26 Aug 2020 15:36:10 +0200
Subject: [PATCH 3/4] Finish implementation of 04 adventure game
---
.../.run/Run 04_adventure_game.run.xml | 2 +-
04/adventure_game/Cargo.lock | 159 +++++++++++++++++
04/adventure_game/Cargo.toml | 5 +-
04/adventure_game/src/main.rs | 162 ++++++++++++------
04/adventure_game/story.json | 71 ++++++++
04/adventure_game/story.txt | 20 ---
6 files changed, 347 insertions(+), 72 deletions(-)
create mode 100644 04/adventure_game/story.json
delete mode 100644 04/adventure_game/story.txt
diff --git a/04/adventure_game/.run/Run 04_adventure_game.run.xml b/04/adventure_game/.run/Run 04_adventure_game.run.xml
index 6895359..0aa8061 100644
--- a/04/adventure_game/.run/Run 04_adventure_game.run.xml
+++ b/04/adventure_game/.run/Run 04_adventure_game.run.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/04/adventure_game/Cargo.lock b/04/adventure_game/Cargo.lock
index 7696895..b38a59c 100644
--- a/04/adventure_game/Cargo.lock
+++ b/04/adventure_game/Cargo.lock
@@ -3,3 +3,162 @@
[[package]]
name = "adventure_game"
version = "0.1.0"
+dependencies = [
+ "rand",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "getrandom"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+
+[[package]]
+name = "libc"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "serde"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
diff --git a/04/adventure_game/Cargo.toml b/04/adventure_game/Cargo.toml
index 50ef64a..6e191c6 100644
--- a/04/adventure_game/Cargo.toml
+++ b/04/adventure_game/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Petr Janik <485122@mail.muni.cz>"]
edition = "2018"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+rand = "0.7.3"
\ No newline at end of file
diff --git a/04/adventure_game/src/main.rs b/04/adventure_game/src/main.rs
index 6589238..2819a6f 100644
--- a/04/adventure_game/src/main.rs
+++ b/04/adventure_game/src/main.rs
@@ -1,95 +1,159 @@
use std::{env, io};
use std::collections::HashMap;
use std::fs::File;
-use std::io::{BufRead, BufReader, Error};
+use std::io::{Error, Read};
-type Choice = (String, u32);
+use rand::Rng;
+use serde::{Deserialize, Serialize};
+#[derive(Serialize, Deserialize)]
+struct Scenes {
+ scenes: Vec
+}
+
+#[derive(Serialize, Deserialize, Clone)]
struct Scene {
+ id: u32,
+ fight_scene_info: Option,
story: String,
choices: Vec,
}
-impl Scene {
- fn from(story: String, choices: Vec) -> Scene {
- Scene {
- story,
- choices,
+#[derive(Serialize, Deserialize, Clone)]
+struct FightSceneInfo {
+ enemy: Character,
+ player: Character,
+ on_death: u32,
+ on_win: u32,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Character {
+ health: i32,
+ attack_min: i32,
+ attack_max: i32,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Choice {
+ text: String,
+ next_scene_id: u32,
+}
+
+trait Executable {
+ /// Takes current scene as `self` and map of all scenes, returns next scene.
+ fn execute(self, scenes: &HashMap) -> Scene;
+}
+
+impl Executable for Scene {
+ fn execute(self, scenes: &HashMap) -> Scene {
+ print_scene_story(&self);
+ print_scene_choices(&self);
+ if self.fight_scene_info.is_none() {
+ let choice = get_choice_from_user(self.choices.len() as u32);
+ let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
+ return scenes[&next_scene_id].clone();
}
+ let mut info = self.fight_scene_info.unwrap();
+ if info.player.health <= 0 {
+ return scenes[&info.on_death].clone();
+ }
+ if info.enemy.health <= 0 {
+ return scenes[&info.on_win].clone();
+ }
+ println!("Your health: {}; attack damage range: {}-{}", info.player.health, info.player.attack_min, info.player.attack_max);
+ println!("Enemy health: {}; attack damage range: {}-{}", info.enemy.health, info.enemy.attack_min, info.enemy.attack_max);
+ let choice = get_choice_from_user(self.choices.len() as u32);
+ // choice 1 is always attack
+ if choice == 1 {
+ let mut rng = rand::thread_rng();
+ let player_dmg = rng.gen_range(info.player.attack_min, info.player.attack_max);
+ let enemy_dmg = rng.gen_range(info.enemy.attack_min, info.enemy.attack_max);
+ println!("Player attacks and deals {} damage.", player_dmg);
+ println!("Enemy attacks at the same time and deals {} damage.", enemy_dmg);
+ info.enemy.health -= player_dmg;
+ info.player.health -= enemy_dmg;
+ return Scene {
+ id: self.id,
+ fight_scene_info: Option::from(info),
+ story: self.story,
+ choices: self.choices,
+ };
+ };
+ let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
+ return scenes[&next_scene_id].clone();
}
}
fn main() -> std::io::Result<()> {
let args = get_args();
- let scenes = parse_scenes(&args)?;
+ let scenes = parse_scenes(&args[1])?;
+ let scenes_map = scenes_into_map(scenes);
- play(&scenes)?;
+ play(scenes_map)?;
Ok(())
}
-fn play(scenes: &HashMap) -> Result<(), Error> {
+fn scenes_into_map(scenes: Scenes) -> HashMap {
+ let scenes_vec = scenes.scenes;
+ let mut scenes_map = HashMap::new();
+ for scene in scenes_vec {
+ scenes_map.insert(scene.id, scene);
+ }
+ scenes_map
+}
+
+fn play(scenes: HashMap) -> Result<(), Error> {
const INITIAL_SCENE_INDEX: u32 = 0;
- let mut current_scene: &Scene = &scenes[&INITIAL_SCENE_INDEX];
- while !current_scene.choices.is_empty() {
- println!("{}", current_scene.story);
- for (choice, _) in ¤t_scene.choices {
- println!("{}", choice);
- }
- let choice = get_choice_from_user(current_scene.choices.len() as u32)?;
- let next_scene: u32 = current_scene.choices[choice as usize - 1].1;
- current_scene = &scenes[&next_scene];
+ let mut current_scene: Scene = scenes.get(&INITIAL_SCENE_INDEX)
+ .expect(format!("Story file must contain scene with id {}", INITIAL_SCENE_INDEX).as_str()).clone();
+ while !is_end_scene(¤t_scene) {
+ current_scene = current_scene.execute(&scenes);
}
- println!("{}", current_scene.story);
+ print_scene_story(¤t_scene);
Ok(())
}
-pub fn get_choice_from_user(num_of_choices: u32) -> Result {
+fn is_end_scene(current_scene: &Scene) -> bool {
+ current_scene.choices.is_empty()
+}
+
+fn print_scene_story(current_scene: &Scene) {
+ println!("\n{}", current_scene.story);
+}
+
+fn print_scene_choices(current_scene: &Scene) {
+ for choice in ¤t_scene.choices {
+ println!("{}", choice.text);
+ }
+}
+
+fn get_choice_from_user(num_of_choices: u32) -> u32 {
loop {
println!("What do you choose to do?");
let mut choice = String::new();
- io::stdin().read_line(&mut choice)?;
+ io::stdin().read_line(&mut choice).expect("Cannot read user input.");
match choice.trim().parse::() {
Ok(val) => {
if val < 1 || val > num_of_choices {
continue;
}
- break Ok(val);
+ break val;
}
Err(_) => continue
};
}
}
-fn parse_scenes(arguments: &[String]) -> Result, Error> {
- let input_file = File::open(&arguments[1])?;
- let input_reader = BufReader::new(input_file);
-
- let mut scenes = HashMap::new();
-
- let mut lines: Vec = Vec::new();
- for line in input_reader.lines() {
- let current_line = line?;
-
- if current_line.is_empty() {
- let id: u32 = lines[0].parse().unwrap();
- let story = String::from(&lines[1]);
- let choices = lines.iter().skip(2).map(|line| {
- let choice_and_next_scene: Vec<&str> = line.split(';').collect();
- (String::from(choice_and_next_scene[0]), choice_and_next_scene[1].trim().parse().unwrap())
- }).collect();
- let scene = Scene::from(
- story,
- choices);
- scenes.insert(id, scene);
- lines.clear();
- } else {
- lines.push(current_line);
- }
- }
+fn parse_scenes(story_file: &str) -> Result {
+ let mut input_file = File::open(story_file)?;
+ let mut data = String::new();
+ input_file.read_to_string(&mut data)?;
+ let scenes: Scenes = serde_json::from_str(&*data)?;
Ok(scenes)
}
diff --git a/04/adventure_game/story.json b/04/adventure_game/story.json
new file mode 100644
index 0000000..55a15e4
--- /dev/null
+++ b/04/adventure_game/story.json
@@ -0,0 +1,71 @@
+{
+ "scenes": [
+ {
+ "id": 0,
+ "story": "The adventure begins!",
+ "choices": [
+ {
+ "text": "1) Go to the city",
+ "next_scene_id": 1
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "story": "You are in the middle of the city.",
+ "choices": [
+ {
+ "text": "1) Go to shop",
+ "next_scene_id": 2
+ },
+ {
+ "text": "2) Go to forest",
+ "next_scene_id": 3
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "story": "You are in the shop.",
+ "choices": [
+ {
+ "text": "1) Leave the shop",
+ "next_scene_id": 1
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "fight_scene_info": {
+ "enemy": {
+ "health": 10,
+ "attack_min": 0,
+ "attack_max": 5
+ },
+ "player": {
+ "health": 20,
+ "attack_min": 0,
+ "attack_max": 3
+ },
+ "on_death": 4,
+ "on_win": 1
+ },
+ "story": "You see a dragon.",
+ "choices": [
+ {
+ "text": "1) Attack the dragon",
+ "next_scene_id": 3
+ },
+ {
+ "text": "2) Run away",
+ "next_scene_id": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "story": "You died, game over!",
+ "choices": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/04/adventure_game/story.txt b/04/adventure_game/story.txt
deleted file mode 100644
index 2f5f76a..0000000
--- a/04/adventure_game/story.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-0
-The adventure begins!
-1) Go left; 1
-2) Go right; 2
-
-1
-You see a dragon.
-1) Attack him; 3
-2) Pet him; 1
-3) Run away; 4
-
-2
-You went right. You win!
-
-3
-You attacked the dragon. He ate you. Game over!
-
-4
-Coward! Game over!
-
From 5c13a23d9e3c587df896d797facff9d2dc082403 Mon Sep 17 00:00:00 2001
From: Petr Janik <485122@mail.muni.cz>
Date: Wed, 26 Aug 2020 16:13:20 +0200
Subject: [PATCH 4/4] Move code to lib, add two tests
---
04/adventure_game/src/lib.rs | 173 ++++++++++++++++++++++++++++++++++
04/adventure_game/src/main.rs | 157 +-----------------------------
2 files changed, 178 insertions(+), 152 deletions(-)
create mode 100644 04/adventure_game/src/lib.rs
diff --git a/04/adventure_game/src/lib.rs b/04/adventure_game/src/lib.rs
new file mode 100644
index 0000000..e77bfd6
--- /dev/null
+++ b/04/adventure_game/src/lib.rs
@@ -0,0 +1,173 @@
+use std::io;
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{Error, Read};
+
+use rand::Rng;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+pub struct Scenes {
+ scenes: Vec
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct Scene {
+ id: u32,
+ fight_scene_info: Option,
+ story: String,
+ choices: Vec,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct FightSceneInfo {
+ enemy: Character,
+ player: Character,
+ on_death: u32,
+ on_win: u32,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Character {
+ health: i32,
+ attack_min: i32,
+ attack_max: i32,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Choice {
+ text: String,
+ next_scene_id: u32,
+}
+
+trait Executable {
+ /// Takes current scene (as `self`) and map of all scenes, returns next scene.
+ fn execute(self, scenes: &HashMap) -> Scene;
+}
+
+impl Executable for Scene {
+ fn execute(self, scenes: &HashMap) -> Scene {
+ print_scene_story(&self);
+ print_scene_choices(&self);
+ if self.fight_scene_info.is_none() {
+ let choice = get_choice_from_user(self.choices.len() as u32);
+ let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
+ return scenes[&next_scene_id].clone();
+ }
+ let mut info = self.fight_scene_info.unwrap();
+ if info.player.health <= 0 {
+ return scenes[&info.on_death].clone();
+ }
+ if info.enemy.health <= 0 {
+ return scenes[&info.on_win].clone();
+ }
+ println!("Your health: {}; attack damage range: {}-{}", info.player.health, info.player.attack_min, info.player.attack_max);
+ println!("Enemy health: {}; attack damage range: {}-{}", info.enemy.health, info.enemy.attack_min, info.enemy.attack_max);
+ let choice = get_choice_from_user(self.choices.len() as u32);
+ // choice 1 is always attack
+ if choice == 1 {
+ let mut rng = rand::thread_rng();
+ let player_dmg = rng.gen_range(info.player.attack_min, info.player.attack_max);
+ let enemy_dmg = rng.gen_range(info.enemy.attack_min, info.enemy.attack_max);
+ println!("Player attacks and deals {} damage.", player_dmg);
+ println!("Enemy attacks at the same time and deals {} damage.", enemy_dmg);
+ info.enemy.health -= player_dmg;
+ info.player.health -= enemy_dmg;
+ return Scene {
+ id: self.id,
+ fight_scene_info: Option::from(info),
+ story: self.story,
+ choices: self.choices,
+ };
+ };
+ let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
+ return scenes[&next_scene_id].clone();
+ }
+}
+
+pub fn scenes_into_map(scenes: Scenes) -> HashMap {
+ let scenes_vec = scenes.scenes;
+ let mut scenes_map = HashMap::new();
+ for scene in scenes_vec {
+ scenes_map.insert(scene.id, scene);
+ }
+ scenes_map
+}
+
+pub fn play(scenes: HashMap) {
+ const INITIAL_SCENE_INDEX: u32 = 0;
+
+ let mut current_scene: Scene = scenes.get(&INITIAL_SCENE_INDEX)
+ .expect(format!("Story file must contain scene with id {}", INITIAL_SCENE_INDEX).as_str()).clone();
+ while !is_end_scene(¤t_scene) {
+ current_scene = current_scene.execute(&scenes);
+ }
+ print_scene_story(¤t_scene);
+}
+
+fn is_end_scene(current_scene: &Scene) -> bool {
+ current_scene.choices.is_empty()
+}
+
+fn print_scene_story(current_scene: &Scene) {
+ println!("\n{}", current_scene.story);
+}
+
+fn print_scene_choices(current_scene: &Scene) {
+ for choice in ¤t_scene.choices {
+ println!("{}", choice.text);
+ }
+}
+
+fn get_choice_from_user(num_of_choices: u32) -> u32 {
+ loop {
+ println!("What do you choose to do?");
+ let mut choice = String::new();
+
+ io::stdin().read_line(&mut choice).expect("Cannot read user input.");
+ match choice.trim().parse::() {
+ Ok(val) => {
+ if val < 1 || val > num_of_choices {
+ continue;
+ }
+ break val;
+ }
+ Err(_) => continue
+ };
+ }
+}
+
+pub fn parse_scenes(story_file: &str) -> Result {
+ let mut input_file = File::open(story_file)?;
+ let mut data = String::new();
+ input_file.read_to_string(&mut data)?;
+ let scenes: Scenes = serde_json::from_str(&*data)?;
+ Ok(scenes)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Choice, is_end_scene, Scene};
+
+ #[test]
+ fn scene_without_choices_is_end_scene() {
+ let end_scene = Scene {
+ id: 0,
+ fight_scene_info: None,
+ story: "".to_string(),
+ choices: vec![],
+ };
+ assert!(is_end_scene(&end_scene));
+ }
+
+ #[test]
+ fn scene_with_choices_is_not_end_scene() {
+ let not_end_scene = Scene {
+ id: 0,
+ fight_scene_info: None,
+ story: "some story text".to_string(),
+ choices: vec![Choice { text: "another story text".to_string(), next_scene_id: 1 }],
+ };
+ assert!(!is_end_scene(¬_end_scene));
+ }
+}
\ No newline at end of file
diff --git a/04/adventure_game/src/main.rs b/04/adventure_game/src/main.rs
index 2819a6f..e2c0232 100644
--- a/04/adventure_game/src/main.rs
+++ b/04/adventure_game/src/main.rs
@@ -1,160 +1,13 @@
-use std::{env, io};
-use std::collections::HashMap;
-use std::fs::File;
-use std::io::{Error, Read};
+use adventure_game::{parse_scenes, scenes_into_map, play};
+use std::env;
-use rand::Rng;
-use serde::{Deserialize, Serialize};
-
-#[derive(Serialize, Deserialize)]
-struct Scenes {
- scenes: Vec
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct Scene {
- id: u32,
- fight_scene_info: Option,
- story: String,
- choices: Vec,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct FightSceneInfo {
- enemy: Character,
- player: Character,
- on_death: u32,
- on_win: u32,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct Character {
- health: i32,
- attack_min: i32,
- attack_max: i32,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct Choice {
- text: String,
- next_scene_id: u32,
-}
-
-trait Executable {
- /// Takes current scene as `self` and map of all scenes, returns next scene.
- fn execute(self, scenes: &HashMap) -> Scene;
-}
-
-impl Executable for Scene {
- fn execute(self, scenes: &HashMap) -> Scene {
- print_scene_story(&self);
- print_scene_choices(&self);
- if self.fight_scene_info.is_none() {
- let choice = get_choice_from_user(self.choices.len() as u32);
- let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
- return scenes[&next_scene_id].clone();
- }
- let mut info = self.fight_scene_info.unwrap();
- if info.player.health <= 0 {
- return scenes[&info.on_death].clone();
- }
- if info.enemy.health <= 0 {
- return scenes[&info.on_win].clone();
- }
- println!("Your health: {}; attack damage range: {}-{}", info.player.health, info.player.attack_min, info.player.attack_max);
- println!("Enemy health: {}; attack damage range: {}-{}", info.enemy.health, info.enemy.attack_min, info.enemy.attack_max);
- let choice = get_choice_from_user(self.choices.len() as u32);
- // choice 1 is always attack
- if choice == 1 {
- let mut rng = rand::thread_rng();
- let player_dmg = rng.gen_range(info.player.attack_min, info.player.attack_max);
- let enemy_dmg = rng.gen_range(info.enemy.attack_min, info.enemy.attack_max);
- println!("Player attacks and deals {} damage.", player_dmg);
- println!("Enemy attacks at the same time and deals {} damage.", enemy_dmg);
- info.enemy.health -= player_dmg;
- info.player.health -= enemy_dmg;
- return Scene {
- id: self.id,
- fight_scene_info: Option::from(info),
- story: self.story,
- choices: self.choices,
- };
- };
- let next_scene_id: u32 = self.choices[choice as usize - 1].next_scene_id;
- return scenes[&next_scene_id].clone();
- }
-}
-
-fn main() -> std::io::Result<()> {
+fn main() {
let args = get_args();
- let scenes = parse_scenes(&args[1])?;
+ let scenes = parse_scenes(&args[1]).expect("Unable to parse scenes");
let scenes_map = scenes_into_map(scenes);
- play(scenes_map)?;
-
- Ok(())
-}
-
-fn scenes_into_map(scenes: Scenes) -> HashMap {
- let scenes_vec = scenes.scenes;
- let mut scenes_map = HashMap::new();
- for scene in scenes_vec {
- scenes_map.insert(scene.id, scene);
- }
- scenes_map
-}
-
-fn play(scenes: HashMap) -> Result<(), Error> {
- const INITIAL_SCENE_INDEX: u32 = 0;
-
- let mut current_scene: Scene = scenes.get(&INITIAL_SCENE_INDEX)
- .expect(format!("Story file must contain scene with id {}", INITIAL_SCENE_INDEX).as_str()).clone();
- while !is_end_scene(¤t_scene) {
- current_scene = current_scene.execute(&scenes);
- }
- print_scene_story(¤t_scene);
- Ok(())
-}
-
-fn is_end_scene(current_scene: &Scene) -> bool {
- current_scene.choices.is_empty()
-}
-
-fn print_scene_story(current_scene: &Scene) {
- println!("\n{}", current_scene.story);
-}
-
-fn print_scene_choices(current_scene: &Scene) {
- for choice in ¤t_scene.choices {
- println!("{}", choice.text);
- }
-}
-
-fn get_choice_from_user(num_of_choices: u32) -> u32 {
- loop {
- println!("What do you choose to do?");
- let mut choice = String::new();
-
- io::stdin().read_line(&mut choice).expect("Cannot read user input.");
- match choice.trim().parse::() {
- Ok(val) => {
- if val < 1 || val > num_of_choices {
- continue;
- }
- break val;
- }
- Err(_) => continue
- };
- }
-}
-
-fn parse_scenes(story_file: &str) -> Result {
- let mut input_file = File::open(story_file)?;
- let mut data = String::new();
- input_file.read_to_string(&mut data)?;
- let scenes: Scenes = serde_json::from_str(&*data)?;
- Ok(scenes)
+ play(scenes_map);
}
fn get_args() -> Vec {