• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Arkanoid w konsoli - Ocena jakości kodu + szukam kumpla do wspólnego programowania.

Object Storage Arubacloud
+3 głosów
687 wizyt
pytanie zadane 5 sierpnia 2016 w C i C++ przez Zibby Początkujący (360 p.)
Cześć, nie wiem czy takie pytania są tu odpowiednie (i gdziekolwiek indziej), ale chciałbym, żeby ktoś sprawdził sensowność mojego kodu. Od razu mówię, że to jest ponad 1000 linijek i nie chodzi mi o bardzo szczegółowe informacje. Głównie chciałbym wiedzieć, czy zastosowany przeze mnie sposób na kolizje przedmiotów jest w porządku. czy nie łamię przy okazji jakiegoś moralnego prawa informatyków, albo czy marnuję w jakiś sposób mnóstwo pamięci. To jest moja największa aplikacja, więc chciałbym po prostu wiedzieć, czy to co tutaj robię jest ok.

Mam nadzieję, że komentarze są w miarę pomocne.

main.cpp --> http://pastebin.com/23AqD6iJ

plik.h --> http://pastebin.com/WRgUZvuj

plik.cpp --> http://pastebin.com/hCJDfCKX

 

Co do wspólnego programowania, to ta gra w 90% pokazuje moje umiejętności, więc jak ktoś jest zainteresowany to zachęcam do prywatnej wiadomości.

2 odpowiedzi

+3 głosów
odpowiedź 5 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)
edycja 5 sierpnia 2016 przez MetRiko

1. Wow.. Just wow.. Te komentarze są naprawdę dobre O.o

2. W kodzie zauważyłem, że praktycznie w ogóle nie stosujesz możliwości omijania "klamerek". Wiele warunków i pętli ma w sobie tylko jedną operację, a mimo to i tak znajdują się tam klamerki. Przykładowo:
for(int i=1 ; i<=record_slowdown[0] ; i++) 
{
    if(record_slowdown[i])
    {
        record_slowdown[i] += record_pause;
    }
}

Można zapisać tak:
for(int i=1 ; i<=record_slowdown[0] ; i++) 
    if(record_slowdown[i]) 
        record_slowdown[i]+=record_pause;

Chyba, że to po prostu dla czytelności.. kwestia gustu.

3. Używasz zdecydowanie za dużo if'ów, a za mało switch'ów.. zwłasza w miejscach gdzie aż się prosi o switcha.. Przykład:
if(can_put) //wypisywanie znakow jesli w tym miejscu jest powerup
{
    if(cells.powerups[w-1][h-1] == 1)
    {
        for(int q=0 ; q<screen.Size ; q++)
            show_part+='I';
    }
//...
    else
    {
        for(int q=0 ; q<screen.Size ; q++)
            show_part+=' ';
    }
}

Można zastąpić prostym switch'em:
if(can_put) //wypisywanie znakow jesli w tym miejscu jest powerup
{
    char tempChar;
    switch(cells.powerups[w-1][h-1])
//Przy okazji możemy uniknąć ciągłego odwoływania się do tej wartości.. pobieramy ją tylko raz.. a nie kilka razy jak w przypadku if'ów
    {
        case 1: tempChar='I'; break;
        case 2: tempChar='D'; break;
        case 3: tempChar='?'; break;
        case 4: tempChar='?'; break;
        case 5: tempChar='?'; break;
        case 6: tempChar=char(3); break;
        case 7: tempChar='B'; break;
        default: tempChar=' '; break;

    }
    for(int q=0 ; q<screen.Size ; q++) show_part+=tempChar;
}

4. Zauważyłem, że tworzysz wiele zmiennych w jednej klasie typu: 
ball.bomb_radius
ball.harmless
ball.go_through
ball.record_slowdown

Jest kilka rzeczy, do których się przyczepię.. opierając się właśnie na takich zmiennych
a) Jeżeli masz zmienną czasową (typowy timer), która zawiera w sobie czas działania danego bonusu to siłą rzeczy, jeżeli timer osiągnie zero to bonus będzie nieaktywny.. nie potrzebujesz dodatkowych zmiennych typu bool, aby to stwierdzić.
b) Dlaczego by nie przedstawić bonusów w postaci klas, które by dziedziczyły od głównej klasy bonus? Przy okazji byś troszeczkę pobawił z dziedziczeniem x) Jak by to miało wyglądać? W klasie Ball znajdowałby się wskaźnik do konkretnego bonusu. Jeżeli wskaźnik wskazywałby na adres nullptr to bonus nie byłby aktywny. A jeżeli bonus zostanie zebrany to możesz przypisać do tego wskaźnika (nazwijmy go CurrentBonus) adres konkretnego bonusu, a potem wywołać metodę (wirtualną) CurrentBonus->Reset(); która by "resetowała" zebrany bonus czyniąc go aktywnym (to rozwiązanie by pomogło uniknąć tworzenia czegoś takiego: CurrentBonus=new Bonus_GoThrough()). Oczywiście w pętli logiki umieszczałbyś coś takiego:
if(CurrentBonus) CurrentBonus->Update();
c) Jeżeli nie podoba ci się rozwiązanie z podpunktu b) to możesz stworzyć tablicę dla takich zmiennych i odwoływać się do poszczególnych zmiennych przez enumerację (ale to już kwestia estetyczna).

5. W ramach ułatwienia sobie życia możesz stworzyć funkcję (wykorzystującą bibliotekę windows.h), która umożliwiałaby ustawianie kursora w konsoli podając tylko konkretne współrzędne.. dzięki temu, mógłbyś uniknąć wielu komplikacji i dbaniu dosłownie o każdą kratkę. (tu za dużo spacji.. tam za mało.. a jak jest bonus to trzeba zabrać jeszcze jedną). Jednak to już od ciebie zależy, czy wykorzystasz ten pomysł.

5.5 Skoro już jestem w temacie renderu.. zauważyłem jedną rzecz.. (nie jestem pewien do końca.. jednak wydaje mi się, że -> ) Stosujesz olbrzymie tablice w celu przechowywania bonusów.. To jest chyba największy minus twojego kodu i jego największa wada.. Powinieneś przechowywać bonusy w jakimś "pojemniku" (np. std::vector). A przez "bonusy" mam na myśli przede wszystkim współrzędne. Dlaczego to wszystko? W celu uniknięcia takich "rozwiązań":
void Cells::clear_powerups()
{
    for(int h=Wall_Lenght-1 ; h>=0 ; h--)
    {
        for(int w=0 ; w<Wall_Lenght ; w++)
        {
            powerups[w][h] = -1;
        }
    }
}

Przecież wyczyszczenie powerup'ów mogłoby wyglądać tak: 
Powerups.Clear(); //O ile Powerups byłby wektorem ze współrzędnymi 

Ale jak już mówiłem.. nie wiem czy do końca zrozumiałem ten fragment kodu.. możliwe, że robi on coś zupełnie innego.

6. Ten punkt powinien ci się spodobać najbardziej x) 
Jest on odpowiedzią na pytanie:
 "Jest jakis sposob, zeby poruszac platforma bez przerwy po pierwszym kroku?"
Ano jest.. tylko wymaga użycia biblioteki windows.h, ale skoro i tak ją "includujesz" zgaduję, że nie będzie ci to przeszkadzać. Powiem więcej.. skoro biblioteki conio.h używasz tylko dla getch() to już teraz możesz ją "usunąć" z kodu. A więc najlepiej będzie mi to pokazać na przykładzie..
Przekopiuj sobie ten kod do jakiegoś pliku .cpp i skompiluj za pomocą jakiegoś kompilatora.. 
Aby zobaczyć efekt naciskaj klawisze [LShift] oraz [O].. Możesz nacisnąć też obydwa.

//To jest potrzebne by została wykryta funkcja: GetConsoleWindow();
#define _WIN32_WINNT 0x0500

#include <iostream>
#include <windows.h>

int main()
{
    //Ustawiamy uchwyt do okna (HWND), tak aby wskazywał na naszą konsolę (konsola to też okno).
    HWND Hwnd = GetConsoleWindow();
    while(true)
    {
        system("cls");
        // Tutaj posługuję się stałą dla lewego shifta.. jednak możesz też bez problemu użyć "kodu" liczbowego.
        //                 Left Shift                    Key O
        if(GetAsyncKeyState(VK_LSHIFT) & GetAsyncKeyState(0x4F)) //Operator bitowy AND (&) pozwala tworzyć kombinacje.. dzięki niemu możemy sprawdzić czy został naciśnięty [LShift] oraz klawisz [O]. 
            std::cout<<"[LShift]+[O] Is preesed..\n";
        else if(GetAsyncKeyState(0x4F))
            std::cout<<"[O] Is preesed..\n";
        else if(GetAsyncKeyState(VK_LSHIFT))
            std::cout<<"[LShift] Is preesed..\n";
    }
    std::cin.get();
}

Jeżeli chciałbyś więcej "kodów" do poszczególnych klawiszy to tu masz link z całą listą:
https://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx

7. No to chyba tyle ode mnie.. Mam nadzieję, że ta "krótka" wypowiedź ci w jakiś sposób pomogła.

PS Animacja spirali rządzi! xD

komentarz 6 sierpnia 2016 przez Zibby Początkujący (360 p.)
Bardzo dziękuję. Na pewno ta "krótka" wypowiedź dużo mnie nauczy, bo trochę mi zajmie przetrawienie wszystkiego. Czas napisać grę jeszcze raz :,)
+2 głosów
odpowiedź 5 sierpnia 2016 przez Szykem2 Nałogowiec (29,510 p.)
edycja 5 sierpnia 2016 przez Szykem2

Błędy i sugestie(oczywiście w subiektywnej ocenie) co do Twojego kodu:

  • klasy powinny być w osobnych plikach(przejrzystość kodu),
  • mechaniki taki jak wyświetlanie, kolizje osobiście zrobiłbym jako metody statyczne w innej klasie, ale może tak zostać,
  • jeśli wolisz funkcje to osobiście bym ich nie zaprzyjaźniał tylko używał wszędzie getter'ów (i tak je masz więc po co, skoro nie używasz, a z nimi jest bezpieczniej),
  • przekazywanie do funkcji lepiej zrobić przez stałe referencje aniżeli przez wartość,
  • pola klas powinny być pisane z małych liter i poprzedzone podkreśleniem(konwencja),
  • gettery: brak przedrostka get (przydaje się, często używana konwencja) brak const w sygnaturze: int getWidth() const {return _width;},
  • pól publicznych nie powinno być,
  • za dużo pustych linii(źle się czyta),
  • jednolinijkowe if'y też się źle czyta. Lepiej dać kod wykonywany po spełnieniu warunku do następnej linii i dać jakiś odstęp
  • ball według mnie powinno być pojedyńczą piłką a nie zestawem 10. Lepiej zrobić std::vector<Ball> balls(10); //vector 10-ciu piłek
  • całą logike przeniusłbym do nowej klasy np. ArkanoidController i w mainie bym tylko stworzył obiekt tej klasy i wywołał metody init i run,
  • tablice pozmieniałbym na vectory(nie trzeba się martwić dealokacją pamięci),
  • jak chcesz wartość bezwzględną float'ów/double to używaj funkcji fabs zamiast abs.
  • i jeszcze jedno unikałbym biblioteki windows.h. Czyni twój kod nieprzenaszalnym na inne platformy.

EDIT: Kolizje nie są jakieś tragiczne, aczkolwiek obsługa kolizji na tablicach do najładniejszych i najprostszych nie należy. Znacznie lepiej zrobić sobie strukturę Velocity/Vector i w niej przechowywać kierunki poruszania się(a do odbijania robisz metodę w tej klasie). Nie mam za bardzo czasu się wczytywać dokładnie w kod ale po długości funkcji widać, że da się to naprawdę sporo uprościć.

komentarz 5 sierpnia 2016 przez MichuDev Pasjonat (20,300 p.)

i jeszcze jedno unikałbym biblioteki windows.h. Czyni twój kod na inne platformy.

 Tak, ale gdy stosujemy tego typu zapisy:

#ifdef WIN32
#include <windows.h>
// również można umieszczać funkcje z windows.h oraz zmienne.
#endif

Można również używać API innych systemów w podobny sposób, ale trzeba pamiętać by nie przesadzić, ponieważ kod, który posiada wiele takich deklaracji jest trudniejszy do zrozumienia.

Ale można to zmienić np. gdy mamy funkcjonalność zależną od systemu piszemy 1 plik nagłówkowy i kilka implementacji.

komentarz 5 sierpnia 2016 przez smh Obywatel (1,940 p.)

jak chcesz wartość bezwzględną float'ów/double to używaj funkcji fabs zamiast abs.

W <cstdlib> mamy std::abs tylko dla liczb całkowitych, ale już std::abs z <cmath> działa również dla typów zmiennoprzecinkowych.

W poprzedzaniu pól klas podkreślnikiem nie widzę sensu, ale ogólnie trafne uwagi.

komentarz 6 sierpnia 2016 przez Zibby Początkujący (360 p.)
Wielkie dzięki za wartościowe uwagi i poważne podejście do mojego tematu. Na pewno napiszę ten program jeszcze raz uwzględniając wszystko co napisałeś ;)
komentarz 6 sierpnia 2016 przez Zibby Początkujący (360 p.)
Chociaż mam pytanie. Powiedziałeś, że wyświetlałbyś planszę statyczną metodą, a z tego co wiem, statyczna metoda służy zmienianiu wartości wszystkim obiektom klasy, więc czym by się różniła od zwykłego voida?

Poza tym kolizje też mi nie pasują, bo jak jedna piłka odbije się od ściany to wtedy wszystkie zmienią kierunek..
komentarz 6 sierpnia 2016 przez Szykem2 Nałogowiec (29,510 p.)
Statyczna metoda służy do tego, aby móc ją wywoływać nie mając utworzonego żadnego obiektu tej klasy, nie może modyfikować jednocześnie zmieniać wartości pól obiektów. Nie może nawet bezpośrednio zmieniać wartości pól obiektów. Pewnie chodzi o zmienną statyczną, która też istnieje poza obiektami, można się odwoływać bez obiektu ale obiekt może się do niej bezpośrednio odwołać jak do swojego pola(i w tedy można nie rozumiejąc działania zmiennych statycznych powiedzieć, że zmieniło się we wszystkich obiektach).

Jeśli tak działają kolizje to sugeruję aby każdy obiekt typu Ball miał obiekt typu Velocity i w tedy nie będzie możliwości zmieniać prędkości dla kilku piłek jednocześnie(przy założeniu jednowątkowości).

Podobne pytania

+4 głosów
0 odpowiedzi 337 wizyt
+1 głos
2 odpowiedzi 228 wizyt
pytanie zadane 17 kwietnia 2020 w Nasze projekty przez Janab Nowicjusz (170 p.)
0 głosów
1 odpowiedź 315 wizyt

92,555 zapytań

141,402 odpowiedzi

319,540 komentarzy

61,938 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...