Rename sheet folders to assure proper sorting

This commit is contained in:
Lukas Kalbertodt
2017-02-16 15:12:56 +01:00
parent aaf0332117
commit b3664c6ffd
74 changed files with 0 additions and 0 deletions

6
aufgaben/sheet05/README.md Executable file
View File

@ -0,0 +1,6 @@
Blatt 5
=======
Ihr kennt das Spiel: auf einem neuen Branch `sheet5` arbeiten.
**Aber:** brancht bitte von `master` und nicht von `sheet4`, damit eure
Aufgaben vom 4. Blatt nicht im PR auftauchen.

49
aufgaben/sheet05/sol1/Cargo.lock generated Executable file
View File

@ -0,0 +1,49 @@
[root]
name = "poki"
version = "0.1.0"
dependencies = [
"term-painter 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term-painter"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
"checksum term-painter 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bea17428eebaef979ea9602d2141612dbdff9caaec7db369537c4ebac6d00aa1"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

View File

@ -0,0 +1,7 @@
[package]
name = "poki"
version = "0.1.0"
authors = ["Lukas Kalbertodt <lukas.kalbertodt@gmail.com>"]
[dependencies]
term-painter = "0.2"

View File

@ -0,0 +1,9 @@
Bulbasaur
Blastoise
1
0
0
0
0
0
0

View File

@ -0,0 +1,169 @@
use db::types::*;
pub 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.
pub 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.
pub const TACKLE: &'static Attack = &ATTACK_DB[0];
pub const VINE_WHIP: &'static Attack = &ATTACK_DB[1];
pub const EMBER: &'static Attack = &ATTACK_DB[2];
pub const WATER_GUN: &'static Attack = &ATTACK_DB[3];

View File

@ -0,0 +1,22 @@
pub mod types;
pub mod data;
pub use self::types::*;
// pub fn pokemon_by_id(id: u16) -> Option<PokemonModel> {
// match data::POKEDEX.get(id as usize) {
// None => None,
// Some(&pm) => Some(pm),
// }
// }
pub fn pokemon_by_name(name: &str) -> Option<PokemonModel> {
for &pm in data::POKEDEX {
if pm.name == name {
return Some(pm);
}
}
None
}

View File

@ -0,0 +1,118 @@
// This module defines types only. Not all fields or variants of those are
// necessarily used, so we don't want all those warnings. This should be safe
// since no algorithm is defined in this module (in which such a warning
// could hint the existance of a bug).
// #![allow(dead_code)]
/// 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)]
pub struct Attack {
pub category: AttackCategory,
pub 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.
pub base_power: u8,
pub type_: Type,
}
/// Category of an attack.
///
/// Note: currently, the category 'status' is missing.
#[derive(Debug, Clone, Copy)]
pub 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)]
pub enum TypeEffectiveness {
NotEffective,
NotVeryEffective,
Normal,
SuperEffective,
}
/// 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)]
pub 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)]
pub 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)]
pub struct PokemonModel {
/// Name of the Pokemon
pub name: &'static str,
/// ID in the international Pokedex
pub id: u16,
pub type_: PokemonType,
pub 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.
pub 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)]
pub struct Stats {
/// Health points
pub hp: u16,
/// Speed, sometimes called initiative (INIT)
pub speed: u16,
/// Strength of physical attacks (like "Tackle")
pub attack: u16,
/// Strength of special attacks (like "Bubble Beam")
pub special_attack: u16,
/// Defense power against physical attacks (like "Tackle")
pub defense: u16,
/// Defense power against special attacks (like "Bubble Beam")
pub special_defense: u16,
}

View File

@ -0,0 +1,120 @@
use db::types::*;
use super::Pokemon;
impl TypeEffectiveness {
/// Returns the type effectiveness of an attack from one attacker type
/// on one defender type.
pub fn of_attack(attacker: Type, defender: Type) -> Self {
use db::Type::*;
use db::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,
(Ground, Flying) => Te::NotEffective,
_ => Te::Normal,
}
}
/// Returns the corresponding multiplier for the damage formula.
pub fn multiplier(&self) -> f64 {
match *self {
TypeEffectiveness::NotEffective => 0.0,
TypeEffectiveness::NotVeryEffective => 0.5,
TypeEffectiveness::Normal => 1.0,
TypeEffectiveness::SuperEffective => 2.0,
}
}
}
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
pub 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
pub 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
}

View File

@ -0,0 +1,57 @@
use db::types::*;
pub mod canon;
pub struct Pokemon {
/// Reference to the kind of the Pokemon, which contains the name, base
/// stats, id and more global data.
model: PokemonModel,
/// These are the actual stats of the pokemon, that fit to the current
/// level
stats: Stats,
/// The current level of the Pokemon.
level: u8,
}
impl Pokemon {
/// Creates a new living Pokemon of the given Pokemon kind (model) with the
/// specified level.
pub fn with_level(model: PokemonModel, level: u8) -> Self {
Pokemon {
model: model,
stats: Stats::at_level(model.base_stats, level),
level: level,
}
}
/// Returns the current stats.
pub fn stats(&self) -> &Stats {
&self.stats
}
/// Returns the Pokemon kind of this Pokemon.
pub fn model(&self) -> &PokemonModel {
&self.model
}
/// Returns the name.
pub fn name(&self) -> &str {
self.model.name
}
/// Returns the current level.
pub fn level(&self) -> u8 {
self.level
}
/// Decreases the Pokemon's HP according to the given attack and attacker.
pub fn endure_attack(&mut self, attacker: &Pokemon, attack: Attack) {
let damage = canon::attack_damage(attacker, self, attack);
self.stats.hp = self.stats.hp.saturating_sub(damage);
}
/// Returns whether or not the Pokemon is still alive (more than 0 HP).
pub fn is_alive(&self) -> bool {
self.stats.hp > 0
}
}

162
aufgaben/sheet05/sol1/src/game.rs Executable file
View File

@ -0,0 +1,162 @@
use db::types::*;
use db;
use engine::Pokemon;
use term_painter::Color::*;
use term_painter::ToStyle;
pub fn fight(mut red: Pokemon, mut blue: Pokemon) {
loop {
fn check_dead(poki: &Pokemon) -> bool {
if poki.is_alive() {
false
} else {
println!(">>>>> {} fainted!", Yellow.paint(poki.name()));
true
}
}
// Print status
println!(
">>>>> Status: {} has {} HP, {} has {} HP",
Yellow.paint(red.name()),
Red.paint(red.stats().hp),
Yellow.paint(blue.name()),
Red.paint(blue.stats().hp),
);
// Execute both attack
if red.stats().speed > blue.stats().speed {
// Red attacks blue
execute_round(&red, &mut blue);
if check_dead(&blue) {
break;
}
// BLue attacks red
execute_round(&blue, &mut red);
if check_dead(&red) {
break;
}
} else {
// BLue attacks red
execute_round(&blue, &mut red);
if check_dead(&red) {
break;
}
// Red attacks blue
execute_round(&red, &mut blue);
if check_dead(&blue) {
break;
}
}
}
}
/// Executes one round of one player:
///
/// - the player chooses one attack to execute
/// - the attack is excuted and the enemy's HP
fn execute_round(attacker: &Pokemon, defender: &mut Pokemon) {
// Tell the user to choose an attack
println!(
">>>>> {} is about to attack! Which move shall it execute?",
Yellow.paint(attacker.model().name)
);
// Print a list of available attacks
let num_attacks = attacker.model().attacks.len();
for i in 0..num_attacks {
println!(
" {}: {}",
i,
Blue.paint(attacker.model().attacks[i].name)
);
}
println!(" !!! Please give me the attack ID:");
// Read attack ID from the user
let attack_id;
loop {
let input = read_usize();
if input >= num_attacks {
println!(" !!! There is no attack with index {}!", input);
} else {
attack_id = input;
break;
}
}
// Execute attack
let attack = *attacker.model().attacks[attack_id];
defender.endure_attack(attacker, attack);
// Status update
println!(
">>>>> {} uses {}! ({} has {} HP left)",
Yellow.paint(attacker.model().name),
Blue.paint(attack.name),
Yellow.paint(defender.model().name),
Red.paint(defender.stats().hp),
);
}
/// Let's the player choose a Pokemon from the database.
pub fn choose_pokemon(player: &str) -> PokemonModel {
// Loop forever until the user has chosen a Pokemon
loop {
println!(
"{}, please choose a Pokemon (or type '?' to get a complete list)",
player,
);
let input = read_string();
if input == "?" {
print_pokemon_list();
} else {
// Try to find a Pokemon with the given name
match db::pokemon_by_name(&input) {
Some(poki) => return poki,
None => {
println!("No pokemon with the name '{}' was found!", input);
}
}
}
}
}
/// Prints a list of all Pokemon in the database.
fn print_pokemon_list() {
for poki in db::data::POKEDEX {
// This strange formatter will print the pokemon ID with three digits,
// filling in 0 from the left if necessary (#003).
println!("#{:0>3} {}", poki.id, poki.name);
}
}
/// 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!"),
}
}
}

View File

@ -0,0 +1,20 @@
extern crate term_painter;
mod db;
mod engine;
mod game;
use game::{choose_pokemon, fight};
use engine::Pokemon;
fn main() {
// Let both players choose their Pokemon
let model_red = choose_pokemon("Player Red");
let poki_red = Pokemon::with_level(model_red, 5);
let model_blue = choose_pokemon("Player Blue");
let poki_blue = Pokemon::with_level(model_blue, 5);
// Let both pokis fight!
fight(poki_red, poki_blue);
}

View File

@ -0,0 +1,81 @@
Aufgabe 5.1: Pokemon umstrukturieren und bunt machen
====================================================
a) Umstrukturieren
------------------
In der ersten Teilaufgabe geht es darum, unser ziemlich großes Pokemon-Spiel
auf mehrere Module aufzuteilen.
Außerdem soll dieses Spiel nun ein Cargo-Projekt sein.
Fangt am besten mit einem leeren Cargo-Projekt an, baut dann die grundlegende
Modulstruktur und kopiert dann Codeteile aus der Lösung zur vorherigen Aufgabe
in die passenden Module.
Ihr könnt entweder eure Lösung oder [die Musterlösung][ml] nutzen.
Das Projekt soll folgendermaßen strukturiert werden:
- *Wurzel*: Hier befindet sich nur `main()`, die auch selber nur wenig
Code enthält.
- `db`: enthält Funktion `pokemon_by_name()`.
- `data`: enthält die konstanten Arrays.
- `types`: enthält viele wichtige Typen (allerdings ohne `impl` Block,
also nur Datenlayout):
`Attack`, `AttackCategory`, `TypeEffectiveness`, `Type`, `PokemonType`,
`PokemonModel`, `Stats`.
- `engine`: enthält den Typen `Pokemon` mit Methoden.
- `canon`: Enthält alle Funktionen und Methoden die Formeln/Algorithmen aus
dem Pokemon-Universum abbilden.
- `game`: Enthält alle Funktionen, die irgendetwas einlesen oder ausgeben.
Insb. soll hier eine Funktion `fight()` definiert werden, die den ganzen
Kampf abwickelt (damit `main()` schön klein bleibt).
Ihr werdet merken, dass ihr nach dem Einfügen des alten Codes viele Pfade
und andere Dinge ändern müsst.
Benutzt bei `use`-Anweisungen keine Wildcards; die einzige Ausnahme ist das
`types` Modul, aus welchem alle Namen einfach mit einem Wildcard genutzt werden
*können*.
Nach einiger Zeit sollte der Compiler dann endlich zufrieden sein und das
Spiel sollte genau so wie vorher laufen.
[ml]: https://github.com/LukasKalbertodt/programmieren-in-rust/tree/master/aufgaben/sheet3/sol2
b) Farben einbauen
------------------
Um einmal den Umgang mit externen Crates zu üben, sollt ihr in der zweiten
Teilaufgabe die Ausgabe eures Pokemon-Spiels etwas bunter gestalten.
Terminals und dergleichen unterstützen oft irgendeine Art von farblichem Text,
oft allerdings mit nur 8 unterschiedliche Farben.
Um farbigen Text auszugeben, müsste man allerdings Code schreiben, der
plattformabhängig und auch oft unschön ist.
Das wollen wir natürlich vermeiden!
Aber selbstverständlich hatte schon mal jemand das Problem.
Daher gibt es für unseren Zweck schon diverse Libraries, die wir nutzen können.
Wie eine schnelle Suche auf [crates.io](https://crates.io) zeigt, gibt es
folgende Möglichkeiten:
- [`term`](https://crates.io/crates/term): Platform**un**abhängige Library,
allerdings recht *low-level*, also aufwändiger zu bedienen.
- [`ansi_term`](https://crates.io/crates/ansi_term): Plattform**abhängige**
Library, die nur für Ansi-Terminals funktioniert.
- [`term-painter`](https://crates.io/crates/term-painter): Diese Library ist
von mir. Sie baut auf `term` auf, ist also plattform**un**abhängig, aber ist
wesentlich bequemer zu bedienen. Es klingt natürlich direkt so, als würde ich
Werbung machen, aber ich denke, dass es tatsächlich die beste Wahl ist, wenn
man plattformunabhängig programmieren möchte.
- ... und ein paar andere
Eine Library davon sollt ihr nutzen, um eure Ausgabe bunt zu gestalten.
Ihr könnt z.B. Pokemon-Namen immer in einer bestimmten Farbe einfärben und
Attacken immer in einer anderen.
Hauptsache die Ausgabe ist ein wenig übersichtlicher und bunt.
Das Hauptproblem bei dieser Aufgabe wird wahrscheinlich sein, die Dokumentation
von fremden Libraries zu lesen und zu verstehen.
Ich denke aber, dass oft genug Beispiele bereitstehen, um zu verstehen, was man
machen muss.
Sonst gibt es ja immer noch Piazza ;-)