1 / 24
Feb 2018

Czas ucieka, człowiek już zrobił dokumentację i zamknięcie roku w księgowości, a w związku z tym dużo więcej czasu spędza w algorytmach, programowaniu czy na technologiach sieciowych (i od dwóch dni walczy z ISPem, IPv4/IPv6, NAS326, …) i innych przyjemnych rzeczach :wink: Niestety, w każdej beczce miodu może znaleźć się łyżeczka dziegciu. Co jednak, jeżeli proporcje wyniosą 1:1?

Na SPOJu nie brakuje uwag dotyczących stylu kodzenia, który u niektórych jest na prawdę fatalny. Trudno się dziwić. Jak słusznie zauważył @mariusz193 - na SPOJu mamy mnóstwo osób początkujących, kilka zaawansowanych i być może zero osób na średnim poziomie zaawansowania. Taka sytuacja, w połączeniu z cechami SPOJa, sprzyja wyrabianiu złych nawyków. Nie ma nic złego w pisaniu kodu “na kolanie”, o ile ktoś wie, jak napisać kod porządnie. Analogicznie w codziennym życiu: nie ma nic złego w pisaniu SMSa z literówkami co trzecie słowo (bo to SMS), o ile ktoś potrafi napisać piękne wypracowanie (bo takie ma być wypracowanie). Obawiam się jednak, że mnóstwo osób nie wie tak na prawdę, w jaki sposób pisać ładny kod. Dla osoby początkującej kwieciste konstrukcje i zasady proponowane przez różne osoby i firmy są koszmarnie złożone. Jestem przekonany, że @narbej swoją wzmianką o szablonach nikogo nie zachęcił do ich używania, skoro od razu można użyć rzutowania typów w stylu C / copy pastować funkcje wprowadzając w niej drobne poprawki (jak zrobił pewien - niestety - mentor młodocianych speców z branży IT).

Szczęśliwie się składa, że w ramach działalności gospodarczej ludzie trafiają do takich miejsc, gdzie nigdy nie było kilkudziesięciu programistów po najlepszych kursach i studiach, porozumiewających się ze sobą jedynie w Pythonie i każdy z grupą krwi C lub C++. Różni informatycy (bo nie zawsze programiści), często ponad 20 lat temu, dokładali swoje trzy grosze do jakiegoś kodu i… znikali. Bo przechodzili do korpo, bo mieli dość, bo mieli innego klienta, … Po ok. 30 latach pewna firma posiada kod źródłowy, który nosi wszelkie cechy kodu NIE nadającego się do dalszej pracy nad nim. Bywa, że łatwiej napisać od nowa całą funkcjonalność (włącznie z tłumaczeniem przez klienta, co ona ma robić, jak ma wyglądać interfejs, …) niż wyjaśnić co robi już stworzony kod (o poprawkach nie mówiąc) :wink: Co ciekawe, kod przypomina dzieła wielu początkujących programistów, tylko liczba linii kodu jest min. 100x większa :wink:

Wiem, że większość używa tutaj C++, natomiast poniższy kod jest napisany w Harbour 2.0. Większość uwag jest jednak ogólna. Oczywiście, jak zawsze, zachęcam do przedstawiania własnych pomysłów i krytykowania moich uwag. Styl programowania to nie nauka ścisła i każda opinia jest bardzo cenna. Co nieco np. tu34, ale też np. tu25.

Ad rem. Poniższe fragmenty kodu skopiowałem z corowego programu pewnej firmy, bez którego to programu firma leży. Wprowadziłem jedynie drobne poprawki redakcyjne i usunąłem zaciemniające obraz rzeczy. Zastanówcie się nad tym, dlaczego tak trudno wprowadzać zmiany w tym kodzie i dlaczego jeden z programistów po zobaczeniu sytuacji, pod pretekstem pójścia po kanapki do samochodu, zwiał z firmy pierwszego dnia pracy :wink:

PROCEDURE CallAveTrans()
  LOCAL sel,ord,magfoot,rec
  LOCAL oldKonOrd := kontr->(IndexOrd())
  PRIVATE ad := ai := ap := ak := {},acounted:=.t.,mag:=mag->nrmag
  PUBLIC LICZENIE_S := .T. //uruchamianie modulu z liczeniem srednich
  prog := par_prog
  zsel = Select()
  rec := Recno()
  PRIVATE SumIlosc:=0, SumWartosc:=0

Oto piękny przykład kodu, którego działania nikt nie rozumie. CallAveTrans - ok, osoby wdrożone kod zrozumieją nazwę funkcji, a ściślej - procedury (w Harbour istnieje różnica, zainteresowanych odsyłam do dokumentacji samego Harboura, ale można też korzystać z (polskojęzycznej!) dokumentacji Clippera: link11). Myślę, że każdy domyśla się też, że sumWartosc to będzie jakaś suma, np. wartości jakiegoś towaru. I teraz zaczynają się schodki…

Dlaczego nazwy polskie są pomieszane z angielskimi? Ze względu na profil firmy, stosowanie nazw polskich jest wskazane. Ba! Ich stosowanie ułatwia pracę z kodem - nikt nie zastanawia się, czy funkcja average(x) jest funkcją standardową, czy z jakiejś powszechnie dostępnej biblioteki czy też stworzoną - być może kilkanaście lat temu, przez osobę, która już nie pracuje - specjalnie na potrzeby projektu. Jeżeli funkcja zdaje się nie działać dla jakiegoś przypadku, mamy problem - gdzie szukać informacji na jej temat? Dobra nazwa funkcji może nam coś zasugerować, zła - wymusić szukanie wszędzie.

Zagadka! Po co jest zmienna sumIlosc? By sumować ilości? Tak, masz rację (choć mylące nazwy też się zdarzają w innych miejscach…). A co robią wymienione zmienne: ad := ai := ap := ak := {}? Też chciałbym wiedzieć :wink: I dowiem się, jak przelecę się przez ileś plików i ileś funkcji i może napiszę program wołający część z nich w określonej kolejności, czyli za tydzień lub dwa, biorąc pod uwagę wielkość plików z kodem. Oczywiście to wersja optymistyczna i nikt z IT nie podziela mojego entuzjazmu.

public to jedna z najgorszych rzeczy w kodzie. Ile razy różne osoby walczyły z programem, który nie działał jak trzeba, bo - jak się okazało - w pliku nietykanym od lat tkwił public, który spowodował konflikt z nowo wprowadzoną zmienną tego zakresu.

private to prawie że public (odsyłam do dokumentacji). Użycie tego tworu zmniejsza ilość klepania przy wołaniu funkcji i procedur, natomiast życzę szczęścia każdemu, kto potem próbuje ustalić sens kolejnych zmiennych. Zresztą wyobraźcie sobie poniższy kod w C++. Oczywiście funkcje są dużo bardziej złożone, a plików jest ok. 20. Życzę powodzenia w analizowaniu takiego kodu, który kompilowałby się, gdyby cpp było zbliżone do xBase. Swoją drogą - czy ktoś wie, czym są zmienne prog albo zsel? Opcje są dwie: albo procedura CallAveTrans jest wołana z innego miejsca w kodzie i tam (może) dowiemy się o co chodzi, albo ktoś tworzy te zmienne de novo jako private, ale z lenistwa nie zaznacza tego w kodzie…

//plik 1.cpp
void g() {
  int x = 1;
  h();
}

//plik 2.cpp
void h() {
   f();
}

//plik 3.cpp
int f() {
  return x;
}

Po temacie samoobjaśniających się zmiennych pozwolę sobie zauważyć, ze zmienne typu ai nie bez powodu zawierają prefiks ‘a’. Jest to próba (wg mnie skrajnie nieudolna) zastosowania notacji węgierskiej, która w jednych miejscach występuje, w innych mnie, w jeszcze innych występuje, ale źle (np. zmienne rzekomo znakowe w rzeczywistości przechowują wartości true/false) :wink: Notacja węgierska jest w Harbourze wskazana, bo typów jest stosunkowo mało, a dynamiczne typowanie w Harbourze jest bardzo, może wręcz za bardzo dynamiczne.

Czy na prawdę zainicjalizowanie takiego rec w momencie deklaracji było aż tak trudne? Kod byłby krótszy, co akurat w tym przypadku pomaga…

Dlaczego private są tworzone w środku kodu? To przykład bardzo złej praktyki - zamiast zerknąć na “górną część” kodu funkcji i wiedzieć co może wystąpić w jej ciele, muszę przygotować się na błądzenie w celu znalezienia odpowiednich zmiennych.

PROCEDURE AverageTrans
PROCEDURE CallAveTrans()
PROCEDURE SumTransFor(wysw,aveonly,hanonly)

Ok… czyli raz funkcje/procedury będą wyglądały jak funkcje/procedury: xxx(), a raz nie. Jeżeli komuś nie chciało się poświęcić sekundy na wklepanie znaków ‘(’ oraz ‘)’, strach pomyśleć, co będzie dalej. Btw - ileż razy na SPOJu i poza nim ludziom nie chciało się np. zrobić wcięcia w kodzie…

  IF Alias() == "MAG"
    @ t+1, l+7 SAY Transform(mag->bo + mag->ilosc - mag->il_blok, "@E 99,999,999")+IF(mag->il_blok>0,'+',' ')
  ELSE
    PRIVATE xKey := field->symbol

Ciąg dalszy zabawy. Tworzymy xKey gdzieś tam daleko w kodzie w jakimś else. Super. Z pewnością 100 linii i pięć miesięcy dalej będę pamiętał, że xKey istnieje tylko wtedy, jeżeli Alias() != “MAG”. Na szczęście ktoś był miły i dodał, że xKey jest nieokreślonego typu - przynajmniej nie będę się dziwił, gdy nagle z field->symbol (czyli typ znakowy) przejdę do field->ilosc (typ numeryczny).

  IF Type("d2")#"D" 

  IF Type('frkontr')<>"L"
  IF !frkontr

Może jeszcze jakiś sposób na negację? Mamy #, mamy <>, mamy (choć nie tutaj) !=, … Może nieprawda_ze_jest_prawda(x)?

 N_ZapKeys()
  IF Len( ai ) < 9
    Komunikat( "Przekaż administratorowi: [AI =" + ATS( Len( ai ) ) + "]" )
    RETURN
  ENDIF

To w końcu jak nazywamy funkcje? AlaMaKota, Alamakota, ala_ma_kota, ALAMAKOTA, … ? A moze AllaHasACat? Wbrew pozorom jasne nazewnictwo jest kluczowe - o ile treść ma być samoobjaśniająca się w sensie “co to robi” (np. przechowuje sumę liczb), o tyle sposób zapisu ma objaśniać “czym to jest” (np. funkcją, zmienną, constem, …). Naciska na to Google, naciskają na to ludzie siedzący w Javie, … ale jak widać, nie dotarło do wszystkich. Bo można szybko, a czas to pieniądz. A że potem już będzie skrajnie wolno - cóż…

@ Row()+1,l+41 SAY SubStr(ak[i],r-l-41,r-l-42)  //Druga czesc listy kontr

Przykład zapisu, który jest dopuszczalny, ale wymaga bardzo dobrych komentarzy tu i wcześniej. Dlaczego? Dlatego, że po tygodniu nikt nie wie, dlaczego r-l-41, a nie np. r-l-42.

 klawisz := 0

Takie coś znalazłem sobie w środku jednej funkcji… i tak sobie to tam było. Ciekawe, co to robi…

#define medSUMWORKING .F.

Takie coś znalazłem w środku pliku przed jakąś funkcją. Zalecam define robić na górze pliku. I nie tylko ja. Przyczyna? Każdy wie, na jakie define zwracać uwagę i jakie są ustawione, a nie szuka ich po pliku

STATIC FUNCTION shTimeLIne()

Kolejny przykład niechlujstwa w nazewnictwie… ale to oczywiście ujdzie w projekcie liczącym dziesiątki tysięcy linii kodu, zwłaszcza, że Harbour jest case insensitive

PROCEDURE L_Trans //Wyswietlanie listy rozchodow/przychodow pozycji magazynowej

STATIC FUNCTION shTimeLIne()
 // Wyswietlic timeline
RETURN //-- shTimeLIne --

PROCEDURE LTrKontr()

PROCEDURE Call_SumtransFor()

A tutaj akurat przykład tego jak bardzo można namieszać w kodzie. Co do idei funkcje są po to, by coś zwracać. Np. w matematyce f(x)=x+3 zwraca nam x powiększone o 3. W kodzie panuje zasada, że procedury mają nic nie zwracać (return nil; odpowiednik nulla), a funkcje mają coś zwracać. Jak to się ma do shTimeLIne()?

LOCAL oldFoot := QFooter('TAB - Wszyscy   ESC - Wyjscie')
  @t+4,l+17 SAY "           powy"+cpl('z')+"ej "+Alltrim(Str(prog))+" %"
      retval = PadR(Left( If( !Empty( dost2 ), '*','' ) + kontr->skrot,offset),offset)
  PRIVATE retval := '',dic
  IF Faktura(field->rej)
PadR(QDShort(dic,Right(field->rej,1))+'/'+Right(field->okres,4)+"/"+AllTrim(Str_Nrdok(field->nrdok)),16)

FUNCTION checkMagShow()
RETURN ( sbRegGet("CURRENT_MAG_ONLY",0) == 1) 

Znowu błąd w sztuce. W końcu t + 4 i x := y czy t+4 i x:=y czy jak? Odpowiedzcie sami próbując to przeczytać :wink:

retval += Transform(Iif(trans->typ="+",trans->cena_zak,trans->cena),"@E 99999.9999")+;
                Transform(Iif(trans->typ="+",trans->cena_wal,trans->cena),"@EZ 9999.99999")+;
                IF(trans->waluta="U","$",trans->waluta)+;
                Transform(Iif(trans->typ="+",trans->cena_cpt,trans->cena),"@EZ 9999.999999")

Udzieliliście odpowiedzi? To przeczytajcie to :wink: Oczywiście na SPOJu cin>>t jest zrozumiałe, ale jest też świat poza SPOJem!

PRIVATE myArr := {}

Ok. Czyli wiemy, że ktoś ma swoją, własną, prywatną (if you know what I mean) tablicę :wink: Ciekawe po co… hm…

    PROCEDURE Trans_ProcLogPrint()
    LOCAL oldScr, oldKeys := N_Savekeys(), cSql  
    PRIVATE myArr := {}, myProcLog := ""                                              
  SAVE SCREEN TO oldScr                                                                
  N_ZapKeys()

Wcięcia?

  SET FILTER TO trans->nrmag = m->mag //AMI-050224 m->mag // AJ 20120308 ?
  SET ORDER TO 0
  SELECT trans
    
  //Czyscimy
  DBSetOrder( oldTransOrd )
  SELECT (oldSel)

Pomijam komentarze, ale mieszanie funkcji i rozkazów nie jest wskazane. Więcej informacji o rozkazach w dokumentacji. Można przyjąć, że rozkaz to taki #define upraszczający wywołanie funkcji.

// 040413 Poprawki do Analizy ¦rednich

Nie używamy polskich znaków, jeżeli nie jest to konieczne. A nie jest.

  Verify_Index('',file,"field->kontr",file,.T.) //cs_path // BC 20120308: moze problem z indeksami jak w drukowaniu pz-tek?
// TODO AJ 20110908 Bardzo Zle Zle Zle !!!
// ale 'na razie'

// TODO AJ 20110914 - Jeszcze gorzej !!!
// 1. Plik to jest .dbf
// 2. Indeks jest w podkatalogu a nie na sciezke

Komentarz ma coś objaśniać, wnosić coś do kodu. Te komentarze nie wnoszą niczego (dobrego)… Hardcore to Delete_File(file) // BC 20120308 kasuje calosc, gdzie komentarz jest akurat dobry - kasujemy plik, który jest tworzony… randomowo :wink:

  ELSE
    AV(1)
    AV(2)
    AV(3)
    AV(4)
    AV(5)
    AV(6)
    AV(9)
    AV(12)
    AV(24)
  ENDIF

A teraz niech ktoś powie co robi AV(x) i dlaczego nie dostaje tablicy / nie ma w sobie zaszytej lokalnej tablicy i nie przelatuje jej for eachem albo “zwykłą” pętlą od i := 1 (Harbour - numeracja od 1) do len(tablica)?

PROCEDURE BezDuzych()

FUNCTION Flt_Search

Polak, Anglik, dwa bratanki?

   PRIVATE x1,x2,x3,x4,x5,x6,x7,x8,x9
  sel:=Select()
  DO WHILE len(ap)<9
    Aadd(ap,0)
  ENDDO
  DO WHILE len(ak)<9
    Aadd(ak,'')
  ENDDO
//  kalina:=0
  SELECT tempik
  SUM field->i1,field->i2,field->i3,field->i4,field->i5,field->i6,field->i7,field->i8,field->i9;
     TO ap[1], ap[2], ap[3], ap[4], ap[5], ap[6], ap[7], ap[8], ap[9]

Ktoś wie, co tu się dzieje?

Oczywiście są to tylko przykłady w Harbour i prośba do was, byście nie robili czegoś takiego. W żadnym języku programowania.

Jeżeli ktoś zaprzyjaźni się z xBase, może się zainteresować, czy w sprzyjającym śmieciowemu kodowaniu języku można stworzyć coś, co działa. Odpowiedź jest taka sama jak w przypadku Ruby czy Pythona. Dużym ułatwieniem w Harbour jest podejście obiektowe, które pozwala ustatycznić typowanie i umożliwia dbanie o porządek w kodzie na dużo wyższym poziomie niż stosowane w firmie podejście proceduralne:

/*
 * $Id: drzewko.prg v 3.0 2018-01-17 BA$
*/

/*
 * !!! NIE PROWOKOWAC TRAGEDII PROBAMI OPTYMALIZACJI KODU !!!
 * 
 * Zlozonosci (generowanie drzewa):
 * Pamieciowa: O(n^k)
 * Czasowa: O(n^k)
 * n - stala pesymistycznie nie mniejsza niz liczba mnoznikow, k - liczba poziomow drzewa
 * Zlozonosci (ocenianie drzewa):
 * Pamieciowa: O(n)
 * Czasowa: O(k)
 * n - liczba rozwiazan, tzn. ciagow wygenerowanych liczb, k - liczba wezlow drzewa
 * Zachodzi warunek: n <= sufit(k / liczba_poziomow_drzewa)
 *
 * Nie modyfikowac klasy clElement i nie uzywac jej poza klasa clDrzewo!
 * Klasa clElement sluzy do opisu elementow drzewa potrzebnego przy "wycenach".
 * Z tego powodu moze zawierac elementy niepotrzebne i niewydajne dla innych drzew. 
 * Najlepiej ograniczyc sie do modyfikacji metod oceniajacych klasy clDrzewo,
 * ewentualnie zmieniac stale i definy, co jednak wymaga testowania.
 *
 * Przed uzyciem clDrzewo nalezy zapoznac sie z uwagami dotyczacymi konstruktora
 *
 * Wersja zatwierdzona przez wodza to 2.1, wersja 3.0 jest z poprawkami w kodzie
 * Wersja 2.1 jest dostepna na 192.168.1.73 (BA)
*/

#include "hbclass.ch"
#include "inkey.ch"

#define ROZMIAR_DRZEWA 9000000 //Najlepiej aby byl z duzym nadmiarem, ogranicza go tylko ilosc RAMu
#define POPRAWKA_PROCENTOWA 0.34 //Program m. in. szuka liczb ludzkich i krotnosci opakowan w przedziale +/- POPRAWKA_PROCENTOWA [%]
#define KROTNOSCI_NA_STRONE 1 //Program wybiera KROTNOSCI_NA_STRONE w lewo i prawo od danej liczby, o ile mieszcza sie w przedziale +/- POPRAWKA_PROCENTOWA [%]
#define INF 999999999.0 //Jezeli cena hurtowa przekroczy ta wartosc, program nie zadziala. Wartosc okresla ostatni, raczej hipotetyczny przedzial <x, INF)
#define EPSILON 0.001 //Wielkosc infinitezymalna uzywana na potrzeby arytmetyki zmiennoprzecinkowej (dzielenia w metodzie oceniajacej)

//#define DEBUG

create class clElement
exported:

  method new_element(nWartosc, nPrzodek, nPoziom) block {| Self, nWartosc, nPrzodek, nPoziom | ::nWartosc := nWartosc, ::nPrzodek := nPrzodek, ::nPoziom := nPoziom, Self} constructor

  method dodaj_syna(nIndeksSyna, nWartoscSyna) inline (hb_hset(::anSynowie, nWartoscSyna, nIndeksSyna))
  method czy_istnieje_syn(nSzukanySyn) inline (hb_hhaskey(::anSynowie, nSzukanySyn))

  method get_wartosc() inline (::nWartosc)
  method get_przodek() inline (::nPrzodek)
  method get_poziom() inline (::nPoziom)
  method get_liczba_synow() inline (len(::anSynowie))

hidden:   

  data nWartosc as numeric
  data nPrzodek as numeric
  data nPoziom as numeric
  data anSynowie as array init hb_hash()

endclass lock

create class clDrzewo
exported:

  //Tablica anOpakowania nie moze zawierac zer jako pustych opakowan. Patrz: definicja metody
  method new_drzewo(nIloscNaMagazynie, anOpakowania, nCenaHurtowa, lWBazieJestKZW, cSciezkaDoBazy, cSciezkaDoIndeksu)
  
  method get_wyniki() inline (::anRozwiazanie)

  //cOpis musi byc wyslany przez referencje, aby zostala zapisana wartosc
  method debuguj(cOpis) block {| Self, cOpis | cOpis := ::cOpisBledu, ::lBlad}



method new_drzewo(nIloscNaMagazynie, anOpakowania, nCenaHurtowa, lWBazieJestKZW, cSciezkaDoBazy, cSciezkaDoIndeksu) class clDrzewo 

  local nIndeksBiezacegoElementu := 1
  local nIlePoziomow := 5
  local lKZW
  local anIlosciIParametry
  local nWartoscNowegoElementu
  local nIl1
  local nPozycjaTablicyZMnoznikami
  local nNajwiekszaIlosc
  local nIleMnoznikow
  local i
  local j

#ifdef DEBUG
  local cCoWyplujeAlgo 
#endif

  ::pobierz_parametry(cSciezkaDoBazy, cSciezkaDoIndeksu, nCenaHurtowa)

  //Wybieram mnozniki adekwatne do ceny
  if(nCenaHurtowa < 2.0)
    nPozycjaTablicyZMnoznikami := 1
  elseif(nCenaHurtowa < 50.0)
    nPozycjaTablicyZMnoznikami := 2
  else
    nPozycjaTablicyZMnoznikami := 3
  endif

  if(!::lBlad)
    /*
      * Aby program uznal przypadek KZW musza byc spelnione dwa warunki konieczne:
      * 1) Kartoteka dla ktorej generowane sa widelki musi pochodzic z KZW wg bazy danych
      * 2) Istnieje tylko opakowanie J2 > 1 przy ilosci w kartotece <= J2 i J2 < il1
      * Drugi warunek pozwala odfiltrowac przypadki, ktore choc sa KZW, powinny byc normalnie traktowane
      * Aby metoda zadzialala osoba uzywajaca klasy odpowiada za niewysylanie do niej zerowych opakowan
      * Dla J2 = 0 nalezy wyslac tablice, w ktorej J2 = J1 = 1 (jak w WMS wg AMA)
      * Opisana metoda nie jest doskonala, ale nie istnieje lepsza przy dostepnych danych
    */
    if(lWBazieJestKZW .and. len(anOpakowania) == 1 .and. anOpakowania[1] > 1 .and. nIloscNaMagazynie <= anOpakowania[1] .and. anOpakowania[1] < ::nIl1)
      lKZW := .t.
      nNajwiekszaIlosc := anOpakowania[1]
    //Jezeli nie ma KZW to il1 biore z tabeli chyba ze istnieje opakowanie w przedziale do +30% * il1 z tabeli
    else
      lKZW := .f.
      nNajwiekszaIlosc := ::nIl1
      aeval(anOpakowania, {| nOpakowania | iif(nOpakowania > nNajwiekszaIlosc .and. nOpakowania < (1 + POPRAWKA_PROCENTOWA) * ::nIl1, nNajwiekszaIlosc := nOpakowania, )})
    endif

    nIleMnoznikow := len(::anMnozniki[nPozycjaTablicyZMnoznikami])

    //Tworze korzen zawierajacy ilosc minimalna, konstruktor inicjuje ::nLiczbaElementow wartoscia 1
    ::aoWezly[1] := clElement():new_element(::nIl5, 0, 1)

    //Funkcje inkrementuja nIndeksBiezacegoElementu w miare dodawania elementow do drzewa
    //Stad return nIndeksBiezacegoElementu jest rownowazny zwroceniu liczby elementow drzewa
    //Fory przebiegaja kolejne elementy drzewa w celu stworzenia ich potomkow
    for i := 1 to nIndeksBiezacegoElementu
      if(::aoWezly[i]:get_poziom() < nIlePoziomow)
        for j := 1 to nIleMnoznikow

          nWartoscNowegoElementu := ::aoWezly[i]:get_wartosc() * ::anMnozniki[nPozycjaTablicyZMnoznikami][j]

          if(nWartoscNowegoElementu <= nNajwiekszaIlosc)
            ::aoWezly[++nIndeksBiezacegoElementu] := clElement():new_element(nWartoscNowegoElementu, i, ::aoWezly[i]:get_poziom() + 1)
            ::aoWezly[i]:dodaj_syna(nIndeksBiezacegoElementu, nWartoscNowegoElementu)
            ++::nLiczbaElementow

            //Jezeli J2 = 0, nalezy uznac, ze J2 = J1 (jak w WMS wg AMA)
            //W najlepszym przypadku anOpakowania zawierajace zera jedynie wydluzaja czas obliczen, ale nie ma gwarancji poprawnosci dzialania tej metody dla zerowych opakowan
            aeval(anOpakowania, {| nOpakowania | ::dodaj_krotnosci(@nIndeksBiezacegoElementu, nOpakowania, i, nNajwiekszaIlosc)})

            ::dodaj_ludzkie(@nIndeksBiezacegoElementu, i, nNajwiekszaIlosc)
          endif
        next 
      endif
    next    

    anIlosciIParametry := ::ocen_drzewo(nIlePoziomow, anOpakowania, nNajwiekszaIlosc, nCenaHurtowa, lKZW)

    //KZW koncze na J2
    if(lKZW)
      anIlosciIParametry[1] := nNajwiekszaIlosc
    endif

    //anRozwiazanie[i][1] zawiera i-ta ilosc il(i), a anRozwiazanie[i][2] i-ta cene c(i), zaczynajac od i = 1 (hurtu). Widelek jest len(anRozwiazanie)
    ::dodaj_ceny(anIlosciIParametry, ::nIl5, nNajwiekszaIlosc, ::nKrotnoscCeny, nCenaHurtowa)
 
#ifdef DEBUG
    cCoWyplujeAlgo := ";"

    for i := 1 to len(::anRozwiazanie)
      cCoWyplujeAlgo += space(6 - len(alltrim(str(::anRozwiazanie[i][1])))) + alltrim(str(::anRozwiazanie[i][1])) + "        w cenie        " + alltrim(str(::anRozwiazanie[i][2])) + " zl;"
    next      
    alert(cCoWyplujeAlgo)
  
#endif
  endif

return Self

Dorzucam też fragment związany z dodawaniem elementów do drzewa wg bardzo złożonego algorytmu, zarówno logicznie (matematycznie) jak i implementacyjnie. O ile zrozumienie ifa jest trudne, o tyle komentarz obrazowo mówi, co ten if robi (i stąd - jakie będą konsekwencje jego usunięcia).

        //Jezeli istnieje przodek, nie dubluje wartosci i miesci sie ona w przedziale to dodaje wujka wzgledem elementu biezacego
        //Np: mam 10 -> 30 -> 90, dla 90 wywolala sie funkcja i 100 jest ludzkie oraz miesci sie w przedziale wiec dodaje 10 -> 100
        if(::aoWezly[nIndeksWezlaTworzacego]:get_przodek() != 0 .and. !::aoWezly[::aoWezly[nIndeksWezlaTworzacego]:get_przodek()]:czy_istnieje_syn(nLudzka);
          .and. nLudzka >= (1 - POPRAWKA_PROCENTOWA) * ::aoWezly[nIndeksWezlaTworzacego]:get_wartosc() .and. nLudzka <= (1 + POPRAWKA_PROCENTOWA) * ::aoWezly[nIndeksWezlaTworzacego]:get_wartosc();
          .and. nLudzka > ::aoWezly[::aoWezly[nIndeksWezlaTworzacego]:get_przodek()]:get_wartosc())

          ::aoWezly[++nIndeksBiezacegoElementu] := clElement():new_element(nLudzka, ::aoWezly[nIndeksWezlaTworzacego]:get_przodek(), ::aoWezly[nIndeksWezlaTworzacego]:get_poziom())  
          ::aoWezly[::aoWezly[nIndeksWezlaTworzacego]:get_przodek()]:dodaj_syna(nIndeksBiezacegoElementu, nLudzka)
          ++::nLiczbaElementow

Można mieć dużo zastrzeżeń i wiele będzie zasadnych - wszak to wciąż kod roboczy. Ale różnicę chyba widać. Podobnie jest w C++ i każdym innym języku - widać, ile ktoś czasu pracował nad kodem.

Dla dowodu kultowy C++. Poniższy kod nie jest idealny (bez dwóch zdań) i teraz napisałbym go inaczej niż daaawno temu, kiedy go pisałem. Ale sami zobaczcie na maina, który jest w oddzielnym pliku main.cpp:

#include <iostream>

#include "game.h"

int main(int argc, char* argv[])
{
	try {
		Game game(argc, argv);
		game.execute();
	}
	catch (const char* string) {
		std::cerr << std::endl << "Exception: " << string << std::endl;
	}
	catch (...) {
		std::cerr << std::endl << "Unknown Exception!" << std::endl;	
	}
}

Popatrzcie nawet na headery - ich ułożenie ma informować o rodzaju includowanych treści, np. widać różnicę między plikami nagłówkowymi w projekcie a bibliotekami standardowymi - są w innych miejscach.

  • created

    Feb '18
  • last reply

    Aug '20
  • 23

    replies

  • 3.7k

    views

  • 6

    users

  • 5

    likes

  • 15

    links

Poniżej jeszcze plik .cpp “silnika” gry:

#include "game.h"

#include <SDL2/SDL_image.h>

#include <iostream>

#include "board.h"
#include "utilities.h"
#include "enums.h"

Game::Game(int argc, char* argv[]) :

        //SDL2 and it's subsystems initialization
        sdl_init_was_correct_(SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS) == 0 ? true : false),
        img_init_was_correct_(!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) == 0 ? true : false),
        mixer_init_was_correct_(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) == 0 ? true : false),
        ttf_init_was_correct_(TTF_Init() == 0 ? true : false),

        //Create textures
        score_texture_(load_texture("../img/score.png", renderer_)),
        boost_texture_(load_texture("../img/boost.png", renderer_)),
        wall_texture_(load_texture("../img/wall.png", renderer_)),
        pacman_open_texture_(load_texture("../img/pacman_open.png", renderer_)),
        pacman_close_texture_(load_texture("../img/pacman_close.png", renderer_)),
        pacman_boost_texture_(load_texture("../img/pacman_boost.png", renderer_)),
        clyde_texture_(load_texture("../img/clyde.png", renderer_)),
        pinky_texture_(load_texture("../img/pinky.png", renderer_)),
        blinky_texture_(load_texture("../img/blinky.png", renderer_)),
        inky_texture_(load_texture("../img/inky.png", renderer_)),
        live_texture_(load_texture("../img/live.png", renderer_)),
        cherry_texture_(load_texture("../img/cherry.png", renderer_)),
        strawberry_texture_(load_texture("../img/strawberry.png", renderer_)),
        orange_texture_(load_texture("../img/orange.png", renderer_)),
        apple_texture_(load_texture("../img/apple.png", renderer_)),
        melon_texture_(load_texture("../img/melon.png", renderer_)),
        galaxian_texture_(load_texture("../img/galaxian.png", renderer_)),
        bell_texture_(load_texture("../img/bell.png", renderer_)),
        key_texture_(load_texture("../img/key.png", renderer_)),
        eyes_texture_(load_texture("../img/eyes.png", renderer_)),
        scared_texture_(load_texture("../img/scared.png", renderer_)),

        //Prepare sound effects
        beginning_sound_(Mix_LoadWAV("../wav/beginning.wav")),
        move_sound_(Mix_LoadWAV("../wav/move.wav")),
        death_sound_(Mix_LoadWAV("../wav/death.wav")),
        eat_fruit_sound_(Mix_LoadWAV("../wav/eat_fruit.wav")),
        eat_ghost_sound_(Mix_LoadWAV("../wav/eat_ghost.wav")),
        booster_sound_(Mix_LoadWAV("../wav/booster.wav")),
        game_over_sound_(Mix_LoadWAV("../wav/over.wav")),

        //Make window and it's renderer
        window_(SDL_CreateWindow("Pacman", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width_, screen_height_, SDL_WINDOW_SHOWN)),
        renderer_(SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)),

        //Make board with ASCII representation of the game
        board_(new Board(board_txt_path_)),

        //Make Pacman (ghosts can't be defined here - they're in body of constructor)
        pacman_(new Pacman(board_->get_starting_position_y(symbols::PACMAN), board_->get_starting_position_x(symbols::PACMAN))),

        //Other stuff
        score_(0),
        highest_score_(0),
        lives_(3),
        eaten_scores_(0),
        eaten_ghosts_(0),
        time_of_status_change_(0),
        time_from_start_(0),
        time_from_chase_(0),
        time_from_random_(20'000),
        update_actual_score_(true),
        update_highest_score_(true),
        render_game_over_(true),
        render_pacman_new_status_(true),
        exit_(false),
        start_(false),
        win_(false),
        game_over_(false),
        was_reset_(true),
        update_time_from_chase_(false),
        update_time_from_random_(false),
        new_direction_(direction::NONE),
        stored_direction_(direction::NONE),
        if_possible_direction_(direction::NONE)
{
        //Disable cursor and check if you succeed
        SDL_ShowCursor(SDL_DISABLE);

        if(SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
                cursor_was_disabled_ = true;
        } else {
                cursor_was_disabled_ = false;
        }

        make_ghosts();

        Mix_AllocateChannels(7);

        was_init_correct();

        if(!Mix_Playing(6)) {
                Mix_HaltChannel(-1);
                Mix_PlayChannel(6, beginning_sound_, 0);
        }
}

Game::~Game()
{
        delete board_;
        delete pacman_;

        delete_ghosts();

        Mix_FreeChunk(beginning_sound_);
        Mix_FreeChunk(move_sound_);
        Mix_FreeChunk(death_sound_);
        Mix_FreeChunk(eat_fruit_sound_);
        Mix_FreeChunk(eat_ghost_sound_);
        Mix_FreeChunk(booster_sound_);
        Mix_FreeChunk(game_over_sound_);

        SDL_DestroyTexture(score_texture_);
        SDL_DestroyTexture(boost_texture_);
        SDL_DestroyTexture(wall_texture_);
        SDL_DestroyTexture(pacman_open_texture_);
        SDL_DestroyTexture(pacman_close_texture_);
        SDL_DestroyTexture(pacman_boost_texture_);
        SDL_DestroyTexture(clyde_texture_);
        SDL_DestroyTexture(pinky_texture_);
        SDL_DestroyTexture(inky_texture_);
        SDL_DestroyTexture(blinky_texture_);
        SDL_DestroyTexture(live_texture_);
        SDL_DestroyTexture(cherry_texture_);
        SDL_DestroyTexture(strawberry_texture_);
        SDL_DestroyTexture(orange_texture_);
        SDL_DestroyTexture(apple_texture_);
        SDL_DestroyTexture(melon_texture_);
        SDL_DestroyTexture(galaxian_texture_);
        SDL_DestroyTexture(bell_texture_);
        SDL_DestroyTexture(key_texture_);
        SDL_DestroyTexture(eyes_texture_);
        SDL_DestroyTexture(scared_texture_);

        SDL_DestroyRenderer(renderer_);
        SDL_DestroyWindow(window_);

        IMG_Quit();
        Mix_CloseAudio();

        SDL_Quit();
}

void Game::was_init_correct()
{
        if(!sdl_init_was_correct_) {
                std::cerr << "Game::was_init_correct(): SDL_Error: " << SDL_GetError() << std::endl;
                throw "SDL_Init() failed!";
        } else if(!img_init_was_correct_) {
                std::cerr << "Game::was_init_correct(): IMG_Error: " << IMG_GetError() << std::endl;
                throw "IMG_Init() failed!";
        } else if(!mixer_init_was_correct_) {
                std::cerr << "Game::was_init_correct(): Mix_Error: " << Mix_GetError() << std::endl;
                throw "Mix_OpenAudio() failed!";
        } else if(!ttf_init_was_correct_) {
                std::cerr << "Game::was_init_correct(): TTF_Error: " << TTF_GetError() << std::endl;
                throw "TTF_Init() failed!";
        } else if(!cursor_was_disabled_) {
                std::cerr << "Game::was_init_correct(): SDL_Error: " << SDL_GetError() << std::endl;
                throw "SDL_ShowCursor() failed! Cursor was not disabled!";
        } else if(Mix_AllocateChannels(-1) != 7) {
                std::cerr << "Game:was_init_correct(): Mix_AllocateChannels(-1): " << Mix_AllocateChannels(-1) << std::endl;
                throw "Mix_AllocateChannels() failed! Number of channels have to be 7!";
        }
}

void Game::make_ghosts()
{
        ghosts_[0] = new Clyde(board_->get_starting_position_x(symbols::CLYDE), board_->get_starting_position_y(symbols::CLYDE), 100'000, 100'000, 10'000);
        ghosts_[1] = new Pinky(board_->get_starting_position_x(symbols::PINKY), board_->get_starting_position_y(symbols::PINKY), 0, 0, 2'000);
        ghosts_[2] = new Blinky(board_->get_starting_position_x(symbols::BLINKY), board_->get_starting_position_y(symbols::BLINKY), 100'000, 0, 0);
        ghosts_[3] = new Inky(board_->get_starting_position_x(symbols::INKY), board_->get_starting_position_y(symbols::INKY), 0, 100'000, 5'000);
}

void Game::delete_ghosts()
{
        for(int i = 0; i < 4; ++i) {
                delete ghosts_[i];
        }
}

void Game::execute()
{
        while(!exit_) {
                if(!start_ && !game_over_) {
                        if(was_reset_) {
                                render();
                                was_reset_ = false;
                        }

                        //Time for other threads (CPU optimization)
                        SDL_Delay(10);

                        input();
                } else {
                        render();
                        input();

                        if(!game_over_) {
                                update();
                        }
                }
        }
}

void Game::render()
{
        //Black background
        SDL_SetRenderDrawColor(renderer_, 0, 0, 0, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer_);

        render_maze();
        render_pacman();
        render_ghosts();
        render_manual();
        render_scores();

        //After above it's time to swap buffer (render new content)
        SDL_RenderPresent(renderer_);
}

void Game::render_maze()
{
        int size = board_->get_size_of_element();

        SDL_Rect view_port;
        view_port.w = size;
        view_port.h = size;

        for(int i = 0; i < board_->get_nr_rows(); ++i) {
                for(int j = 0; j < board_->get_nr_cols(); ++j) {

                        view_port.x = j * size;
                        view_port.y = i * size;

                        if(board_->get_element_by_row_col(i, j) == static_cast<char>(symbols::WALL)) {
                                renderTexture(wall_texture_, renderer_, view_port);
                        } else if(board_->get_element_by_row_col(i, j) == static_cast<char>(symbols::SCORE) && !board_->is_eaten(view_port.x, view_port.y)) {
                                renderTexture(score_texture_, renderer_, view_port);
                        } else if(board_->get_element_by_row_col(i, j) == static_cast<char>(symbols::BOOSTER) && !board_->is_eaten(view_port.x, view_port.y)) {
                                renderTexture(boost_texture_, renderer_, view_port);
                        } else if(board_->get_element_by_row_col(i, j) == static_cast<char>(symbols::FRUIT) && board_->is_fruit(view_port.x, view_port.y)) {

                                switch(board_->get_fruit_type()) {

                                case object_type::CHERRY:
                                        renderTexture(cherry_texture_, renderer_, view_port);
                                        break;
                                case object_type::STRAWBERRY:
                                        renderTexture(strawberry_texture_, renderer_, view_port);
                                        break;
                                case object_type::ORANGE:
                                        renderTexture(orange_texture_, renderer_, view_port);
                                        break;
                                case object_type::APPLE:
                                        renderTexture(apple_texture_, renderer_, view_port);
                                        break;
                                case object_type::MELON:
                                        renderTexture(melon_texture_, renderer_, view_port);
                                        break;
                                case object_type::GALAXIAN:
                                        renderTexture(galaxian_texture_, renderer_, view_port);
                                        break;
                                case object_type::BELL:
                                        renderTexture(bell_texture_, renderer_, view_port);
                                        break;
                                case object_type::KEY:
                                        renderTexture(key_texture_, renderer_, view_port);
                                        break;
                                default:
                                        std::cerr << "Game::render_maze(): " << board_->get_element_by_row_col(i, j) << std::endl;
                                        throw "Game::render_maze() failed! There is not such a fruit!";
                                }
                        }
                }
        }
}

void Game::render_pacman()
{
        SDL_Rect view_port;
        view_port.w = board_->get_size_of_element();
        view_port.h = board_->get_size_of_element();
        view_port.y = pacman_->get_y();
        view_port.x = pacman_->get_x();

        if(pacman_->get_status() == status::BOOST) {
                renderTexture(pacman_boost_texture_, renderer_, view_port, rotation_texture_angle());

                render_pacman_new_status_ = true;

        } else if(pacman_->get_status() == status::OPEN) {
                renderTexture(pacman_open_texture_, renderer_, view_port, rotation_texture_angle());

                if(time_of_status_change_ < SDL_GetTicks() - snap_velocity_ && pacman_->get_direction() != direction::NONE && render_pacman_new_status_) {
                        pacman_->set_status(status::CLOSE);
                        time_of_status_change_ = SDL_GetTicks();
                }
        } else if(pacman_->get_status() == status::CLOSE) {
                renderTexture(pacman_close_texture_, renderer_, view_port, rotation_texture_angle());

                if(time_of_status_change_ < SDL_GetTicks() - snap_velocity_ && pacman_->get_direction() != direction::NONE && render_pacman_new_status_) {
                        pacman_->set_status(status::OPEN);
                        time_of_status_change_ = SDL_GetTicks();
                }
        }
}

void Game::render_ghosts()
{
        SDL_Rect view_port;
        view_port.w = board_->get_size_of_element();
        view_port.h = board_->get_size_of_element();

        for(int i = 0; i < 4; ++i) {
                view_port.x = ghosts_[i]->get_x();
                view_port.y = ghosts_[i]->get_y();

                if(ghosts_[i]->get_is_dead()) {
                        renderTexture(eyes_texture_, renderer_, view_port);
                } else {
                        if(ghosts_[i]->get_is_scared()) {
                                renderTexture(scared_texture_, renderer_, view_port);
                        } else {
                                if(i == 0) {
                                        renderTexture(clyde_texture_, renderer_, view_port);
                                } else if(i == 1) {
                                        renderTexture(pinky_texture_, renderer_, view_port);
                                } else if(i == 2) {
                                        renderTexture(blinky_texture_, renderer_, view_port);
                                } else {
                                        renderTexture(inky_texture_, renderer_, view_port);
                                }
                        }
                }
        }
}

double Game::rotation_texture_angle() const
{
        switch(static_cast<int>(pacman_->get_direction())) {

        case static_cast<int>(direction::UP):
                return 270.0;
        case static_cast<int>(direction::LEFT):
                return 180.0;
        case static_cast<int>(direction::DOWN):
                return 90.0;
        case static_cast<int>(direction::RIGHT):
                return 0.0;
        case static_cast<int>(direction::NONE):
                return 0.0;
        default:
                std::cerr << "Game::rotation_texture_angle(): " << static_cast<int>(pacman_->get_direction()) << std::endl;
                throw "Game::rotation_texture_angle() failed! There is not such a direction";
        }
}

void Game::render_manual()
{
        SDL_Color font_color = {255, 255, 255, 255};

        if(start_) {
                int x_position_of_live_image = 585;
                int size_of_live_image = 2 * board_->get_size_of_element();

                SDL_Rect view_port;
                view_port.w = size_of_live_image;
                view_port.h = size_of_live_image;
                view_port.y = 280;

                for(int i = 0; i < lives_; ++i) {
                        view_port.x = x_position_of_live_image + i * size_of_live_image;
                        renderTexture(live_texture_, renderer_, view_port);
                }
        } else {
                if(game_over_) {
                        manual_caption_font_ = renderTextWrapped("It's over! Press space to reset", font_name_, font_color, manual_font_size_, renderer_, 200);
                        renderTexture(manual_caption_font_, renderer_, 585, 260);
                } else if(win_) {
                        manual_caption_font_ = renderTextWrapped("You won! Press space to start next level", font_name_, font_color, manual_font_size_, renderer_, 200);
                        renderTexture(manual_caption_font_, renderer_, 585, 260);
                } else {
                        manual_caption_font_ = renderTextWrapped("Press space to start", font_name_, font_color, manual_font_size_, renderer_, 200);
                        renderTexture(manual_caption_font_, renderer_, 585, 260);
                }
        }
}

void Game::render_scores()
{
        SDL_Color font_color;

        font_color = {255, 0, 0, 255};
        actual_score_caption_font_ = renderText("SCORE", font_name_, font_color, caption_font_size_, renderer_);
        renderTexture(actual_score_caption_font_, renderer_, 585, 40);

        if(update_actual_score_) {
                font_color = {255, 255, 255, 255};
                actual_score_font_ = renderText(std::to_string(score_), font_name_, font_color, score_font_size_, renderer_);
                update_actual_score_ = false;
        }

        renderTexture(actual_score_font_, renderer_, 585, 100);

        font_color = {0, 255, 0, 255};
        highest_score_caption_font_ = renderText("HIGH.", font_name_, font_color, caption_font_size_, renderer_);
        renderTexture(highest_score_caption_font_, renderer_, 585, 150);

        if(update_highest_score_) {
                font_color = {255, 255, 255, 255};
                highest_score_font_ = renderText(std::to_string(highest_score_), font_name_, font_color, score_font_size_, renderer_);
                update_highest_score_ = false;
        }

        renderTexture(highest_score_font_, renderer_, 585, 210);

        SDL_DestroyTexture(actual_score_caption_font_);
        SDL_DestroyTexture(highest_score_caption_font_);
}
void Game::input()
{
        SDL_Event event;

        while(SDL_PollEvent(&event)) {

                if(event.type == SDL_QUIT) {
                        exit_ = true;
                }

                if(event.type == SDL_KEYDOWN) {

                        switch(event.key.keysym.sym) {

                        case SDLK_ESCAPE:
                                exit_ = true;
                                break;
                        case SDLK_F11:
                                //If game window is in fullscreen mode - restore default resolution. Otherwise set fullscreen mode
                                if (SDL_GetWindowFlags(window_) & SDL_WINDOW_FULLSCREEN) {
                                        SDL_SetWindowFullscreen(window_, 0);
                                } else {
                                        SDL_SetWindowFullscreen(window_, SDL_WINDOW_FULLSCREEN);
                                }
                                break;
                        case SDLK_SPACE:
                                if(!start_) {
                                        start_ = true;
                                        game_over_ = false;
                                        win_ = false;

                                        if(!game_over_) {
                                                time_from_start_ = SDL_GetTicks();
                                        }

                                        time_of_status_change_ = time_from_start_;
                                }
                                break;
                        default:
                                break;
                        }

                        //This conditional makes pacman movement impossible if space was not pressed
                        if(start_) {

                                switch(event.key.keysym.sym) {

                                case SDLK_UP:
                                        new_direction_ = direction::UP;
                                        break;
                                case SDLK_DOWN:
                                        new_direction_ = direction::DOWN;
                                        break;
                                case SDLK_LEFT:
                                        new_direction_ = direction::LEFT;
                                        break;
                                case SDLK_RIGHT:
                                        new_direction_ = direction::RIGHT;
                                        break;
                                default:
                                        //Default direction is NONE (player didn't push any arrow key)
                                        new_direction_ = direction::NONE;
                                        break;
                                }
                        }
                }
        }
}

void Game::update()
{
        handle_ghosts_collision();
        handle_teleports();
        handle_eatable_collision();
        handle_pacman_moves();
        handle_ghosts_moves();
}

void Game::handle_ghosts_collision()
{
        for(int i = 0; i < 4; ++i) {
                if(!ghosts_[i]->get_is_dead()) {

                        int distance_x = abs(pacman_->get_x() - ghosts_[i]->get_x());
                        int distance_y = abs(pacman_->get_y() - ghosts_[i]->get_y());
                        int tollerance;

                        if(ghosts_[i]->get_direction() == pacman_->get_direction()) {
                                tollerance = board_->get_size_of_element() / 5 * 4;
                        } else {
                                tollerance = board_->get_size_of_element() / 2;
                        }

                        if(distance_x < tollerance && distance_y < tollerance) {
                                if(ghosts_[i]->get_is_scared()) {
                                        ++eaten_ghosts_;
                                        score_ += 100 * static_cast<int>(pow(2.0, eaten_ghosts_));
                                        update_actual_score_ = true;
                                        ghosts_[i]->set_scared(false);
                                        ghosts_[i]->set_is_dead(true);

                                        if(score_ > highest_score_) {
                                                highest_score_ = score_;
                                                update_highest_score_ = true;
                                        }
                                        if(!Mix_Playing(1)) {
                                                Mix_PlayChannel(1, eat_ghost_sound_, 0);
                                        }
                                } else {
                                        handle_dead();
                                        return;
                                }
                        }
                }
        }
}

void Game::handle_teleports()
{
        int pacman_x = pacman_->get_x();
        int pacman_y = pacman_->get_y();

        if(board_->is_teleport_collision(pacman_->get_x(), pacman_->get_y())) {
                board_->teleport_object(&pacman_x, &pacman_y);
                pacman_->set_position(pacman_x, pacman_y);
        }

        for(int i = 0; i < 4; ++i) {
                int ghost_x = ghosts_[i]->get_x();
                int ghost_y = ghosts_[i]->get_y();

                if(board_->is_teleport_collision(ghost_x, ghost_y)) {
                        board_->teleport_object(&ghost_x, &ghost_y);
                        ghosts_[i]->set_position(ghost_x, ghost_y);
                }
        }
}

void Game::handle_eatable_collision()
{
        int pacman_x = pacman_->get_x();
        int pacman_y = pacman_->get_y();
        bool was_fruit_eaten = false;
        bool was_booster_eaten = false;

        if(board_->is_eatable_collision(pacman_x, pacman_y)) {
                char object_at_pacman_position = board_->get_element_by_center(pacman_x, pacman_y);

                if(object_at_pacman_position == static_cast<char>(symbols::SCORE)) {
                        score_ += 1;
                        board_->set_was_eaten(pacman_x, pacman_y, true);
                        ++eaten_scores_;
                } else if(object_at_pacman_position == static_cast<char>(symbols::BOOSTER)) {
                        score_ += 10;
                        board_->set_was_eaten(pacman_x, pacman_y, true);
                        ++eaten_scores_;
                        was_booster_eaten = true;


                        if(!Mix_Playing(3)) {
                                Mix_PlayChannel(3, booster_sound_, 0);
                        }
                } else if(object_at_pacman_position == static_cast<char>(symbols::FRUIT) && board_->is_fruit(pacman_x, pacman_y)) {
                        object_type fruit_type = board_->get_fruit_type();

                        auto it = scoring.find(fruit_type);
                        if(it == scoring.end()) {
                                std::cerr << "Game::update(): " << static_cast<char>(fruit_type) << std::endl;
                                throw "Game::update() failed! There is not such a fruit type!";
                        }

                        score_ += it->second;
                        was_fruit_eaten = true;

                        if(!Mix_Playing(2)) {
                                Mix_PlayChannel(2, eat_fruit_sound_, 0);
                        }
                }

                if(eaten_scores_ == board_->get_objects_to_win()) {
                        handle_win();
                        return;
                }

                update_actual_score_ = true;

                if(score_ > highest_score_) {
                        highest_score_ = score_;
                        update_highest_score_ = true;
                }
        }

        handle_booster(was_booster_eaten);
        board_->handle_fruit(time_from_start_, was_fruit_eaten);
}

void Game::handle_booster(bool was_eaten_moment_ago)
{
        if(was_eaten_moment_ago) {
                pacman_->set_boosted(true);
                pacman_->set_status(status::BOOST);

                for(int i = 0; i < 4; ++i) {
                        if(!ghosts_[i]->get_is_dead()) {
                                ghosts_[i]->set_scared(true);
                                ghosts_[i]->change_direction_to_opposite();
                        }
                }

                eaten_ghosts_ = 0;
        } else {
                pacman_->boost_countdown();

                if(!pacman_->get_boosted()) {

                        for(int i = 0; i < 4; ++i) {
                                if(!ghosts_[i]->get_is_dead()) {
                                        ghosts_[i]->set_scared(false);
                                }
                        }

                        for(int i = 0; i < 4; ++i) {
                                if(!ghosts_[i]->get_is_dead()) {
                                        ghosts_[i]->make_correction(*board_);
                                }
                        }
                }
        }
}

void Game::handle_ghosts_moves()
{
        for(int i = 0; i < 4; ++i) {
                if(!ghosts_[i]->get_is_dead()) {
                        if(SDL_GetTicks() - time_from_start_ < ghosts_[i]->get_delay()) {
                                ghosts_[i]->AI(pacman_->get_x(), pacman_->get_y(), *board_);
                        } else if(ghosts_[i]-> get_is_inside()) {
                                ghosts_[i]->AI(board_->get_navigation_position_x(), board_->get_navigation_position_y(), *board_, false);
                                if(ghosts_[i]->get_x() == board_->get_navigation_position_x() && ghosts_[i]->get_y() == board_->get_navigation_position_y())
                                        ghosts_[i]->set_is_inside(false);
                        } else {
                                //20 seconds of chasing and 10 seconds of "stupid-mode"
                                if(SDL_GetTicks() - time_from_start_ > time_from_chase_ && SDL_GetTicks() - time_from_start_ < time_from_random_) {
                                        ghosts_[i]->AI(pacman_->get_x(), pacman_->get_y(), *board_);
                                        update_time_from_chase_ = true;
                                } else if(update_time_from_chase_) {
                                        time_from_chase_ += 30'000;
                                        update_time_from_chase_ = false;
                                }

                                if(SDL_GetTicks() - time_from_start_ > time_from_random_ && SDL_GetTicks() - time_from_start_ < time_from_chase_) {
                                        ghosts_[i]->AI(ghosts_[i]->get_corner_x(), ghosts_[i]->get_corner_y(), *board_);
                                        update_time_from_random_ = true;
                                } else if(update_time_from_random_) {
                                        time_from_random_ += 30'000;
                                        update_time_from_random_ = false;
                                }
                        }
                } else {
                        ghosts_[i]->AI(board_->get_resurection_position_x(), board_->get_resurection_position_y(), *board_, false);
                        if(ghosts_[i]->get_x() == board_->get_resurection_position_x() && ghosts_[i]->get_y() == board_->get_resurection_position_y()) {
                                ghosts_[i]->set_is_dead(false);
                                ghosts_[i]->set_is_inside(true);
                        }
                }
        }
}

void Game::handle_pacman_moves()
{
        //Predicted Pacman position is equal to it's current position +/- his velocity (correction is made later by predict_pacman_position())
        int predicted_x = pacman_->get_x();
        int predicted_y = pacman_->get_y();

        //Default: Pacman is snapping
        render_pacman_new_status_ = true;

        //If new_direction_ exists and move is possible - do it. If not - remember this direction
        pacman_->predict_pacman_position(&predicted_x, &predicted_y, new_direction_);
        if(new_direction_ != direction::NONE) {
                if(!board_->is_wall_collision(predicted_x, predicted_y, new_direction_)) {
                        pacman_->move(predicted_x, predicted_y, new_direction_);
                        stored_direction_ = new_direction_;
                        if_possible_direction_ = new_direction_;

                        if(Mix_Playing(0) == 0) {
                                Mix_PlayChannel(0, move_sound_, 0);
                        }

                        return;
                } else {
                        if_possible_direction_ = new_direction_;
                }
        }

        //Previous values were changed by predict_pacman_position()
        predicted_x = pacman_->get_x();
        predicted_y = pacman_->get_y();

        //If if_possible_direction_ exists and move is possible - do it
        pacman_->predict_pacman_position(&predicted_x, &predicted_y, if_possible_direction_);
        if(if_possible_direction_ != direction::NONE) {
                if(!board_->is_wall_collision(predicted_x, predicted_y, if_possible_direction_)) {
                        pacman_->move(predicted_x, predicted_y, if_possible_direction_);
                        stored_direction_ = if_possible_direction_;

                        if(Mix_Playing(0) == 0) {
                                Mix_PlayChannel(0, move_sound_, 0);
                        }

                        return;
                }
        }

        //Previous values were changed by predict_pacman_position()
        predicted_x = pacman_->get_x();
        predicted_y = pacman_->get_y();

        //If above moves are impossible - check stored_direction_
        pacman_->predict_pacman_position(&predicted_x, &predicted_y, stored_direction_);
        if(stored_direction_ != direction::NONE && !board_->is_wall_collision(predicted_x, predicted_y, stored_direction_)) {
                pacman_->move(predicted_x, predicted_y, stored_direction_);

                if(Mix_Playing(0) == 0) {
                        Mix_PlayChannel(0, move_sound_, 0);
                }

                return;
        }

        //If Pacman does not move (no return was called up to this moment) than he is not snapping
        render_pacman_new_status_ = false;

        Mix_HaltChannel(0);
}

void Game::handle_win()
{
        Mix_HaltChannel(-1);
        Mix_PlayChannel(6, beginning_sound_, 0);

        time_from_chase_ = 0;
        eaten_scores_ = 0;
        time_from_random_ = 20'000;
        update_time_from_chase_ = false;
        update_time_from_random_ = false;
        was_reset_ = true;
        win_ = true;
        start_ = false;
        new_direction_ = direction::NONE;
        stored_direction_ = direction::NONE;
        if_possible_direction_ = direction::NONE;

        delete board_;
        board_ = new Board(board_txt_path_);

        delete pacman_;
        pacman_ = new Pacman(board_->get_starting_position_y(symbols::PACMAN), board_->get_starting_position_x(symbols::PACMAN));

        delete_ghosts();
        make_ghosts();
}

void Game::handle_dead()
{
        time_from_chase_ = 0;
        time_from_random_ = 20'000;
        was_reset_ = true;
        start_ = false;
        new_direction_ = direction::NONE;
        stored_direction_ = direction::NONE;
        if_possible_direction_ = direction::NONE;

        delete pacman_;
        pacman_ = new Pacman(board_->get_starting_position_y(symbols::PACMAN), board_->get_starting_position_x(symbols::PACMAN));

        delete_ghosts();
        make_ghosts();

        Mix_HaltChannel(-1);

        if(--lives_ == 0) {
                handle_game_over();
                return;
        }

        Mix_PlayChannel(4, death_sound_, 0);
}

void Game::handle_game_over()
{
        Mix_PlayChannel(5, game_over_sound_, 0);

        lives_ = 3;
        score_ = 0;
        eaten_scores_ = 0;
        game_over_ = true;
        update_actual_score_ = true;

        delete board_;
        board_ = new Board(board_txt_path_);
}

I powiązany z nim plik nagłówkowy:

#ifndef SRC_GAME_H_
#define SRC_GAME_H_

#include <string>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_mixer.h>

#include "enums.h"
#include "board.h"
#include "pacman.h"
#include "clyde.h"
#include "pinky.h"
#include "blinky.h"
#include "inky.h"

class Game
{
public:
        Game(int argc, char* argv[]);
        ~Game();

        //Game loop
        void execute();

private:
        const int screen_width_ = 800;
        const int screen_height_ = 620;

        //Font sizes
        const int score_font_size_ = 24;
        const int caption_font_size_ = 38;
        const int manual_font_size_ = 18;

        //The more it grow, the slower it snapps
        const int snap_velocity_ = 250;

        const std::string board_txt_path_ = "board.txt";
        const std::string font_name_ = "../fonts/NES-Chimera/NES-Chimera.ttf";

        const std::map<object_type, int> scoring = {
                {object_type::CHERRY, 100},
                {object_type::STRAWBERRY, 300},
                {object_type::ORANGE, 500},
                {object_type::APPLE, 700},
                {object_type::MELON, 1000},
                {object_type::GALAXIAN, 2000},
                {object_type::BELL, 3000},
                {object_type::KEY, 5000}
        };

        int score_;
        int highest_score_;
        int eaten_scores_;
        int eaten_ghosts_;

        int lives_;

        //When was a last time you changed a pacman status (from OPEN to CLOSE or from CLOSE to OPEN)?
        int time_of_status_change_;

        int time_from_start_;
        int time_from_chase_;
        int time_from_random_;

        bool render_game_over_;
        bool render_mazee;
        bool render_pacman_new_status_;

        bool update_highest_score_;
        bool update_actual_score_;
        bool update_time_from_chase_;
        bool update_time_from_random_;

        bool was_reset_;
        bool start_;
        bool win_;
        bool game_over_;
        bool exit_;

        //Debug variables
        bool sdl_init_was_correct_;
        bool img_init_was_correct_;
        bool mixer_init_was_correct_;
        bool ttf_init_was_correct_;
        bool cursor_was_disabled_;

        //Board contains ASCII representation of the game
        Board* board_;

        //Screen handling
        SDL_Window* window_;
        SDL_Renderer* renderer_;

        //Sound handling
        Mix_Chunk* beginning_sound_;
        Mix_Chunk* move_sound_;
        Mix_Chunk* death_sound_;
        Mix_Chunk* eat_fruit_sound_;
        Mix_Chunk* eat_ghost_sound_;
        Mix_Chunk* booster_sound_;
        Mix_Chunk* game_over_sound_;

        //These variables are used to print text
        SDL_Texture* highest_score_caption_font_;
        SDL_Texture* highest_score_font_;
        SDL_Texture* actual_score_caption_font_;
        SDL_Texture* actual_score_font_;
        SDL_Texture* game_over_font_;
        SDL_Texture* counting_down_font_;
        SDL_Texture* manual_caption_font_;

        //These variables are used to render .png files
        SDL_Texture* score_texture_;
        SDL_Texture* boost_texture_;
        SDL_Texture* wall_texture_;
        SDL_Texture* cherry_texture_;
        SDL_Texture* strawberry_texture_;
        SDL_Texture* orange_texture_;
        SDL_Texture* apple_texture_;
        SDL_Texture* melon_texture_;
        SDL_Texture* galaxian_texture_;
        SDL_Texture* bell_texture_;
        SDL_Texture* key_texture_;
        SDL_Texture* scared_texture_;
        SDL_Texture* eyes_texture_;
        SDL_Texture* pacman_open_texture_;
        SDL_Texture* pacman_close_texture_;
        SDL_Texture* pacman_boost_texture_;
        SDL_Texture* clyde_texture_;
        SDL_Texture* pinky_texture_;
        SDL_Texture* blinky_texture_;
        SDL_Texture* inky_texture_;
        SDL_Texture* live_texture_;

        //These will be used in game loop to change pacman direction
        direction new_direction_;
        direction stored_direction_;
        direction if_possible_direction_;

        Pacman* pacman_;
        Ghost* ghosts_[4];

        //Game loop's methods
        void input();
        void update();
        void render();

        void render_maze();
        void render_pacman();
        void render_ghosts();
        void render_scores();
        void render_manual();

        double rotation_texture_angle() const;

        //Check debug variables. If sth is wrong - throw exception
        void was_init_correct();

        void handle_win();
        void handle_game_over();
        void handle_pacman_moves();
        void handle_ghosts_moves();
        void handle_dead();
        void handle_booster(bool was_eaten_moment_ago = false);
        void handle_ghosts_collision();
        void handle_teleports();
        void handle_eatable_collision();

        void delete_ghosts();
        void make_ghosts();
};
#endif

Jeżeli takie kody (oczywiście pod względem stylu, a nie objętości i złożoności :wink: ) będziecie wklejali na SPOJa, to poza szacunkiem i uznaniem z mojej strony (zawsze plus jedna osoba, nawet tak nijaka :wink: ), a także szybszą odpowiedzią ze strony innych (bo każdy woli analizować logiczny kod a nie zlepek losowych instrukcji), zwiększycie szanse na znalezienie pracy na dwa sposoby: w porządnej developerce jakość kodu ma znaczenie bo zwiększa wydajność zespołu i wy tą wydajność na pewno zwiększycie, a także szybciej przyswoicie sobie dowolny język programowania (mniej szukania oczywistych błędów) :wink:

I jakby tu podsumować ten spam :wink: Oczywiście najważniejsze, aby program działał, tzn. realizował określone funkcjonalności w określonym czasie. Druga istotna cecha kodu, to jego czytelność. Czytelność staje się istotna w kodzie, który ma być czytany wielokrotnie. Za czytelność w największym stopniu odpowiadają (kolejność od najważniejszego do najmniej ważnego, przy czym jest to moja skromna opinia):

  1. Prawidłowa indentacja
  2. Obrazowe, intuicyjne nazewnictwo zmiennych i funkcji
  3. Nieużywanie / nienadużywanie trudnych do analizowania instrukcji, jak np. goto
  4. Dobre komentarze
  5. Dobre rozbicie kodu na pliki, funkcje, klasy, funkcjonalności, …
  6. Konsystentny kod - wszędzie należy stosować te same metody nazewnictwa, robienia wcięć itd. W żadnym miejscu nie należy robić wyjątków, np. z 8 spacji na wcięcie przechodzić na 2 spacje, albo z ifów tworzonych w stylu “} else if(…) {” przechodzić na styl “else if(…) {”

Prosząc o pomoc na SPOJu zaleca się zwracać uwagę na 1), 2) i w mniejszym stopniu resztę. W realnym życiu życzę powodzenia każdemu, kto oleje którykolwiek z tych punktów robiąc projekt na > 5k linii kodu.

1 month later

Z ciekawostek w tym temacie - dzisiaj poznałem kolejny zły nawyk w programowaniu :wink:

Jak pewnie napisałem powyżej, nazwa funkcji powinna mówić, co ona mniej więcej robi. Można oczywiście używać skrótów, np. pow zamiast power albo PowerOfTwoRealNumbers. Zasadę tą zastosował autor funkcji o nazwie napr_spier(). Funkcja ta, jak sugeruje nazwa, służy do naprawy rzeczy, które są (gorzej niż) spierniczone :wink:

Takie rozwiązanie jest oczywiście niezgodne ze sztuką. W ramach naprawy można dodać jednego ifa wzwyż do kodu (choć też bez przesady z ich liczbą), natomiast co do zasady funkcje powinny być napisane w taki sposób, by nie trzeba było potem wołać napr_spier().

Można, pod warunkiem, że są ogólnoznane, np typu angielskiego skrótu pow [power?]. W przeciwnym wypadku należy unikać skrótów jak ognia i ich nieużywać! I takie właśnie jest zalecenie “mądrzejszych” w temacie pisania czystego kodu. Ale oczywiście Ty piszesz jak NIE pisać.

Na początku tygodnia dostałem w pracy czyjś kod, klasa ok. 130 linii, który był pokryty 15 testami, ale kod nie działał dla pewnych wartości nie uwzględnionych w tych testach. Dopisałem je jako nowe testy i nieco po omacku naprawiałem kod tak żeby działał dla starych i nowych testów. I tak, pojawiło się coś

if(spier) {
    napraw;
}

Wniosek - nie wiesz jak coś działa? - pokryj testami i naparawiaj do skutku. (Albo przepisz żeby przechodziło testy i było czytelne :P)

Myślę, że sqrt, isodd, pow, log, exp, vat itp to dosyć jednoznaczne skróty. Nie zaznaczyłem tego, ale o takich funkcjach myślałem pisząc ten post. Myślę jednak, że dało się to wywnioskować.

Nie jestem pewien, ale czy to jakiś zarzut? Gdzie w którymkolwiek kodzie zastosowałem niejednoznaczny skrót, albo zasugerowałem stosowanie losowych nazw funkcji jako jakiegoś standardu, który sam stosuję? W moim mniemaniu poszedłem jak coś w drugą skrajność, tzn. zbyt rozbudowane nazewnictwo.

19 days later

Poniżej przedstawiam kod stworzony przez @eminiox_01 w celu rozwiązania zadania 31318. Register & Login [FR_07_03] (link11). Na szczęście rozwiązanie jest błędne, a sam kod został przeze mnie lekko zmodyfikowany, aby jego naprawa nie była zbyt łatwa (to na wypadek, gdyby ktoś miał mi zarzucić, że pozbawiłem go radości z trzaskania zadanka podsuwając gotowca) :wink: Minimalnie obniżyło to jakość kodu, ale myślę, że dla osób początkujących i tak może być źródłem wiedzy o C++ (wykorzystanie namespace, map, …) i sposobie pisania czytelnego kodu.

Wstawiam w kolorze bo to cały kod a nie fragmenty złożonych kodów, które umieszczałem tu do tej pory. Oczywiście kod nie jest idealny i pewne rzeczy mogą uchodzić za dyskusyjne, ale w pełni przemyślane i zatwierdzone przez iluś ludzi kody to chyba jedynie w książkach, o których wspomina kolega @narbej w temacie Jak czysto pisać w C++

#include <iostream>
#include <string>
#include <map>
 
namespace Detail
{
	bool IsDigit(const char &c)
	{
		return (c >= '0' && c <= '9');
	}
 
	bool IsLetter(const char &c)
	{
		if (c < 0 || c > 255)
			return false;
 
		return (tolower(c) >= 'a' && tolower(c) <= 'z');
	}
 
	bool IsSpecial(const char &c)
	{
		if (!IsDigit(c) && !IsLetter(c))
			return true;
                else if(c == 'a' || c == '0')
            		return false;

                return false;
	}
 
	bool CheckForLetters(const std::string &string)
	{
		for (int i = 0; i < string.size(); i++)
			if (IsLetter(string[i]))
				return false;
 
		return true;
	}
 
	bool CheckForDigits(const std::string &string)
	{
		for (int i = 0; i < string.size(); i++)
			if (IsDigit(string[i]))
				return false;
 
		return true;
	}
 
	bool CheckForLower(const std::string &string)
	{
		for (int i = 0; i < string.size() - 1; i++)
			if (string[i] >= 'a' && string[i] <= 'z')
				return false;
 
		return true;
	}
 
	bool CheckForUpper(const std::string &string)
	{
		for (int i = 0; i < string.size(); i++)
			if (string[i] >= 'A' && string[i] <= 'N')
				return false;
 
		return true;
	}
 
	bool CheckForSpecial(const std::string &string)
	{
		for (int i = 0; i < string.size(); i++)
			if (IsSpecial(string[i]))
				return true;
 
		return false;
	}
}
 
bool CheckLogin(const std::string &login)
{
	if (login.size() < 3 && login.size() > 12)
		return false;
 
	if (Detail::CheckForSpecial(login))
		return false;
 
	return true;
}
 
bool CheckPassword(const std::string &password)
{
	if (password.size() < 5 && password.size() > 15)
		return false;
 
	return (Detail::CheckForUpper(password) && Detail::CheckForLower(password) && Detail::CheckForDigits(password) && (Detail::CheckForSpecial(password)));
}
 
const char *ERROR = "Blad";
const char *LOGIN_NOT_AVABLIVE = "Login zajety";
const char *REGISTER_SUCCESS = "Zarejestrowano";
const char *INVALID_ACCOUNT = "Konto nie istnieje";
const char *INVALID_PASSWORD = "Zle haslo";
const char *LOGIN_SUCCESS = "Zalogowano";
 
std::map<std::string, std::string> accountMap;
 
void RegisterAccount()
{
	std::string login;
	std::cin >> login;
 
	std::string password;
	std::cin >> password;
 
	if (!CheckLogin(login) || !CheckPassword(password))
		std::cout << ERROR << "\n";
	else
	{
		auto find = accountMap.find(login);
		if (find != accountMap.end())
		{
			std::cout << LOGIN_NOT_AVABLIVE << "\n";
		}
		else
		{
			accountMap.insert(std::make_pair(login, password));
			std::cout << REGISTER_SUCCESS << "\n";
		}
	}
}
 
void LoginAccount()
{
	std::string login;
	std::cin >> login;
 
	std::string password;
	std::cin >> password;
 
	auto find = accountMap.find(login);
	if (find != accountMap.end())
	{
		if (find->second == password)
			std::cout << LOGIN_SUCCESS << "\n";
		else
			std::cout << INVALID_PASSWORD << "\n";
	}
	else
	{
		std::cout << INVALID_ACCOUNT << "\n";
	}
}
 
int main()
{
	std::string input;
	while (std::cin >> input)
	{
		int data;
		std::cin >> data;
		for (int i = 0; i < data; i++)
		{
			if (input == "register")
				RegisterAccount();
			if (input == "login")
				LoginAccount();
		}
	}
 
	return 0;
}

Oczywiście w życiu czasem jest inaczej niż na spoju. I tak jak używanie namespace std:: przy każdej instrukcji na spoju nie ma większego sensu to w życiu jest zupełnie inaczej :slight_smile:

23 days later

Czuję się zupełnie pogubuiony i mocno zakłopotany. Czy twój wątek dotyczy jak nie programować? W takim razie ok, bardzo dobry przykład, bo nie podejrzewam, że to Ty swoimi modyfikacjami tak go sknociłeś. Oczywiście kod jest bardzo czytelnie napisany, ale to nie znaczy, że jest zwyczajnie dobry. A jaki jest pożytek z kiepskiego kodu, który jest [tylko] pięknie sformatowany? Czy tylko to kwalifikuje go do podawania go za wzór do naśladowania? Chyba, że jest to antywzór?

PS
Miałem skomentować dużo wcześniej, ale zwyczajnie, po prostu mi się już nie chciało i dalej nie chce [rozwijać konstruktywnej krytyki]. :wink:

Mój wątek dotyczy tego jak ( nie? ) programować. Jak wolisz temat jest w rodzaju “luźne przemyślenia przynajmniej jednej osoby na temat tego jak powinien a jak nie powinien wyglądać kod z przykładami, przy czym autor nie zamierza robić się na autorytet ani specjalistę w tym temacie, a jedynie mówi co uważa luźno nawiązując do znanych mu specjalistów i autorytetów”

Bardzo słuszna uwaga, np. pierwsze moje zastrzeżenia dotyczą funkcji isDigit, a najważniejsze z nich to fakt, że w ogóle ktoś taką funkcję napisał :wink: Niemniej jak zaznaczyłem:

Ten temat straci jakikolwiek sens, jeżeli nie będzie tu różnych przykładów i możliwie czepialskich i wrednych uwag. Każdy komentarz jest cenny i w swej idealistycznej, naiwnej wizji świata głęboko się łudzę, że ktoś kiedyś to przeczyta i pomyśli nim wstawi kod taki jak w temacie Przedszkolanka [PRZEDSZK], który to kod załączam na końcu posta i który to kod trafnie skomentował @redysz:

Kod:

#include <iostream>

using namespace std;

int pakiety;
int a,b;

int e;

int main()
{

    cin >> pakiety;

    for(int i=0; i<pakiety; i++){

    cin >> a;
    cin >> b;

    if(a>=10 && b<=30){
        e=a*b;

    do{
        if(a>b) a=a-b;
            else b=b-a;
    if(a==b){

    int d=e/a;
    cout <<d << endl;
    }
    }
    while(a!=b);
    }


    }

    return 0;
}
10 days later
3 months later

Jeżeli piszesz jakiś program i ma on być rozwijany w (niedalekiej) przyszłości to na prawdę fajnie zobaczyć w nim jakąś funkcję typu main a nie zbiór różnych funkcji bez maina. Oczywiście mówimy o dużych programach.

Jeżeli bardzo potrzebujesz zmiennych globalnych - czyli niezmiernie rzadko - niech są w jakimś widocznym miejscu i najlepiej by były const oraz miały określoną konwencję nazewnictwa, np. zaczynały się od prefiksu “Pub” czy coś w ten deseń. Zmienne globalne powoływane do życia w ciele wybranych i nie zawsze wywoływanych funkcji - bad idea.

1 year later

No dobrze skoro nie używać PUBLIC to co używać ? W jaki sposób rozwiązać kod ?

W całym powyższym tekście znalazłem [funkcją ctrl f, przeglądarki] tylko jedno wystąpienie słowa PUBLIC, to twoje, więc zupełnie nie wiem do czego się odnosisz, i do jakiego języka programowania. Możesz wyjaśnić to bliżej?

Nie ma to znaczenia. Jeżeli twoim oczekiwaniem jest AC, to rozwiąż w taki sposób żeby to uzyskać. Jeżeli chcesz otrzymać ładny kod, to właśnie tak to zrób.

Może takie porównanie. Jeżeli masz zamiar nauczyć się pływać, to nie zaczynaj od czytania teorii pływania i czytania jak wygląda praca rąk i nóg w stylu craul czy motyl-delfin. Zacznij od oswojenia się z wodą i umiejętności utrzymywania się na wodzie.

Więc tu tak samo. Po prostu zacznij rozwiązywać zadania, a jakim sposobem, czy użyjesz zmiennych globalnych czy nie to na razie zupełnie bez znaczenia. Jak nabędziesz już wprawy, wtedy zacznij się zastanawiać nad stylem swojego kodu.

Więc nie sama teoria i czytanie czy oglądanie filmików na youtube ale jak najszybciej praktyka + trochę teorii, a nie sama sucha teoria i zastanawianie się: be PUBLIC or not to be.

Bardzo dziękuje za odpowiedz.
Nie spodziewałem się ,że w ogóle ktoś to czyta :slight_smile:
Moja historia wygląda tak ::
Obecnie pracuje jako programista Harbour / Clipper (z domieszkami C i ASM w clipperze) - jestem samoukiem , nie miałem od kogo się uczyć. Program jest dość duży cały czas jest rozbudowywany no ale jest też trochę błędów
Posiadam jeden kod źródłowy pod Harbour i Clipper, pod różne wersje programu a także pod różne wersje baz danych i pod różne systemy operacyjne co mi daje możliwość uruchamiania programu w praktycznie w każdym środowisku (Dos , Windows ,Linux)
Niestety mam też bardzo dużo zmiennych PUBLIC które są potrzebne przy starcie programu.
Niestety też jestem amatorem i nie wiem jak to zmienić :wink:

Pozdrawiam

Myślałem, że te dwie rzeczy się wykluczają, ale zobaczyłem do słownika i jest pewna możliwość.
Jeśli jesteś tego rodzaju amatorem to może lepiej nic nie ruszać.
Ehh, pewnie przemawia przeze mnie zazdrość. :japanese_ogre:

LOCAL, LOCAL tylko lokal :wink:

Tak naprawdę nie chodzi o to żeby nie używać. Chodzi, żeby używać z głową. Żeby wiedzieć , że używanie PUBLIC może się wiąząć już jutro czy w najbliższej przyszłości z dużym bólem głowy. Z gorszą diagnozą błędów, gorszą przenośnością czy pielęgnacją albo rozbudową programu.

Jeżeli zmienne są potrzebne przy starcie tego twojego programu to ich nie ruszaj, i dlaczego chcesz coś zmieniać. Pierwsza zasada - działa Broń Cie PANIE od dotykania i zmieniania.

To dotyczy Ciebie, a nie odpowiadasz, za to co zastałeś i jeżeli system jest cały czas rozbudowywany i Ty go rozbudowujesz, to Ty odpowiadasz, za rozbudowę i co i jak robisz i czy użyjesz - z głową - zmiennych PUBLIC, czy nie. Skup się na tym, żeby tą rozbudowę robić dobrze, a w wolnym czasie :wink: studiuj Harbora, Clipera, bazy danych i wszystkie wersje i kombinacje tego już istniejącego programu.

Chyba, że to kto inny robi? Więc co tak naprawdę tam robisz? Testujesz program w różnych wersjach, pod różne systemy baz, w różnych środowiskach? Jeżeli tak to i tak jak wyżej.

Zmienne globalne czy PUBLIC nie są same w sobie złę i same nie powodują powstawania błędów. To tylko programista, który z nich korzysta i może z tego powodu -zmiennych globalnych - tworzyć kod bardziej podatny na błędy i … i w sumie jak wyżej.

No tak , kiedyś raz może czy dwa się złapałem na konflikcie zmiennych PUBLIC więc staram się dokładnie sprawdzać czy tego o czym myślę już gdzieś nie ma. Program jest na tyle duży ,że nie sposób spamiętać gdzie co jest (mimo to opisuje zawsze co zmieniłem w konkretnej wersji bo zmian jest bardzo dużo czasem, do tego bywają ulepszenia obecnego stanu). Ostatnio się zacząłem interesować jakością tworzenia kodu bo czasem faktycznie widzę ,że coś zajmuje mi za dużo miejsca. Testowaniem też się zajmuję bo zanim wydam oprogramowanie to wolę przetestować - chociaż i tu bywają błędy - u mnie działa a w innym środowisku bywają czasem problemy ale tak jestem umówiony z ludźmi ,że w razie czego od razu poprawiam. Krótko mówiąc działam zupełnie w pojedynkę - chociaż mam takiego kolegę co też ma program tego typu i jak czegoś nie wiem to go nieraz pytam i on mi pokazuje drogę na skróty.

W takim razie będę postępował tak jak do tej pory - będę czytał i obserwował :wink: