diff --git a/aufgaben/sheet3/README.md b/aufgaben/sheet3/README.md new file mode 100755 index 0000000..2bca524 --- /dev/null +++ b/aufgaben/sheet3/README.md @@ -0,0 +1,7 @@ +Blatt 3 +======= + +Wie beim letzten (und allen noch folgenden) Blättern soll die Bearbeitung +dieser Aufgaben auf einem anderen Git-Branch geschehen. +Legt dazu einen Branch `sheet3` an, von welchem ihr später den PR öffnet. +Wichtig: Stellt sicher, dass ihr wirklich auf eurem neuen Branch seid! diff --git a/aufgaben/sheet3/task1/README.md b/aufgaben/sheet3/task1/README.md new file mode 100755 index 0000000..11c6b87 --- /dev/null +++ b/aufgaben/sheet3/task1/README.md @@ -0,0 +1,94 @@ +Aufgabe 3.1: Rule 90 +==================== + +Zelluläre Automaten simulieren ein System von Zellen mit diskreten Werten in +diskreten Zeitschritten, wobei sich der Zustand des Systems zum Zeitpunkt +*n + 1* deterministisch aus dem Zustand zum Zeitpunkt *n* berechnen lässt. +In den meisten Fällen hängt der Wert einer Zelle allein von seinem eigenen Wert +und den Werten seiner Nachbarn zum vorherigen Zeitpunkt ab. + +["Rule 90"][1] ist ein eindimensionaler, zellulärer Automat, dessen Zellen +boolesch sind, also nur zwei Werte annehmen können. +Oft spricht man bei einer Zelle mit einer 0 von einer "toten Zelle", bei einer +1 von einer "lebenden" Zelle. + +Diese Zellen können nun nach bestimmten Vorschriften in diskreten Zeitschritten +simuliert werden. Der Wert einer Zelle zu dem Zeitschritt *n + 1* hängt von +seinem Wert und der Wert seiner beiden direkten Nachbarn zum Zeitpunkt *n* ab. +Sind z.B. zum Zeitpunkt *n* alle drei Zellen tot, wird die mittlere der Zellen +zum Zeitpunkt *n + 1* ebenfalls tot bleiben. + +Die vollständigen Regeln lauten wie folgt: + +| Vorheriger Zeitschritt | `111` | `110` | `101` | `100` | `011` | `010` | `001` | `000` | +| ----------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | +| Neuer Wert für mittlere Zelle | `_0_` | `_1_` | `_0_` | `_1_` | `_1_` | `_0_` | `_1_` | `_0_` | + +Nun kann man die diskreten Zeitschritte zeilenweise anzeigen. Das sieht dann +z.B. so aus (in diesem Beispiel ist der Automat 5 Zellen groß): + +``` +0 1 1 0 0 +1 1 1 1 0 +1 0 0 1 0 +0 1 1 0 0 +``` + +Zu diesem Zeitpunkt sollte man sich fragen: Was ist mit den Randzellen? +In dieser Aufgabe gehen wir davon aus, dass der Anfang und das Ende des Systems +verbunden sind, d.h. der linke Nachbar der ersten Zelle ist die letzte Zelle +und der rechte Nachbar der letzten Zelle ist die erste Zelle. + +--- + +In dieser Aufgabe sollt ihr ein Programm schreiben, welches den Automaten +"Rule 90" für Nutzereingaben simuliert. Diese Funktionalität soll in mehrere +Funktionen aufgeteilt werden. + + +### a) Eingabe einlesen + +Zunächst muss vom Nutzer die initiale Konfiguration des Automaten eingelesen +werden. +Dafür soll eine Funktion `read_input()` geschrieben werden, die teilweise +bereits von uns bereitgestellt wurde. +In dem schon existierenden Code wird lediglich ein String vom Nutzer +eingelesen, der nur '0' und '1' enthält. +Eure Aufgabe ist es, aus diesem String einen `Vec` zu erstellen, der für +jede '0' im String einen `false`-Eintrag und für jede '1' einen `true`-Eintrag +enthält (in der richtigen Reihenfolge). +Dieser Vektor soll dann von der Funktion zurückgegeben werden. + + +### b) Zeitschritt simulieren + +Nun sollt ihr eine Funktion `next_step()` schreiben, die aus der Konfiguration +des Automaten zu einem Zeitpunkt den nächsten Zeitpunkt berechnen soll. +Den Automaten zu einem Zeitpunkt stellen wir weiterhin einfach als ein +Boolean-Array dar; es muss also kein neuer Typ erstellt werden. + + +### c) Funktionen in `main()` benutzen + +Nun müssen beide Funktionen noch genutzt werden. +In der `main()` Funktion wird eine initiale Konfiguration des Automaten +eingelesen, welcher dann über z.B. 20 Zeitschritte simuliert werden soll. + +In jedem Zeitschritt soll der Zustand in einer Zeile auf dem Terminal +ausgegeben werden. Die Ausgabe kann entweder in Form von 0en und 1en geschehen +(wie oben im Beispiel) oder ihr könnt andere eindeutige Zeichen verwenden. +Es würde sich z.B. anbieten, für tote Zellen zwei Leerzeichen und für lebende +Zellen zwei U+2588 FULL BLOCK ('█') auszugeben: + +``` + ████ +████████ +██ ██ + ████ +``` + +Wenn alles klappt, probiert mal die initiale Konfiguration +`0000000000000001000000000000000` aus! + + +[1]: https://en.wikipedia.org/wiki/Rule_90 diff --git a/aufgaben/sheet3/task1/rule90.rs b/aufgaben/sheet3/task1/rule90.rs new file mode 100755 index 0000000..8ba8d57 --- /dev/null +++ b/aufgaben/sheet3/task1/rule90.rs @@ -0,0 +1,51 @@ +//! Task 3.1: Rule 90 + +fn main() { + // TODO: Task 1c) +} + +/// Reads a valid initial configuration for our automaton from the terminal. +fn read_input() -> Vec { + // This tries to read a string from the terminal, checks whether it's + // valid (only contains 1's and 0's). If the user fails to input a correct + // string, this routine will ask again until the user finally manages to + // give us a correct string. + // + // You don't need to understand this routine yet; that's why I've written + // it already ;-) + // + // You only need to use the `input` variable (of type `String`). You can + // also assume that it only contains '0' and '1' chars. + let input = { + let mut buffer = String::new(); + + loop { + println!("Please give me the initial configuration (a string of '0' and '1'!):"); + buffer.clear(); + + // `read_line` returns an error if the input isn't valid UTF8 or if + // a strange IO error occured. We just panic in that case... + std::io::stdin() + .read_line(&mut buffer) + .expect("something went seriously wrong :O"); + + if buffer.trim().chars().all(|c| c == '1' || c == '0') { + break; + } + } + + buffer.trim().to_string() + }; + + // TODO: Task 1a) +} + +// TODO: Task 1b) + +#[test] +fn rule90_rules() { + assert_eq!(next_step(&[false, false, false]), vec![false, false, false]); + assert_eq!(next_step(&[ true, false, false]), vec![false, true, true]); + assert_eq!(next_step(&[ true, true, false]), vec![ true, true, false]); + assert_eq!(next_step(&[ true, true, true]), vec![false, false, false]); +} diff --git a/aufgaben/sheet3/task2/README.md b/aufgaben/sheet3/task2/README.md new file mode 100755 index 0000000..edfca7d --- /dev/null +++ b/aufgaben/sheet3/task2/README.md @@ -0,0 +1,163 @@ +Aufgabe 3.2: Pokemon +==================== + +In dieser Aufgabe soll ein kleines, Terminal-basiertes Pokemon-Spiel +programmiert werden. +Dieses Spiel werden wir in den nächsten Übungszetteln stetig erweitern. + +Um direkt mal etwas zu zeigen: *In etwa* so wird das Ergebnis dieser Aufgabe +aussehen: + +``` +Player Red, please choose a Pokemon (or type '?' to get a complete list) +? +#001 Bulbasaur +#002 Ivysaur +#003 Venusaur +#004 Charmander +#005 Charmeleon +#006 Charizard +#007 Squirtle +#008 Wartortle +#009 Blastoise +Player Red, please choose a Pokemon (or type '?' to get a complete list) +Charmander +Player Blue, please choose a Pokemon (or type '?' to get a complete list) +Wartortle +>>>>> Status: Charmander has 18 HP, Wartortle has 20 HP +>>>>> Charmander is about to attack! Which move shall it execute? + 0: Tackle + !!! Please give me the attack ID: +0 +>>>>> Charmander uses Tackle! (Wartortle has 15 HP left) +Wartortle is about to attack! Which move shall it execute? + 0: Tackle + 1: Water Gun + !!! Please give me the attack ID: +1 +>>>>> Wartortle uses Water Gun! (Charmander has 7 HP left) +>>>>> Status: Charmander has 7 HP, Wartortle has 15 HP +Charmander is about to attack! Which move shall it execute? + 0: Tackle + !!! Please give me the attack ID: +0 +>>>>> Charmander uses Tackle! (Wartortle has 10 HP left) +Wartortle is about to attack! Which move shall it execute? + 0: Tackle + 1: Water Gun + !!! Please give me the attack ID: +0 +>>>>> Wartortle uses Tackle! (Charmander has 1 HP left) +>>>>> Status: Charmander has 1 HP, Wartortle has 10 HP +Charmander is about to attack! Which move shall it execute? + 0: Tackle + !!! Please give me the attack ID: +0 +>>>>> Charmander uses Tackle! (Wartortle has 5 HP left) +Wartortle is about to attack! Which move shall it execute? + 0: Tackle + 1: Water Gun + !!! Please give me the attack ID: +1 +>>>>> Wartortle uses Water Gun! (Charmander has 0 HP left) +>>>>> Charmander fainted! +``` + + +## Umfang und Art der Aufgabe + +In dieser Aufgabe wird bereits recht viel Code bereitgestellt und insgesamt +wird die Lösung eher lang. Derzeit ist der komplette Code noch in einer Datei; +das wird sich aber in der nächsten Woche ändern, wenn wir Module kennenlernen. + +Trotzdem soll diese Aufgabe schulen, sich in Rust-Code zurechtzufinden. Falls +einige Sachen unklar sein sollten, zögert nicht, auf Piazza zu fragen! Viele +gute Fragen auf Piazza helfen auch den anderen. + +Außerdem geht es in dieser Aufgabe natürlich um Pokemon. Ich habe versucht, +alles so zu formulieren, dass es auch Menschen verstehen, die nie etwas mit +Pokemon am Hut hatten. Falls mir das irgendwo nicht gelungen ist, sagt mir +bitte Bescheid oder fragt direkt auf Piazza. "Pokemon" stand nämlich nicht als +Voraussetzung in der Kursbeschreibung :P + +Aber grob beschrieben: In Pokemon gibt es komische Tiere/Monster, die "Pokemon" +genannt werden. Diese Pokemon können von Menschen gefangen werden, meist um +damit gegen andere "Pokemon-Trainer" zu kämpfen. Es gibt unterschiedliche +Pokemon und unterschiedliche Attacken. + +In der bereitgestellten Datei gibt es bereits diversen Code. Davon besteht ein +größerer Teil nur aus Konstanten. Das sind einfach Daten über Pokemon und +Attacken, die nicht weiter verstanden werden müssen. Wichtig ist aber, dass +all diese Daten direkt in der Executable gespeichert sind und daher immer die +`'static` Lifetime haben. + + +## a) Pokemon wählen lassen + +In diesem Teil geht es darum, die beiden Spieler zu fragen, welches Pokemon sie +benutzen möchten. Dazu sollt ihr ein paar Funktionen erstellen: + +- `print_pokemon_list`: Druckt eine Liste aller verfügbaren Pokemon (in der + `POKEDEX` Konstante gespeichert) auf dem Terminal. +- `find_pokemon_by_name`: Bekommt einen Namen und sucht nach einem PokemonModel + mit diesem Namen im `POKEDEX`. Gibt das PokemonModel zurück, oder `None`, + wenn der Name nicht gefunden wurde. Die Suche darf gerne in O(n) sein. +- `choose_pokemon`: Fordert den Spieler auf, den Namen eines Pokemon einzugeben. + Bietet außerdem an, alle Pokemon aufzulisten, wenn der Spieler '?' eingibt. + Diese Funktion liefert dann ein PokemonModel zurück (falls der Spieler + einen ungültigen Namen eingibt, erneut auffordern, einen richtigen Namen + einzugeben). Hierfür ist die Funktion `read_string()` nützlich! + +Wenn diese Funktionen funktionieren, soll `choose_pokemon()` in der `main()`- +Funktion aufgerufen werden, sodass beide Spieler ein Pokemon wählen können. + + + +## b) `Pokemon` Typ implementieren + +Bis jetzt gibt es nur einen Typ `PokemonModel`, der globale Eigenschaften von +einer Pokemon-Art speichert (wie z.B.: "Pferde haben vier Beine"). +Allerdings brauchen wir noch einen Typ, der eine Instanz eines Pokemon +darstellt (wie z.B. "dieses Tier ist ein Pferd und hat braunes Fell"). +Dazu soll ein neuer Typ `Pokemon` angelegt werden. + +Dieser Typ speichert sich folgende Daten: + +- Zu welcher Pokemon-Art er gehört (Referenz auf ein `PokemonModel`; wichtig: + alle PokemonModels haben eine `'static` Lifetime). +- Die derzeitigen "stats" (Typ `Stats`), also z.B. auch die HP +- Das derzeitige Level (mögliche Level: 1 bis 100) + +Der Typ soll folgende Funktionen und Methoden besitzen: + +- Konstruktor-Funktion `with_level`: Gegeben wird ein PokemonModel und ein + Level, eine gültige `Pokemon`-Instanz soll zurückgegeben werden. Hinweis: + Nützlich ist die Funktoin `Stats::at_level()`. +- Getter-Methoden (in Rust verzichtet man auf `get_`, also nicht `get_foo`, + sondern oft nur `foo`): + - `stats()` + - `model()` + - `name() ` + - `level()` +- `is_alive()` +- `endure_attack()`: Bekommt eine Referenz auf ein anderes Pokemon und auf + eine Attacke. Abhängig davon werden die HP von dem jetzigen Pokemon + angepasst. Ihr braucht die Funktion `attack_damage()` dafür. + + +## c) Kampfsystem + +Zuletzt müssen wir nur noch das eigentliche Kampfsystem bauen. Dazu sollen +abwechselnd beide Spieler aufgefordert werden, eine Attacke auszusuchen. +Diese Attacke wird dann vom Pokemon ausgeführt und so das andere Pokemon +verletzt. Die ungefähren Anforderungen: + +- Beide Spieler werden abwechselnd gefragt +- Es werden die verfügbaren Attacken aufgelistet +- Die gewählte Attacke wird ausgeführt +- Der Status beider Pokemon wird regelmäßig angezeigt +- Wenn ein Pokemon stirbt, soll sich das Programm beenden + +Euer Programm muss nicht genau so aussehen, wie oben im Beispiel gezeigt. + +Bonusaufgabe: Zuerst darf das Pokemon mit dem höheren `speed` Wert angreifen. diff --git a/aufgaben/sheet3/task2/poki.rs b/aufgaben/sheet3/task2/poki.rs new file mode 100755 index 0000000..3bd3fff --- /dev/null +++ b/aufgaben/sheet3/task2/poki.rs @@ -0,0 +1,446 @@ +//! Task 3.2: Pokemon + +fn main() {} + + + +/// Describes an attack with all its properties. This type is similar to +/// `PokemonModel`, as there are finite many, immutable instances of this type +/// in a database. This is not a type whose instances change over time. +#[derive(Debug, Clone, Copy)] +struct Attack { + category: AttackCategory, + name: &'static str, + /// Base power of the move. The actual inflicted damage is calculated with + /// a formula using the move's power and a few other parameters. + base_power: u8, + type_: Type, +} + +/// Category of an attack. +/// +/// Note: currently, the category 'status' is missing. +#[derive(Debug, Clone, Copy)] +enum AttackCategory { + /// Attacks with body contact, like "Tackle" or "Bite" + Physical, + /// Attacks without body contact, like "Bubble Beam" or "Thunderbolt" + Special, +} + +/// Describes how effective an attack of one type is on a Pokemon of another +/// type. +/// +/// Note that a Pokemon can have two types. In order to determine the +/// effectiveness, the multipliers of the effectivenesses on both types +/// are multiplied. As such, there can be 0.25 and 4.0 multipliers! +#[derive(Debug, Clone, Copy)] +enum TypeEffectiveness { + NotEffective, + NotVeryEffective, + Normal, + SuperEffective, +} + +impl TypeEffectiveness { + /// Returns the type effectiveness of an attack from one attacker type + /// on one defender type. + fn of_attack(attacker: Type, defender: Type) -> Self { + use Type::*; + use TypeEffectiveness as Te; + + // TODO: complete this + match (attacker, defender) { + (Fire, Water) => Te::NotVeryEffective, + (Fire, Grass) => Te::SuperEffective, + (Water, Fire) => Te::SuperEffective, + (Water, Grass) => Te::NotVeryEffective, + (Grass, Fire) => Te::NotVeryEffective, + (Grass, Water) => Te::SuperEffective, + _ => Te::Normal, + } + } + + /// Returns the corresponding multiplier for the damage formula. + fn multiplier(&self) -> f64 { + match *self { + TypeEffectiveness::NotEffective => 0.0, + TypeEffectiveness::NotVeryEffective => 0.5, + TypeEffectiveness::Normal => 1.0, + TypeEffectiveness::SuperEffective => 2.0, + } + } +} + + +/// Types (sometimes called "elements") of the Pokemon universe. Each +/// attack-move has exactly one type, Pokemons can have one or two types. +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +enum Type { + Normal, + Fire, + Fighting, + Water, + Flying, + Grass, + Poison, + Electric, + Ground, + Psychic, + Rock, + Ice, + Bug, + Dragon, + Ghost, + Dark, + Steel, + Fairy, +} + +/// Describes the type of a Pokemon. Pokemon can have one or two types. +#[derive(Debug, Clone, Copy)] +enum PokemonType { + One(Type), + Two(Type, Type), +} + +/// Describes a kind of Pokemon, e.g. "Pikachu". +/// +/// This is different than an actual, living Pokemon. This struct just +/// describes properties that are the same for every creature of this +/// Pokemon kind. +#[derive(Debug, Clone, Copy)] +struct PokemonModel { + /// Name of the Pokemon + name: &'static str, + /// ID in the international Pokedex + id: u16, + type_: PokemonType, + base_stats: Stats, + /// This is different from the real Pokemon games: attacks are not part + /// of the Pokemon model, but of the Pokemon itself (as they change over + /// time). A pokemon just has an abstract learnset of potential attacks. + /// But this is easier for now. + attacks: &'static [&'static Attack] +} + +/// Describes the basic stats of a Pokemon. +/// +/// Each living Pokemon has an actual stat value, but each Pokemon kind also +/// has so called "base stats". These base stats are used to calculate the +/// actual stats, whose depend on the Pokemon's current level. Stronger Pokemon +/// have higher base stats. +#[derive(Debug, Clone, Copy)] +struct Stats { + /// Health points + hp: u16, + /// Speed, sometimes called initiative (INIT) + speed: u16, + /// Strength of physical attacks (like "Tackle") + attack: u16, + /// Strength of special attacks (like "Bubble Beam") + special_attack: u16, + /// Defense power against physical attacks (like "Tackle") + defense: u16, + /// Defense power against special attacks (like "Bubble Beam") + special_defense: u16, +} + +impl Stats { + /// Given the base stats and a level, this function returns the actual + /// stats for that level. + /// + /// This function doesn't implement the correct formula used by Pokemon + /// games. It is a simplified version of the original formula for now: we + /// ignore IVs, EVs and the Pokemon's nature). The complete formula can be + /// found [here (HP)][1] and [here (other stats)][2]. + /// + /// [1]: http://bulbapedia.bulbagarden.net/wiki/File:HPStatCalcGen34.png + /// [2]: http://bulbapedia.bulbagarden.net/wiki/File:OtherStatCalcGen34.png + fn at_level(base: Self, level: u8) -> Self { + /// The formula is the same for all stats != hp + fn stat_formula(base: u16, level: u8) -> u16 { + ((base as f64 * level as f64) / 50.0 + 5.0) as u16 + } + + let hp = ( + (base.hp as f64 * level as f64) / 50.0 + + level as f64 + + 10.0 + ) as u16; + + Stats { + hp: hp, + speed: stat_formula(base.speed, level), + attack: stat_formula(base.attack, level), + special_attack: stat_formula(base.special_attack, level), + defense: stat_formula(base.defense, level), + special_defense: stat_formula(base.special_defense, level), + } + } +} + +// =========================================================================== +// =========================================================================== +// =========================================================================== +// Formulas to calculate stuff +// =========================================================================== +// =========================================================================== +// =========================================================================== + +/// Calculates the damage of an attack. We don't use the exact formula, but +/// a simplified version of it. In particular, we simplified the "Modifier" +/// term quite a bit. The correct and complete formula can be found [here][1]. +/// +/// [1]: http://bulbapedia.bulbagarden.net/wiki/Damage#Damage_formula +// fn attack_damage(attacker: &Pokemon, defender: &Pokemon, attack: Attack) -> u16 { +// // Depending on the attack category, get the correct stats +// let (attack_mod, defense_mod) = match attack.category { +// AttackCategory::Physical => { +// (attacker.stats().attack, defender.stats().defense) +// } +// AttackCategory::Special => { +// (attacker.stats().special_attack, defender.stats().special_defense) +// } +// }; + +// // Cast everything to f64 to reduce noise in actual formula +// let (attack_mod, defense_mod) = (attack_mod as f64, defense_mod as f64); +// let base_power = attack.base_power as f64; +// let attacker_level = attacker.level() as f64; + +// // The modifier only depends on the type effectiveness (in our simplified +// // version!). +// let modifier = match defender.model().type_ { +// PokemonType::One(ty) => { +// TypeEffectiveness::of_attack(attack.type_, ty).multiplier() +// } +// PokemonType::Two(ty_a, ty_b) => { +// TypeEffectiveness::of_attack(attack.type_, ty_a).multiplier() +// * TypeEffectiveness::of_attack(attack.type_, ty_b).multiplier() +// } +// }; + +// // With every parameter prepared above, here is the formula +// ( +// ( +// ((2.0 * attacker_level + 10.0) / 250.0) +// * (attack_mod / defense_mod) +// * base_power +// + 2.0 +// ) * modifier +// ) as u16 +// } + +// =========================================================================== +// =========================================================================== +// =========================================================================== +// This is just constant data! +// =========================================================================== +// =========================================================================== +// =========================================================================== + +/// The Pokemon database! +/// +/// Apart from the "attacks" field, all values are correct. +const POKEDEX: &'static [PokemonModel] = &[ + PokemonModel { + name: "Bulbasaur", + id: 001, + type_: PokemonType::Two(Type::Poison, Type::Grass), + base_stats: Stats { + hp: 45, + attack: 49, + defense: 49, + special_attack: 65, + special_defense: 65, + speed: 45, + }, + attacks: &[TACKLE], + }, + PokemonModel { + name: "Ivysaur", + id: 002, + type_: PokemonType::Two(Type::Poison, Type::Grass), + base_stats: Stats { + hp: 60, + attack: 62, + defense: 63, + special_attack: 80, + special_defense: 80, + speed: 60, + }, + attacks: &[TACKLE, VINE_WHIP], + }, + PokemonModel { + name: "Venusaur", + id: 003, + type_: PokemonType::Two(Type::Poison, Type::Grass), + base_stats: Stats { + hp: 80, + attack: 82, + defense: 83, + special_attack: 100, + special_defense: 100, + speed: 80, + }, + attacks: &[TACKLE, VINE_WHIP], + }, + PokemonModel { + name: "Charmander", + id: 004, + type_: PokemonType::One(Type::Fire), + base_stats: Stats { + hp: 39, + attack: 52, + defense: 43, + special_attack: 60, + special_defense: 50, + speed: 65, + }, + attacks: &[TACKLE], + }, + PokemonModel { + name: "Charmeleon", + id: 005, + type_: PokemonType::One(Type::Fire), + base_stats: Stats { + hp: 58, + attack: 64, + defense: 58, + special_attack: 80, + special_defense: 65, + speed: 80, + }, + attacks: &[TACKLE, EMBER], + }, + PokemonModel { + name: "Charizard", + id: 006, + type_: PokemonType::Two(Type::Fire, Type::Flying), + base_stats: Stats { + hp: 78, + attack: 84, + defense: 78, + special_attack: 109, + special_defense: 85, + speed: 100, + }, + attacks: &[TACKLE, EMBER], + }, + PokemonModel { + name: "Squirtle", + id: 007, + type_: PokemonType::One(Type::Water), + base_stats: Stats { + hp: 44, + attack: 48, + defense: 65, + special_attack: 50, + special_defense: 64, + speed: 43, + }, + attacks: &[TACKLE], + }, + PokemonModel { + name: "Wartortle", + id: 008, + type_: PokemonType::One(Type::Water), + base_stats: Stats { + hp: 59, + attack: 63, + defense: 80, + special_attack: 65, + special_defense: 80, + speed: 58, + }, + attacks: &[TACKLE, WATER_GUN], + }, + PokemonModel { + name: "Blastoise", + id: 009, + type_: PokemonType::One(Type::Water), + base_stats: Stats { + hp: 79, + attack: 83, + defense: 100, + special_attack: 85, + special_defense: 105, + speed: 78, + }, + attacks: &[TACKLE, WATER_GUN], + }, +]; + +/// List of all attacks. +/// +/// Of course, these are not all attacks. We will probably provide a much +/// bigger database with the next sheet. +const ATTACK_DB: &'static [Attack] = &[ + Attack { + category: AttackCategory::Physical, + name: "Tackle", + base_power: 50, + type_: Type::Normal, + }, + Attack { + category: AttackCategory::Special, + name: "Vine Whip", + base_power: 45, + type_: Type::Grass, + }, + Attack { + category: AttackCategory::Special, + name: "Ember", + base_power: 40, + type_: Type::Fire, + }, + Attack { + category: AttackCategory::Special, + name: "Water Gun", + base_power: 40, + type_: Type::Water, + }, +]; + +// These are just some easy names to be more expressive in the Pokedex. +const TACKLE: &'static Attack = &ATTACK_DB[0]; +const VINE_WHIP: &'static Attack = &ATTACK_DB[1]; +const EMBER: &'static Attack = &ATTACK_DB[2]; +const WATER_GUN: &'static Attack = &ATTACK_DB[3]; + + + + +// =========================================================================== +// =========================================================================== +// =========================================================================== +// Helper functions (you don't need to understand how they work yet) +// =========================================================================== +// =========================================================================== +// =========================================================================== + +/// Reads a string from the terminal/user. +fn read_string() -> String { + let mut buffer = String::new(); + std::io::stdin() + .read_line(&mut buffer) + .expect("something went horribly wrong..."); + + // Discard trailing newline + let new_len = buffer.trim_right().len(); + buffer.truncate(new_len); + + buffer +} + +/// Reads a valid `usize` integer from the terminal/user. +fn read_usize() -> usize { + loop { + match read_string().parse() { + Ok(res) => return res, + Err(_) => println!("That was not an integer! Please try again!"), + } + } +}