Gra wąż

0

Witajcie

Ostatnio zrobiłem popularną grę węża :).

Pozdrawiam

Ps. Proszę o opinie i komentarze. Czy wam się podoba czy też nie :).

3

Wrzuć kod na jakiegoś vcsa, ja w żadnym wypadku nie ściągnął bym nic wrzuconego w archiwum

1

Pascal i wcięcia na 5 spacji? Nonkonformista.

1
procedure DrawingSnakeAndFood(wsp_x,wsp_y: integer);
var
   wi,wj: byte;
   point_x1,point_y1,point_x2,point_y2: integer;
   wl,wk: integer;
begin
     point_x1 := 0; point_y1 := 0;
     for wi := 1 to 10 do
        begin
             for wj := 1 to 10 do
                begin
                     point_y1 := wsp_y * (wi - 1);
                     point_y2 := point_y1 + wsp_y;
                     if wi in [2..3] then
                       begin
                            point_y2 := point_y2 - 1;
                            if wi = 3 then
                              point_y1 := point_y1 - 1
                       end
                     else
                         if wi in [4..6] then
                           begin
                                point_y1 := point_y1 - 1;
                                point_y2 := point_y2 - 1;
                                if wi = 6 then
                                  point_y2 := point_y2 - 1
                           end
                         else
                             if wi = 10 then
                               point_y1 := point_y1 - 3
                             else
                                 if wi in [7..9] then
                                   begin
                                        point_y1 := point_y1 - 2;
                                        point_y2 := point_y2 - 2;
                                        if wi = 9 then
                                          point_y2 := point_y2 - 1
                                   end;

                     point_x1 := wsp_x * (wj - 1);
                     point_x2 := point_x1 + wsp_x;

                     if wj in [6..10] then
                       begin
                            if wj = 6 then
                              point_x2 := point_x2 - 1
                            else
                                begin
                                     point_x1 := point_x1 - 1;
                                     point_x2 := point_x2 - 1
                                end
                       end;

                    if board[wi,wj] = 'X' then
                      begin
                           setcolor(4);
                           rectangle(point_x1,point_y1,point_x2,point_y2);
                           setcolor(10);
                           circle(point_x1 + (wsp_x div 2),point_y1 + (wsp_y div 2),wsp_y div 2);
                           putpixel(point_x1 + (wsp_x div 2),point_y1 + (wsp_y div 2),12)
                      end
                    else
                        if board[wi,wj] = '0' then
                          begin
                               setcolor(2);
                               rectangle(point_x1,point_y1,point_x2,point_y2);
                               setcolor(13);
                               circle(point_x1 + (wsp_x div 2),point_y1 + (wsp_y div 2),wsp_y div 2);
                               putpixel(point_x1 + (wsp_x div 2),point_y1 + (wsp_y div 2),11)
                          end
                end
        end
end;

Podoba mi się, Takie nieoczywiste

5

Działa ale ...

  1. Strasznie miga. Niepotrzebnie przerysowujesz cały ekran. Czyścisz i rysujesz na nowo. Tak można robić ale tylko gdy grafikę buforujesz.
  2. Procedura rysowania planszy jest "kosmiczna". Widać, że to efekt końcowy zgadywania tak długo aż zadziała a nie przemyśleń jak ma to działać. Wystarczyła pojedyncza pętla bez wszelkich ifów ... rysująca węża na podstawie tablicy i 1 linijka rysująca jedzenie ( wywołanie procedury rys. jedzenie ). ( przecież i tak kasujesz obraz op każdym kroku ). Co jakby plansza była 1000x1000 wykonywałbyś te operacje if'y, pypisania itp ... milion razy na każdą klatkę?

Skoro już to napisałeś i działa to teraz przemyśl cały algorytm jeszcze raz i napisz ponownie ale tym razem poprawnie. Brawo za dobre chęci ale obecna wersja niestety nie zasługuje na pochwałę od strony planowania i samego kodu.
Jeśli będziesz miał pytania to pomogę.

3

Przy rysowaniu węża istotne jest odmalowywanie tylko tych fragmentów, które uległy zmianie – dotyczy to zarówno węża, jak i interfejsu (punktów, długości itp.). W przypadku konsolowych aplikacji jest to banalne, bo skasowanie bieżącego znaku i namalowanie nowego to po prostu namalowanie nowego – stary znika. Jeśli chodzi o węża, to przemalowuje się go tylko wtedy, gdy się poruszył, a jeśli się poruszył, to zamalowujesz jego ogon i malujesz głowę w nowym miejscu.

To tak w skrócie. I popraw te wcięcia, bo kod wygląda z nimi tragicznie! W Pascalu używa się wcięć dwuznakowych, we wszystkich konstrukcjach kodu. Są wyjątki, ale póki nie potrafisz formatować zwyczajnego kodu w praktycznie w ogóle, to zostawmy temat wyjątków na kiedyś indziej.

2

Generalnie stosując pascal'ową bibliotekę graph / BGI tak należałoby zrobić ( rysowaćtylko zmiany ) bo w sumie nie ma innej możliwości - ale to z wielu powodów problematyczna metoda.. Z tego co pamiętam to w Graph.pas nie było obsługi buforowania aczkolwiek tego typu sztuczek już od lat się nie stosuje bo przekopiowanie bufora do pamięci 20 razy na sek. to czas właściwie pomijalny a metoda taka rozwiązuje wszelkie problemy z nachodzeniem się obiektów, kolorowym tłem ( np. obrazek ) i wiele innych...
Najbardziej podstawowe funkcje do rysowania wykorzystywane w WinApi to SetDIBitsToDevice + GetDC jak o nich poczytasz to będziesz miał solidną bazę do dalszych zabaw.
Funkcje wstępnie wyglądają strasznie ale w praktyce nie jest trudno.

W sumie całość sprowadza się do ( delphi / lazarus ):


procedure Rysuj();
const

  maxBufferX = 1920 ;
  maxBufferY = 1080 ;

Type

  // Twój bufor ekranu jeden typ liniowy, drugi z dostępem do poszczególnych składowych kolorów.
  //
  TScreen = array [ 0 .. maxBufferX*maxBufferY ] of Dword ; // planujemy układ kolorów RGBA
  TScreenRGBA = Array [ 0 .. maxBufferX*maxBufferY ] of record 
    R, G, B, A :byte ;
  end ;

Var

  canvasHandle : HWND ;
  _bmpinf:TbitmapInfoHeader;
  _bmpin:TBITMAPINFO;
  _rgbq:array [0..0]of TRGBQUAD;
  
  Screen : TScreen ;
  ScreenRGBA : TScreenRGBA absolute Screen ; // absolute rzutuje obszar pamięci na Screen  
  
  x, y : integer ;

begin

  //
  // Inicjujemy niezbędne struktury opisujące parametry bufora obrazu
  //
  
  canvasHandle := [HWND] ; // Tu przypisać HWND okna w którym rysujemy.
  
  _bmpinf.bisize:=sizeof(_bmpinf);
  _bmpinf.biwidth:=MaxX;
  _bmpinf.biheight:=MaxY;
  _bmpinf.biplanes:=1;
  _bmpinf.bibitcount:=32;
  _bmpinf.bicompression:=0;
  _bmpinf.bisizeimage:=0;
  _bmpinf.biXpelspermeter:=0;
  _bmpinf.biYpelspermeter:=0;
  _bmpinf.biclrused:=0;
  _bmpinf.biclrimportant:=0;
  _rgbq[0].rgbblue:=1;
  _rgbq[0].rgbgreen:=2;
  _rgbq[0].rgbred:=3;
  _rgbq[0].rgbreserved:=0;
  _bmpin.bmiheader:=_bmpinf;
  _bmpin.bmicolors[0]:=_rgbq[0];
  
  // 
  // Tutaj rysujesz cokolwiek w bufrze ...
  // np. wypełnienie losowymi odcieniami czerwonego
  //
  
  for x := 0 to maxBufferX - 1 do  
  for y := 0 to maxBufferY - 1 do
  begin
    ScreenRGBA [ x + maxBufferX * Y ].R = random(255) ;
  end;
  
  
  
  //
  // Wyrzucasz na ekran / do okna.
  //

  SetDIBitsToDevice( GetDc ( canvasHandle ), 0, 0 ,maxBufferX, maxBufferY, 0, 0, 0, maxBufferY, @Screen[0], _bmpin,DIB_RGB_COLORS ) ;

end;

czyli właśnie kopiowanie zawartości bufora na ekran ... Niestety wszystko rysować trzeba samemu lub użyć jakiejś biblioteki.

1

W załączniku przygotowałem Ci przykładowy program gotowy do kompilacji w Lazarus.

screenshot-20200916200129.png

0

Standardowe wykorzystanie tylnego bufora to nie jest jedyny sposób na przyspieszenie renderowania. W poważniejszych projektach bazujących na FCL i LCL (np. mój Deep Platformer lub ostatnio Richtris), można spokojnie korzystać z natywnego GDI+ i wydajność jest całkiem niezła (tym bardziej w porównaniu do Linuchów) i tam bitmapy jako bufory sprawdzają się super.

Tutaj problemem jest to, że rysowanie odbywa się bezpośrednio na docelowym płótnie (obojętne na którym). Ten problem można rozwiązać poprzez zamalowanie ”nieważnego” kafla zwykłym Rectangle i namalowanie go od nowa. Taki sposób i tak znacząco przyspieszy proces renderowania, dzięki czemu nic nie będzie migać. Oczywiście o ile gra bazuje na tabelarycznie zbudowanej planszy i wężu przesuwającym się o cały kafel (a nie o piksel/kilka pikseli w każdej klatce).

W każdym razie zawsze da się zrobić lepiej niż malować wszystko od nowa. ;)

0

Poprawiłem zgodnie z sugestiami :).

0

Do snake dodałem stronę tytułową umożliwiającą wybór długości węża na początku gry.

2

Nie chcę Cię dobić @Adept123, bo wiadomo że każdy się uczy itd., ale z przykrością stwierdzam, że produkujesz paskudny kod. Paskudny pod względem wizualnym (czytelność jest tragiczna), jak również pod względem algorytmicznym i struktur danych. Łamiesz wszelkie możliwe wytyczne związane z formatowaniem kodu, używasz najróżniejszych przestarzałych technik, a nawet wykorzystujesz iście starożytne własności języka.

Pracujesz na pececie z DOS-em, że używasz krótkich nazw, w dodatku tylko dużymi literami? Mamy 2020 rok, w Lazarusie możesz używać długich nazw plików, a także stosować przestrzenie nazw (kropki wewnątrz nazwy modułu), dzięki czemu łatwiej zarządzać plikami projektu. Poza tym podzieliłeś projekt na dwa moduły w sumie bez żadnej potrzeby – nieco ponad 300 linijek kodu może istnieć w jednym module.

Pierwszy problem to formatowanie kodu, co od razu odpycha:

  • wcięcia są nieregularnej głębokości – w Pascalu obowiązują wcięcia w rozmiarze dwóch spacji,
  • nie stosujesz pustych linii pomiędzy procedurami i do oddzielania niezwiązanych logicznie ze sobą bloków kodu,
  • niektóre słowa kluczowe piszesz dużą literą, choć w Pascalu zawsze wszystkie słowa kluczowe pisze się małymi literami,
  • używasz nic nie mówiących identyfikatorów dla zmiennych, w dodatku w notacji przeznaczonej dla języka C,
  • nazywasz argumenty procedur tak samo jak zmienne, przez co nie da się ich odróżnić od zmiennych, bez zaglądnięcia do nagłówka procedury (lub skorzystania z podpowiedzi edytora kodu), w dodatku wybierasz nazwy kolidujące z wbudowanymi funkcjami (np. Length),
  • piszesz po kilka instrukcji w jednej linijce, co potwornie komplikuje analizę kodu,
  • nazywasz procedury w zły sposób – nazwa każdej procedury powinna się zaczynać od czasownika w najprostszej formie, a nie od rzeczownika.
  • pomijasz znak średnika w każdej ostatniej instrukcji, przez komplikujesz kod i utrudniasz sobie jego pisanie.

Druga rzecz to struktura kodu:

  • używasz kupy zmiennych globalnych, zamiast przekazywanych w parametrze procedur zmiennych lokalnych,
  • tworzysz bardzo długie i wszystko robiące procedury, zamiast pisać więcej krótkich procedur odpowiadających tylko za jedną czynność,
  • piszesz bardzo długie i złożone instrukcje warunkowe, znów robiące wiele rzeczy,
  • piszesz kod proceduralnie, choć znacznie krócej, szybciej i wygodniej jest pisać kod obiektowy (olać, jeśli po prostu uczysz się pisać kod niskopoziomowo – to też ważna umiejętność),
  • porównujesz zmienne logiczne z wartościami True/False w instrukcjach warunkowych, co jest niespotykane/dziwne,
  • nie stosujesz skróconego zapisu inkrementacji (operator += i temu podobne), który jest przecież natywny dla FPC,
  • tworzysz kilka jednoargumentowych warunków zamiast jednego wieloargumentowego (wydłużasz kod).

To tak ogólnie. Jeśli chcesz wiedzieć jak formatować kod, przeczytaj artykuł Object Pascal Style Guide – są pokazane liczne przykłady co robić i czego nie robić. Co do technicznych aspektów to cóż – kursów i tutoriali w sieci mnóstwo, więc jest co czytać.

Nie zniechęcaj się – po prostu przełknij powyższe i zastosuj się do tego, a gwarantuję, że z biegiem czasu będziesz pisał coraz lepszy i coraz ładniejszy kod, który łatwo będzie utrzymywać i łatwo analizować przez osobny postronne.


PS: jeśli publikujesz źródła, to nie wrzucaj osobnych modułów, a po prostu wszystkie pliki projektu, bez pliku wykonywalnego, binarek i kopii zapasowych (zwykle znajdują się w katalogu lib), oraz bez pliku z zapisem sesji (rozszerzenie .lps). Kompletne źródła projektu to plik z rozszerzeniem .lps, wszystkie .pp lub .pas (zależy których używasz) oraz ew. grafiki, dźwięki, pliki konfiguracyjne i inne, bez których program nie będzie działał poprawnie.

Jak jesteś zbyt leniwy na ręczne kopiowanie przed archiwizacją, to skorzystaj z opcji w Lazarusie – w głównym menu kliknij w Project i wybierz opcję Publish project.... Będziesz mógł albo tylko stworzyć kopię plików projektu, albo dodatkowo spakować je do archiwum. Tylko czytaj to co jest napisanie w komunikatach podczas publikowania.

0

Małe zmiany w kodzie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1