Vylepšení ovládání Hada

Možná si všimneš, zvlášť jestli jsi už nějakou verzi hada hrála, že ovládání tvé nové hry je trošku frustrující. A možná není úplně jednoduché přijít na to, proč.

Můžou za to (hlavně) dva důvody:

  1. Když zmáčkneš dvě šipky rychle za sebou, v dalším „tahu“ hada se projeví jen ta druhá.
  2. Když se had plazí doleva a hráč zmáčkne šipku doprava, had se otočí a hlavou si narazí do krku.

Pojďme je vyřešit.

Fronta pokynů

Když zmáčkneš dvě šipky rychle za sebou, v dalším „tahu“ hada se projeví jen ta druhá.

Z pohledu programu to chování dává smysl – po stisknutí šipky se uloží její směr, a při „tahu“ hada se použije poslední uložený směr. S tímhle chováním je ale složité hada rychle otáčet: hráč si musí pohlídat, aby pro každý „tah“ hada nezmáčkl víc než jednu šipku. Lepší by bylo, kdyby se ukládaly všechny stisknuté klávesy, a had by v každém tahu reagoval maximálně jednu. Další by si „schoval“ na další tahy.

Takovou „frontu“ stisků kláves lze uchovávat v seznamu. Přidej si na to do stavu hry seznam (v metodě __init__):

        self.queued_directions = []

Tuhle frontu plň po každém stisku klávesy, metodou append. Je potřeba změnit většinu funkce on_key_press – místo změny atributu se nový směr přidá do seznamu. Abys nemusela psát čtyřikrát append, můžeš uložit nový směr do pomocné proměnné:

@window.event
def on_key_press(key_code, modifier):
    if key_code == pyglet.window.key.LEFT:
        new_direction = -1, 0
    if key_code == pyglet.window.key.RIGHT:
        new_direction = 1, 0
    if key_code == pyglet.window.key.DOWN:
        new_direction = 0, -1
    if key_code == pyglet.window.key.UP:
        new_direction = 0, 1
    state.queued_directions.append(new_direction)

A zpátky k logice. V metodě move místo dir_x, dir_y = self.snake_direction z fronty vyber první nepoužitý prvek. Nezapomeň ho pak z fronty smazat, ať se dostane i na další:

        if self.queued_directions:
            new_direction = self.queued_directions[0]
            del self.queued_directions[0]
            self.snake_direction = new_direction

Zkontroluj, že to funguje.

Zpátky ni krok

Když hráč zmáčkne šipku opačného směru, než se had právě plazí, had se otočí a hlavou si narazí do krku.

Z pohledu programu to opět dává smysl: políčko napravo od hlavy je plné, had na něj tedy nemůže vstoupit a hráč prohrává. Z pohledu hry (a biologie!) ale narážení do krku moc smyslu nedává. Lepší by bylo obrácení směru úplně ignorovat.

A jak poznat opačný směr? Když se had plazí doprava, (1, 0), tak je opačný směr doleva, (-1, 0). Když se plazí dolů, (0, -1), tak naopak je nahoru, (0, 1). Obecně, k (x, y) je opačný směr (-x, -y).

Zatím ale pracujeme s celými n-ticemi, je potřeba obě na x a y „rozbalit“. Kód tedy bude vypadat takto:

            old_x, old_y = self.snake_direction
            new_x, new_y = new_direction
            if (old_x, old_y) != (-new_x, -new_y):
                self.snake_direction = new_direction

Dej ho místo puvodního self.snake_direction = new_direction.

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2019/brno-podzim-snake:extensions:0",
      "title": "Vylepšené ovládání",
      "html": "\n          \n    \n\n    <h1>Vylep&#x161;en&#xED; ovl&#xE1;d&#xE1;n&#xED; Hada</h1>\n<p>Mo&#x17E;n&#xE1; si v&#x161;imne&#x161;, zvl&#xE1;&#x161;&#x165; jestli jsi u&#x17E; n&#x11B;jakou verzi hada hr&#xE1;la,\n&#x17E;e ovl&#xE1;d&#xE1;n&#xED; tv&#xE9; nov&#xE9; hry je tro&#x161;ku frustruj&#xED;c&#xED;.\nA mo&#x17E;n&#xE1; nen&#xED; &#xFA;pln&#x11B; jednoduch&#xE9; p&#x159;ij&#xED;t na to, pro&#x10D;.</p>\n<p>M&#x16F;&#x17E;ou za to (hlavn&#x11B;) dva d&#x16F;vody:</p>\n<ol>\n<li>Kdy&#x17E; zm&#xE1;&#x10D;kne&#x161; dv&#x11B; &#x161;ipky rychle za sebou, v&#xA0;dal&#x161;&#xED;m &#x201E;tahu&#x201C;\nhada se projev&#xED; jen ta druh&#xE1;.</li>\n<li>Kdy&#x17E; se had plaz&#xED; doleva a hr&#xE1;&#x10D; zm&#xE1;&#x10D;kne &#x161;ipku doprava,\nhad se oto&#x10D;&#xED; a hlavou si naraz&#xED; do krku.</li>\n</ol>\n<p>Poj&#x10F;me je vy&#x159;e&#x161;it.</p>\n<h2>Fronta pokyn&#x16F;</h2>\n<p>Kdy&#x17E; zm&#xE1;&#x10D;kne&#x161; dv&#x11B; &#x161;ipky rychle za sebou, v&#xA0;dal&#x161;&#xED;m &#x201E;tahu&#x201C; hada se projev&#xED; jen\nta druh&#xE1;.</p>\n<p>Z&#xA0;pohledu programu to chov&#xE1;n&#xED; d&#xE1;v&#xE1; smysl &#x2013; po stisknut&#xED; &#x161;ipky se ulo&#x17E;&#xED;\njej&#xED; sm&#x11B;r, a p&#x159;i &#x201E;tahu&#x201C; hada se pou&#x17E;ije posledn&#xED; ulo&#x17E;en&#xFD; sm&#x11B;r.\nS t&#xED;mhle chov&#xE1;n&#xED;m je ale slo&#x17E;it&#xE9; hada rychle ot&#xE1;&#x10D;et: hr&#xE1;&#x10D; si mus&#xED; pohl&#xED;dat,\naby pro ka&#x17E;d&#xFD; &#x201E;tah&#x201C; hada nezm&#xE1;&#x10D;kl v&#xED;c ne&#x17E; jednu &#x161;ipku.\nLep&#x161;&#xED; by bylo, kdyby se ukl&#xE1;daly <em>v&#x161;echny</em> stisknut&#xE9; kl&#xE1;vesy, a had by\nv&#xA0;ka&#x17E;d&#xE9;m tahu reagoval maxim&#xE1;ln&#x11B; jednu.\nDal&#x161;&#xED; by si &#x201E;schoval&#x201C; na dal&#x161;&#xED; tahy.</p>\n<p>Takovou &#x201E;frontu&#x201C; stisk&#x16F; kl&#xE1;ves lze uchov&#xE1;vat v&#xA0;seznamu.\nP&#x159;idej si na to do stavu hry seznam (v&#xA0;metod&#x11B; <code>__init__</code>):</p>\n<div class=\"highlight\"><pre><span></span>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">queued_directions</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n</pre></div><p>Tuhle frontu pl&#x148; po ka&#x17E;d&#xE9;m stisku kl&#xE1;vesy, metodou <code>append</code>.\nJe pot&#x159;eba zm&#x11B;nit v&#x11B;t&#x161;inu funkce <code>on_key_press</code> &#x2013; m&#xED;sto zm&#x11B;ny\natributu se nov&#xFD; sm&#x11B;r p&#x159;id&#xE1; do seznamu.\nAbys nemusela ps&#xE1;t &#x10D;ty&#x159;ikr&#xE1;t <code>append</code>,\nm&#x16F;&#x17E;e&#x161; ulo&#x17E;it nov&#xFD; sm&#x11B;r do pomocn&#xE9; prom&#x11B;nn&#xE9;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"nd\">@window.event</span>\n<span class=\"k\">def</span> <span class=\"nf\">on_key_press</span><span class=\"p\">(</span><span class=\"n\">key_code</span><span class=\"p\">,</span> <span class=\"n\">modifier</span><span class=\"p\">):</span>\n    <span class=\"k\">if</span> <span class=\"n\">key_code</span> <span class=\"o\">==</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">key</span><span class=\"o\">.</span><span class=\"n\">LEFT</span><span class=\"p\">:</span>\n        <span class=\"n\">new_direction</span> <span class=\"o\">=</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">0</span>\n    <span class=\"k\">if</span> <span class=\"n\">key_code</span> <span class=\"o\">==</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">key</span><span class=\"o\">.</span><span class=\"n\">RIGHT</span><span class=\"p\">:</span>\n        <span class=\"n\">new_direction</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">0</span>\n    <span class=\"k\">if</span> <span class=\"n\">key_code</span> <span class=\"o\">==</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">key</span><span class=\"o\">.</span><span class=\"n\">DOWN</span><span class=\"p\">:</span>\n        <span class=\"n\">new_direction</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span>\n    <span class=\"k\">if</span> <span class=\"n\">key_code</span> <span class=\"o\">==</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">key</span><span class=\"o\">.</span><span class=\"n\">UP</span><span class=\"p\">:</span>\n        <span class=\"n\">new_direction</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">1</span>\n    <span class=\"n\">state</span><span class=\"o\">.</span><span class=\"n\">queued_directions</span><span class=\"o\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">new_direction</span><span class=\"p\">)</span>\n</pre></div><p>A zp&#xE1;tky k&#xA0;logice. V&#xA0;metod&#x11B; <code>move</code> m&#xED;sto\n<code>dir_x, dir_y = self.snake_direction</code> z&#xA0;fronty vyber prvn&#xED; nepou&#x17E;it&#xFD; prvek.\nNezapome&#x148; ho pak z&#xA0;fronty smazat, a&#x165; se dostane i na dal&#x161;&#xED;:</p>\n<div class=\"highlight\"><pre><span></span>        <span class=\"k\">if</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">queued_directions</span><span class=\"p\">:</span>\n            <span class=\"n\">new_direction</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">queued_directions</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n            <span class=\"k\">del</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">queued_directions</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">snake_direction</span> <span class=\"o\">=</span> <span class=\"n\">new_direction</span>\n</pre></div><p>Zkontroluj, &#x17E;e to funguje.</p>\n<h3>Zp&#xE1;tky ni krok</h3>\n<p>Kdy&#x17E; hr&#xE1;&#x10D; zm&#xE1;&#x10D;kne &#x161;ipku opa&#x10D;n&#xE9;ho sm&#x11B;ru, ne&#x17E; se had pr&#xE1;v&#x11B; plaz&#xED;, had se oto&#x10D;&#xED; a \nhlavou si naraz&#xED; do krku.</p>\n<p>Z&#xA0;pohledu programu to op&#x11B;t d&#xE1;v&#xE1; smysl: pol&#xED;&#x10D;ko napravo od hlavy je pln&#xE9;,\nhad na n&#x11B;j tedy nem&#x16F;&#x17E;e vstoupit a hr&#xE1;&#x10D; prohr&#xE1;v&#xE1;.\nZ pohledu hry (a biologie!) ale nar&#xE1;&#x17E;en&#xED; do krku moc smyslu ned&#xE1;v&#xE1;.\nLep&#x161;&#xED; by bylo obr&#xE1;cen&#xED; sm&#x11B;ru &#xFA;pln&#x11B; ignorovat.</p>\n<p>A jak poznat opa&#x10D;n&#xFD; sm&#x11B;r?\nKdy&#x17E; se had plaz&#xED; doprava, <code>(1, 0)</code>, tak je opa&#x10D;n&#xFD; sm&#x11B;r doleva, <code>(-1, 0)</code>.\nKdy&#x17E; se plaz&#xED; dol&#x16F;, <code>(0, -1)</code>, tak naopak je nahoru, <code>(0, 1)</code>.\nObecn&#x11B;, k&#xA0;(<var>x</var>, <var>y</var>) je opa&#x10D;n&#xFD; sm&#x11B;r\n(-<var>x</var>, -<var>y</var>).</p>\n<p>Zat&#xED;m ale pracujeme s&#xA0;cel&#xFD;mi <var>n</var>-ticemi, je pot&#x159;eba ob&#x11B;\nna <var>x</var> a <var>y</var> &#x201E;rozbalit&#x201C;.\nK&#xF3;d tedy bude vypadat takto:</p>\n<div class=\"highlight\"><pre><span></span>            <span class=\"n\">old_x</span><span class=\"p\">,</span> <span class=\"n\">old_y</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">snake_direction</span>\n            <span class=\"n\">new_x</span><span class=\"p\">,</span> <span class=\"n\">new_y</span> <span class=\"o\">=</span> <span class=\"n\">new_direction</span>\n            <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">old_x</span><span class=\"p\">,</span> <span class=\"n\">old_y</span><span class=\"p\">)</span> <span class=\"o\">!=</span> <span class=\"p\">(</span><span class=\"o\">-</span><span class=\"n\">new_x</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"n\">new_y</span><span class=\"p\">):</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">snake_direction</span> <span class=\"o\">=</span> <span class=\"n\">new_direction</span>\n</pre></div><p>Dej ho m&#xED;sto puvodn&#xED;ho <code>self.snake_direction = new_direction</code>.</p>\n\n\n        "
    }
  }
}