Food and Free Cell
Nowa głowa plus stare ciało budują ruch za jednym przejściem — a ta sama zasada wielu wolnych komórek decyduje, gdzie uczciwie umieścić jedzenie.
Elegancka alternatywa dla cyklu segmentowego
Zamiast przenosić każdy istniejący segment osobno (Rozdział 19.17), Można zbudować zupełnie nową lista – nową głowę przed sobą, stare ciało za nimi, bez Ostatni element (jeśli wąż nie urośnie):
def move_snake(snake, new_head, *, grow):
if grow:
return [new_head, *snake]
return [new_head, *snake[:-1]]
Jednocześnie ogon naturalnie „opada” snake[:-1]
odrzuca ostatni element. Brak pętli, brak ryzyka zamieszania kolejności aktualizacji:
Efekt powstaje w jednym przejściu, zamiast zmieniać istniejące pozycje jedna po drugiej.
Jedzenie to losowa wolna komórka
W rozdziale 19.3 komórka na jabłko była wybierana bez sprawdzenia — przy dość długim wężu jabłko czasem znajdowało się bezpośrednio w ciele. Uczciwa wersja wybiera tylko spośród naprawdę wolnych komórek:
def choose_food(snake, rng, *, half=280, step=20):
occupied = set(snake)
free = tuple(
cell for cell in all_cells(half=half, step=step) if cell not in occupied
)
if not free:
return None
return rng.choice(free)
choose_food() przyjmuje gotowy random.Randomzamiast czytać globalne random moduł bezpośrednio. W prawdziwej grze, random.Random() jest prawdziwym wypadkiem; w testach — random.Random(seed) z ustalonym ziarnistością, aby wynik był przewidywalny i możliwy do zweryfikowania assert-om.Niezmiennik: pokarm — tylko z komórek wolnych
Kontrakt choose_food() można zapisać wzorem:
food ∈ FREE_CELLS, gdzie
FREE_CELLS = ALL_CELLS − SNAKE_CELLS. Musi pozostać
prawdziwe ZAWSZE, w tym skrajne przypadki, które rzadko są osiągane w praktyce, ale które
Trzeba zapewnić: wąż urósł tak bardzo, że zajmuje dosłownie każdą komórkę
pola. Potem FREE_CELLS pusto, i uczciwej odpowiedzi, gdzie położyć
jedzenie, nie ma.
snake[0], gdy nie ma wolnych komórek: wtedy food == snake[0], a niezmiennik „jedzenie nigdy nie jest w wężu” None: Nie ma faktycznie darmowej klatki na jedzenie, a osoba wywołująca kod (Rozdział 19.32) musi to wyraźnie załatwić, zamiast udawać, że jedzenie nadal istnieje.B SnakeApp.game_tick() wygląda to tak: jeśli wąż zjadł
jedzenie i choose_food() powrócił None,
gra przechodzi w stan końcowy GameStatus.WON —
Stawka jest całkowicie zajęta, nie ma dokąd się posunąć, a to jest zwycięstwo, a nie błąd.
state.food w tym stanie również staje się
Noneoraz rendering (sekcja 19.29) muszą to wyraźnie zweryfikować
zanim narysuje jedzenie na ekranie.