Základy programovania v JavaScripte

Lekcia 10

Milan Herda, 05/2020

Video k tejto lekcii:

Bonusová úloha:

Skúste Karla rozpohybovať pomocou klávesnice.

Zistite si (google, MDN), ako reagovať na stlačenie klávesy a zabezpečte, aby fungovali štyri klávesy (obdobne, ako naše štyri tlačidlá):

  • šípka hore: pohnutie Karla o jeden krok
  • šípka doprava: otočenie doprava
  • šípka doľava: otočenie doľava
  • medzerovník: uloženie/zodvihnutie značky

Riešenie:

Vygooglime ako v JS reagovať na klávesnicu:

Riešenie:

Vygooglime ako v JS reagovať na klávesnicu:

Zistíme, že stlačenie klávesy (podobne ako kliknutie na myši) v prehliadači vyvolá event.

A rovnako, ako vieme v JS reagovať na eventy spôsobené myšou

element.addEventListener('click', function (event) {

tak vieme reagovať aj na eventy vyvolané klávesnicou

Informácie o stlačenej klávese nájdeme v objekte event

Riešenie:

  • Keď zatlačíte (alebo držíte) klávesu, tak prehliadač vyvolá event "keydown".
  • Keď klávesu uvoľníte, tak vyvolá event "keyup".
  • Môžete reagovať na ktorýkoľvek z nich (odporúčam si vyskúšať oba)

Riešenie:

Objekt event, ktorý prehliadač odovzdá do vášho listenera obsahuje property

  • keyCode - číslo klávesy
  • code - názov klávesy ako reťazec

Tu zistíte, ako tieto kódy vyzerajú pre klávesy, ktorými chcete ovládať Karla: https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event

Riešenie:

Musíme sa rozhodnúť, na aký element zavesiť náš listener.

Najlepšie bude priamo na document. Používateľovi tak bude hneď všetko fungovať a nemusí predtým nikam klikať, ani sa presúvať myšou.

Riešenie:

document.addEventListener('keydown', function (event) {
    switch (event.keyCode) {
        case 38:
            karel.krok();
            break;

        case 39:
            karel.vpravoVbok();
            break;

        case 37:
            karel.vlavoVbok();
            break;

        case 32:
            karel.zmenZnacku();
            break;
    }
});

Pracujeme v súbore index.html

Riešenie:

document.addEventListener('keydown', function (event) {
    switch (event.code) {
        case 'ArrowUp':
            karel.krok();
            break;

        case 'ArrowRight':
            karel.vpravoVbok();
            break;

        case 'ArrowLeft':
            karel.vlavoVbok();
            break;

        case 'Space':
            karel.zmenZnacku();
            break;
    }
});

Pracujeme v súbore index.html

Vaša vlastná mapa pre Karla

Pri programovaní objektov karel a mapa sme sa snažili mať od seba oddelené dáta (premenné a metódy v js/karel.js) od ich vizuálnej reprezentácie (mapa a info o karlovi na index.html)

Toto oddelenie nie je 100%, lebo používame funkcie krokDopredu, otocDolava a pod., ktoré priamo a bez našej kontroly kreslia do mapy.

Urobíme si vlastné kreslenie do mapy, prestaneme používať funkcie krokDopredu, otocDolava a vytvoríme si tak nášho úplne vlastného Karla.

Ako nakreslíme mapu?

Pôvodného Karla a mapu kreslím ako SVG obrázok

SVG sme nepreberali, takže musíme použiť prostriedky HTML

A na toto sa nám hodí tabuľka.

<table>
    <tr>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
    </tr>
</table>

Toto je tabuľka o dvoch riadkoch a dvoch stĺpcoch.

Karlova mapa bude tabuľka o 20 riadkoch a 20 stĺpcoch

Vyskreslime ju nad tlačidlá.

<div class="column">
    <div id="root"></div>
</div>
<div class="column">
    <table>
      <tr>
          <td></td>
          <!-- dalších 19 rovnakých riadkov -->
      </tr>
      <!-- ďalších 19 opakovaní tr a v nich 19-krát td -->
      <!-- jop, to je niečo cez 400 riadkov -->
    </table>
    <section>
        <div class="buttons has-addons">
            

Takto by sme sa upísali k smrti.

Ako správni programátori si tabuľku vygenerujeme v kóde pomocou cyklu.

Pracujeme v súbore index.html

<div class="column">
    <div id="root"></div>
</div>
<div class="column">
    <div id="map-table"></div>
    <section>
        <div class="buttons has-addons">
<!-- atď -->
<script>
function vygenerujMapu () {
    let tabulka = '<table>';
  
    for (let cisloRiadku = 0; cisloRiadku < mapa.vyska; cisloRiadku = cisloRiadku + 1) {
        tabulka = tabulka + vygenerujRiadok(cisloRiadku);
    }
  
    tabulka = '</table>';
  
    return tabulka;
};

function nakresliMapu() {
    const tabulka = vygenerujMapu();
    document.querySelector('#map-table').innerHTML = tabulka;
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

<script>
function vygenerujRiadok(cisloRiadku) {
    let riadokTabulky = '<tr>';
            
    for (let cisloStlpca = 0; cisloStlpca < mapa.sirka; cisloStlpca = cisloStlpca + 1) {
        riadokTabulky = riadokTabulky + vygenerujBunku(cisloRiadku, cisloStlpca);
    }

    riadokTabulky = riadokTabulky + '</tr>';

    return riadokTabulky;
};
  
function vygenerujMapu() {
    // ...
};

function nakresliMapu() {
    // ...
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

<script>
function vygenerujBunku(cisloRiadku, cisloStlpca) {
    return '<td></td>';
};
  
function vygenerujRiadok(cisloRiadku) {
    // ...
};
  
function vygenerujMapu() {
    // ...
};

function nakresliMapu() {
    // ...
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

Tabuľku nevidíme, lebo jej bunky nemajú žiaden obsah.

Problém obídeme nastavením šírky a výšky každej bunky cez CSS

<style>
td {
    border: solid 1px black;
    width: 20px;
    height: 20px;
    font-size: 12px;
}
</style>

Pracujeme v súbore index.html

Ako nakreslíme Karla?

Do bunky, kde sa Karel nachádza môžeme vložiť obrázok (pomocou tagu <img>)

Budeme potrebovať štyri obrázky (pre každý smer) alebo jeden, ktorý budeme otáčať cez CSS.

Ja to urobím jednoduchšie a Karla nebudem kresliť, ale písať :)

Tieto znaky budú reprezentovať Karla a jeho natočenie:

  • ^
  • >
  • <
  • V

Ako nakreslíme Karla?

  • ^
  • >
  • <
  • V

Kľudne použite emoji

  • ⬆️
  • ➡️
  • ⬅️
  • ⬇️
<script>
function vygenerujKarla() {
    let znak = '^';

    if (karel.smer === VYCHOD) {
        znak = '>';
    } else if (karel.smer === ZAPAD) {
        znak = '<';
    } else if (karel.smer === JUH) {
        znak = 'V';
    }

    return '<span class="karel">' + znak + '</span>';
};
  
function vygenerujBunku(cisloRiadku, cisloStlpca) {
    // ...
};
  
function vygenerujRiadok(cisloRiadku) {
    // ...
};
  
function vygenerujMapu() {
    // ...
};

function nakresliMapu() {
    // ...
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

<style>
td {
    border: solid 1px black;
    width: 20px;
    height: 20px;
    font-size: 12px;
}

span.karel {
    color: red;
    font-weight: bold;
}
</style>

Dáme Karlovi štýl

Pracujeme v súbore index.html

<script>
function vygenerujKarla() {
    // ...
};
  
function vygenerujBunku(cisloRiadku, cisloStlpca) {
    let bunka = '<td>';

    if ((karel.riadok === cisloRiadku) && (karel.stlpec === cisloStlpca)) {
        bunka = bunka + vygenerujKarla();
    }

    bunka = bunka + '</td>';

    return bunka;
};
  
function vygenerujRiadok(cisloRiadku) {
    // ...
};
  
function vygenerujMapu() {
    // ...
};

function nakresliMapu() {
    // ...
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

Hmm, prečo sa nehýbe?

Lebo sme mapu dali vykresliť iba raz.

Po každej zmene ju musíme prekresliť.

karel.poZmeneSmeru = function (aktualnySmer) {
    document.querySelector('#direction').innerHTML = aktualnySmer;
    nakresliMapu();
};

karel.poZmenePozicie = function (novyRiadok, novyStlpec) {
    document.querySelector('#row').innerHTML = novyRiadok;
    document.querySelector('#col').innerHTML = novyStlpec;
    nakresliMapu();
};

Pracujeme v súbore index.html

Ako nakreslím položenú značku?

Opäť môžeme buď do bunky vykreslovať obrázok...

Alebo jej jednoducho nastavíme inú farbu pozadia

Pracujeme v súbore index.html

<style>
td {
    border: solid 1px black;
    width: 20px;
    height: 20px;
    font-size: 12px;
}
  
td.znacka {
    background-color: black;
}

span.karel {
    color: red;
    font-weight: bold;
}
</style>
<script>
function vygenerujKarla() {
    // ...
};
  
function vygenerujBunku(cisloRiadku, cisloStlpca) {
    let bunka = '<td>';
  
    if (mapa.bunky[cisloStlpca][cisloRiadku].jeOznacene) {
        bunka = '<td class="znacka">';
    }

    if ((karel.riadok === cisloRiadku) && (karel.stlpec === cisloStlpca)) {
        bunka = bunka + vygenerujKarla();
    }

    bunka = bunka + '</td>';

    return bunka;
};
  
function vygenerujRiadok(cisloRiadku) {
    // ...
};
  
function vygenerujMapu() {
    // ...
};

function nakresliMapu() {
    // ...
};

nakresliMapu();
</script>

Pracujeme v súbore index.html

Prečo sa uloženie značky neprejaví okamžite, ale až po pohybe?

Lebo objekt karel nám nedal možnosť reagovať na uloženie/zdvihnutie značky.

Pracujeme v súbore js/karel.js

const karel = {
    smer: SEVER,
    riadok: 19,
    stlpec: 0,

    poZmeneSmeru: null,
    poZmenePozicie: null,
    poZmeneZnacky: null,
  
    // ...

    zmenZnacku: function () {
        if (stojisNaZnacke()) {
            zdvihniZnacku();
            mapa.zrusOznacenie(this.stlpec, this.riadok);
        } else {
            polozZnacku();
            mapa.oznacBunku(this.stlpec, this.riadok);
        }

        this.upravInformaciuOZnacke();
    },

    upravInformaciuOZnacke: function () {
        if (this.poZmeneZnacky) {
            this.poZmeneZnacky();
        }
    }
};

Pracujeme v súbore index.html

karel.poZmenePozicie = function (novyRiadok, novyStlpec) {
    document.querySelector('#row').innerHTML = novyRiadok;
    document.querySelector('#col').innerHTML = novyStlpec;
    nakresliMapu();
};

karel.poZmeneZnacky = function () {
    nakresliMapu();
};

V tejto chvíli už máme funkčného Karla s kreslením do našej vlastnej mapy.

Môžeme pristúpiť k tomu, aby sme odobrali pôvodne používané funkcie (krokDopredu, otocDolava, stojisNaZnacke...) a spoliehali sa už iba na naše.

...potrebujeme napísať náhradu za tie, ktoré sme ešte nenapísali:

Ale najskôr...

  • stojisNaZnacke
  • jePredTebouStena

Urobte vašu vlastnú implementáciu týchto  funkcií. Nech sú to metódy objektu karel.

  • karel.jePredTebouStena()
  • karel.stojisNaZnacke()

Pracujeme v súbore js/karel.js

const karel = {
    // ...

    stojisNaZnacke: function () {
        if (mapa.bunky[this.stlpec][this.riadok].jeOznacene) {
            return true;
        }

        return false;
    },

    jePredTebouStena: function () {
        if ((this.smer === SEVER) && (this.riadok === 0)) {
            return true;
        }

        if ((this.smer === VYCHOD) && (this.stlpec === 19)) {
            return true;
        }

        if ((this.smer === JUH) && (this.riadok === 19)) {
            return true;
        }

        if ((this.smer === ZAPAD) && (this.stlpec === 0)) {
            return true;
        }

        return false;
    }
};

Pracujeme v súbore js/karel.js

const karel = {
    // ...

    stojisNaZnacke: function () {
        if (mapa.bunky[this.stlpec][this.riadok].jeOznacene) {
            return true;
        }

        return false;
    },

    jePredTebouStena: function () {
        if ((this.smer === SEVER) && (this.riadok === 0)) {
            return true;
        }

        if ((this.smer === VYCHOD) && (this.stlpec === 19)) {
            return true;
        }

        if ((this.smer === JUH) && (this.riadok === 19)) {
            return true;
        }

        if ((this.smer === ZAPAD) && (this.stlpec === 0)) {
            return true;
        }

        return false;
    }
};

Toto tu (riadok 5) je hrozne škaredé a odhaľuje to internú štruktúru objektu mapa.

Pracujeme v súbore js/karel.js

const mapa = {
    // ...
    jeZnackaNaPozicii: function (cisloRiadku, cisloStlpca) {
        return this.bunky[cisloStlpca][cisloRiadku].jeOznacene;
    }
};

const karel = {
    // ...

    stojisNaZnacke: function () {
        if (mapa.jeZnackaNaPozicii(this.riadok, this.stlpec)) {
            return true;
        }

        return false;
    },

    jePredTebouStena: function () {
        // ...
    }
};

Pracujeme v súbore index.html

const vygenerujBunku = function (cisloRiadku, cisloStlpca) {
    let bunka = '<td>';
                
    if (mapa.jeZnackaNaPozicii(cisloRiadku, cisloStlpca)) {
        bunka = '<td class="znacka">';
    }

    if (karel.riadok === cisloRiadku && karel.stlpec === cisloStlpca) {
        bunka = bunka + vygenerujKarla();
    }

    bunka = bunka + '</td>';

    return bunka;
};

Teraz už môžeme z objektu karel vyhodiť pôvodne používané funkcie na manipuláciu s Karlom

const karel = {
    // ...
    vpravoVbok: function () {
        //otocDoprava(); <-- môžeme zmazať
        this.smer = akyJeDalsiSmerVpravo(this.smer);
        this.upravInformaciuOSmere();
    },

    vlavoVbok: function () {
        //otocDolava(); <-- môžeme zmazať
        this.smer = akyJeDalsiSmerVlavo(this.smer);
        this.upravInformaciuOSmere();
    },

    krok: function () {
        if (!this.jePredTebouStena()) { // <-- nahradili sme
            //krokDopredu(); <-- môžeme zmazať
            this.zmenPoziciu();
            this.upravInformaciuOPozicii();
        }
    },

    zmenZnacku: function () {
        if (this.stojisNaZnacke()) { // <-- nahradili sme
            //zdvihniZnacku(); <-- môžeme zmazať
            mapa.zrusOznacenie(this.stlpec, this.riadok);
        } else {
            //polozZnacku(); <-- môžeme zmazať
            mapa.oznacBunku(this.stlpec, this.riadok);
        }

        this.upravInformaciuOZnacke();
    },
};

Pracujeme v súbore js/karel.js

Teraz sa nám zmeny prejavujú už iba na našej mape.

Z index.html môžeme odstrániť pôvodnú mapu a ponecháme si iba našu vlastnú.

Odstránime aj pôvodné JS knižnice (tri script tagy na konci súboru) a ponecháme si iba svoje.

<div class="container" id="main-container">
    <div class="columns">
        <div class="column">
            <section id="table"></section>
            <!-- nahradili sme pôvodnú mapu tou našou -->
        </div>
        <div class="column">
            <section>
                <div class="buttons has-addons">
    
    <!-- ... -->
                  
    <!-- tieto tri script tagy na konci súboru zmažte -->
    <script>
        ! function(l) {
          // ...
        }([])
    </script>            
    <script src="./static/js/1.b368cc4a.chunk.js"></script>
    <script src="./static/js/main.18ec5a2d.chunk.js"></script>
</body>
</html>

Pracujeme v súbore index.html

Tento Karel je už iba váš.

Všetok kód z dnešnej lekcie:

Ďakujem za pozornosť