Wąż - opis


Podrozdziały:

 

Kolejna, stara gra komputerowa z lat 70-tych ubiegłego wieku. Zasady są następujące. Po prostokątnej planszy porusza się wąż, którym steruje gracz za pomocą klawiszy kursora. W przypadkowych miejscach planszy pojawiają się malinki zjadane przez węża. Po zjedzeniu malinki długość węża rośnie. Gra kończy się, gdy gracz uderzy wężem w ścianę planszy lub w samego węża. Oczywiście zawsze stanie się to prędzej czy później, ponieważ wąż nigdy się nie zatrzymuje i wymaga od gracza ciągłej uwagi.

 

Rysowanie węża

Podstawowym problemem w tej grze jest rysowanie wizerunku węża na planszy. Wąż będzie zbudowany z segmentów o rozmiarze jednego znaku. Na segment wybierzemy literkę O. Po planszy porusza się jedynie głowa węża. Po każdym ruchu głowy ostatni segment jest usuwany z planszy - tzn. na jego pozycji będziemy drukować znak spacji. W rezultacie otrzymamy wrażenie ruchu węża.

obrazek

Do wykonania tej animacji musimy przechowywać współrzędne poszczególnych segmentów węża. Wykorzystamy tutaj specjalną strukturę danych zwaną kolejką cykliczną o następującej budowie:

 

  1. Struktura zawierająca kolejkę składa się z ciągu N elementów ponumerowanych kolejno od 0 do N-1. Dane będą umieszczane w tych elementach.
  2. Do zapisu danych służy wskaźnik początku kolejki pk, który zawiera indeks pierwszego elementu kolejki. Przed zapisem do kolejki nowej informacji wskaźnik ten jest zawsze zwiększany o 1 modulo N. Następnie nowa informacja zapisywana jest w elemencie o indeksie pk. Zwiększanie o 1 modulo N oznacza, iż po osiągnięciu końca struktury, czyli elementu o indeksie N-1, następnym elementem będzie element o indeksie 0.
  3. Do odczytu danych służy wskaźnik końca kolejki kk, który zawiera indeks ostatniego elementu kolejki. Po odczycie danych zawartych w tym elemencie, wskaźnik kk jest zwiększany o 1 modulo N.
 
elementy e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 e11 e12 e13 e14 e15
wskaźniki   kk

kolejka

pk  

 

Powyżej podajemy przykład kolejki zrealizowanej w 16-to elementowym ciągu elementów. Początek kolejki, czyli jej pierwszy element znajduje się w elemencie e11. Koniec kolejki, czyli jej ostatni element, znajduje się w elemencie e5. Kolejka zawiera elementy e11, e10, ..., e6 i e5. Zapis do kolejki trafi do elementu e12 - wskaźnik pk przesunie się na tą pozycję. Odczyt danych z kolejki będzie dotyczył elementu e5, po czym wskaźnik kk przesunie się na pozycję e6.

Aby obliczyć długość kolejki cyklicznej nie wystarczy wykonać odejmowanie pk - kk, ponieważ wskaźniki mogą być przewijane na początek struktury danych z uwagi na operację modulo, jak w poniższym przykładzie:


elementy e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 e11 e12 e13 e14 e15
wskaźniki kolejka pk                   kk

kolejka

 

Teraz kolejka obejmuje elementy e2,e1,e0,e15,e14,e13 i e12. Aby zatem obliczyć długość kolejki cyklicznej na podstawie indeksów pk i kk musimy zastosować poniższą metodę:


  if pk > kk then
    dk := pk - kk + 1
  else
    dk := n + pk - kk + 1;
pk - wskaźnik początku kolejki
kk - wskaźnik końca kolejki
dk - długość kolejki
n   - liczba elementów struktury zawierającej kolejkę

 

Długość kolejki jest dla nas istotnym parametrem, ponieważ określa ilość segmentów węża. Struktura przechowująca kolejkę powinna posiadać wystarczająco dużo elementów, aby nigdy nie zdarzyło się, iż początek kolejki nadpisuje jej koniec. Dla prostoty załóżmy, że potrzebne jest tyle elementów, ile wynosi liczba pozycji znakowych na planszy gry. Plansza posiadać będzie 22 wiersze po 78 kolumn, zatem struktura powinna mieć rozmiar 1716 elementów.

 

Odczyt znaku z określonej pozycji okienka konsoli

Wąż żyje na planszy tak długo, aż nie uderzy głową w barierkę lub w samego siebie. Również musimy sprawdzać, czy głowa węża trafiła w malinkę, której zjedzenie spowoduje wydłużenie węża o 1 segment. Wynika z tego, iż potrzebna nam jest procedura odczytująca znak umieszczony na zadanej pozycji okna konsoli. Procedury takiej nie ma w module Crt, zatem napiszemy ją sami.

Biblioteka Win32 API posiada wszystkie niezbędne procedury do obsługi okienek konsoli. Aby z niej skorzystać, musimy dołączyć do naszego programu moduł Windows, w którym znajdują się deklaracje tych procedur oraz wykorzystywanych przez nie struktur danych. Na początku programu umieszczamy wpis:

 

...
uses Crt, Windows;
...

 

Od tego momentu mamy w naszym programie dostęp do wszystkich procedur biblioteki Win32 API. Do odczytu znaku wykorzystamy następujące struktury danych i procedury:

HANDLE - liczba 32-bitowa zawierająca tzw. numer dojścia lub uchwyt. Biblioteka Win32 posługuje się dojściami przy odwołaniu do jej wewnętrznych struktur danych.

COORD - struktura definiująca współrzędne pozycji w okienku konsoli. Definicja tej struktury dla języka Pascal jest następująca:

 

type
  COORD = record
            X,Y : word;
          end;

 

Pole X przechowuje numer kolumny, a pole Y przechowuje numer wiersza pozycji w oknie konsoli. Przy zapisie danych do tych pól musimy pamiętać o zmniejszeniu wartości o 1, ponieważ konsola Windows numeruje wiersze i kolumny od 0, a nie od 1 jak w module Crt. Zatem pozycja (1,1) powinna być zapisana w strukturze COORD jako (0,0).

 

GetStdHandle(typ_dojścia) - funkcja biblioteki Win32 API udostępniająca dojście do odpowiedniego kanału przesyłu danych w okienku konsoli. Jako parametr stosujemy jedną ze stałych zdefiniowanych w module Windows:

Stała Opis
STD_INPUT_HANDLE standardowe dojście do wejścia danych
STD_OUTPUT_HANDLE standardowe dojście do wyjścia danych
STD_ERROR_HANDLE standardowe dojście do wyjścia wiadomości o błędach

 

Nas będzie interesowało dojście do wyjścia danych, czyli do zawartości okienka konsoli - STD_OUTPUT_HANDLE.

 

ReadConsoleOutputCharacter(dojście, bufor, ile, współrzędne, odczytane) - procedura odczytująca z okienka konsoli podaną liczbę znaków do bufora. Parametry są następujące:

 

dojście tutaj przekazujemy wartość dojścia do standardowego wyjścia danych konsoli, które otrzymamy z wywołania funkcji GetStdHandle(STD_OUTPUT_HANDLE).
bufor adres zmiennej znakowej, do której zostaną przesłane znaki odczytane z okienka konsoli
ile liczba znaków do odczytania. Nas będzie interesował tylko jeden znak na danej pozycji, zatem parametr ten przyjmie wartość 1.
współrzędne struktura COORD zawierająca pozycję w okienku konsoli, z której mają zostać odczytane znaki.
odczytane adres zmiennej, w której procedura umieści liczbę odczytanych znaków.

 

Funkcja zwracająca znak z zadanej pozycji okienka konsoli ma następującą postać:

 

function Znak_na_pozycji(p : COORD) : char;
var
  c : char;
  k : integer;
begin
  dec(p.x); dec(p.y); // Dostosowujemy współrzędne
  ReadConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE),addr(c),1,p,k);
  Result := c;
end;

 

Sterowanie ruchami węża

Wąż jest w ciągłym ruchu. Klawisze kursora nie sterują zatem ruchem węża, lecz jego kierunkiem. Aby gra posiadała odpowiednią dynamikę odczyt klawiatury będziemy wykonywać co około 10 milisekund. Natomiast ruchy węża wykonywane będą co 16 obiegów pętli. Pętla będzie posiadała następujący schemat:

 

Schemat pętli głównej gry
  1. Sprawdź klawiaturę i jeśli naciśnięto klawisz kursora, ustaw odpowiedni kierunek ruchu głowy węża.
  2. Odczekaj 10 milisekund.
  3. Co 16 obiegów tej pętli obsłuż ruch węża.
  4. Jeśli wąż nie uderzył w barierkę ani w samego siebie, wróć się do kroku 1.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe