Files
JR-priprava-na-skusku/priprava/rust_priprava12.md
2026-03-05 12:09:44 +01:00

28 KiB
Raw Blame History

impl, trait, impl Trait for — krok za krokom

Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej.


Kapitola 1: impl blok — metódy a asociované funkcie

impl blok pridáva funkcie ku štruktúre alebo enumu.

struct Pes {
    meno: String,
    vek: u8,
}

impl Pes {
    // Toto sú funkcie, ktoré patria k Pes
}

Dva typy vecí v impl bloku:

Asociovaná funkcia — nemá self

Volá sa cez Typ::nazov(). Najčastejšie new:

impl Pes {
    fn new(meno: &str, vek: u8) -> Pes {
        Pes {
            meno: meno.to_string(),
            vek,
        }
    }
}

// Volanie:
let rex = Pes::new("Rex", 5);

Nemá self → nie je viazaná na konkrétnu inštanciu → voláš cez Pes::new().

Metóda — má self

Volá sa cez instancia.nazov():

impl Pes {
    fn stekaj(&self) {
        println!("{} štekol!", self.meno);
    }
}

// Volanie:
let rex = Pes::new("Rex", 5);
rex.stekaj();

&self → viazaná na inštanciu → voláš cez rex.stekaj().

3 varianty self

impl Pes {
    // &self — len čítanie, inštancia žije ďalej
    fn meno(&self) -> &str {
        &self.meno
    }

    // &mut self — mení dáta, inštancia žije ďalej
    fn premenuj(&mut self, nove_meno: &str) {
        self.meno = nove_meno.to_string();
    }

    // self — skonzumuje inštanciu, po volaní neexistuje
    fn rozluc_sa(self) -> String {
        format!("Zbohom, {}!", self.meno)
        // po tomto rex neexistuje
    }
}
Signatúra Čo môžeš Inštancia po volaní
&self čítať žije ďalej
&mut self čítať + meniť žije ďalej
self čítať + meniť + konzumovať neexistuje

Kedy let vs let mut

let rex = Pes::new("Rex", 5);
rex.stekaj();         // OK — &self, len čítame
rex.meno();           // OK — &self

// rex.premenuj("Fido");  // CHYBA! premenuj je &mut self, ale rex nie je mut

let mut rex = Pes::new("Rex", 5);
rex.premenuj("Fido");  // OK — &mut self a rex je mut

Viacero impl blokov

// Úplne legálne — Rust ich spojí
impl Pes {
    fn new(meno: &str, vek: u8) -> Pes { /* ... */ }
}

impl Pes {
    fn stekaj(&self) { /* ... */ }
}

Úlohy 1

1a. Napíš štruktúru Obdlznik { sirka: f64, vyska: f64 } a impl blok s:

  • new(sirka, vyska) -> Obdlznik (asociovaná funkcia)
  • obsah(&self) -> f64
  • obvod(&self) -> f64
  • je_stvorec(&self) -> bool
  • zvacsit(&mut self, faktor: f64) — vynásob obe strany

1b. Napíš štruktúru Pocitadlo { hodnota: i32 } s:

  • new() -> Pocitadlo (začni na 0)
  • zvys(&mut self)
  • zniz(&mut self)
  • resetuj(&mut self)
  • hodnota(&self) -> i32

Zavolaj každú metódu a over výsledok.

1c. Čo je zle?

struct Bod { x: f64, y: f64 }
impl Bod {
    fn posun(&mut self, dx: f64, dy: f64) {
        self.x += dx;
        self.y += dy;
    }
}
fn main() {
    let b = Bod { x: 0.0, y: 0.0 };
    b.posun(1.0, 2.0);
}

1d. Čo je rozdiel? Kedy ktoré použiješ?

Pes::new("Rex", 5)   // ???
rex.stekaj()          // ???

Kapitola 2: Metódy vracajúce referencie

Metódy často vracajú referencie na interné dáta:

struct Osoba {
    meno: String,
    zaujmy: Vec<String>,
}

impl Osoba {
    // Vráť referenciu na String — volajúci nemôže meniť
    fn meno(&self) -> &str {
        &self.meno
    }

    // Vráť referenciu na Vec — volajúci nemôže meniť
    fn zaujmy(&self) -> &Vec<String> {
        &self.zaujmy
    }

    // Vráť kópiu — volajúci dostane vlastnú hodnotu
    fn meno_owned(&self) -> String {
        self.meno.clone()
    }
}

Vracanie Option s referenciou

struct Skupina {
    clenovia: Vec<String>,
}

impl Skupina {
    // Nájdi člena — vráť referenciu zabalenú v Option
    fn najdi(&self, meno: &str) -> Option<&String> {
        self.clenovia.iter().find(|c| c.as_str() == meno)
    }

    // Nájdi index a odstráň — &mut self lebo meníme Vec
    fn odstran(&mut self, meno: &str) -> Result<String, ()> {
        let pos = self.clenovia.iter()
            .position(|c| c == meno)
            .ok_or(())?;
        Ok(self.clenovia.remove(pos))
    }
}

Vracanie Vec referencií

impl Skupina {
    // Filtrovanie — vráť Vec referencií
    fn zacinajuce_na(&self, pismeno: char) -> Vec<&String> {
        self.clenovia.iter()
            .filter(|c| c.starts_with(pismeno))
            .collect()
    }
}

Úlohy 2

2a. Máš:

struct Kniznica { knihy: Vec<Kniha> }
struct Kniha { nazov: String, autor: String, rok: u16 }

Napíš:

  • najdi_podla_nazvu(&self, nazov: &str) -> Option<&Kniha>
  • knihy_autora(&self, autor: &str) -> Vec<&Kniha>
  • odstran(&mut self, nazov: &str) -> Result<Kniha, ()>
  • najnovsia(&self) -> Option<&Kniha>

2b. Prečo najdi_podla_nazvu vracia Option<&Kniha> a nie Option<Kniha>?

2c. Prečo odstran vracia Result<Kniha, ()> a nie Result<&Kniha, ()>?


Kapitola 3: Trait — čo to je

Trait je sada metód, ktoré typ musí implementovať. Je to ako rozhranie (interface) v iných jazykoch.

// Definícia traitu — hovorí ČO treba vedieť
trait Zviera {
    fn zvuk(&self) -> &str;
    fn meno(&self) -> &str;
}

Trait hovorí: "ktokoľvek chce byť Zviera, musí vedieť zvuk() a meno()."

Prečo traity?

Bez traitov: každý typ má vlastné metódy s rôznymi názvami.

// BEZ traitu — chaos
impl Pes { fn stekni(&self) -> &str { "Haf!" } }
impl Macka { fn maukni(&self) -> &str { "Miau!" } }
impl Krava { fn buckni(&self) -> &str { "Múú!" } }
// Každý má iný názov metódy — nemôžeš ich volať jednotne

S traitom: všetky typy majú rovnakú metódu.

// S traitom — poriadok
trait Zviera {
    fn zvuk(&self) -> &str;
}
// Teraz každý implementuje rovnakú metódu zvuk()
// a ty ich môžeš volať jednotne

Úlohy 3

3a. Čo je trait vlastnými slovami?

3b. Navrhni trait Tvar s metódami obsah(&self) -> f64 a obvod(&self) -> f64. Len definuj trait, nič neimplementuj.

3c. Navrhni trait Ucet pre bankový účet s metódami:

  • zostatok(&self) -> f64
  • vloz(&mut self, suma: f64)
  • vyber(&mut self, suma: f64) -> Result<(), ()>

Kapitola 4: impl Trait for — implementácia traitu

trait Zviera {
    fn zvuk(&self) -> &str;
    fn meno(&self) -> &str;
}

struct Pes {
    meno: String,
}

struct Macka {
    meno: String,
}

// Implementácia traitu Zviera PRE typ Pes
impl Zviera for Pes {
    fn zvuk(&self) -> &str {
        "Haf!"
    }

    fn meno(&self) -> &str {
        &self.meno
    }
}

// Implementácia traitu Zviera PRE typ Macka
impl Zviera for Macka {
    fn zvuk(&self) -> &str {
        "Miau!"
    }

    fn meno(&self) -> &str {
        &self.meno
    }
}

Teraz obe zvieratá majú metódy zvuk() a meno():

let rex = Pes { meno: "Rex".into() };
let micka = Macka { meno: "Micka".into() };

println!("{} robí {}", rex.meno(), rex.zvuk());     // Rex robí Haf!
println!("{} robí {}", micka.meno(), micka.zvuk());  // Micka robí Miau!

Trait + vlastný impl blok

Štruktúra môže mať aj trait metódy aj vlastné metódy:

// Trait metódy
impl Zviera for Pes {
    fn zvuk(&self) -> &str { "Haf!" }
    fn meno(&self) -> &str { &self.meno }
}

// Vlastné metódy (nie z traitu)
impl Pes {
    fn new(meno: &str) -> Pes {
        Pes { meno: meno.to_string() }
    }

    fn aport(&self) {
        println!("{} ide po loptičku!", self.meno);
    }
}

let rex = Pes::new("Rex");
rex.zvuk();   // z traitu
rex.aport();  // vlastná metóda

Musíš implementovať VŠETKY metódy

trait Zviera {
    fn zvuk(&self) -> &str;
    fn meno(&self) -> &str;
}

// CHYBA — chýba meno()
impl Zviera for Pes {
    fn zvuk(&self) -> &str { "Haf!" }
}
// error: not all trait items implemented, missing: `meno`

Úlohy 4

4a. Máš trait z 3b (Tvar s obsah a obvod). Implementuj ho pre:

  • Kruh { polomer: f64 } — obsah = π×r², obvod = 2×π×r
  • Obdlznik { a: f64, b: f64 } — obsah = a×b, obvod = 2×(a+b)
  • Trojuholnik { a: f64, b: f64, c: f64, vyska_a: f64 } — obsah = a×vyska_a/2, obvod = a+b+c

4b. Implementuj trait Ucet z 3c pre:

struct SporiaciUcet { zostatok: f64 }
struct BeznyUcet { zostatok: f64, limit: f64 }

BeznyUcet má limit prečerpania — vyber() môže ísť do mínusu až po limit.

4c. Čo je zle?

trait Pozdrav {
    fn pozdrav(&self) -> String;
}
struct Robot;
impl Pozdrav for Robot { }

Kapitola 5: Trait s parametrom — impl Trait v argumentoch

Trait môžeš použiť ako typ parametra. Funkcia potom príjme akýkoľvek typ, ktorý implementuje daný trait.

trait Zviera {
    fn zvuk(&self) -> &str;
    fn meno(&self) -> &str;
}

// Funkcia príjme čokoľvek čo je Zviera
fn predstav(zviera: &impl Zviera) {
    println!("{} robí {}", zviera.meno(), zviera.zvuk());
}

let rex = Pes::new("Rex");
let micka = Macka { meno: "Micka".into() };

predstav(&rex);     // OK — Pes implementuje Zviera
predstav(&micka);   // OK — Macka implementuje Zviera

Na skúške — IOManager pattern

Na skúške je typický vzor: metóda berie impl Trait parameter pre I/O operácie. Toto umožňuje testovanie — na skúšku sa dá ConsoleIOManager, na testy MockIOManager.

// Trait definuje AKO sa získava vstup
trait IOManager {
    fn ziskaj_pismeno(&mut self) -> char;
}

// Reálna implementácia — číta z konzoly
struct ConsoleIOManager;

impl IOManager for ConsoleIOManager {
    fn ziskaj_pismeno(&mut self) -> char {
        let mut vstup = String::new();
        std::io::stdin().read_line(&mut vstup).unwrap();
        vstup.trim().chars().next().unwrap_or(' ')
    }
}

// Hra používa trait, nie konkrétny typ
struct Hra {
    slovo: String,
    // ...
}

impl Hra {
    // Berie impl IOManager — funguje s akoukoľvek implementáciou
    fn hraj(&mut self, mut io: impl IOManager) {
        loop {
            let pismeno = io.ziskaj_pismeno();
            // ... spracuj písmeno ...
            if self.je_koniec() { break; }
        }
    }

    fn je_koniec(&self) -> bool { todo!() }
}

// Volanie:
fn main() {
    let mut hra = Hra { slovo: "rust".into() };
    hra.hraj(ConsoleIOManager);
}

Prečo impl IOManager a nie priamo ConsoleIOManager?

  • Ak napíšeš io: ConsoleIOManager, metóda funguje len s konzolou
  • Ak napíšeš io: impl IOManager, funguje s čímkoľvek čo implementuje IOManager
  • Testy môžu použiť MockIOManager ktorý vracia vopred určené písmená

mut v parametri

// Ak trait metóda má &mut self, parameter musí byť mut
fn hraj(&mut self, mut io: impl IOManager) {
    //                ^^^ mut lebo ziskaj_pismeno je &mut self
    let pismeno = io.ziskaj_pismeno();
}

// Ak trait metóda má &self, netreba mut
trait Zobrazovac {
    fn zobraz(&self, text: &str);
}

fn vypis(zobrazovac: &impl Zobrazovac) {
    zobrazovac.zobraz("ahoj");
}

Úlohy 5

5a. Máš trait:

trait Logger {
    fn log(&self, sprava: &str);
}

Implementuj ho pre:

  • KonzolaLogger — vypíše do println!
  • SuborLogger { cesta: String } — len println! s prefixom "[FILE]"
  • TichyLogger — nič nerobí

Napíš funkciu spracuj(data: &str, logger: &impl Logger) ktorá zaloguje "Spracovávam: {data}".

5b. Máš:

trait Validator {
    fn je_platny(&self, vstup: &str) -> bool;
}

Implementuj pre:

  • DlzkaValidator { min: usize, max: usize } — dĺžka v rozsahu
  • EmailValidator — obsahuje '@' a '.'

Napíš funkciu validuj(vstup: &str, validator: &impl Validator) -> bool.

5c. Prečo hraj berie mut io: impl IOManager a nie io: &impl IOManager?


Kapitola 6: Returning impl Trait — vracanie impl

impl Iterator — najčastejšie na skúške

Keď metóda vracia iterátor, typ iterátora je komplikovaný. impl Iterator ho skryje:

struct Skupina {
    mena: Vec<String>,
    vyluceni: HashSet<String>,
}

impl Skupina {
    // Vráť iterátor cez referencie na mená
    fn aktivni(&self) -> impl Iterator<Item = &String> {
        self.mena.iter().filter(|m| !self.vyluceni.contains(*m))
    }
}

Čo to znamená: Funkcia vráti "niečo čo je Iterator a dáva &String". Volajúci nevie presný typ, ale vie iterovať.

Konkrétne na skúške — iterátor na člen štruktúry

Najčastejší vzor: vráť iterátor priamo na pole štruktúry.

use std::collections::HashSet;

struct Hra {
    hladane_slovo: String,
    uhadnute_pismena: HashSet<char>,
    skusane_pismena: HashSet<char>,
    aktualny_pocet_zivotov: u8,
}

impl Hra {
    // Vráť iterátor na uhadnute_pismena
    fn daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char> {
        self.uhadnute_pismena.iter()
    }

    // Vráť iterátor na skusane_pismena
    fn daj_skusane_pismena(&self) -> impl Iterator<Item = &char> {
        self.skusane_pismena.iter()
    }
}

Volajúci potom môže:

let hra = Hra::new("rust");

// Iterovať
for pismeno in hra.daj_uhadnute_pismena() {
    println!("{}", pismeno);
}

// Collect do Vec
let pismena: Vec<&char> = hra.daj_uhadnute_pismena().collect();

// Count
let pocet = hra.daj_skusane_pismena().count();

Pravidlo: Item typ = čo .iter() dáva

Kolekcia .iter() dáva Item typ
Vec<String> &String Item = &String
HashSet<char> &char Item = &char
HashMap<K, V> (&K, &V) Item = (&K, &V)
Vec<Kniha> &Kniha Item = &Kniha

Takže:

fn daj_knihy(&self) -> impl Iterator<Item = &Kniha> {
    self.knihy.iter()
}

fn daj_kategorie(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
    self.slovnik.iter()
}

impl Iterator s filtrom

impl Kniznica {
    // Filtrovanie — stále len impl Iterator
    fn knihy_autora(&self, autor: &str) -> impl Iterator<Item = &Kniha> + '_ {
        self.knihy.iter().filter(move |k| k.autor == autor)
    }
}

+ '_ — lifecycle annotation. Treba ho ak closure v .filter() zachytáva parameter. Ak ti kompilér kričí o lifetimoch pri impl Iterator s filtrom, pridaj + '_.

move — closure zachytí autor hodnotou. Treba ak filter používa parameter funkcie.

Pre skúšku: ak ti to robí problémy, jednoducho vráť Vec<&Kniha> namiesto impl Iterator. Je to menej elegantné, ale funguje:

// Jednoduchšia alternatíva — vždy funguje, žiadne lifetime problémy
fn knihy_autora(&self, autor: &str) -> Vec<&Kniha> {
    self.knihy.iter().filter(|k| k.autor == autor).collect()
}

Úlohy 6

6a. Máš:

struct Trieda {
    studenti: Vec<String>,
    znamky: HashMap<String, Vec<u8>>,
}

Napíš:

  • daj_studentov(&self) -> impl Iterator<Item = &String>
  • daj_znamky(&self, student: &str) -> Option<&Vec<u8>>

6b. Máš struct Kosik { polozky: HashSet<String> }. Napíš:

  • daj_polozky(&self) -> impl Iterator<Item = &String>

6c. Kedy vrátit impl Iterator<Item = &T> a kedy Vec<&T>?


Kapitola 7: Display trait — impl fmt::Display

Na skúške skoro vždy treba implementovať Display, aby si mohol robiť println!("{}", instancia).

use std::fmt;

struct Kniha {
    nazov: String,
    autor: String,
    rok: u16,
}

impl fmt::Display for Kniha {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} — {} ({})", self.nazov, self.autor, self.rok)
    }
}

// Teraz funguje:
let kniha = Kniha { nazov: "Duna".into(), autor: "Herbert".into(), rok: 1965 };
println!("{}", kniha);  // "Duna — Herbert (1965)"

Display pre enum

use std::fmt;

enum Stav { Nova, Pouzivana, Poskodena, Vyradena }

impl fmt::Display for Stav {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Stav::Nova => write!(f, "Nová"),
            Stav::Pouzivana => write!(f, "Používaná"),
            Stav::Poskodena => write!(f, "Poškodená"),
            Stav::Vyradena => write!(f, "Vyradená"),
        }
    }
}

// Teraz funguje:
println!("{}", Stav::Nova);  // "Nová"

Display pre štruktúru s enumom

impl fmt::Display for Kniha {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} — {} ({}) [{}]", self.nazov, self.autor, self.rok, self.stav)
        //                                                                ^^^^^^^^
        //                                      stav.fmt() sa zavolá automaticky
        //                                      lebo Stav implementuje Display
    }
}

println!("{}", kniha);  // "Duna — Herbert (1965) [Nová]"

Display vs Debug

Trait Macro Derive Účel
Display {} NIE — musíš ručne Pre používateľa
Debug {:?} ÁNO — #[derive(Debug)] Pre programátora
#[derive(Debug)]  // Debug cez derive
struct Bod { x: f64, y: f64 }

impl fmt::Display for Bod {  // Display ručne
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

let b = Bod { x: 1.0, y: 2.0 };
println!("{}", b);    // (1, 2)         — Display
println!("{:?}", b);  // Bod { x: 1.0, y: 2.0 }  — Debug

Úlohy 7

7a. Implementuj Display pre:

enum VysledokPokusu { Uhadnute, Neuhadnute, Neplatne }

Výstup: "Uhádnuté", "Neuhádnuté", "Neplatné".

7b. Implementuj Display pre:

struct Zamestnanec { meno: String, pozicia: String, plat: u32 }

Výstup: "Anna (Programátor) - 3000€"

7c. Implementuj Display pre enum aj štruktúru:

enum Priorita { Nizka, Stredna, Vysoka }
struct Uloha { nazov: String, priorita: Priorita, hotova: bool }

Uloha výstup: "[✓] Upratovanie (Vysoká)" alebo "[ ] Upratovanie (Vysoká)" podľa hotova.


Kapitola 8: Default trait — ručne

#[derive(Default)] funguje keď chceš štandardné default hodnoty (0, "", vec![], None). Keď chceš vlastné, implementuj ručne.

// derive Default — automatické
#[derive(Default)]
struct Config {
    port: u16,         // 0
    host: String,      // ""
    debug: bool,       // false
}

// Ručný Default — vlastné hodnoty
struct Config2 {
    port: u16,
    host: String,
    debug: bool,
}

impl Default for Config2 {
    fn default() -> Self {
        Config2 {
            port: 8080,           // vlastná hodnota
            host: "localhost".to_string(),
            debug: false,
        }
    }
}

let c = Config2::default();
// port = 8080, host = "localhost", debug = false

Default pre enum

Enum nemá automatický default — musíš povedať ktorý variant:

enum Stav { Nova, Pouzivana, Poskodena, Vyradena }

impl Default for Stav {
    fn default() -> Self {
        Stav::Nova  // Nova je default
    }
}

let s = Stav::default();  // Stav::Nova

Úlohy 8

8a. Implementuj Default pre:

struct Nastavenia {
    jazyk: String,      // "sk"
    tema: String,       // "svetla"
    velkost_fontu: u8,  // 14
    zvuk: bool,         // true
}

8b. Implementuj Default pre enum:

enum Obtiaznost { Lahka, Stredna, Tazka }

Default: Stredna.


Kapitola 9: Viacero traitov na jednom type

Jedna štruktúra môže implementovať koľko traitov chce:

use std::fmt;
use serde::{Serialize, Deserialize};

// Enum s veľa traitmi
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum Stav { Nova, Pouzivana, Poskodena, Vyradena }

// Ručné traity
impl fmt::Display for Stav {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Stav::Nova => write!(f, "Nová"),
            Stav::Pouzivana => write!(f, "Používaná"),
            Stav::Poskodena => write!(f, "Poškodená"),
            Stav::Vyradena => write!(f, "Vyradená"),
        }
    }
}

impl Default for Stav {
    fn default() -> Self { Stav::Nova }
}

// Teraz Stav má: Debug, Clone, PartialEq, Serialize, Deserialize, Display, Default

Štruktúra s derive + ručné traity + vlastný impl

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Kniha {
    nazov: String,
    autor: String,
    stav: Stav,
}

// Display — ručne
impl fmt::Display for Kniha {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} — {} [{}]", self.nazov, self.autor, self.stav)
    }
}

// Vlastné metódy
impl Kniha {
    fn new(nazov: &str, autor: &str) -> Kniha {
        Kniha {
            nazov: nazov.to_string(),
            autor: autor.to_string(),
            stav: Stav::default(),
        }
    }
}

Tri rôzne impl bloky pre jeden typ — úplne OK:

  1. #[derive(...)] — automatické traity
  2. impl Display for Kniha — ručný trait
  3. impl Kniha — vlastné metódy

Úlohy 9

9a. Vytvor kompletný enum VysledokPokusu s hodnotami Uhadnute, Neuhadnute, Neplatne. Pridaj:

  • derive: Debug, Clone, PartialEq
  • Display: vlastné slovenské texty Napíš 3 testy, kde porovnávaš varianty cez == a vypisuješ cez {}.

9b. Vytvor kompletný typ pre skúšku:

// Enum Stav s derive + Display + Default
// Struct Kniha s derive + Display + new()
// Struct Kniznica s derive + Default + nacitaj/uloz + metódy

Napíš len signatúry a derive/impl hlavičky — telo metód nechaj ako todo!().


Kapitola 10: Kompletný vzor — Obesenec zo skúšky

Toto je celý vzor, ako by vyzerala skúška. Prejdi si ho riadok po riadku.

use std::collections::HashSet;
use std::fmt;
use serde::{Serialize, Deserialize};
use std::path::PathBuf;
use std::collections::HashMap;
use rand::seq::SliceRandom;

// ============================================================
// ENUM — derive + Display
// ============================================================
#[derive(Debug, Clone, PartialEq)]
pub enum VysledokPokusu {
    Uhadnute,
    Neuhadnute,
    Neplatne,
}

impl fmt::Display for VysledokPokusu {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            VysledokPokusu::Uhadnute => write!(f, "Uhádnuté"),
            VysledokPokusu::Neuhadnute => write!(f, "Neuhádnuté"),
            VysledokPokusu::Neplatne => write!(f, "Neplatné"),
        }
    }
}

// ============================================================
// TRAIT — definícia rozhrania
// ============================================================
pub trait IOManager {
    fn ziskaj_pismeno(&mut self) -> char;
}

// ============================================================
// ŠTRUKTURA Hra — logika hry
// ============================================================
pub struct Hra {
    hladane_slovo: String,
    uhadnute_pismena: HashSet<char>,
    skusane_pismena: HashSet<char>,
    aktualny_pocet_zivotov: u8,
}

// Vlastné metódy (asociované funkcie + metódy)
impl Hra {
    // Asociovaná funkcia — Hra::new()
    pub fn new(slovo: &str) -> Hra {
        Hra {
            hladane_slovo: slovo.to_lowercase(),
            uhadnute_pismena: HashSet::new(),
            skusane_pismena: HashSet::new(),
            aktualny_pocet_zivotov: 5,
        }
    }

    // &mut self — mení stav hry
    pub fn tipni_pismeno(&mut self, pismeno: char) -> VysledokPokusu {
        let p = pismeno.to_lowercase().next().unwrap();

        // Už bolo skúšané → Neplatné
        if !self.skusane_pismena.insert(p) {
            return VysledokPokusu::Neplatne;
        }

        // Je v slove → Uhádnuté
        if self.hladane_slovo.contains(p) {
            self.uhadnute_pismena.insert(p);
            VysledokPokusu::Uhadnute
        } else {
            self.aktualny_pocet_zivotov -= 1;
            VysledokPokusu::Neuhadnute
        }
    }

    // &self — len čítanie, vráti iterátor
    pub fn daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char> {
        self.uhadnute_pismena.iter()
    }

    pub fn daj_skusane_pismena(&self) -> impl Iterator<Item = &char> {
        self.skusane_pismena.iter()
    }

    // &self — vracia nový String
    pub fn daj_slovo_skryto(&self) -> String {
        self.hladane_slovo.chars().map(|c| {
            if self.uhadnute_pismena.contains(&c) { c } else { '_' }
        }).collect()
    }

    pub fn daj_dlzku_slova(&self) -> usize {
        self.hladane_slovo.chars().count()
    }

    pub fn je_koniec_hry(&self) -> bool {
        self.aktualny_pocet_zivotov == 0 || self.je_vyhra()
    }

    pub fn je_vyhra(&self) -> bool {
        self.hladane_slovo.chars()
            .all(|c| self.uhadnute_pismena.contains(&c))
    }

    // &mut self + impl IOManager parameter
    pub fn hraj(&mut self, mut io: impl IOManager) {
        while !self.je_koniec_hry() {
            // Vypíš stav
            println!("Životov: {}", self.aktualny_pocet_zivotov);
            println!("Slovo: {}", self.daj_slovo_skryto());
            let skusane: Vec<&char> = self.daj_skusane_pismena().collect();
            println!("Skúšané: {:?}", skusane);

            // Získaj písmeno cez IOManager trait
            let pismeno = io.ziskaj_pismeno();

            // Tipni a vypíš výsledok
            let vysledok = self.tipni_pismeno(pismeno);
            println!("{}", vysledok);
        }

        // Koniec
        if self.je_vyhra() {
            println!("Výhra! Slovo bolo: {}", self.hladane_slovo);
        } else {
            println!("Prehra! Slovo bolo: {}", self.hladane_slovo);
        }
    }
}

// ============================================================
// ŠTRUKTURA SpravcaHry — serde + HashMap
// ============================================================
#[derive(Serialize, Deserialize, Default)]
pub struct SpravcaHry {
    slovnik: HashMap<String, Vec<String>>,
}

impl SpravcaHry {
    // Statická — SpravcaHry::nacitaj_zo_suboru()
    pub fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option<SpravcaHry> {
        let raw = std::fs::read_to_string(cesta).ok()?;
        serde_json::from_str(&raw).ok()
    }

    // &self metóda
    pub fn uloz_do_suboru(&self, cesta: &PathBuf) -> bool {
        let Ok(json) = serde_json::to_string_pretty(&self) else {
            return false;
        };
        std::fs::write(cesta, json).is_ok()
    }

    // &self — vracia Option<Hra>
    pub fn vytvor_novu_hru(&self, kategoria: &str) -> Option<Hra> {
        let slova = self.slovnik.get(kategoria)?;
        if slova.is_empty() { return None; }
        let mut rng = rand::thread_rng();
        let slovo = slova.choose(&mut rng)?;
        Some(Hra::new(slovo))
    }

    // &mut self
    pub fn pridaj_kategoriu(&mut self, kategoria: &str) -> Result<(), ()> {
        if self.slovnik.contains_key(kategoria) {
            return Err(());
        }
        self.slovnik.insert(kategoria.to_string(), Vec::new());
        Ok(())
    }

    // &mut self
    pub fn pridaj_slovo(&mut self, kategoria: &str, slovo: &str) -> Result<(), ()> {
        let slova = self.slovnik.get_mut(kategoria).ok_or(())?;
        slova.push(slovo.to_string());
        Ok(())
    }
}

// ============================================================
// IMPL TRAIT FOR — ConsoleIOManager implementuje IOManager
// ============================================================
pub struct ConsoleIOManager;

impl IOManager for ConsoleIOManager {
    fn ziskaj_pismeno(&mut self) -> char {
        let mut vstup = String::new();
        std::io::stdin().read_line(&mut vstup).unwrap();
        vstup.trim().chars().next().unwrap_or(' ')
    }
}

Úlohy 10

10a. Bez pozerania na kód vyššie, napíš:

  • Enum VysledokPokusu s derive a Display
  • Trait IOManager s metódou ziskaj_pismeno
  • Struct ConsoleIOManager a jeho impl IOManager

10b. Bez pozerania, napíš struct Hra a tieto metódy:

  • Hra::new(slovo: &str) -> Hra
  • tipni_pismeno(&mut self, pismeno: char) -> VysledokPokusu
  • daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char>
  • daj_slovo_skryto(&self) -> String
  • je_koniec_hry(&self) -> bool

10c. Bez pozerania, napíš struct SpravcaHry a:

  • nacitaj_zo_suboru(cesta: &PathBuf) -> Option<SpravcaHry>
  • uloz_do_suboru(&self, cesta: &PathBuf) -> bool
  • vytvor_novu_hru(&self, kategoria: &str) -> Option<Hra>
  • pridaj_kategoriu(&mut self, kategoria: &str) -> Result<(), ()>