Rozdział 19 · Zasady gry

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):

2
1
H
snake to
2
1
H
[nowy _head] + snake[:-1]
move_snake.py
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:

choose_food.py
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)
rng jako parametr – ten sam pomysł przyda się w sekcji 19.30
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.

ALL_CELLS
wszystkie komórki pola
odejmowanie
minus SNAKE_CELLS
pola zajęte przez węża
=
FREE_CELLS
food ∈ FREE_CELLS
Jeśli FREE_CELLS pusty, nie ma fizycznej wolnej klatki do jedzenia.
Nie możesz potajemnie zastąpić klatki dla węży None
Kuszącą, ale błędną decyzją jest na przykład powrót, 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.

Rzeczywiste okno: pole gry całkowicie wypełnione ciałem węża, na górze tablica wyników, na środku napis ZWYCIĘSTWO i końcowy wynik
Rzeczywiste okno po tym, jak wąż uczciwie zajął każdą legalną komórkę pola — GameStatus.WON, a nie jedzenie wewnątrz ciała.
Praktyka: jedzenie tylko na wolnym polu
Automatyczna kontrola – move_snake() i choose_food(): działa jedzenie nigdy nie znajduje się w wężu
Otwórz praktykę →