DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc

„Știu că nu știu nimic” Socrate

Pentru cine: pentru oamenii IT cărora nu le pasă de toți dezvoltatorii și doresc să-și joace jocurile!

Despre ce: despre cum să începeți să scrieți jocuri în C/C++, dacă aveți nevoie dintr-o dată!

De ce ar trebui să citești asta: Dezvoltarea de aplicații nu este domeniul meu de expertiză, dar încerc să codific în fiecare săptămână. Pentru că îmi plac jocurile!

buna numele meu este Andrei Grankin, sunt DevOps la Luxoft. Dezvoltarea de aplicații nu este specialitatea mea, dar încerc să codific în fiecare săptămână. Pentru că îmi plac jocurile!

Industria jocurilor pe computer este uriașă, despre care se zvonește că este chiar mai mare decât industria filmelor de astăzi. Jocurile au fost scrise încă de la începuturile computerelor, folosind, conform standardelor moderne, metode complexe și de bază de dezvoltare. De-a lungul timpului, au început să apară motoare de joc cu grafică, fizică și sunet deja programate. Ele vă permit să vă concentrați pe dezvoltarea jocului în sine și să nu vă faceți griji cu privire la fundația acestuia. Dar odată cu ei, cu motoarele, dezvoltatorii „orbesc” și se degradează. Producția de jocuri în sine este pusă pe bandă rulantă. Iar cantitatea de produse începe să prevaleze asupra calității sale.

În același timp, când jucăm jocurile altora, suntem în mod constant limitați de locațiile, intriga, personajele și mecanica de joc cu care au venit alți oameni. Așa că mi-am dat seama că...

... a sosit momentul să-mi creez propriile lumi, supuse doar mie. Lumi în care Eu sunt Tatăl, Fiul și Duhul Sfânt!

Și cred sincer că, scriindu-ți propriul motor de joc și jucând pe el, vei putea să-ți dai jos pantofii, să ștergi geamurile și să-ți modernizezi cabina, devenind un programator mai experimentat și mai complet.

În acest articol voi încerca să vă spun cum am început să scriu jocuri mici în C/C++, cum este procesul de dezvoltare și unde îmi găsesc timp pentru un hobby într-un mediu aglomerat. Este subiectiv și descrie procesul unui început individual. Material despre ignoranță și credință, despre imaginea mea personală despre lume în acest moment. Cu alte cuvinte, „Administrația nu este responsabilă pentru creierul tău personal!”

Practică

„Cunoașterea fără practică este inutilă, practica fără cunoștințe este periculoasă” Confucius

Caietul meu este viața mea!


Deci, în practică, pot spune că pentru mine totul începe cu un blocnotes. Nu numai că îmi notez sarcinile zilnice acolo, ci și desenez, programez, proiectez organigrame și rezolv probleme, inclusiv matematice. Folosiți întotdeauna un blocnotes și scrieți numai cu creion. Este curat, convenabil și de încredere, IMHO.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Caietul meu (deja plin cu). Așa arată el. Conține sarcini de zi cu zi, idei, desene, diagrame, soluții, contabilitate neagră, cod și așa mai departe

În această etapă, am reușit să finalizez trei proiecte (acest lucru este în înțelegerea mea despre „completitudine”, deoarece orice produs poate fi dezvoltat relativ la nesfârșit).

  • Proiectul 0: Aceasta este o scenă Demo 3D Architect scrisă în C# folosind motorul de joc Unity. Pentru platformele macOS și Windows.
  • Joc 1: joc de consolă Simple Snake (cunoscut de toată lumea ca „Snake”) pentru Windows. Scris în C.
  • Joc 2: jocul de consolă Crazy Tanks (cunoscut de toată lumea ca „Tanks”), scris în C++ (folosind clase) și, de asemenea, pentru Windows.

Proiect 0. Demo arhitect

  • platforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Limba: C#
  • Motor de joc: Unitate
  • Inspirație: Darrin Lile
  • Repertoriu: GitHub

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Demo 3D Scene Architect

Primul proiect a fost implementat nu în C/C++, ci în C# folosind motorul de joc Unity. Acest motor nu a fost la fel de solicitant la hardware ca Motorul Unrealși, de asemenea, părea mai ușor de instalat și utilizat. Nu am luat in calcul alte motoare.

Scopul meu în Unity nu a fost să dezvolt un joc. Am vrut să creez o scenă 3D cu un anumit caracter. El, sau mai bine zis Ea (am modelat fata de care eram îndrăgostită =) a trebuit să se miște și să interacționeze cu lumea din jurul lui. Era important doar să înțelegem ce este Unity, care este procesul de dezvoltare și cât de mult efort este necesar pentru a crea ceva. Așa s-a născut proiectul Architect Demo (numele a fost aproape inventat de nicăieri). Programarea, modelarea, animația, texturarea mi-a luat probabil două luni de muncă zilnică.

Am început cu videoclipuri tutorial pe YouTube despre crearea modelelor 3D în Blender. Blender este un instrument gratuit excelent pentru modelarea 3D (și multe altele) care nu necesită instalare. Și aici mă aștepta un șoc... Se dovedește că modelarea, animația, texturarea sunt subiecte uriașe separate despre care poți scrie cărți. Acest lucru este valabil mai ales pentru personaje. Pentru a modela degetele, dinții, ochii și alte părți ale corpului, veți avea nevoie de cunoștințe de anatomie. Cum sunt structurați mușchii faciali? Cum se mișcă oamenii? A trebuit să „introduc” oase în fiecare braț, picior, deget, falange ale degetelor!

Modelați claviculele și oasele de pârghie suplimentare pentru a face animația să pară naturală. După astfel de lecții, îți dai seama câtă muncă fac creatorii de filme animate doar pentru a crea 30 de secunde de videoclip. Dar filmele 3D durează ore întregi! Și apoi părăsim cinematografele și spunem ceva de genul: „Este un desen animat/film porcărie! S-ar fi putut descurca mai bine..." Prostii!

Și încă ceva referitor la programarea în acest proiect. După cum sa dovedit, cea mai interesantă parte pentru mine a fost cea matematică. Dacă rulați scena (link către depozitul din descrierea proiectului), veți observa că camera se rotește în jurul personajului fetei într-o sferă. Pentru a programa o astfel de rotație a camerei, a trebuit să calculez mai întâi coordonatele punctului de poziție pe cerc (2D), iar apoi pe sferă (3D). Lucrul amuzant este că uram matematica la școală și o știam cu un C-minus. Parțial, probabil, pentru că la școală pur și simplu nu îți explică cum naiba se aplică această matematică în viață. Dar când ești obsedat de scopul tău, visul tău, mintea ta se limpezește și se deschide! Și începi să percepi sarcinile dificile ca pe o aventură interesantă. Și apoi te gândești: „Ei bine, de ce nu ți-ar putea spune matematicianul tău preferat* unde pot fi aplicate aceste formule?”

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Calculul formulelor pentru calcularea coordonatelor unui punct pe un cerc și pe o sferă (din caietul meu)

Joc 1. Snake simplu

  • platforma: Windows (testat pe Windows 7, 10)
  • Limba: Cred că l-am scris în C pur
  • Motor de joc: Consola Windows
  • Inspirație: javidx9
  • Repertoriu: GitHub

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Joc simplu Snake

O scenă 3D nu este un joc. În plus, modelarea și animarea obiectelor 3D (în special a personajelor) necesită timp și este dificilă. După ce am jucat cu Unity, mi-am dat seama că trebuie să continui, sau mai degrabă să încep de la elementele de bază. Ceva simplu și rapid, dar în același timp global, pentru a înțelege însăși structura jocurilor.

Ce este simplu și rapid? Așa e, consolă și 2D. Mai exact, chiar și consola și simbolurile. Din nou am plecat să caut inspirație pe Internet (în general, cred că Internetul este cea mai revoluționară și periculoasă invenție a secolului XXI). Am dezgropat un videoclip cu un programator care a făcut consola Tetris. Și după asemănarea jocului lui, am decis să fac un „șarpe”. Din videoclip am învățat despre două lucruri fundamentale - bucla de joc (cu trei funcții/părți de bază) și ieșire în buffer.

Bucla de joc ar putea arăta cam așa:

int main()
   {
      Setup();
      // a game loop
      while (!quit)
      {
          Input();
          Logic();
          Draw();
          Sleep(gameSpeed);  // game timing
      }
      return 0;
   }

Codul prezintă întreaga funcție main() simultan. Și ciclul jocului începe după comentariul corespunzător. Există trei funcții de bază în buclă: Input(), Logic(), Draw(). Mai întâi, introduceți datele de intrare (în principal controlul apăsărilor de taste), apoi procesarea datelor introduse Logic, apoi ieșire pe ecran - Desenare. Și așa mai departe fiecare cadru. Așa se creează animația. E ca în desene animate. De obicei, procesarea datelor introduse durează cel mai mult timp și, din câte știu, determină rata de cadre a jocului. Dar aici funcția Logic() se execută foarte repede. Prin urmare, trebuie să controlați rata de cadre folosind funcția Sleep() cu parametrul gameSpeed ​​​​, care determină această viteză.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Ciclul jocului. Programarea unui „șarpe” într-un bloc de note

Dacă dezvoltați un joc de consolă bazat pe personaje, nu veți putea să scoateți date pe ecran folosind fluxul obișnuit „cout” - este foarte lent. Prin urmare, ieșirea trebuie trimisă în memoria tampon de ecran. Acest lucru este mult mai rapid și jocul va rula fără probleme. Sincer să fiu, nu prea înțeleg ce este un screen buffer și cum funcționează. Dar voi da aici un exemplu de cod și poate cineva poate clarifica situația în comentarii.

Obținerea unui tampon de ecran (ca să spunem așa):

// create screen buffer for drawings
   HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0,
 							   NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
   DWORD dwBytesWritten = 0;
   SetConsoleActiveScreenBuffer(hConsole);

Afișarea directă a unui anumit șir de caractere scorLine (linia de afișare a scorului):

// draw the score
   WriteConsoleOutputCharacter(hConsole, scoreLine, GAME_WIDTH, {2,3}, &dwBytesWritten);

În teorie, nu este nimic complicat în acest joc; cred că este un exemplu bun de nivel de intrare. Codul este scris într-un singur fișier și formatat în mai multe funcții. Fără clase, fără moștenire. Puteți vedea totul în codul sursă al jocului, accesând depozitul de pe GitHub.

Jocul 2. Tancuri nebune

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Joc Crazy Tanks

Imprimarea caracterelor pe consolă este probabil cel mai simplu lucru pe care îl poți transforma într-un joc. Dar apoi apare o problemă: simbolurile au înălțimi și lățimi diferite (înălțimea este mai mare decât lățimea). În acest fel, totul va arăta disproporționat, iar mișcarea în jos sau în sus va apărea mult mai rapid decât deplasarea la stânga sau la dreapta. Acest efect este foarte vizibil în Snake (Jocul 1). „Tancurile” (Jocul 2) nu au acest dezavantaj, deoarece ieșirea acolo este organizată prin pictarea pixelilor ecranului cu culori diferite. Ai putea spune că am scris un renderer. Adevărat, acest lucru este puțin mai complicat, deși mult mai interesant.

Pentru acest joc, va fi suficient să descriu sistemul meu de afișare a pixelilor pe ecran. Consider că aceasta este partea principală a jocului. Și poți veni singur cu orice altceva.

Deci, ceea ce vedeți pe ecran este doar un set de dreptunghiuri multicolore în mișcare.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Set de dreptunghiuri

Fiecare dreptunghi este reprezentat de o matrice plină cu numere. Apropo, pot evidenția o nuanță interesantă - toate matricele din joc sunt programate ca o matrice unidimensională. Nu bidimensional, ci unidimensional! Matricele unidimensionale sunt mult mai ușor și mai rapid de lucrat.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Exemplu de matrice de rezervor de joc

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Reprezentarea matricei rezervorului de joc ca o matrice unidimensională

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Un exemplu mai vizual de reprezentare a unei matrice ca o matrice unidimensională

Dar accesul la elementele matricei are loc într-o buclă dublă, de parcă nu ar fi o matrice unidimensională, ci una bidimensională. Acest lucru se face pentru că încă lucrăm cu matrici.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Parcurgerea unui tablou unidimensional într-o buclă dublă. Y - identificatorul rândului, X - identificatorul coloanei

Vă rugăm să rețineți: în locul identificatorilor obișnuiți de matrice i, j, folosesc identificatorii x și y. În acest fel, mi se pare, este mai plăcut ochiului și mai ușor de înțeles pentru creier. În plus, o astfel de notație face posibilă proiectarea convenabilă a matricelor utilizate pe axele de coordonate ale unei imagini bidimensionale.

Acum despre pixeli, culoare și ieșire pe ecran. Funcția StretchDIBits este utilizată pentru ieșire (Header: windows.h; Library: gdi32.lib). Această funcție, printre altele, primește următoarele: dispozitivul pe care este afișată imaginea (în cazul meu, este consola Windows), coordonatele de început ale afișajului imaginii, lățimea/înălțimea acesteia și imaginea însăși în forma unui bitmap, reprezentat printr-o matrice de octeți. Bitmap ca o matrice de octeți!

Funcția StretchDIBits() în acțiune:

// screen output for game field
   StretchDIBits(
               deviceContext,
               OFFSET_LEFT, OFFSET_TOP,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               0, 0,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               m_p_bitmapMemory, &bitmapInfo,
               DIB_RGB_COLORS,
               SRCCOPY
               );

Memoria este alocată în avans pentru acest bitmap folosind funcția VirtualAlloc(). Adică, numărul necesar de octeți este rezervat pentru a stoca informații despre toți pixelii, care vor fi apoi afișați pe ecran.

Crearea bitmap-ului m_p_bitmapMemory:

// create bitmap
   int bitmapMemorySize = (PMATRIX_WIDTH * PMATRIX_HEIGHT) * BYTES_PER_PIXEL;
   void* m_p_bitmapMemory = VirtualAlloc(0, bitmapMemorySize, MEM_COMMIT, PAGE_READWRITE);

În linii mari, un bitmap constă dintr-un set de pixeli. Fiecare patru octeți din matrice este un pixel RGB. Un octet per valoare de culoare roșie, un octet per valoare de culoare verde (G) și un octet per valoare de culoare albastră (B). În plus, a mai rămas un octet pentru indentare. Aceste trei culori - Roșu/Verde/Albastru (RGB) - sunt amestecate între ele în proporții diferite pentru a crea culoarea pixelilor rezultată.

Acum, din nou, fiecare dreptunghi, sau obiect de joc, este reprezentat printr-o matrice numerică. Toate aceste obiecte de joc sunt plasate într-o colecție. Și apoi sunt plasate pe terenul de joc, formând o matrice numerică mare. Am asociat fiecare număr din matrice cu o anumită culoare. De exemplu, numărul 8 corespunde cu albastru, numărul 9 cu galben, numărul 10 cu gri închis și așa mai departe. Astfel, putem spune că avem o matrice a terenului de joc, unde fiecare număr este o culoare.

Deci, avem o matrice numerică a întregului teren de joc pe o parte și un bitmap pentru afișarea imaginii pe cealaltă parte. Până acum, bitmap-ul este „gol” - nu conține încă informații despre pixelii culorii dorite. Aceasta înseamnă că ultimul pas va fi completarea bitmap-ului cu informații despre fiecare pixel pe baza matricei numerice a terenului de joc. Un exemplu clar al unei astfel de transformări este în imaginea de mai jos.

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Un exemplu de completare a unui bitmap (Matrice de pixeli) cu informații bazate pe matricea digitală a terenului de joc (indicii de culoare nu se potrivesc cu cei din joc)

De asemenea, voi prezenta o bucată de cod real din joc. Variabilei colorIndex la fiecare iterație a buclei i se atribuie o valoare (indice de culoare) din matricea numerică a terenului de joc (mainDigitalMatrix). Variabila de culoare este apoi setată la culoarea însăși pe baza indexului. Culoarea rezultată este apoi împărțită în raportul dintre roșu, verde și albastru (RGB). Și împreună cu pixelPadding, această informație este scrisă în pixel din nou și din nou, formând o imagine color în bitmap.

Codul folosește pointeri și operații pe biți, care pot fi dificil de înțeles. Așa că vă sfătuiesc să citiți undeva separat cum funcționează astfel de structuri.

Completarea hărții de bit cu informații bazate pe matricea numerică a terenului de joc:

// set pixel map variables
   int colorIndex;
   COLORREF color;
   int pitch;
   uint8_t* p_row;
 
   // arrange pixels for game field
   pitch = PMATRIX_WIDTH * BYTES_PER_PIXEL;     // row size in bytes
   p_row = (uint8_t*)m_p_bitmapMemory;       //cast to uint8 for valid pointer arithmetic
   							(to add by 1 byte (8 bits) at a time)   
   for (int y = 0; y < PMATRIX_HEIGHT; ++y)
   {
       uint32_t* p_pixel = (uint32_t*)p_row;
       for (int x = 0; x < PMATRIX_WIDTH; ++x)
       {
           colorIndex = mainDigitalMatrix[y * PMATRIX_WIDTH + x];
           color = Utils::GetColor(colorIndex);
           uint8_t blue = GetBValue(color);
           uint8_t green = GetGValue(color);
           uint8_t red = GetRValue(color);
           uint8_t pixelPadding = 0;
 
           *p_pixel = ((pixelPadding << 24) | (red << 16) | (green << 8) | blue);
           ++p_pixel;
       }
       p_row += pitch;
   }

Conform metodei descrise mai sus, în jocul Crazy Tanks se formează o imagine (cadru) și se afișează pe ecran în funcția Draw(). După înregistrarea tastelor în funcția Input() și prelucrarea lor ulterioară în funcția Logic() se formează o nouă imagine (cadru). Adevărat, obiectele de joc pot avea deja o poziție diferită pe terenul de joc și, în consecință, sunt desenate într-un loc diferit. Așa se întâmplă animația (mișcarea).

În teorie (dacă nu am uitat nimic), înțelegerea buclei de joc din primul joc („Șarpe”) și a sistemului de afișare a pixelilor pe ecran din al doilea joc („Tanks”) este tot ce ai nevoie pentru a scrie orice. dintre jocurile dvs. 2D sub Windows. Fără sunet! 😉 Restul părților sunt doar un zbor de fantezie.

Desigur, jocul „Tanks” este mult mai complex decât „Snake”. Am folosit deja limbajul C++, adică am descris diferite obiecte de joc cu clase. Mi-am creat propria colecție - codul poate fi vizualizat în anteturi/Box.h. Apropo, colecția are cel mai probabil o scurgere de memorie. Indicatoare folosite. A lucrat cu memorie. Trebuie să spun că cartea m-a ajutat foarte mult Începerea C++ prin programarea jocurilor. Acesta este un început excelent pentru începătorii în C++. Este mic, interesant și bine organizat.

A durat aproximativ șase luni pentru a dezvolta acest joc. Am scris mai ales în timpul prânzului și gustărilor la serviciu. S-a așezat în bucătăria biroului, a călcat în picioare mâncare și a scris cod. Sau la cina acasă. Așa că am ajuns cu aceste „războaie în bucătărie”. Ca întotdeauna, am folosit activ un caiet și toate lucrurile conceptuale s-au născut în el.

Pentru a finaliza partea practică, voi face câteva scanări ale caietului meu. Pentru a arăta exact ce am notat, desenat, numărat, proiectat...

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Proiectarea imaginilor tancurilor. Și stabilirea câți pixeli ar trebui să ocupe fiecare rezervor pe ecran

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Calculul algoritmului și formulelor de rotație a rezervorului în jurul axei sale

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Schema colecției mele (cea în care există o scurgere de memorie, cel mai probabil). Colecția este creată în funcție de tipul Linked List

DevOps C++ și „războaiele din bucătărie”, sau Cum am început să scriu jocuri în timp ce mănânc
Și acestea sunt încercări zadarnice de a atașa inteligența artificială la joc

Теория

„Chiar și o călătorie de o mie de mile începe cu primul pas” (înțelepciunea chineză veche)

Să trecem de la practică la teorie! Cum să găsești timp pentru hobby-ul tău?

  1. Stabilește ce vrei cu adevărat (din păcate, aceasta este partea cea mai grea).
  2. Stabiliți priorități.
  3. Sacrifici totul „în plus” de dragul priorităților mai înalte.
  4. Îndreptați-vă spre obiective în fiecare zi.
  5. Nu vă așteptați la două sau trei ore de timp liber pe care să le petreceți pentru un hobby.

Pe de o parte, trebuie să determinați ce doriți și să prioritizați. Pe de altă parte, este posibil să se abandoneze unele activități/proiecte în favoarea acestor priorități. Cu alte cuvinte, va trebui să sacrifici totul „în plus”. Am auzit undeva că ar trebui să existe maxim trei activități principale în viață. Atunci le vei putea face la cea mai înaltă calitate. Și proiectele/direcțiile suplimentare vor începe pur și simplu să se supraîncărce. Dar toate acestea sunt probabil subiective și individuale.

Există o anumită regulă de aur: să nu ai niciodată o zi de 0%! Am aflat despre asta într-un articol al unui dezvoltator indie. Dacă lucrați la un proiect, faceți ceva în acest sens în fiecare zi. Și nu contează cât de mult faci. Scrieți un cuvânt sau o linie de cod, vizionați un videoclip tutorial sau bateți un cui într-o tablă - doar faceți ceva. Cel mai greu este să începi. Odată ce ai început, probabil vei ajunge să faci puțin mai mult decât ți-ai fi dorit. Astfel te vei îndrepta constant spre scopul tău și, crede-mă, foarte repede. La urma urmei, principalul obstacol în calea tuturor lucrurilor este amânarea.

Și este important să rețineți că nu trebuie să subestimați și să ignorați „rumegușul” liber de timp de 5, 10, 15 minute, așteptați niște „bușteni” mari care durează o oră sau două. Stai la coada? Gândește-te la ceva pentru proiectul tău. Luați scara rulantă? Scrie ceva pe un blocnotes. Călătorești cu autobuzul? Super, citește un articol. Profită de fiecare oportunitate. Nu mai urmăriți câini și pisici pe YouTube! Nu-ți polua creierul!

Și un ultim lucru. Dacă, după ce ați citit acest articol, v-a plăcut ideea de a crea jocuri fără a folosi motoare de joc, atunci amintiți-vă numele Casey Muratori. Tipul ăsta are website. În secțiunea „ceas -> EPISODE ANTERIOARE” veți găsi minunate tutoriale video gratuite despre crearea unui joc profesional de la zero. În cinci lecții de Introducere în C pentru Windows, probabil vei învăța mai mult decât în ​​cinci ani de studii universitare (cineva a scris despre asta în comentariile de sub videoclip).

Casey explică, de asemenea, că, dezvoltându-vă propriul motor de joc, veți avea o mai bună înțelegere a oricăror motoare existente. Într-o lume a cadrelor în care toată lumea încearcă să automatizeze, înveți să creezi mai degrabă decât să folosești. Înțelegi însăși natura computerelor. Și vei deveni, de asemenea, un programator mult mai inteligent și mai matur - un profesionist.

Mult succes pe drumul ales! Și să facem lumea mai profesionistă.

Autor: Grankin Andrey, DevOps



Sursa: www.habr.com